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

feat(stats): refine top models chart scaling

Adam 3 месяцев назад
Родитель
Сommit
f55b70b4e3

+ 392 - 9
packages/stats/app/src/routes/index.css

@@ -407,6 +407,9 @@
 [data-page="stats"] [data-section="chart"],
 [data-page="stats"] [data-section="newsletter"] {
   border-bottom: 1px solid var(--stats-line);
+  box-shadow:
+    inset 1px 0 var(--stats-line),
+    inset -1px 0 var(--stats-line);
   padding: var(--stats-section-padding) var(--stats-page-padding);
 }
 
@@ -590,6 +593,208 @@
   min-height: 0;
 }
 
+[data-page="stats"] [data-section="top-models"] {
+  box-sizing: border-box;
+  margin-top: 1px;
+  box-shadow:
+    0 -1px var(--stats-line),
+    inset 0 -1px var(--stats-line),
+    inset 1px 0 var(--stats-line),
+    inset -1px 0 var(--stats-line);
+  padding: 80px 40px;
+  color: var(--stats-text);
+}
+
+[data-page="stats"] [data-slot="top-models-title"] {
+  max-width: 1200px;
+  margin-bottom: 40px;
+  color: var(--stats-muted);
+  font-size: 28px;
+  font-weight: 400;
+  line-height: 1.5;
+  letter-spacing: 0;
+}
+
+[data-page="stats"] [data-slot="top-models-title"] strong {
+  color: var(--stats-text);
+  font-weight: 500;
+}
+
+[data-page="stats"] [data-slot="top-models-title"] span {
+  color: var(--stats-muted);
+  font-weight: 400;
+}
+
+[data-page="stats"] [data-slot="top-models-mobile-controls"] {
+  display: none;
+}
+
+[data-page="stats"] [data-component="top-models-chart"] {
+  position: relative;
+  display: grid;
+  grid-template-rows: 34px minmax(0, 1fr);
+  gap: 12px;
+  width: 100%;
+  height: 560px;
+}
+
+[data-page="stats"] [data-slot="top-models-axis"],
+[data-page="stats"] [data-slot="top-models-bars"] {
+  display: flex;
+  gap: 12px;
+  min-width: 0;
+}
+
+[data-page="stats"] [data-slot="top-models-axis"] > div {
+  flex: 1 1 0;
+  min-width: 0;
+  color: var(--stats-faint);
+  font-size: 11px;
+  font-weight: 500;
+  line-height: 1.5;
+}
+
+[data-page="stats"] [data-slot="top-models-axis"] > div[data-active="true"] {
+  color: var(--stats-text);
+  font-weight: 500;
+}
+
+[data-page="stats"] [data-slot="axis-label"] {
+  display: flex;
+  flex-direction: column;
+}
+
+[data-page="stats"] [data-slot="top-models-bars"] {
+  min-height: 0;
+}
+
+[data-page="stats"] [data-slot="top-models-bar"] {
+  position: relative;
+  flex: 1 1 0;
+  min-width: 0;
+  height: 100%;
+  outline: none;
+  cursor: pointer;
+}
+
+[data-page="stats"] [data-slot="top-models-bar"]::before {
+  position: absolute;
+  inset: 0;
+  content: "";
+  background: var(--stats-dot);
+  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-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-repeat: repeat;
+  -webkit-mask-size: 6px 6px;
+}
+
+[data-page="stats"] [data-slot="top-models-stack"] {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1;
+  display: grid;
+  height: var(--top-models-bar-height);
+  min-height: 0;
+  overflow: hidden;
+  background: var(--stats-bg);
+}
+
+[data-page="stats"] [data-slot="top-models-stack"] i {
+  box-sizing: border-box;
+  display: block;
+  min-height: 0;
+  transition: background 120ms ease;
+}
+
+[data-page="stats"] [data-slot="top-models-stack"] i + i {
+  border-top: 2px solid var(--stats-bg);
+}
+
+[data-page="stats"] [data-slot="mobile-filter-button"] {
+  display: inline-flex;
+  flex: 1 1 0;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+  height: 32px;
+  min-width: 0;
+  overflow: hidden;
+  padding: 0 12px;
+  color: var(--stats-text);
+  background: var(--stats-bg);
+  border: 0;
+  border-radius: 0;
+  box-shadow:
+    0 0 0 0 #00000024,
+    0 0 0 0.5px #00000024,
+    0 1px 1.5px 0 #0000001a;
+  font-size: 13px;
+  font-weight: 600;
+  line-height: 1.1;
+}
+
+[data-page="stats"] [data-slot="mobile-filter-button"] svg {
+  flex: 0 0 auto;
+  color: var(--stats-muted);
+}
+
+[data-page="stats"] [data-component="mobile-filter-sheet"] {
+  position: fixed;
+  inset: 0;
+  z-index: 30;
+  display: flex;
+  align-items: flex-end;
+  padding: 0 8px 8px;
+  background: #00000066;
+  backdrop-filter: blur(3px);
+}
+
+[data-page="stats"] [data-slot="filter-sheet-panel"] {
+  width: 100%;
+  background: var(--stats-bg);
+  box-shadow: 0 8px 24px #00000026;
+}
+
+[data-page="stats"] [data-slot="filter-sheet-panel"] button {
+  position: relative;
+  display: flex;
+  align-items: center;
+  width: 100%;
+  min-height: 44px;
+  padding: 0 42px;
+  color: var(--stats-faint);
+  background: transparent;
+  border: 0;
+  border-radius: 0;
+  border-bottom: 1px solid var(--stats-line);
+  font-size: 13px;
+  font-weight: 600;
+  line-height: 1.1;
+  text-align: left;
+}
+
+[data-page="stats"] [data-slot="filter-sheet-panel"] button:last-child {
+  border-bottom: 0;
+}
+
+[data-page="stats"] [data-slot="filter-sheet-panel"] button[data-active="true"] {
+  color: var(--stats-text);
+  background: transparent;
+}
+
+[data-page="stats"] [data-slot="filter-sheet-panel"] button[data-active="true"]::before {
+  position: absolute;
+  left: 20px;
+  width: 6px;
+  height: 6px;
+  content: "";
+  background: currentColor;
+}
+
 [data-page="stats"] [data-slot="section-header"] {
   display: flex;
   justify-content: space-between;
@@ -636,6 +841,25 @@
   background: var(--stats-text);
 }
 
+[data-page="stats"] button[data-slot="mobile-filter-button"] {
+  padding: 0 12px;
+  color: var(--stats-text);
+  background: var(--stats-bg);
+  border-radius: 0;
+}
+
+[data-page="stats"] [data-slot="filter-sheet-panel"] button {
+  padding: 0 42px;
+  color: var(--stats-faint);
+  background: transparent;
+  border-radius: 0;
+}
+
+[data-page="stats"] [data-slot="filter-sheet-panel"] button[data-active="true"] {
+  color: var(--stats-text);
+  background: transparent;
+}
+
 [data-page="stats"] [data-component="usage-chart"],
 [data-page="stats"] [data-component="country-map"] {
   position: relative;
@@ -710,6 +934,10 @@
   margin-top: 32px;
 }
 
+[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
+  margin-top: 40px;
+}
+
 [data-page="stats"] [data-component="usage-filter"] {
   display: flex;
   align-items: center;
@@ -720,44 +948,51 @@
 }
 
 [data-page="stats"] [data-component="usage-filter"][data-variant="range"] {
-  gap: 4px;
+  gap: 6px;
 }
 
 [data-page="stats"] [data-component="usage-filter"] button {
   color: var(--stats-faint);
   background: transparent;
-  border: 1px solid transparent;
+  border: 0;
   border-radius: 0;
   padding: 0;
   font-size: 13px;
+  font-weight: 500;
   line-height: 17px;
 }
 
+[data-page="stats"] [data-component="usage-filter"] button:hover {
+  color: var(--stats-text);
+}
+
 [data-page="stats"] [data-component="usage-filter"] button[data-active="true"] {
   color: var(--stats-text);
   background: transparent;
+  font-weight: 500;
 }
 
 [data-page="stats"] [data-component="usage-filter"][data-variant="product"] button[data-active="true"] {
   display: flex;
   align-items: center;
-  gap: 8px;
+  gap: 6px;
 }
 
 [data-page="stats"] [data-component="usage-filter"][data-variant="product"] button[data-active="true"]::before {
   content: "";
-  width: 8px;
-  height: 8px;
-  background: var(--stats-text);
+  width: 16px;
+  height: 16px;
+  background: linear-gradient(var(--stats-text), var(--stats-text)) center / 6px 6px no-repeat;
 }
 
 [data-page="stats"] [data-component="usage-filter"][data-variant="range"] button {
-  min-width: 34px;
-  padding: 3px 7px;
+  height: 24px;
+  width: 36px;
+  padding: 0;
 }
 
 [data-page="stats"] [data-component="usage-filter"][data-variant="range"] button[data-active="true"] {
-  border-color: var(--stats-line-strong);
+  background: var(--stats-layer-2);
 }
 
 [data-page="stats"] [data-component="chart-tooltip"],
@@ -821,6 +1056,91 @@
   color: var(--stats-text);
 }
 
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] {
+  top: 110px;
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  gap: 0;
+  width: 192px;
+  min-width: 192px;
+  padding: 0;
+  background: #fffffff2;
+  border: 0;
+  box-shadow:
+    0 0 0 0.5px #00000024,
+    0 8px 16px #0000000f,
+    0 4px 8px #00000014;
+  color: var(--stats-text);
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"][data-placement="right"] {
+  right: auto;
+  left: calc(100% + 8px);
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"][data-placement="left"] {
+  right: calc(100% + 8px);
+  left: auto;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] strong,
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] > span {
+  display: block;
+  font-size: 11px;
+  line-height: 12px;
+  white-space: nowrap;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] strong {
+  padding: 8px 8px 0;
+  font-weight: 500;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] > span {
+  padding: 4px 8px 8px;
+  color: var(--stats-muted);
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] [data-slot="tooltip-divider"] {
+  height: 0.5px;
+  margin: 0;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] p {
+  grid-template-columns: minmax(0, 1fr) auto;
+  gap: 4px;
+  height: 16px;
+  margin: 4px 0 0;
+  padding: 0 8px;
+  font-size: 11px;
+  font-weight: 500;
+  line-height: 12px;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] [data-slot="tooltip-divider"] + p {
+  margin-top: 8px;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] p:last-child {
+  margin-bottom: 8px;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] [data-slot="tooltip-label"] {
+  grid-template-columns: 16px minmax(0, 1fr);
+  gap: 4px;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] i {
+  width: 6px;
+  height: 6px;
+  justify-self: center;
+}
+
+[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] b {
+  font-weight: 500;
+}
+
 [data-page="stats"] [data-component="leaderboard"] {
   display: block;
 }
@@ -1547,6 +1867,12 @@
   }
 }
 
+@media (max-width: 74rem) {
+  [data-page="stats"] [data-section="top-models"] {
+    padding: 64px 32px;
+  }
+}
+
 @media (max-width: 58rem) {
   [data-page="stats"] {
     --stats-page-padding: 24px;
@@ -1584,6 +1910,15 @@
     flex-wrap: wrap;
   }
 
+  [data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
+    align-items: center;
+    flex-direction: row;
+  }
+
+  [data-page="stats"] [data-section="top-models"] [data-component="usage-filter"] {
+    flex-wrap: nowrap;
+  }
+
   [data-page="stats"] [data-slot="leaderboard-grid"],
   [data-page="stats"] [data-slot="leaderboard-compact"] {
     grid-template-columns: 1fr;
@@ -1653,6 +1988,54 @@
   }
 }
 
+@media (max-width: 47.999rem) {
+  [data-page="stats"] [data-section="top-models"] {
+    padding: 48px 24px;
+  }
+
+  [data-page="stats"] [data-slot="top-models-title"] {
+    margin-bottom: 32px;
+    font-size: 16px;
+  }
+
+  [data-page="stats"] [data-slot="top-models-mobile-controls"] {
+    display: flex;
+    gap: 8px;
+    margin-bottom: 32px;
+  }
+
+  [data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
+    display: none;
+  }
+
+  [data-page="stats"] [data-component="top-models-chart"] {
+    grid-template-rows: 40px minmax(0, 1fr);
+    height: 400px;
+  }
+
+  [data-page="stats"] [data-slot="top-models-axis"] > div {
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 40px;
+    font-weight: 600;
+  }
+
+  [data-page="stats"] [data-slot="axis-label"] {
+    position: absolute;
+    left: 50%;
+    width: max-content;
+    max-width: 96px;
+    transform: rotate(-90deg) translateX(-50%);
+    transform-origin: left center;
+  }
+
+  [data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] {
+    display: none;
+  }
+}
+
 @media (max-width: 40rem) {
   [data-page="stats"] [data-component="footer"],
   [data-page="stats"] [data-component="legal"] {

+ 258 - 149
packages/stats/app/src/routes/index.tsx

@@ -17,13 +17,19 @@ import {
 } from "@opencode-ai/stats-core/domain/home"
 import { runtime } from "@opencode-ai/stats-core/runtime"
 import { createAsync, query } from "@solidjs/router"
-import { scaleBand, scaleLinear } from "d3-scale"
 import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show, type JSX } from "solid-js"
 import { getRequestEvent } from "solid-js/web"
 
-const products = ["All Users", "Zen", "Go", "Enterprise"] as const
-const tokenProducts = ["Zen", "Go", "Enterprise"] as const
-const ranges = ["1D", "1W", "1M", "3M", "YTD", "ALL"] as const
+const products = ["All Users", "Zen", "Go"] as const
+const tokenProducts = ["Zen", "Go"] as const
+const ranges = ["1D", "1W", "2W", "1M", "2M"] as const
+const rangeLabels: Record<UsageRange, string> = {
+  "1D": "1 Day",
+  "1W": "1 Week",
+  "2W": "2 Weeks",
+  "1M": "1 Month",
+  "2M": "2 Months",
+}
 const headerLinks = [
   { href: "#top-models", label: "Top Models" },
   { href: "#leaderboard", label: "Leaderboard" },
@@ -31,7 +37,19 @@ const headerLinks = [
   { href: "#token-cost", label: "Token Cost" },
   { href: "#session-cost", label: "Session Cost" },
 ] as const
-const usageColors = ["#ff5d64", "#ff8a00", "#8bef00", "#12c8b3", "#18c7dc", "#6c7dff", "#9d73f7"]
+const usageColors = [
+  "#ed6aff",
+  "#a684ff",
+  "#7c86ff",
+  "#51a2ff",
+  "#00d3f2",
+  "#00d5be",
+  "#00bc7d",
+  "#9ae600",
+  "#ffb900",
+  "#ff8904",
+  "#ff6467",
+]
 const marketColors = ["#ed6aff", "#a684ff", "#7c86ff", "#51a2ff", "#00d3f2", "#00d5be", "#00bc7d", "#9ae600", "#ffb900"]
 const countryPositions = [
   { x: 112, y: 96 },
@@ -79,7 +97,7 @@ export default function StatsHome() {
             {(stats) => (
               <>
                 <Hero updatedAt={stats().updatedAt} />
-                <UsageSection data={stats().usage} />
+                <TopModelsSection data={stats().usage} />
                 <LeaderboardSection data={stats().leaderboard} />
                 <MarketShareSection data={stats().market} />
                 <TokenCostSection data={stats().tokenCost} />
@@ -180,23 +198,154 @@ function formatUpdatedAt(value: string, timeZone: string) {
   }).format(date)
 }
 
-function UsageSection(props: { data: StatsHomeData["usage"] }) {
+function TopModelsSection(props: { data: StatsHomeData["usage"] }) {
   const [product, setProduct] = createSignal<UsageProduct>("All Users")
   const [range, setRange] = createSignal<UsageRange>("1W")
+  const [sheet, setSheet] = createSignal<"product" | "range">()
   const data = createMemo(() => props.data[product()][range()])
 
+  createEffect(() => {
+    if (!sheet()) return
+    if (typeof document === "undefined") return
+    const htmlOverflow = document.documentElement.style.overflow
+    const bodyOverflow = document.body.style.overflow
+    document.documentElement.style.overflow = "hidden"
+    document.body.style.overflow = "hidden"
+    const onKeyDown = (event: KeyboardEvent) => {
+      if (event.key === "Escape") setSheet(undefined)
+    }
+    document.addEventListener("keydown", onKeyDown)
+    onCleanup(() => {
+      document.documentElement.style.overflow = htmlOverflow
+      document.body.style.overflow = bodyOverflow
+      document.removeEventListener("keydown", onKeyDown)
+    })
+  })
+
   return (
-    <ChartSection id="top-models" title="Usage">
+    <section id="top-models" data-section="top-models">
+      <h2 data-slot="top-models-title">
+        <strong>Top models.</strong> <span>Usage of models across OpenCode.</span>
+      </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
         when={data().some((item) => usageTotal(item) > 0)}
         fallback={<EmptyState title="No usage data" description="No model_stat rows matched this product and range." />}
       >
-        <UsageChart data={data()} />
+        <TopModelsChart data={data()} range={range()} />
       </Show>
       <div data-slot="chart-footer">
         <StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
       </div>
-    </ChartSection>
+      <Show when={sheet()}>
+        {(kind) => (
+          <MobileFilterSheet
+            kind={kind()}
+            product={product()}
+            range={range()}
+            onProductSelect={(value) => {
+              setProduct(value)
+              setSheet(undefined)
+            }}
+            onRangeSelect={(value) => {
+              setRange(value)
+              setSheet(undefined)
+            }}
+            onClose={() => setSheet(undefined)}
+          />
+        )}
+      </Show>
+    </section>
+  )
+}
+
+function MobileFilterButton(props: { label: string; value: string; expanded: boolean; onClick: () => void }) {
+  return (
+    <button
+      data-slot="mobile-filter-button"
+      type="button"
+      aria-label={props.label}
+      aria-expanded={props.expanded ? "true" : "false"}
+      onClick={props.onClick}
+    >
+      <span>{props.value}</span>
+      <ChevronDown />
+    </button>
+  )
+}
+
+function MobileFilterSheet(props: {
+  kind: "product" | "range"
+  product: UsageProduct
+  range: UsageRange
+  onProductSelect: (product: UsageProduct) => void
+  onRangeSelect: (range: UsageRange) => void
+  onClose: () => void
+}) {
+  return (
+    <div data-component="mobile-filter-sheet" role="presentation" onClick={props.onClose}>
+      <div data-slot="filter-sheet-panel" role="radiogroup" aria-label={props.kind === "product" ? "Product filter" : "Date range"}>
+        <Show
+          when={props.kind === "product"}
+          fallback={
+            <For each={ranges}>
+              {(item) => (
+                <button
+                  type="button"
+                  role="radio"
+                  aria-checked={props.range === item}
+                  data-active={props.range === item ? "true" : undefined}
+                  onClick={(event) => {
+                    event.stopPropagation()
+                    props.onRangeSelect(item)
+                  }}
+                >
+                  {rangeLabels[item]}
+                </button>
+              )}
+            </For>
+          }
+        >
+          <For each={products}>
+            {(item) => (
+              <button
+                type="button"
+                role="radio"
+                aria-checked={props.product === item}
+                data-active={props.product === item ? "true" : undefined}
+                onClick={(event) => {
+                  event.stopPropagation()
+                  props.onProductSelect(item)
+                }}
+              >
+                {item}
+              </button>
+            )}
+          </For>
+        </Show>
+      </div>
+    </div>
+  )
+}
+
+function ChevronDown() {
+  return (
+    <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" fill="none">
+      <path d="M5 7L8 10L11 7" stroke="currentColor" />
+    </svg>
   )
 }
 
@@ -252,158 +401,118 @@ function FilterPills<T extends string>(props: {
   )
 }
 
-function UsageChart(props: { data: UsagePoint[] }) {
+function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
   const [activeIndex, setActiveIndex] = createSignal<number>()
-  const [activeSegment, setActiveSegment] = createSignal<number>()
-  const height = 434
-  const width = 920
-  const headerOffset = 46
-  const segmentGap = 2
-  const maxTotal = createMemo(() => Math.max(1, Math.max(...props.data.map((item) => usageTotal(item))) * 1.02))
+  const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data))
   const activePoint = createMemo(() => props.data[activeIndex() ?? -1])
-  const y = createMemo(() => scaleLinear([0, maxTotal()], [height, 0]))
-  const x = createMemo(() =>
-    scaleBand(
-      props.data.map((_, index) => String(index)),
-      [0, width],
-    ).paddingInner(0.08),
-  )
-  const activeBar = createMemo(() => {
-    const index = activeIndex()
-    const point = activePoint()
-    if (index === undefined) return
-    if (!point) return
-    return {
-      point,
-      x: x()(String(index)) ?? 0,
-      width: x().bandwidth(),
-    }
-  })
 
   return (
-    <div data-component="usage-chart">
-      <svg viewBox={`0 0 ${width} ${height + headerOffset}`} role="img" aria-label="Stacked usage chart">
-        <defs>
-          <pattern id="stats-usage-dot-grid" width="6" height="6" patternUnits="userSpaceOnUse">
-            <rect x="1" y="1" width="2" height="2" fill="var(--stats-dot)" />
-          </pattern>
-        </defs>
+    <div data-component="top-models-chart" data-range={props.range} role="img" aria-label="Stacked top model usage chart">
+      <div data-slot="top-models-axis" aria-hidden="true">
         <For each={props.data}>
-          {(day, dayIndex) => {
-            const barX = x()(String(dayIndex())) ?? 0
-            const barWidth = x().bandwidth()
-            const stackTop = y()(usageTotal(day))
-            return (
-              <g
-                role="button"
-                tabIndex={0}
-                aria-label={`${day.date} ${formatTokens(usageTotal(day))}`}
-                data-active={activeIndex() === dayIndex() ? "true" : undefined}
-                onPointerEnter={() => {
-                  setActiveIndex(dayIndex())
-                  setActiveSegment(undefined)
-                }}
-                onPointerLeave={(event) => {
-                  if (event.pointerType === "touch") return
-                  setActiveIndex(undefined)
-                  setActiveSegment(undefined)
-                }}
-                onClick={() => setActiveIndex(dayIndex())}
-                onFocus={() => {
-                  setActiveIndex(dayIndex())
-                  setActiveSegment(undefined)
-                }}
-                onBlur={() => {
-                  setActiveIndex(undefined)
-                  setActiveSegment(undefined)
-                }}
-                onKeyDown={(event) => {
-                  if (event.key !== "Enter" && event.key !== " ") return
-                  event.preventDefault()
-                  setActiveIndex(dayIndex())
-                }}
-              >
-                <rect
-                  x={barX}
-                  y="0"
-                  width={barWidth}
-                  height={height + headerOffset}
-                  fill="transparent"
-                  pointer-events="all"
-                />
-                <text x={barX} y="17" class="chart-total">
-                  {formatTokens(usageTotal(day))}
-                </text>
-                <text x={barX} y="34" class="chart-date">
-                  {day.date}
-                </text>
-                <rect x={barX} y={headerOffset} width={barWidth} height={stackTop} fill="url(#stats-usage-dot-grid)" />
-                <For each={day.segments}>
-                  {(segment, index) => {
-                    const previous = day.segments.slice(0, index()).reduce((sum, item) => sum + item.value, 0)
-                    const segmentHeight = y()(previous) - y()(previous + segment.value)
-                    const segmentInset = index() === day.segments.length - 1 ? 0 : segmentGap
-                    return (
-                      <rect
-                        x={barX}
-                        y={headerOffset + y()(previous + segment.value) + segmentInset}
-                        width={barWidth}
-                        height={Math.max(segmentHeight - segmentInset, 0)}
-                        data-segment-active={
-                          activeIndex() === dayIndex() && activeSegment() === index() ? "true" : undefined
-                        }
-                        opacity={getUsageSegmentOpacity(activeIndex() === dayIndex(), activeSegment(), index())}
-                        fill={activeIndex() === dayIndex() ? usageColors[index()] : "var(--stats-bar-idle)"}
-                        onPointerEnter={(event) => {
-                          event.stopPropagation()
-                          setActiveIndex(dayIndex())
-                          setActiveSegment(index())
-                        }}
-                      />
-                    )
-                  }}
+          {(day, index) => (
+            <div data-active={activeIndex() === index() ? "true" : undefined}>
+              <span data-slot="axis-label">
+                <span>{formatTokens(usageTotal(day))}</span>
+                <span>{day.date}</span>
+              </span>
+            </div>
+          )}
+        </For>
+      </div>
+      <div data-slot="top-models-bars">
+        <For each={props.data}>
+          {(day, dayIndex) => (
+            <div
+              data-slot="top-models-bar"
+              role="button"
+              tabIndex={0}
+              aria-label={`${day.date} ${formatTokens(usageTotal(day))}`}
+              data-active={activeIndex() === dayIndex() ? "true" : undefined}
+              data-muted={activeIndex() !== undefined && activeIndex() !== dayIndex() ? "true" : undefined}
+              style={{ "--top-models-bar-height": `${getTopModelsBarHeight(usageTotal(day), maxTotal())}%` }}
+              onPointerEnter={() => setActiveIndex(dayIndex())}
+              onPointerLeave={(event) => {
+                if (event.pointerType === "touch") return
+                setActiveIndex(undefined)
+              }}
+              onClick={() => setActiveIndex(dayIndex())}
+              onFocus={() => setActiveIndex(dayIndex())}
+              onBlur={() => setActiveIndex(undefined)}
+              onKeyDown={(event) => {
+                if (event.key !== "Enter" && event.key !== " ") return
+                event.preventDefault()
+                setActiveIndex(dayIndex())
+              }}
+            >
+              <div data-slot="top-models-stack" style={{ "grid-template-rows": getTopModelsSegmentRows(day) }}>
+                <For each={visibleTopModelsSegments(day)}>
+                  {(item) => (
+                    <i
+                      style={{
+                        background: getTopModelsSegmentColor(
+                          item.index,
+                          activeIndex() !== undefined && activeIndex() !== dayIndex(),
+                        ),
+                      }}
+                    />
+                  )}
                 </For>
-              </g>
-            )
-          }}
+              </div>
+              <Show when={activeIndex() === dayIndex() && activePoint()}>
+                {(point) => (
+                  <div data-component="chart-tooltip" data-placement={dayIndex() > props.data.length * 0.62 ? "left" : "right"}>
+                    <strong>{point().date}</strong>
+                    <span>{formatTokens(usageTotal(point()))} total</span>
+                    <div data-slot="tooltip-divider" />
+                    <For each={visibleTopModelsSegments(point())}>
+                      {(item) => (
+                        <p>
+                          <span data-slot="tooltip-label">
+                            <i style={{ background: usageColors[item.index] }} /> {item.segment.model}
+                          </span>
+                          <b>{formatTokens(item.segment.value)}</b>
+                        </p>
+                      )}
+                    </For>
+                  </div>
+                )}
+              </Show>
+            </div>
+          )}
         </For>
-      </svg>
-      <Show when={activeBar()}>
-        {(bar) => (
-          <div
-            data-component="chart-tooltip"
-            data-placement={bar().x > width * 0.62 ? "left" : "right"}
-            style={getUsageTooltipStyle(bar().x, bar().width, width)}
-          >
-            <strong>{bar().point.date}</strong>
-            <span>{formatTokens(usageTotal(bar().point))} total</span>
-            <div data-slot="tooltip-divider" />
-            <For each={bar().point.segments}>
-              {(segment, index) => (
-                <p data-active={activeSegment() === index() ? "true" : undefined}>
-                  <span data-slot="tooltip-label">
-                    <i style={{ background: usageColors[index()] }} /> {segment.model}
-                  </span>
-                  <b>{formatTokens(segment.value)}</b>
-                </p>
-              )}
-            </For>
-          </div>
-        )}
-      </Show>
+      </div>
     </div>
   )
 }
 
-function getUsageTooltipStyle(barX: number, barWidth: number, width: number) {
-  if (barX > width * 0.62) return { left: "auto", right: `${((width - barX + 12) / width) * 100}%` }
-  return { left: `${((barX + barWidth + 12) / width) * 100}%`, right: "auto" }
+function getTopModelsBarHeight(total: number, max: number) {
+  if (total <= 0) return 0
+  return Math.max(2, Math.min(100, (total / max) * 100))
+}
+
+function getTopModelsMaxTotal(data: UsagePoint[]) {
+  const max = Math.max(0, ...data.map((item) => usageTotal(item)))
+  if (max === 0) return 1
+  if (data.length === 1) return max * 1.75
+  return max
+}
+
+function getTopModelsSegmentRows(point: UsagePoint) {
+  const total = usageTotal(point)
+  if (total <= 0) return ""
+  return visibleTopModelsSegments(point)
+    .map((item) => `${(item.segment.value / total) * 100}%`)
+    .join(" ")
+}
+
+function visibleTopModelsSegments(point: UsagePoint) {
+  return point.segments.map((segment, index) => ({ segment, index })).filter((item) => item.segment.value > 0)
 }
 
-function getUsageSegmentOpacity(isActiveBar: boolean, activeSegment: number | undefined, index: number) {
-  if (!isActiveBar) return 1
-  if (activeSegment === undefined) return 1
-  return activeSegment === index ? 1 : 0.38
+function getTopModelsSegmentColor(index: number, muted: boolean) {
+  if (muted) return "var(--stats-layer-2)"
+  return usageColors[index] ?? "var(--stats-text)"
 }
 
 function usageTotal(point: UsagePoint) {

+ 36 - 15
packages/stats/core/src/domain/home.ts

@@ -6,7 +6,7 @@ import { ProviderStatRepo, type ProviderStatMetric } from "./provider"
 
 export type UsageProduct = "All Users" | "Zen" | "Go" | "Enterprise"
 export type TokenProduct = "Zen" | "Go" | "Enterprise"
-export type UsageRange = "1D" | "1W" | "1M" | "3M" | "YTD" | "ALL"
+export type UsageRange = "1D" | "1W" | "2W" | "1M" | "2M" | "3M" | "YTD" | "ALL"
 export type UsagePoint = { date: string; segments: { model: string; value: number }[] }
 export type MarketDay = { date: string; total: number; authors: { author: string; share: number; tokens: number }[] }
 export type LeaderboardEntry = { model: string; author: string; tokens: number; change: number; rank: number }
@@ -134,9 +134,9 @@ function buildUsagePoints(rows: StatMetricRow[], product: UsageProduct, range: U
     return {
       date: bucket.label,
       segments: [
-        ...segmentTokens.map((item) => ({ model: item.model, value: round(item.tokens / 1_000_000_000_000, 2) })),
-        { model: "Other", value: round(Math.max(totalTokens - knownTokens, 0) / 1_000_000_000_000, 2) },
-      ].filter((item) => item.value > 0),
+        ...segmentTokens.map((item) => ({ model: item.model, value: round(item.tokens / 1_000_000_000_000, 4) })),
+        { model: "Other", value: round(Math.max(totalTokens - knownTokens, 0) / 1_000_000_000_000, 4) },
+      ],
     }
   })
 }
@@ -309,13 +309,17 @@ function getWindow(range: UsageRange, earliest: number, latest: number): DateWin
       ? latest
       : range === "1W"
         ? latest - 6 * DAY_MS
-        : range === "1M"
-          ? latest - 29 * DAY_MS
-          : range === "3M"
-            ? latest - 89 * DAY_MS
-            : range === "YTD"
-              ? Date.UTC(new Date(latest).getUTCFullYear(), 0, 1)
-              : earliest,
+        : range === "2W"
+          ? latest - 13 * DAY_MS
+          : range === "1M"
+            ? latest - 27 * DAY_MS
+            : range === "2M"
+              ? latest - 55 * DAY_MS
+              : range === "3M"
+                ? latest - 89 * DAY_MS
+                : range === "YTD"
+                  ? Date.UTC(new Date(latest).getUTCFullYear(), 0, 1)
+                  : earliest,
   )
   const duration = end - start
   return { start, end, previousStart: start - duration, previousEnd: start }
@@ -323,12 +327,21 @@ function getWindow(range: UsageRange, earliest: number, latest: number): DateWin
 
 function createBuckets(window: DateWindow, range: UsageRange): Bucket[] {
   const span = Math.max(window.end - window.start, DAY_MS)
-  const count = Math.max(1, Math.min(7, Math.ceil(span / DAY_MS)))
+  const count =
+    range === "1D"
+      ? 1
+      : range === "2W"
+        ? 14
+        : range === "1M"
+          ? 4
+          : range === "2M"
+            ? 8
+            : Math.max(1, Math.min(7, Math.ceil(span / DAY_MS)))
   const size = span / count
   return Array.from({ length: count }, (_, index) => {
     const start = window.start + index * size
     const end = index === count - 1 ? window.end : window.start + (index + 1) * size
-    return { start, end, label: formatBucketLabel(start, range) }
+    return { start, end, label: formatBucketLabel(start, end, range) }
   })
 }
 
@@ -353,7 +366,9 @@ function createRangeRecord<T>(value: (range: UsageRange) => T): Record<UsageRang
   return {
     "1D": value("1D"),
     "1W": value("1W"),
+    "2W": value("2W"),
     "1M": value("1M"),
+    "2M": value("2M"),
     "3M": value("3M"),
     YTD: value("YTD"),
     ALL: value("ALL"),
@@ -428,13 +443,19 @@ function periodKeyTime(value: string) {
   return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))
 }
 
-function formatBucketLabel(value: number, range: UsageRange) {
-  const date = new Date(value)
+function formatBucketLabel(start: number, end: number, range: UsageRange) {
+  const date = new Date(start)
   if (range === "YTD") return months[date.getUTCMonth()]
   if (range === "ALL")
     return date.getUTCFullYear() === new Date().getUTCFullYear()
       ? months[date.getUTCMonth()]
       : String(date.getUTCFullYear())
+  if (range === "1M" || range === "2M") return `${formatDay(start)} - ${formatDay(end - DAY_MS)}`
+  return formatDay(start)
+}
+
+function formatDay(value: number) {
+  const date = new Date(value)
   return `${months[date.getUTCMonth()]} ${date.getUTCDate()}`
 }