fix(stats): improve mobile charts

This commit is contained in:
Adam
2026-05-29 07:27:30 -05:00
parent 6102fb2e3f
commit fb6275e04a
2 changed files with 100 additions and 23 deletions

View File

@@ -1719,6 +1719,7 @@
[data-page="stats"] [data-slot="leader-copy"] strong { [data-page="stats"] [data-slot="leader-copy"] strong {
color: var(--stats-text); color: var(--stats-text);
font-weight: 600; font-weight: 600;
line-height: 1.3;
} }
[data-page="stats"] [data-slot="leader-copy"] span { [data-page="stats"] [data-slot="leader-copy"] span {
@@ -1799,6 +1800,14 @@
font-weight: 600; font-weight: 600;
} }
[data-page="stats"] [data-slot="market-axis-label"] {
display: grid;
}
[data-page="stats"] [data-slot="market-date-mobile"] {
display: none;
}
[data-page="stats"] [data-slot="market-bars"] { [data-page="stats"] [data-slot="market-bars"] {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -1977,23 +1986,21 @@
} }
[data-page="stats"] [data-component="metric-bar"] { [data-page="stats"] [data-component="metric-bar"] {
display: flex; position: relative;
align-items: center; display: block;
gap: 3px;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
height: 5px; height: 5px;
background: var(--stats-layer-2);
font-style: normal; font-style: normal;
} }
[data-page="stats"] [data-component="metric-bar"] b,
[data-page="stats"] [data-component="metric-bar"] em {
display: block;
height: 5px;
}
[data-page="stats"] [data-component="metric-bar"] b { [data-page="stats"] [data-component="metric-bar"] b {
flex-basis: 0; position: absolute;
inset: 0 auto 0 0;
display: block;
width: var(--metric-bar-fill);
height: 5px;
background: var(--stats-text); background: var(--stats-text);
} }
@@ -2002,12 +2009,10 @@
} }
[data-page="stats"] [data-component="metric-bar"] em { [data-page="stats"] [data-component="metric-bar"] em {
flex: 1; display: none;
min-width: 8px;
background: var(--stats-layer-2);
} }
[data-page="stats"] [data-component="metric-bar"][data-active="true"] em { [data-page="stats"] [data-component="metric-bar"][data-active="true"] {
background: var(--stats-line-strong); background: var(--stats-line-strong);
} }
@@ -2311,6 +2316,13 @@
grid-template-rows: none; grid-template-rows: none;
} }
[data-page="stats"] [data-component="token-tooltip"] {
position: static;
order: -1;
width: 100%;
margin-bottom: 8px;
}
[data-page="stats"] [data-slot="market-footer"] { [data-page="stats"] [data-slot="market-footer"] {
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
@@ -2376,6 +2388,11 @@
height: 400px; height: 400px;
} }
[data-page="stats"] [data-component="market-share"] {
grid-template-rows: 40px minmax(0, 1fr);
height: 400px;
}
[data-page="stats"] [data-slot="top-models-axis"] > div { [data-page="stats"] [data-slot="top-models-axis"] > div {
position: relative; position: relative;
display: flex; display: flex;
@@ -2385,6 +2402,14 @@
font-weight: 600; font-weight: 600;
} }
[data-page="stats"] [data-slot="market-labels"] button {
position: relative;
align-items: center;
justify-content: center;
height: 40px;
line-height: 1.2;
}
[data-page="stats"] [data-slot="axis-label"] { [data-page="stats"] [data-slot="axis-label"] {
position: absolute; position: absolute;
left: 50%; left: 50%;
@@ -2394,16 +2419,35 @@
transform-origin: left center; transform-origin: left center;
} }
[data-page="stats"] [data-slot="market-axis-label"] {
position: absolute;
left: 50%;
width: max-content;
max-width: 72px;
transform: rotate(-90deg) translateX(-50%);
transform-origin: left center;
}
[data-page="stats"] [data-slot="top-models-axis"] > div[data-mobile-hidden="true"] [data-slot="axis-label"], [data-page="stats"] [data-slot="top-models-axis"] > div[data-mobile-hidden="true"] [data-slot="axis-label"],
[data-page="stats"] [data-slot="axis-total"], [data-page="stats"] [data-slot="axis-total"],
[data-page="stats"] [data-slot="axis-date-full"] { [data-page="stats"] [data-slot="axis-date-full"] {
display: none; display: none;
} }
[data-page="stats"] [data-slot="market-labels"] button[data-mobile-hidden="true"] [data-slot="market-axis-label"],
[data-page="stats"] [data-slot="market-total"],
[data-page="stats"] [data-slot="market-date-full"] {
display: none;
}
[data-page="stats"] [data-slot="axis-date-mobile"] { [data-page="stats"] [data-slot="axis-date-mobile"] {
display: block; display: block;
} }
[data-page="stats"] [data-slot="market-date-mobile"] {
display: block;
}
[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] { [data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] {
position: fixed; position: fixed;
top: auto; top: auto;

View File

@@ -864,11 +864,17 @@ function MarketShare(props: {
<button <button
type="button" type="button"
data-active={props.inspecting && props.activeIndex === index() ? "true" : undefined} data-active={props.inspecting && props.activeIndex === index() ? "true" : undefined}
data-mobile-hidden={isMarketMobileLabelHidden(index(), props.data.length) ? "true" : undefined}
onClick={() => props.onActiveIndexChange(index())} onClick={() => props.onActiveIndexChange(index())}
onPointerEnter={() => props.onActiveIndexChange(index())} onPointerEnter={() => props.onActiveIndexChange(index())}
> >
<span>{formatTrillions(day.total)}</span> <span data-slot="market-axis-label">
<span>{day.date}</span> <span data-slot="market-total">{formatTrillions(day.total)}</span>
<span data-slot="market-date">
<span data-slot="market-date-full">{day.date}</span>
<span data-slot="market-date-mobile">{formatMarketMobileDate(day.date)}</span>
</span>
</span>
</button> </button>
)} )}
</For> </For>
@@ -964,21 +970,43 @@ function getMarketSegmentColor(author: string, color: string, activeAuthor: stri
return "var(--stats-bar-idle)" return "var(--stats-bar-idle)"
} }
function isMarketMobileLabelHidden(index: number, count: number) {
return count > 7 && index % 2 === 1
}
function formatMarketMobileDate(label: string) {
return marketDateParts(label).start
}
function formatTrillions(value: number) { function formatTrillions(value: number) {
return `${value.toFixed(value >= 10 ? 0 : 1)}T` return `${value.toFixed(value >= 10 ? 0 : 1)}T`
} }
function formatMarketDate(day: MarketDay | undefined) { function formatMarketDate(day: MarketDay | undefined) {
if (!day) return "No data" if (!day) return "No data"
return `${day.date} ${new Date().getFullYear()}` return formatMarketDateLabel(day.date)
} }
function formatMarketRange(data: MarketDay[]) { function formatMarketRange(data: MarketDay[]) {
const first = data[0]?.date const first = data[0]?.date
const last = data[data.length - 1]?.date const last = data[data.length - 1]?.date
if (!first || !last) return "No data" if (!first || !last) return "No data"
const start = marketDateParts(first).start
const end = marketDateParts(last).end
if (start === end) return formatMarketDateLabel(start)
return `${start} ${new Date().getFullYear()}${end} ${new Date().getFullYear()}`
}
function formatMarketDateLabel(label: string) {
const parts = marketDateParts(label)
const year = new Date().getFullYear() const year = new Date().getFullYear()
return `${first} ${year}${last} ${year}` if (parts.start === parts.end) return `${parts.start} ${year}`
return `${parts.start} ${year}${parts.end} ${year}`
}
function marketDateParts(label: string) {
const [start, end] = label.split(" - ")
return { start: start ?? label, end: end ?? start ?? label }
} }
function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) { function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) {
@@ -1019,7 +1047,7 @@ function TokenCostChart(props: {
activeIndex: number activeIndex: number
onActiveIndexChange: (index: number) => void onActiveIndexChange: (index: number) => void
}) { }) {
const max = createMemo(() => Math.max(1, ...props.data.map((item) => item.total))) const max = createMemo(() => Math.max(0, ...props.data.map((item) => item.total)) || 1)
const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0]) const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0])
return ( return (
@@ -1066,9 +1094,14 @@ function formatDollars(value: number) {
} }
function MetricBar(props: { value: number; max: number; active: boolean }) { function MetricBar(props: { value: number; max: number; active: boolean }) {
const fill = createMemo(() => Math.min(1, Math.max(props.value / props.max, props.value > 0 ? 0.03 : 0)))
return ( return (
<i data-component="metric-bar" data-active={props.active ? "true" : undefined}> <i
<b style={{ "flex-grow": Math.max(props.value / Math.max(props.max, 1), 0.05) }} /> data-component="metric-bar"
data-active={props.active ? "true" : undefined}
style={{ "--metric-bar-fill": `${fill() * 100}%` } as JSX.CSSProperties}
>
<b />
<em /> <em />
</i> </i>
) )
@@ -1115,8 +1148,8 @@ function SessionCostChart(props: {
activeIndex: number activeIndex: number
onActiveIndexChange: (index: number) => void onActiveIndexChange: (index: number) => void
}) { }) {
const maxCost = createMemo(() => Math.max(1, ...props.data.map((item) => item.cost))) const maxCost = createMemo(() => Math.max(0, ...props.data.map((item) => item.cost)) || 1)
const maxTokens = createMemo(() => Math.max(1, ...props.data.map((item) => item.tokens))) const maxTokens = createMemo(() => Math.max(0, ...props.data.map((item) => item.tokens)) || 1)
const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0]) const active = createMemo(() => props.data[props.activeIndex] ?? props.data[0])
return ( return (