Combine top models and leaderboard sections

This commit is contained in:
Adam 2026-05-29 13:55:05 -05:00
commit c977cb7f31
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
2 changed files with 212 additions and 112 deletions

View file

@ -1033,7 +1033,7 @@
grid-template-rows: 34px minmax(0, 1fr); grid-template-rows: 34px minmax(0, 1fr);
gap: 12px; gap: 12px;
width: 100%; width: 100%;
height: 560px; height: clamp(320px, 44vh, 400px);
} }
[data-page="stats"] [data-slot="top-models-axis"], [data-page="stats"] [data-slot="top-models-axis"],
@ -1418,7 +1418,7 @@
} }
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] { [data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
margin-top: 40px; margin-top: 32px;
} }
[data-page="stats"] [data-component="usage-filter"] { [data-page="stats"] [data-component="usage-filter"] {
@ -1706,27 +1706,15 @@
font-weight: 400; font-weight: 400;
} }
[data-page="stats"] [data-component="leaderboard"],
[data-page="stats"] [data-slot="leaderboard-featured"],
[data-page="stats"] [data-slot="leaderboard-compact"],
[data-page="stats"] [data-slot="leaderboard-column"],
[data-page="stats"] [data-slot="leaderboard-mobile"] {
display: grid;
}
[data-page="stats"] [data-component="leaderboard"] { [data-page="stats"] [data-component="leaderboard"] {
gap: 0; display: block;
} width: 100%;
[data-page="stats"] [data-slot="leaderboard-featured"],
[data-page="stats"] [data-slot="leaderboard-compact"] {
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
gap: 8px;
} }
[data-page="stats"] [data-slot="leaderboard-pattern"] { [data-page="stats"] [data-slot="leaderboard-pattern"] {
scroll-margin-top: 88px;
height: 16px; height: 16px;
margin: 16px 0; margin: 32px 0 16px;
overflow: hidden; overflow: hidden;
background: var(--stats-hero-pattern); background: var(--stats-hero-pattern);
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-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");
@ -1739,16 +1727,28 @@
-webkit-mask-size: 6px 6px; -webkit-mask-size: 6px 6px;
} }
[data-page="stats"] [data-slot="leaderboard-column"] { [data-page="stats"] [data-slot="leaderboard-scroll"] {
box-sizing: border-box;
display: flex;
gap: 8px; gap: 8px;
width: calc(100% + 80px);
margin-inline: -40px;
padding: 4px 40px 8px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-padding-inline: 40px;
scroll-snap-type: x proximity;
scrollbar-width: none;
} }
[data-page="stats"] [data-slot="leaderboard-mobile"] { [data-page="stats"] [data-slot="leaderboard-scroll"]::-webkit-scrollbar {
display: none; display: none;
} }
[data-page="stats"] [data-section="leaderboard"] [data-slot="chart-footer"] { [data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
margin-top: 32px; flex: 0 0 clamp(240px, 31vw, 360px);
width: clamp(240px, 31vw, 360px);
scroll-snap-align: start;
} }
[data-page="stats"] [data-component="leader-card"] { [data-page="stats"] [data-component="leader-card"] {
@ -1889,6 +1889,26 @@
color: #b82d35; color: #b82d35;
} }
[data-page="stats"] [data-component="leader-card"][data-active="true"] {
border-color: var(--stats-line-strong);
background: #ffffff;
box-shadow:
0 0 0 0.5px #00000024,
0 6px 16px #0000000d,
0 2px 6px #0000000f;
transform: translateY(-1px);
}
[data-page="stats"] [data-component="leader-card"][data-active="true"] [data-slot="leader-watermark"] {
color: var(--stats-layer-2);
opacity: 0.86;
}
[data-page="stats"] [data-component="leader-card"][data-active="true"] [data-slot="leader-avatar"] {
border-color: var(--stats-line-strong);
color: var(--stats-text);
}
@media (hover: hover) { @media (hover: hover) {
[data-page="stats"] [data-component="leader-card"]:hover, [data-page="stats"] [data-component="leader-card"]:hover,
[data-page="stats"] [data-component="leader-card"]:focus-visible { [data-page="stats"] [data-component="leader-card"]:focus-visible {
@ -2331,8 +2351,12 @@
0 2px 4px #0000003d; 0 2px 4px #0000003d;
} }
[data-page="stats"][data-theme="dark"] [data-component="leader-card"][data-active="true"],
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover, [data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover,
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible, [data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible,
:root[data-stats-theme="dark"]
[data-page="stats"]:not([data-theme="light"])
[data-component="leader-card"][data-active="true"],
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="leader-card"]:hover, :root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="leader-card"]:hover,
:root[data-stats-theme="dark"] :root[data-stats-theme="dark"]
[data-page="stats"]:not([data-theme="light"]) [data-page="stats"]:not([data-theme="light"])
@ -2344,8 +2368,13 @@
0 2px 6px #00000052; 0 2px 6px #00000052;
} }
[data-page="stats"][data-theme="dark"] [data-component="leader-card"][data-active="true"] [data-slot="leader-watermark"],
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover [data-slot="leader-watermark"], [data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover [data-slot="leader-watermark"],
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible [data-slot="leader-watermark"], [data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible [data-slot="leader-watermark"],
:root[data-stats-theme="dark"]
[data-page="stats"]:not([data-theme="light"])
[data-component="leader-card"][data-active="true"]
[data-slot="leader-watermark"],
:root[data-stats-theme="dark"] :root[data-stats-theme="dark"]
[data-page="stats"]:not([data-theme="light"]) [data-page="stats"]:not([data-theme="light"])
[data-component="leader-card"]:hover [data-component="leader-card"]:hover
@ -2455,6 +2484,9 @@
0 2px 4px #0000003d; 0 2px 4px #0000003d;
} }
:root:not([data-stats-theme="light"])
[data-page="stats"]:not([data-theme="light"])
[data-component="leader-card"][data-active="true"],
:root:not([data-stats-theme="light"]) :root:not([data-stats-theme="light"])
[data-page="stats"]:not([data-theme="light"]) [data-page="stats"]:not([data-theme="light"])
[data-component="leader-card"]:hover, [data-component="leader-card"]:hover,
@ -2468,6 +2500,10 @@
0 2px 6px #00000052; 0 2px 6px #00000052;
} }
:root:not([data-stats-theme="light"])
[data-page="stats"]:not([data-theme="light"])
[data-component="leader-card"][data-active="true"]
[data-slot="leader-watermark"],
:root:not([data-stats-theme="light"]) :root:not([data-stats-theme="light"])
[data-page="stats"]:not([data-theme="light"]) [data-page="stats"]:not([data-theme="light"])
[data-component="leader-card"]:hover [data-component="leader-card"]:hover
@ -2560,6 +2596,14 @@
[data-page="stats"] [data-section="session-cost"] { [data-page="stats"] [data-section="session-cost"] {
padding: 64px 32px; padding: 64px 32px;
} }
[data-page="stats"] [data-slot="leaderboard-scroll"] {
width: calc(100% + 64px);
margin-inline: -32px;
padding-right: 32px;
padding-left: 32px;
scroll-padding-inline: 32px;
}
} }
@media (max-width: 58rem) { @media (max-width: 58rem) {
@ -2603,9 +2647,9 @@
flex-wrap: nowrap; flex-wrap: nowrap;
} }
[data-page="stats"] [data-slot="leaderboard-featured"], [data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
[data-page="stats"] [data-slot="leaderboard-compact"] { flex-basis: 280px;
grid-template-columns: 1fr; width: 280px;
} }
[data-page="stats"] [data-component="leader-card"][data-size="featured"], [data-page="stats"] [data-component="leader-card"][data-size="featured"],
@ -2701,19 +2745,62 @@
[data-page="stats"] [data-slot="top-models-mobile-controls"] { [data-page="stats"] [data-slot="top-models-mobile-controls"] {
display: flex; display: flex;
gap: 8px; gap: 8px;
margin-bottom: 32px; width: 100%;
} }
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] { [data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
display: flex;
margin-top: 24px;
}
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] [data-component="usage-filter"] {
display: none; display: none;
} }
[data-page="stats"] [data-slot="leaderboard-featured"], [data-page="stats"] [data-slot="leaderboard-featured"],
[data-page="stats"] [data-slot="leaderboard-pattern"],
[data-page="stats"] [data-slot="leaderboard-compact"] { [data-page="stats"] [data-slot="leaderboard-compact"] {
display: none; display: none;
} }
[data-page="stats"] [data-slot="leaderboard-pattern"] {
margin: 24px 0 12px;
}
[data-page="stats"] [data-slot="leaderboard-scroll"] {
gap: 12px;
width: calc(100% + 48px);
margin-inline: -24px;
padding-right: 24px;
padding-left: 24px;
scroll-padding-inline: 24px;
}
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
flex: 0 0 240px;
width: 240px;
min-height: 156px;
gap: 32px;
}
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-slot="leader-body"] {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-slot="leader-avatar"] {
width: 28px;
height: 28px;
padding: 4px;
border-radius: 6px;
}
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-slot="leader-watermark"] {
right: -68px;
width: 240px;
height: 240px;
}
[data-page="stats"] [data-slot="leaderboard-mobile"] { [data-page="stats"] [data-slot="leaderboard-mobile"] {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
@ -2761,7 +2848,7 @@
[data-page="stats"] [data-component="top-models-chart"] { [data-page="stats"] [data-component="top-models-chart"] {
grid-template-rows: 40px minmax(0, 1fr); grid-template-rows: 40px minmax(0, 1fr);
height: 400px; height: 360px;
} }
[data-page="stats"] [data-component="market-share"] { [data-page="stats"] [data-component="market-share"] {

View file

@ -159,8 +159,7 @@ export default function StatsHome() {
{(stats) => ( {(stats) => (
<> <>
<Hero updatedAt={stats().updatedAt} /> <Hero updatedAt={stats().updatedAt} />
<TopModelsSection data={stats().usage} /> <TopModelsSection data={stats().usage} leaderboard={stats().leaderboard} />
<LeaderboardSection data={stats().leaderboard} />
<MarketShareSection data={stats().market} /> <MarketShareSection data={stats().market} />
<TokenCostSection data={stats().tokenCost} /> <TokenCostSection data={stats().tokenCost} />
<SessionCostSection data={stats().sessionCost} /> <SessionCostSection data={stats().sessionCost} />
@ -368,11 +367,13 @@ function formatUpdatedAtLabel(value: { date: string; time: string }) {
return `${value.date}, ${value.time}` return `${value.date}, ${value.time}`
} }
function TopModelsSection(props: { data: StatsHomeData["usage"] }) { function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: StatsHomeData["leaderboard"] }) {
const [product, setProduct] = createSignal<UsageProduct>("All Users") const [product, setProduct] = createSignal<UsageProduct>("All Users")
const [range, setRange] = createSignal<UsageRange>("2M") const [range, setRange] = createSignal<UsageRange>("2M")
const [sheet, setSheet] = createSignal<"product" | "range">() const [sheet, setSheet] = createSignal<"product" | "range">()
const [activeModel, setActiveModel] = createSignal<string>()
const data = createMemo(() => props.data[product()][range()]) const data = createMemo(() => props.data[product()][range()])
const leaderboard = createMemo(() => props.leaderboard[product()][range()])
createEffect(() => { createEffect(() => {
if (!sheet()) return if (!sheet()) return
@ -397,28 +398,42 @@ function TopModelsSection(props: { data: StatsHomeData["usage"] }) {
<h2 data-slot="top-models-title"> <h2 data-slot="top-models-title">
<strong>Top models.</strong> <span>Usage of models across OpenCode.</span> <strong>Top models.</strong> <span>Usage of models across OpenCode.</span>
</h2> </h2>
<div data-slot="top-models-mobile-controls">
<MobileFilterButton
label="Product filter"
value={product()}
expanded={sheet() === "product"}
onClick={() => setSheet(sheet() === "product" ? undefined : "product")}
/>
<MobileFilterButton
label="Date range"
value={range()}
expanded={sheet() === "range"}
onClick={() => setSheet(sheet() === "range" ? undefined : "range")}
/>
</div>
<Show <Show
when={data().some((item) => usageTotal(item) > 0)} when={data().some((item) => usageTotal(item) > 0)}
fallback={<EmptyState title="No usage data" description="No model_stat rows matched this product and range." />} fallback={<EmptyState title="No usage data" description="No model_stat rows matched this product and range." />}
> >
<TopModelsChart data={data()} range={range()} /> <TopModelsChart
data={data()}
range={range()}
activeModel={activeModel()}
onActiveModelChange={setActiveModel}
/>
</Show>
<div id="leaderboard" data-slot="leaderboard-pattern" aria-hidden="true" />
<Show
when={leaderboard().length > 0}
fallback={
<EmptyState title="No leaderboard data" description="No model_stat rows matched this product and range." />
}
>
<Leaderboard data={leaderboard()} activeModel={activeModel()} onActiveModelChange={setActiveModel} />
</Show> </Show>
<div data-slot="chart-footer"> <div data-slot="chart-footer">
<StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} /> <StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
<div data-slot="top-models-mobile-controls">
<MobileFilterButton
label="Product filter"
value={product()}
expanded={sheet() === "product"}
onClick={() => setSheet(sheet() === "product" ? undefined : "product")}
/>
<MobileFilterButton
label="Date range"
value={range()}
expanded={sheet() === "range"}
onClick={() => setSheet(sheet() === "range" ? undefined : "range")}
/>
</div>
</div> </div>
<Show when={sheet()}> <Show when={sheet()}>
{(kind) => ( {(kind) => (
@ -575,9 +590,13 @@ function FilterPills<T extends string>(props: {
) )
} }
function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) { function TopModelsChart(props: {
data: UsagePoint[]
range: UsageRange
activeModel: string | undefined
onActiveModelChange: (model: string | undefined) => void
}) {
const [activeIndex, setActiveIndex] = createSignal<number>() const [activeIndex, setActiveIndex] = createSignal<number>()
const [activeSegment, setActiveSegment] = createSignal<number>()
const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data)) const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data))
const activePoint = createMemo(() => props.data[activeIndex() ?? -1]) const activePoint = createMemo(() => props.data[activeIndex() ?? -1])
@ -622,31 +641,34 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
onPointerDown={(event) => { onPointerDown={(event) => {
if (event.pointerType !== "touch") return if (event.pointerType !== "touch") return
setActiveIndex(dayIndex()) setActiveIndex(dayIndex())
setActiveSegment(undefined) props.onActiveModelChange(undefined)
}} }}
onPointerEnter={() => { onPointerEnter={() => {
setActiveIndex(dayIndex()) setActiveIndex(dayIndex())
setActiveSegment(undefined) props.onActiveModelChange(undefined)
}} }}
onPointerLeave={(event) => { onPointerLeave={(event) => {
if (event.pointerType === "touch") return if (event.pointerType === "touch") return
setActiveIndex(undefined) setActiveIndex(undefined)
setActiveSegment(undefined) props.onActiveModelChange(undefined)
}}
onClick={() => {
setActiveIndex(dayIndex())
props.onActiveModelChange(undefined)
}} }}
onClick={() => setActiveIndex(dayIndex())}
onFocus={() => { onFocus={() => {
setActiveIndex(dayIndex()) setActiveIndex(dayIndex())
setActiveSegment(undefined) props.onActiveModelChange(undefined)
}} }}
onBlur={() => { onBlur={() => {
setActiveIndex(undefined) setActiveIndex(undefined)
setActiveSegment(undefined) props.onActiveModelChange(undefined)
}} }}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key !== "Enter" && event.key !== " ") return if (event.key !== "Enter" && event.key !== " ") return
event.preventDefault() event.preventDefault()
setActiveIndex(dayIndex()) setActiveIndex(dayIndex())
setActiveSegment(undefined) props.onActiveModelChange(undefined)
}} }}
> >
<div data-slot="top-models-stack" style={{ "grid-template-rows": getTopModelsSegmentRows(day) }}> <div data-slot="top-models-stack" style={{ "grid-template-rows": getTopModelsSegmentRows(day) }}>
@ -654,28 +676,30 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
{(item) => ( {(item) => (
<i <i
data-series={item.index} data-series={item.index}
data-active={activeSegment() === item.index ? "true" : undefined} data-model={item.segment.model}
data-active={props.activeModel === item.segment.model ? "true" : undefined}
style={{ style={{
background: getTopModelsSegmentColor( background: getTopModelsSegmentColor(
item.segment.model,
item.index, item.index,
activeIndex() !== undefined && activeIndex() !== dayIndex(), activeIndex() !== undefined && activeIndex() !== dayIndex(),
activeSegment(), props.activeModel,
), ),
}} }}
onPointerEnter={(event) => { onPointerEnter={(event) => {
event.stopPropagation() event.stopPropagation()
setActiveIndex(dayIndex()) setActiveIndex(dayIndex())
setActiveSegment(item.index) props.onActiveModelChange(item.segment.model)
}} }}
onPointerDown={(event) => { onPointerDown={(event) => {
event.stopPropagation() event.stopPropagation()
setActiveIndex(dayIndex()) setActiveIndex(dayIndex())
setActiveSegment(item.index) props.onActiveModelChange(item.segment.model)
}} }}
onClick={(event) => { onClick={(event) => {
event.stopPropagation() event.stopPropagation()
setActiveIndex(dayIndex()) setActiveIndex(dayIndex())
setActiveSegment(item.index) props.onActiveModelChange(item.segment.model)
}} }}
/> />
)} )}
@ -693,9 +717,11 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
<For each={visibleTopModelsSegments(point())}> <For each={visibleTopModelsSegments(point())}>
{(item) => ( {(item) => (
<p <p
data-active={activeSegment() === item.index ? "true" : undefined} data-active={props.activeModel === item.segment.model ? "true" : undefined}
data-muted={ data-muted={
activeSegment() !== undefined && activeSegment() !== item.index ? "true" : undefined props.activeModel !== undefined && props.activeModel !== item.segment.model
? "true"
: undefined
} }
> >
<span data-slot="tooltip-label"> <span data-slot="tooltip-label">
@ -746,9 +772,14 @@ function stackedTopModelsSegments(point: UsagePoint) {
.sort((a, b) => a.segment.value - b.segment.value || a.index - b.index) .sort((a, b) => a.segment.value - b.segment.value || a.index - b.index)
} }
function getTopModelsSegmentColor(index: number, muted: boolean, activeSegment: number | undefined) { function getTopModelsSegmentColor(
if (activeSegment !== undefined) model: string,
return activeSegment === index ? (usageColors[index] ?? "var(--stats-text)") : "var(--stats-layer-2)" index: number,
muted: boolean,
activeModel: string | undefined,
) {
if (activeModel !== undefined)
return activeModel === model ? (usageColors[index] ?? "var(--stats-text)") : "var(--stats-layer-2)"
if (muted) return "var(--stats-layer-2)" if (muted) return "var(--stats-layer-2)"
return usageColors[index] ?? "var(--stats-text)" return usageColors[index] ?? "var(--stats-text)"
} }
@ -781,67 +812,49 @@ function formatTokens(value: number) {
return `${Math.round(value * 1000)}B` return `${Math.round(value * 1000)}B`
} }
function LeaderboardSection(props: { data: StatsHomeData["leaderboard"] }) { function Leaderboard(props: {
const [product, setProduct] = createSignal<UsageProduct>("All Users") data: LeaderboardEntry[]
const [range, setRange] = createSignal<UsageRange>("2M") activeModel: string | undefined
const data = createMemo(() => props.data[product()][range()]) onActiveModelChange: (model: string | undefined) => void
}) {
return (
<section id="leaderboard" data-section="leaderboard">
<SectionTitle
title="Leaderboard"
description="Shown are the sum of prompt and completion tokens per model, including reasoning tokens."
/>
<Show
when={data().length > 0}
fallback={
<EmptyState title="No leaderboard data" description="No model_stat rows matched this product and range." />
}
>
<Leaderboard data={data()} />
</Show>
<div data-slot="chart-footer">
<StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
</div>
</section>
)
}
function Leaderboard(props: { data: LeaderboardEntry[] }) {
const featured = createMemo(() => props.data.slice(0, 3))
const columns = createMemo(() =>
[0, 1, 2].map((index) => props.data.slice(3 + index * 5, 8 + index * 5)).filter((column) => column.length > 0),
)
return ( return (
<div data-component="leaderboard" role="list" aria-label="Model token leaderboard"> <div data-component="leaderboard" role="list" aria-label="Model token leaderboard">
<div data-slot="leaderboard-featured"> <div data-slot="leaderboard-scroll" aria-label="Scrollable model token leaderboard">
<For each={featured()}>{(entry) => <LeaderboardCard entry={entry} size="featured" />}</For> <For each={props.data}>
</div> {(entry) => (
<div data-slot="leaderboard-pattern" aria-hidden="true" /> <LeaderboardCard
<div data-slot="leaderboard-compact"> entry={entry}
<For each={columns()}> active={props.activeModel === entry.model}
{(column) => ( onActiveModelChange={props.onActiveModelChange}
<div data-slot="leaderboard-column"> />
<For each={column}>{(entry) => <LeaderboardCard entry={entry} size="compact" />}</For>
</div>
)} )}
</For> </For>
</div> </div>
<div data-slot="leaderboard-mobile" aria-label="Scrollable model token leaderboard">
<For each={props.data}>{(entry) => <LeaderboardCard entry={entry} size="featured" />}</For>
</div>
</div> </div>
) )
} }
function LeaderboardCard(props: { entry: LeaderboardEntry; size: "featured" | "compact" }) { function LeaderboardCard(props: {
entry: LeaderboardEntry
active: boolean
onActiveModelChange: (model: string | undefined) => void
}) {
return ( return (
<article <article
data-component="leader-card" data-component="leader-card"
data-size={props.size} data-size="featured"
data-active={props.active ? "true" : undefined}
role="listitem" role="listitem"
tabIndex={0}
aria-label={`${String(props.entry.rank).padStart(2, "0")} ${props.entry.model} by ${props.entry.author}`} aria-label={`${String(props.entry.rank).padStart(2, "0")} ${props.entry.model} by ${props.entry.author}`}
onPointerEnter={() => props.onActiveModelChange(props.entry.model)}
onPointerLeave={(event) => {
if (event.pointerType === "touch") return
props.onActiveModelChange(undefined)
}}
onFocus={() => props.onActiveModelChange(props.entry.model)}
onBlur={() => props.onActiveModelChange(undefined)}
onClick={() => props.onActiveModelChange(props.entry.model)}
> >
<span data-slot="rank">{String(props.entry.rank).padStart(2, "0")}</span> <span data-slot="rank">{String(props.entry.rank).padStart(2, "0")}</span>
<ProviderIcon data-slot="leader-watermark" aria-hidden="true" id={getProviderIconId(props.entry.author)} /> <ProviderIcon data-slot="leader-watermark" aria-hidden="true" id={getProviderIconId(props.entry.author)} />