feat(stats): add model momentum section

This commit is contained in:
Adam 2026-07-03 10:13:56 -05:00
parent eba0bd0397
commit 7d84cb05ef
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
3 changed files with 685 additions and 190 deletions

View file

@ -198,7 +198,7 @@ const en = {
"model.knowledge": "Knowledge",
"model.release": "Release",
"model.inputs": "Inputs",
"model.overviewDescription": "Recent OpenCode Go tokens, unique users, and market position.",
"model.overviewDescription": "Recent tokens, unique users, and market position.",
"model.noSummaryTitle": "No usage summary",
"model.noSummaryDescription": "This model has no OpenCode Go usage rows yet.",
"model.tokens": "Tokens",

View file

@ -48,6 +48,7 @@ import {
const statsUnfurlPath = "banner.png"
const geoMapWidth = 960
const geoMapHeight = 430
const shortMonths = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] as const
type IsoCountryCode = readonly [string, string, string]
type WorldCountryProperties = GeoJsonProperties & { name?: string }
@ -119,8 +120,7 @@ export default function StatsModel() {
const statsUnfurlUrl = new URL(statsUnfurlPath, localizedUrl("en", "/data/")).toString()
const modelHeaderLinks = createMemo<readonly HeaderLink[]>(() => [
{ href: "#overview", label: i18n.t("nav.overview") },
{ href: "#usage", label: i18n.t("nav.usage") },
{ href: "#users", label: i18n.t("nav.users") },
{ href: "#momentum", label: i18n.t("model.momentum") },
{ href: "#efficiency", label: i18n.t("nav.efficiency") },
{ href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown") },
{ href: "#peers", label: i18n.t("nav.peers") },
@ -181,9 +181,8 @@ export default function StatsModel() {
catalogData={catalog() ?? null}
labName={labName()}
/>
<ModelOverview data={stats() ?? null} />
<ModelUsageSection data={stats()?.usage ?? []} />
<ModelUsersSection data={stats()?.usage ?? []} />
<ModelOverview catalog={catalogEntry() ?? null} />
<ModelMomentumSection data={stats() ?? null} />
<ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
<ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
<ModelPeersSection data={stats() ?? null} />
@ -423,15 +422,59 @@ function ChevronDownIcon() {
)
}
function ModelOverview(props: { data: StatsModelData | null }) {
function ModelOverview(props: { catalog: ModelCatalogEntry | null }) {
const i18n = useI18n()
const language = useLanguage()
const specs = createMemo(() => [
{
label: i18n.t("model.context"),
value: formatCatalogLimit(props.catalog?.limit?.context, i18n.t("home.unknown")),
},
{
label: i18n.t("model.output"),
value: formatCatalogLimit(props.catalog?.limit?.output, i18n.t("home.unknown")),
},
{
label: i18n.t("model.knowledge"),
value: formatCatalogMonth(props.catalog?.knowledge, language.tag(language.locale()), i18n.t("home.unknown")),
},
{
label: i18n.t("model.release"),
value: formatCatalogMonth(props.catalog?.releaseDate, language.tag(language.locale()), i18n.t("home.unknown")),
},
{
label: i18n.t("model.inputs"),
value: formatCatalogModalities(
props.catalog?.modalities.input ?? [],
language.tag(language.locale()),
i18n.t("home.unknown"),
),
},
])
return (
<section id="model-overview" data-section="model-panel">
<SectionTitle
href="#model-overview"
title={i18n.t("nav.overview")}
description={i18n.t("model.overviewDescription")}
/>
<section id="model-overview" data-section="model-specs" aria-label={i18n.t("nav.overview")}>
<For each={specs()}>
{(spec) => (
<div data-component="model-spec">
<span>{spec.label}</span>
<strong>{spec.value}</strong>
</div>
)}
</For>
</section>
)
}
function ModelMomentumSection(props: { data: StatsModelData | null }) {
const i18n = useI18n()
const language = useLanguage()
return (
<section id="momentum" data-section="model-momentum">
<h2 data-slot="model-momentum-title">
<a href="#momentum">{i18n.t("model.momentum")}.</a>
<span>{i18n.t("model.overviewDescription")}</span>
</h2>
<div data-slot="model-momentum-pattern" aria-hidden="true" />
<Show
when={props.data}
fallback={
@ -439,189 +482,85 @@ function ModelOverview(props: { data: StatsModelData | null }) {
}
>
{(data) => (
<div data-component="model-metric-grid">
<MetricCard
label={i18n.t("model.tokens")}
value={formatTokens(data().totals.tokens)}
detail={i18n.t("model.lastTwoMonths")}
/>
<MetricCard
label={i18n.t("model.uniqueUsers")}
value={formatUsers(data().totals.uniqueUsers)}
detail={i18n.t("model.lastTwoMonths")}
/>
<MetricCard
label={i18n.t("model.sessions")}
value={formatInteger(data().totals.sessions)}
detail={i18n.t("model.completedSessions")}
/>
<MetricCard
label={i18n.t("model.tokenShare")}
value={formatPercent(data().tokenShare)}
detail={i18n.t("model.totalModels", { count: data().totalModels })}
/>
<MetricCard
label={i18n.t("model.momentum")}
value={formatChange(data().tokenChange)}
detail={i18n.t("model.vsPreviousWindow")}
state={data().tokenChange < 0 ? "negative" : "positive"}
/>
</div>
<>
<MomentumChart data={data()} locale={language.tag(language.locale())} />
<div data-slot="model-momentum-metrics">
<MomentumMetric label={i18n.t("model.uniqueUsers")} value={formatUsers(data().totals.uniqueUsers)} />
<MomentumMetric
label={capitalizeLabel(i18n.t("model.completedSessions"))}
value={formatInteger(data().totals.sessions)}
/>
<MomentumMetric label={i18n.t("model.tokenShare")} value={formatPercent(data().tokenShare)} />
<MomentumMetric
label="Rank"
value={formatRankLabel(data().rank)}
watermark={formatRankLabel(data().rank)}
/>
</div>
</>
)}
</Show>
</section>
)
}
function ModelUsageSection(props: { data: ModelUsagePoint[] }) {
const i18n = useI18n()
function MomentumChart(props: { data: StatsModelData; locale: string }) {
const chart = createMemo(() => momentumChart(props.data.usage, props.data.updatedAt))
const changeState = createMemo(() => (props.data.tokenChange < 0 ? "negative" : "positive"))
return (
<section id="usage" data-section="model-panel">
<SectionTitle href="#usage" title={i18n.t("nav.usage")} description={i18n.t("model.usageDescription")} />
<Show
when={props.data.some((item) => item.tokens > 0)}
fallback={
<ModelEmptyState title={i18n.t("model.noUsageTitle")} description={i18n.t("model.noUsageDescription")} />
}
>
<ModelColumnChart data={props.data} metric="tokens" ariaLabel={i18n.t("model.dailyTokenChart")} />
</Show>
</section>
)
}
function ModelUsersSection(props: { data: ModelUsagePoint[] }) {
const i18n = useI18n()
return (
<section id="users" data-section="model-panel">
<SectionTitle href="#users" title={i18n.t("model.uniqueUsers")} description={i18n.t("model.usersDescription")} />
<Show
when={props.data.some((item) => item.users > 0)}
fallback={
<ModelEmptyState title={i18n.t("model.noUsersTitle")} description={i18n.t("model.noUsersDescription")} />
}
>
<ModelColumnChart data={props.data} metric="users" ariaLabel={i18n.t("model.dailyUserChart")} />
</Show>
</section>
)
}
function ModelColumnChart(props: { data: ModelUsagePoint[]; metric: "tokens" | "users"; ariaLabel: string }) {
const i18n = useI18n()
const [activeIndex, setActiveIndex] = createSignal<number>()
const max = createMemo(() => Math.max(0, ...props.data.map((item) => modelUsageMetricValue(item, props.metric))) || 1)
const activePoint = createMemo(() => {
const index = activeIndex()
if (index === undefined) return undefined
return props.data[index]
})
return (
<div
data-component="model-usage-chart"
data-metric={props.metric}
data-dense-labels={isModelUsageDense(props.data.length) ? "true" : undefined}
role="img"
aria-label={props.ariaLabel}
style={{ "--model-usage-count": props.data.length } as JSX.CSSProperties}
onPointerLeave={(event) => {
if (event.pointerType === "touch") return
setActiveIndex(undefined)
}}
>
<div data-slot="model-usage-axis" aria-hidden="true">
<For each={props.data}>
{(point, index) => (
<div
data-active={activeIndex() === index() ? "true" : undefined}
data-label-hidden={isModelUsageLabelHidden(index(), props.data.length) ? "true" : undefined}
>
<span data-slot="model-usage-label">
<span data-slot="model-usage-total">{formatModelUsageValue(point, props.metric)}</span>
<span data-slot="model-usage-date">{point.date}</span>
</span>
</div>
)}
</For>
<div data-component="model-momentum-chart" role="img" aria-label="Recent model token momentum">
<div data-slot="model-momentum-summary">
<div data-slot="model-momentum-total">
<strong>{formatTokens(props.data.totals.tokens)} tokens</strong>
<span data-state={changeState()}>{formatChange(props.data.tokenChange)}</span>
</div>
<p>
<span>{formatMomentumDate(chart().startDate, props.locale, props.data.updatedAt)}</span>
<span aria-hidden="true"></span>
<span>{formatMomentumDate(chart().endDate, props.locale, props.data.updatedAt)}</span>
</p>
</div>
<div data-slot="model-usage-bars">
<For each={props.data}>
{(point, index) => (
<div
data-slot="model-usage-column"
role="button"
tabIndex={0}
aria-label={`${point.date} ${formatModelUsageValue(point, props.metric)} ${modelUsageLabel(props.metric, i18n)}`}
data-active={activeIndex() === index() ? "true" : undefined}
data-muted={activeIndex() !== undefined && activeIndex() !== index() ? "true" : undefined}
onPointerDown={(event) => {
if (event.pointerType !== "touch") return
setActiveIndex(index())
}}
onPointerEnter={() => setActiveIndex(index())}
onPointerMove={(event) => {
if (event.pointerType === "touch") return
setActiveIndex(index())
}}
onClick={() => setActiveIndex(index())}
onFocus={() => setActiveIndex(index())}
onBlur={() => setActiveIndex(undefined)}
onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault()
setActiveIndex(index())
}}
>
<div
data-slot="model-usage-bar"
style={
{
"--model-usage-fill": `${modelUsageHeight(modelUsageMetricValue(point, props.metric), max())}%`,
} as JSX.CSSProperties
}
<div data-slot="model-momentum-plot">
<svg viewBox="0 0 1200 370" preserveAspectRatio="none" aria-hidden="true">
<path data-slot="model-momentum-line-muted" d={chart().previousPath} />
<path data-slot="model-momentum-line-active" d={chart().currentPath} />
<For each={chart().markers}>
{(marker) => (
<rect
data-slot="model-momentum-marker"
data-active={marker.active ? "true" : undefined}
x={marker.x - 3}
y={marker.y - 3}
width="6"
height="6"
/>
<Show when={activeIndex() === index() && activePoint()}>
{(active) => (
<div
data-component="chart-tooltip"
data-placement={index() > props.data.length * 0.62 ? "left" : "right"}
>
<strong>{active().date}</strong>
<span>
{formatModelUsageValue(active(), props.metric)} {modelUsageLabel(props.metric, i18n)}
</span>
<div data-slot="tooltip-divider" />
<p>
<span data-slot="tooltip-label">
<i /> {i18n.t("chart.daily")} {modelUsageLabel(props.metric, i18n)}
</span>
<b>{formatModelUsageValue(active(), props.metric)}</b>
</p>
</div>
)}
</Show>
</div>
)}
)}
</For>
</svg>
<span data-slot="model-momentum-end" data-state={changeState()} style={chart().endStyle}>
<i />
{formatChange(props.data.tokenChange)}
</span>
</div>
<div data-slot="model-momentum-months" aria-hidden="true">
<For each={momentumMonthLabels(chart().startDate, props.locale, props.data.updatedAt)}>
{(month) => <span style={{ left: `${month.x}%` }}>{month.label}</span>}
</For>
</div>
</div>
)
}
function modelUsageMetricValue(point: ModelUsagePoint, metric: "tokens" | "users") {
if (metric === "users") return point.users
return point.tokens
}
function formatModelUsageValue(point: ModelUsagePoint, metric: "tokens" | "users") {
if (metric === "users") return formatUsers(point.users)
return formatTokens(point.tokens)
}
function modelUsageLabel(metric: "tokens" | "users", i18n: ReturnType<typeof useI18n>) {
if (metric === "users") return i18n.t("format.users")
return i18n.t("format.tokens")
function MomentumMetric(props: { label: string; value: string; watermark?: string }) {
return (
<div data-component="model-momentum-metric">
<Show when={props.watermark}>
{(watermark) => <em aria-hidden="true">{watermark()}</em>}
</Show>
<span>{props.label}</span>
<strong>{props.value}</strong>
</div>
)
}
function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: ModelCatalogEntry | null }) {
@ -979,19 +918,110 @@ function formatGeoShare(value: number) {
return `${value.toFixed(value > 0 && value < 1 ? 1 : 0)}%`
}
function modelUsageHeight(tokens: number, max: number) {
if (tokens <= 0) return 0
return Math.max(2, Math.min(100, (tokens / max) * 100))
function momentumChart(data: ModelUsagePoint[], updatedAt: string | null) {
const fallbackDate = updatedAt ? formatMomentumDateLabel(updatedAt) : "JAN 1"
const points = data.length > 1 ? data : [data[0] ?? emptyUsagePoint(fallbackDate), data[0] ?? emptyUsagePoint(fallbackDate)]
const max = Math.max(1, ...points.map((point) => point.tokens))
const split = Math.max(1, Math.floor((points.length - 1) / 2))
const coordinates = points.map((point, index) => ({
date: point.date,
tokens: point.tokens,
x: (index / Math.max(1, points.length - 1)) * 1200,
y: 364 - (point.tokens / max) * 364,
}))
const end = coordinates[coordinates.length - 1]
return {
startDate: coordinates[0].date,
endDate: end.date,
previousPath: smoothLinePath(coordinates.slice(0, split + 1)),
currentPath: smoothLinePath(coordinates.slice(split)),
markers: [
{ ...coordinates[0], active: false },
{ ...coordinates[split], active: true },
{ ...end, active: true },
],
endStyle: {
"--momentum-end-x": `${Math.min(94, Math.max(0, ((end.x + 8) / 1200) * 100))}%`,
"--momentum-end-y": `${Math.min(96, Math.max(0, ((end.y - 7) / 370) * 100))}%`,
} as JSX.CSSProperties,
}
}
function isModelUsageDense(count: number) {
return count > 20
function emptyUsagePoint(date: string): ModelUsagePoint {
return { date, tokens: 0, users: 0, sessions: 0, cost: 0 }
}
function isModelUsageLabelHidden(index: number, count: number) {
if (count <= 16) return false
const interval = Math.ceil(count / 8)
return index !== count - 1 && index % interval !== 0
function smoothLinePath(points: { x: number; y: number }[]) {
if (points.length === 0) return ""
if (points.length === 1) return `M${formatSparklinePoint(points[0].x)} ${formatSparklinePoint(points[0].y)}`
return points
.map((point, index) => {
if (index === 0) return `M${formatSparklinePoint(point.x)} ${formatSparklinePoint(point.y)}`
const previous = points[index - 1]
const next = points[index + 1] ?? point
const beforePrevious = points[index - 2] ?? previous
const controlStart = {
x: previous.x + (point.x - beforePrevious.x) / 6,
y: previous.y + (point.y - beforePrevious.y) / 6,
}
const controlEnd = {
x: point.x - (next.x - previous.x) / 6,
y: point.y - (next.y - previous.y) / 6,
}
return `C${formatSparklinePoint(controlStart.x)} ${formatSparklinePoint(controlStart.y)} ${formatSparklinePoint(controlEnd.x)} ${formatSparklinePoint(controlEnd.y)} ${formatSparklinePoint(point.x)} ${formatSparklinePoint(point.y)}`
})
.join(" ")
}
function momentumMonthLabels(startDate: string, locale: string, updatedAt: string | null) {
const start = parseMomentumDate(startDate, updatedAt)
const first = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() - 1, 1))
return Array.from({ length: 5 }, (_, index) => {
const date = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + index, 1))
return {
label: new Intl.DateTimeFormat(locale, { month: "short", timeZone: "UTC" }).format(date).toUpperCase(),
x: index === 4 ? 98 : index * 24.5,
}
})
}
function formatMomentumDate(date: string, locale: string, updatedAt: string | null) {
return new Intl.DateTimeFormat(locale, {
month: "short",
day: "numeric",
year: "numeric",
timeZone: "UTC",
})
.format(parseMomentumDate(date, updatedAt))
.toUpperCase()
}
function parseMomentumDate(date: string, updatedAt: string | null): Date {
const iso = /^(\d{4})-(\d{2})-(\d{2})/.exec(date)
if (iso) return new Date(Date.UTC(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3])))
const label = /^([A-Za-z]{3})\s+(\d{1,2})$/.exec(date.trim())
const month = label ? shortMonths.findIndex((item) => item.toLowerCase() === label[1].toLowerCase()) : -1
if (!label || month < 0) return new Date(Date.UTC(1970, 0, 1))
const anchor = updatedAt ? parseMomentumDate(updatedAt, null) : new Date(Date.UTC(1970, 0, 1))
const year = month > anchor.getUTCMonth() + 1 ? anchor.getUTCFullYear() - 1 : anchor.getUTCFullYear()
return new Date(Date.UTC(year, month, Number(label[2])))
}
function formatMomentumDateLabel(date: string) {
const parsed = parseMomentumDate(date, null)
if (parsed.getUTCFullYear() === 1970) return "JAN 1"
return `${shortMonths[parsed.getUTCMonth()]} ${parsed.getUTCDate()}`
}
function formatRankLabel(rank: number | null) {
if (rank === null) return "--"
return `#${String(rank).padStart(2, "0")}`
}
function capitalizeLabel(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
function formatRankMove(change: number) {
@ -1050,6 +1080,28 @@ function formatTokens(value: number) {
return String(Math.round(value))
}
function formatCatalogLimit(value: number | undefined, unknown: string) {
return value === undefined ? unknown : formatTokens(value)
}
function formatCatalogMonth(value: string | undefined, locale: string, unknown: string) {
if (!value) return unknown
const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
if (!match) return value
return new Intl.DateTimeFormat(locale, {
month: match[2] ? "short" : undefined,
year: "numeric",
timeZone: "UTC",
}).format(new Date(Date.UTC(Number(match[1]), match[2] ? Number(match[2]) - 1 : 0, 1)))
}
function formatCatalogModalities(values: string[], locale: string, unknown: string) {
if (values.length === 0) return unknown
const labels = values.map((value) => value.replace(/[-_]/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase()))
if (labels.length === 1) return labels[0] ?? unknown
return new Intl.ListFormat(locale, { style: "long", type: "conjunction" }).format(labels)
}
function formatInteger(value: number) {
return new Intl.NumberFormat("en").format(value)
}

View file

@ -3335,6 +3335,321 @@
height: 24px;
}
[data-page="stats"] [data-section="model-specs"] {
position: relative;
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
min-height: 144px;
color: var(--stats-text);
box-shadow:
inset 0 1px var(--stats-line),
inset 0 -1px var(--stats-line),
inset 1px 0 var(--stats-line),
inset -1px 0 var(--stats-line);
}
[data-page="stats"] [data-component="model-spec"] {
display: grid;
align-content: start;
gap: 8px;
min-width: 0;
min-height: 144px;
box-sizing: border-box;
padding: 40px;
border-left: 1px solid var(--stats-line);
}
[data-page="stats"] [data-component="model-spec"]:first-child {
border-left: 0;
}
[data-page="stats"] [data-component="model-spec"] span {
color: var(--stats-muted);
font-size: 13px;
font-weight: 500;
line-height: 1.1;
}
[data-page="stats"] [data-component="model-spec"] strong {
min-width: 0;
overflow: hidden;
color: var(--stats-text);
font-size: 28px;
font-weight: 500;
line-height: 1.5;
letter-spacing: 0;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-section="model-momentum"] {
position: relative;
box-sizing: border-box;
display: grid;
gap: 40px;
min-height: 938px;
padding: 80px 40px;
color: var(--stats-text);
box-shadow:
inset 1px 0 var(--stats-line),
inset -1px 0 var(--stats-line);
}
[data-page="stats"] [data-slot="model-momentum-title"] {
min-width: 0;
height: 42px;
margin: 0;
overflow: hidden;
color: var(--stats-muted);
font-size: 28px;
font-weight: 400;
line-height: 42px;
letter-spacing: 0;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-slot="model-momentum-title"] a {
color: var(--stats-text);
font-weight: 700;
text-decoration: none;
}
[data-page="stats"] [data-slot="model-momentum-title"] a:hover,
[data-page="stats"] [data-slot="model-momentum-title"] a:focus-visible {
color: var(--stats-text);
outline: none;
text-decoration: none;
}
[data-page="stats"] [data-slot="model-momentum-title"] span {
margin-left: 12px;
}
[data-page="stats"] [data-slot="model-momentum-pattern"] {
width: 100%;
height: 16px;
overflow: hidden;
background: var(--stats-line);
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
mask-position: center top;
mask-repeat: repeat;
mask-size: 6px 6px;
-webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
-webkit-mask-position: center top;
-webkit-mask-repeat: repeat;
-webkit-mask-size: 6px 6px;
}
[data-page="stats"] [data-component="model-momentum-chart"] {
position: relative;
display: grid;
grid-template-rows: 64px 370px 14px;
gap: 16px;
height: 480px;
min-width: 0;
}
[data-page="stats"] [data-slot="model-momentum-summary"] {
display: grid;
align-content: start;
gap: 8px;
width: fit-content;
min-width: 0;
}
[data-page="stats"] [data-slot="model-momentum-total"] {
display: flex;
align-items: center;
gap: 16px;
min-width: 0;
height: 42px;
}
[data-page="stats"] [data-slot="model-momentum-total"] strong {
min-width: 0;
overflow: hidden;
color: var(--stats-text);
font-size: 28px;
font-weight: 700;
line-height: 42px;
letter-spacing: 0;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-slot="model-momentum-total"] span,
[data-page="stats"] [data-slot="model-momentum-end"] {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
box-sizing: border-box;
padding: 0 8px;
background: #198b4324;
color: #198b43;
font-size: 13px;
font-weight: 700;
line-height: 14px;
white-space: nowrap;
}
[data-page="stats"] [data-slot="model-momentum-total"] span[data-state="negative"],
[data-page="stats"] [data-slot="model-momentum-end"][data-state="negative"] {
background: #b82d3524;
color: #b82d35;
}
[data-page="stats"] [data-slot="model-momentum-summary"] p {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
margin: 0;
color: var(--stats-muted);
font-size: 13px;
font-weight: 500;
line-height: 14px;
white-space: nowrap;
}
[data-page="stats"] [data-slot="model-momentum-plot"] {
position: relative;
min-width: 0;
height: 370px;
}
[data-page="stats"] [data-slot="model-momentum-plot"] svg {
display: block;
width: 100%;
height: 100%;
overflow: visible;
}
[data-page="stats"] [data-slot="model-momentum-line-muted"],
[data-page="stats"] [data-slot="model-momentum-line-active"] {
fill: none;
stroke-width: 1.5;
vector-effect: non-scaling-stroke;
}
[data-page="stats"] [data-slot="model-momentum-line-muted"] {
stroke: var(--stats-bar-idle);
}
[data-page="stats"] [data-slot="model-momentum-line-active"] {
stroke: #198b43;
}
[data-page="stats"] [data-slot="model-momentum-marker"] {
fill: var(--stats-bar-idle);
}
[data-page="stats"] [data-slot="model-momentum-marker"][data-active="true"] {
fill: #198b43;
}
[data-page="stats"] [data-slot="model-momentum-end"] {
position: absolute;
top: var(--momentum-end-y);
left: var(--momentum-end-x);
gap: 8px;
height: 14px;
padding: 0;
background: transparent;
transform: translateY(-50%);
}
[data-page="stats"] [data-slot="model-momentum-end"] i {
display: block;
width: 6px;
height: 6px;
background: currentColor;
}
[data-page="stats"] [data-slot="model-momentum-months"] {
position: relative;
height: 14px;
color: var(--stats-muted);
font-size: 13px;
font-weight: 500;
line-height: 14px;
}
[data-page="stats"] [data-slot="model-momentum-months"] span {
position: absolute;
top: 0;
transform: translateX(-50%);
}
[data-page="stats"] [data-slot="model-momentum-months"] span:first-child {
transform: none;
}
[data-page="stats"] [data-slot="model-momentum-months"] span:last-child {
transform: translateX(-100%);
}
[data-page="stats"] [data-slot="model-momentum-metrics"] {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
min-width: 0;
}
[data-page="stats"] [data-component="model-momentum-metric"] {
position: relative;
display: grid;
align-content: space-between;
min-width: 0;
min-height: 120px;
box-sizing: border-box;
padding: 16px;
overflow: hidden;
border: 1px solid var(--stats-line);
background:
linear-gradient(180deg, color-mix(in srgb, var(--stats-bg) 76%, transparent), transparent),
var(--stats-layer);
}
[data-page="stats"] [data-component="model-momentum-metric"] span {
position: relative;
z-index: 1;
color: var(--stats-muted);
font-size: 13px;
font-weight: 500;
line-height: 14px;
}
[data-page="stats"] [data-component="model-momentum-metric"] strong {
position: relative;
z-index: 1;
min-width: 0;
overflow: hidden;
color: var(--stats-text);
font-size: 28px;
font-weight: 500;
line-height: 42px;
letter-spacing: 0;
text-overflow: ellipsis;
white-space: nowrap;
}
[data-page="stats"] [data-component="model-momentum-metric"] em {
position: absolute;
top: -90px;
left: -69px;
color: var(--stats-text);
font-size: 200px;
font-style: normal;
font-weight: 700;
line-height: 300px;
opacity: 0.04;
pointer-events: none;
white-space: nowrap;
}
[data-page="stats"] [data-section="lab-overview"] {
position: relative;
box-sizing: border-box;
@ -4966,6 +5281,7 @@
[data-page="stats"] [data-section="session-cost"],
[data-page="stats"] [data-section="model-hero"],
[data-page="stats"] [data-section="lab-hero"],
[data-page="stats"] [data-section="model-momentum"],
[data-page="stats"] [data-section="model-panel"] {
padding: 64px 32px;
}
@ -5056,6 +5372,48 @@
min-height: 0;
}
[data-page="stats"] [data-section="model-specs"] {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
[data-page="stats"] [data-component="model-spec"] {
min-height: 132px;
padding: 32px;
}
[data-page="stats"] [data-component="model-spec"]:nth-child(3n + 1) {
border-left: 0;
}
[data-page="stats"] [data-component="model-spec"]:nth-child(n + 4) {
border-top: 1px solid var(--stats-line);
}
[data-page="stats"] [data-section="model-momentum"] {
min-height: auto;
}
[data-page="stats"] [data-slot="model-momentum-title"] {
height: auto;
overflow: visible;
line-height: 1.5;
text-overflow: clip;
white-space: normal;
}
[data-page="stats"] [data-component="model-momentum-chart"] {
grid-template-rows: auto 320px 14px;
height: 434px;
}
[data-page="stats"] [data-slot="model-momentum-plot"] {
height: 320px;
}
[data-page="stats"] [data-slot="model-momentum-metrics"] {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
[data-page="stats"] [data-component="model-metric-grid"],
[data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
[data-page="stats"] [data-component="lab-model-grid"] {
@ -5184,6 +5542,7 @@
[data-page="stats"] [data-section="session-cost"],
[data-page="stats"] [data-section="model-hero"],
[data-page="stats"] [data-section="lab-hero"],
[data-page="stats"] [data-section="model-momentum"],
[data-page="stats"] [data-section="model-panel"] {
padding: 48px 24px;
}
@ -5351,6 +5710,90 @@
line-height: 22px;
}
[data-page="stats"] [data-section="model-specs"] {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
[data-page="stats"] [data-component="model-spec"] {
min-height: 116px;
padding: 24px;
}
[data-page="stats"] [data-component="model-spec"]:nth-child(n) {
border-left: 1px solid var(--stats-line);
border-top: 0;
}
[data-page="stats"] [data-component="model-spec"]:nth-child(2n + 1) {
border-left: 0;
}
[data-page="stats"] [data-component="model-spec"]:nth-child(n + 3) {
border-top: 1px solid var(--stats-line);
}
[data-page="stats"] [data-component="model-spec"]:last-child:nth-child(odd) {
grid-column: 1 / -1;
}
[data-page="stats"] [data-component="model-spec"] strong {
font-size: 24px;
line-height: 1.35;
}
[data-page="stats"] [data-section="model-momentum"] {
gap: 28px;
}
[data-page="stats"] [data-slot="model-momentum-title"] {
font-size: 20px;
line-height: 30px;
}
[data-page="stats"] [data-slot="model-momentum-title"] span {
margin-left: 8px;
}
[data-page="stats"] [data-component="model-momentum-chart"] {
grid-template-rows: auto 260px 14px;
gap: 14px;
height: 380px;
}
[data-page="stats"] [data-slot="model-momentum-total"] {
align-items: flex-start;
flex-direction: column;
gap: 6px;
height: auto;
}
[data-page="stats"] [data-slot="model-momentum-total"] strong {
font-size: 24px;
line-height: 32px;
}
[data-page="stats"] [data-slot="model-momentum-summary"] p {
flex-wrap: wrap;
white-space: normal;
}
[data-page="stats"] [data-slot="model-momentum-plot"] {
height: 260px;
}
[data-page="stats"] [data-slot="model-momentum-end"] {
display: none;
}
[data-page="stats"] [data-component="model-momentum-metric"] {
min-height: 112px;
}
[data-page="stats"] [data-component="model-momentum-metric"] strong {
font-size: clamp(20px, 5.5vw, 24px);
line-height: 34px;
}
[data-page="stats"] [data-slot="model-catalog-grid"] {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));