Bläddra i källkod

fix(stats): restore leaderboard spacing

Adam 2 månader sedan
förälder
incheckning
44350bced9
2 ändrade filer med 89 tillägg och 92 borttagningar
  1. 29 71
      packages/stats/app/src/routes/index.css
  2. 60 21
      packages/stats/app/src/routes/index.tsx

+ 29 - 71
packages/stats/app/src/routes/index.css

@@ -83,6 +83,10 @@
   background: var(--stats-bg);
   background: var(--stats-bg);
 }
 }
 
 
+[data-page="stats"] [hidden] {
+  display: none !important;
+}
+
 [data-page="stats"] [data-component="content"] {
 [data-page="stats"] [data-component="content"] {
   color: var(--stats-text);
   color: var(--stats-text);
   font-family:
   font-family:
@@ -1706,15 +1710,30 @@
   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"] {
-  display: block;
+  gap: 0;
   width: 100%;
   width: 100%;
+  margin-top: 36px;
+  scroll-margin-top: 88px;
+}
+
+[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: 32px 0 16px;
+  margin: 16px 0;
   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");
@@ -1727,30 +1746,15 @@
   -webkit-mask-size: 6px 6px;
   -webkit-mask-size: 6px 6px;
 }
 }
 
 
-[data-page="stats"] [data-slot="leaderboard-scroll"] {
-  box-sizing: border-box;
-  display: flex;
+[data-page="stats"] [data-slot="leaderboard-column"] {
   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;
+  align-content: start;
 }
 }
 
 
-[data-page="stats"] [data-slot="leaderboard-scroll"]::-webkit-scrollbar {
+[data-page="stats"] [data-slot="leaderboard-mobile"] {
   display: none;
   display: none;
 }
 }
 
 
-[data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
-  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"] {
   position: relative;
   position: relative;
   box-sizing: border-box;
   box-sizing: border-box;
@@ -2598,14 +2602,6 @@
   [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) {
@@ -2649,9 +2645,9 @@
     flex-wrap: nowrap;
     flex-wrap: nowrap;
   }
   }
 
 
-  [data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
-    flex-basis: 280px;
-    width: 280px;
+  [data-page="stats"] [data-slot="leaderboard-featured"],
+  [data-page="stats"] [data-slot="leaderboard-compact"] {
+    grid-template-columns: 1fr;
   }
   }
 
 
   [data-page="stats"] [data-component="leader-card"][data-size="featured"],
   [data-page="stats"] [data-component="leader-card"][data-size="featured"],
@@ -2760,49 +2756,11 @@
   }
   }
 
 
   [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;

+ 60 - 21
packages/stats/app/src/routes/index.tsx

@@ -368,7 +368,7 @@ function formatUpdatedAtLabel(value: { date: string; time: string }) {
 }
 }
 
 
 function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: StatsHomeData["leaderboard"] }) {
 function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: StatsHomeData["leaderboard"] }) {
-  const [product, setProduct] = createSignal<UsageProduct>("All Users")
+  const [product, setProduct] = createSignal<UsageProduct>("Go")
   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 [activeModel, setActiveModel] = createSignal<string>()
@@ -409,7 +409,6 @@ function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: St
           onActiveModelChange={setActiveModel}
           onActiveModelChange={setActiveModel}
         />
         />
       </Show>
       </Show>
-      <div id="leaderboard" data-slot="leaderboard-pattern" aria-hidden="true" />
       <Show
       <Show
         when={leaderboard().length > 0}
         when={leaderboard().length > 0}
         fallback={
         fallback={
@@ -418,7 +417,7 @@ function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: St
       >
       >
         <Leaderboard data={leaderboard()} activeModel={activeModel()} onActiveModelChange={setActiveModel} />
         <Leaderboard data={leaderboard()} activeModel={activeModel()} onActiveModelChange={setActiveModel} />
       </Show>
       </Show>
-      <div data-slot="chart-footer">
+      <div data-slot="chart-footer" hidden>
         <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">
         <div data-slot="top-models-mobile-controls">
           <MobileFilterButton
           <MobileFilterButton
@@ -848,13 +847,50 @@ function Leaderboard(props: {
   activeModel: string | undefined
   activeModel: string | undefined
   onActiveModelChange: (model: string | undefined) => void
   onActiveModelChange: (model: string | undefined) => void
 }) {
 }) {
+  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-slot="leaderboard-scroll" aria-label="Scrollable model token leaderboard">
+    <div id="leaderboard" data-component="leaderboard" role="list" aria-label="Model token leaderboard">
+      <div data-slot="leaderboard-featured">
+        <For each={featured()}>
+          {(entry) => (
+            <LeaderboardCard
+              entry={entry}
+              size="featured"
+              active={props.activeModel === entry.model}
+              onActiveModelChange={props.onActiveModelChange}
+            />
+          )}
+        </For>
+      </div>
+      <div data-slot="leaderboard-pattern" aria-hidden="true" />
+      <div data-slot="leaderboard-compact">
+        <For each={columns()}>
+          {(column) => (
+            <div data-slot="leaderboard-column">
+              <For each={column}>
+                {(entry) => (
+                  <LeaderboardCard
+                    entry={entry}
+                    size="compact"
+                    active={props.activeModel === entry.model}
+                    onActiveModelChange={props.onActiveModelChange}
+                  />
+                )}
+              </For>
+            </div>
+          )}
+        </For>
+      </div>
+      <div data-slot="leaderboard-mobile" aria-label="Scrollable model token leaderboard">
         <For each={props.data}>
         <For each={props.data}>
           {(entry) => (
           {(entry) => (
             <LeaderboardCard
             <LeaderboardCard
               entry={entry}
               entry={entry}
+              size="featured"
               active={props.activeModel === entry.model}
               active={props.activeModel === entry.model}
               onActiveModelChange={props.onActiveModelChange}
               onActiveModelChange={props.onActiveModelChange}
             />
             />
@@ -867,13 +903,14 @@ function Leaderboard(props: {
 
 
 function LeaderboardCard(props: {
 function LeaderboardCard(props: {
   entry: LeaderboardEntry
   entry: LeaderboardEntry
+  size: "featured" | "compact"
   active: boolean
   active: boolean
   onActiveModelChange: (model: string | undefined) => void
   onActiveModelChange: (model: string | undefined) => void
 }) {
 }) {
   return (
   return (
     <article
     <article
       data-component="leader-card"
       data-component="leader-card"
-      data-size="featured"
+      data-size={props.size}
       data-active={props.active ? "true" : undefined}
       data-active={props.active ? "true" : undefined}
       role="listitem"
       role="listitem"
       tabIndex={0}
       tabIndex={0}
@@ -986,17 +1023,19 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
           <span>[*]</span>
           <span>[*]</span>
           <strong>{inspecting() ? formatMarketDate(activeDay()) : formatMarketRange(data())}</strong>
           <strong>{inspecting() ? formatMarketDate(activeDay()) : formatMarketRange(data())}</strong>
         </p>
         </p>
-        <FilterPills
-          items={ranges}
-          selected={range()}
-          label="Date range"
-          variant="range"
-          onSelect={(item) => {
-            setRange(item)
-            setActiveAuthor(undefined)
-            setInspecting(false)
-          }}
-        />
+        <div hidden>
+          <FilterPills
+            items={ranges}
+            selected={range()}
+            label="Date range"
+            variant="range"
+            onSelect={(item) => {
+              setRange(item)
+              setActiveAuthor(undefined)
+              setInspecting(false)
+            }}
+          />
+        </div>
       </div>
       </div>
     </section>
     </section>
   )
   )
@@ -1215,7 +1254,7 @@ function marketDateParts(label: string) {
 }
 }
 
 
 function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) {
 function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) {
-  const [product, setProduct] = createSignal<TokenProduct>("Zen")
+  const [product, setProduct] = createSignal<TokenProduct>("Go")
   const [activeIndex, setActiveIndex] = createSignal(2)
   const [activeIndex, setActiveIndex] = createSignal(2)
   const data = createMemo(() => props.data[product()])
   const data = createMemo(() => props.data[product()])
   const visible = createMemo(() => data().slice(0, 13))
   const visible = createMemo(() => data().slice(0, 13))
@@ -1233,7 +1272,7 @@ function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) {
       >
       >
         <TokenCostChart data={visible()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
         <TokenCostChart data={visible()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
       </Show>
       </Show>
-      <div data-slot="token-footer">
+      <div data-slot="token-footer" hidden>
         <FilterPills
         <FilterPills
           items={tokenProducts}
           items={tokenProducts}
           selected={product()}
           selected={product()}
@@ -1313,7 +1352,7 @@ function MetricBar(props: { value: number; max: number; active: boolean }) {
 }
 }
 
 
 function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) {
 function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) {
-  const [product, setProduct] = createSignal<TokenProduct>("Zen")
+  const [product, setProduct] = createSignal<TokenProduct>("Go")
   const [activeIndex, setActiveIndex] = createSignal(2)
   const [activeIndex, setActiveIndex] = createSignal(2)
   const data = createMemo(() => props.data[product()])
   const data = createMemo(() => props.data[product()])
   const visible = createMemo(() => data().slice(0, 16))
   const visible = createMemo(() => data().slice(0, 16))
@@ -1334,7 +1373,7 @@ function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) {
       >
       >
         <SessionCostChart data={visible()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
         <SessionCostChart data={visible()} activeIndex={selectedIndex()} onActiveIndexChange={setActiveIndex} />
       </Show>
       </Show>
-      <div data-slot="token-footer">
+      <div data-slot="token-footer" hidden>
         <FilterPills
         <FilterPills
           items={tokenProducts}
           items={tokenProducts}
           selected={product()}
           selected={product()}