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

fix(stats): refine hero layout for responsive Figma match

Adam 3 месяцев назад
Родитель
Сommit
3ce9b4be0d
2 измененных файлов с 46 добавлено и 19 удалено
  1. 30 11
      packages/stats/app/src/routes/index.css
  2. 16 8
      packages/stats/app/src/routes/index.tsx

+ 30 - 11
packages/stats/app/src/routes/index.css

@@ -89,7 +89,7 @@
 [data-page="stats"] [data-component="container"] {
   box-sizing: border-box;
   width: 100%;
-  max-width: 80rem;
+  max-width: calc(80rem + 2px);
   margin: 0 auto;
   border-left: 1px solid var(--stats-line);
   border-right: 1px solid var(--stats-line);
@@ -461,6 +461,7 @@
 }
 
 [data-page="stats"] [data-section="hero"] h1 {
+  order: 1;
   color: var(--stats-text);
   font-size: 64px;
   font-weight: 500;
@@ -469,20 +470,30 @@
 }
 
 [data-page="stats"] [data-slot="hero-copy"] {
+  order: 3;
   color: var(--stats-hero-muted);
   font-size: 16px;
   font-weight: 400;
   line-height: 1.5;
 }
 
+[data-page="stats"] [data-slot="hero-copy-break"] {
+  display: none;
+}
+
 [data-page="stats"] [data-slot="hero-pattern"] {
+  order: 2;
   flex: 0 0 auto;
   width: 100%;
   height: 16px;
   overflow: hidden;
-  background-image: radial-gradient(circle, var(--stats-hero-pattern) 1px, transparent 1.1px);
-  background-position: 0 0;
-  background-size: 6px 6px;
+  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-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="hero-meta"] {
@@ -496,7 +507,7 @@
   background: var(--stats-layer-2);
   color: var(--stats-hero-muted);
   font-size: 13px;
-  font-weight: 600;
+  font-weight: 500;
   line-height: 1.1;
   overflow: hidden;
   white-space: nowrap;
@@ -514,10 +525,10 @@
   text-overflow: ellipsis;
 }
 
-@media (min-width: 58rem) {
+@media (min-width: 48rem) {
   [data-page="stats"] [data-section="hero"] {
     gap: 16px;
-    padding: 128px 40px 24px;
+    padding: 128px 32px 32px;
   }
 
   [data-page="stats"] [data-slot="hero-canvas"] {
@@ -553,11 +564,21 @@
     right: 0;
     bottom: 0;
     z-index: 1;
-    width: min(36rem, 100%);
+    width: min(563px, 100%);
     padding: 12px 0 0 16px;
     background: var(--stats-bg);
     text-align: right;
   }
+
+  [data-page="stats"] [data-slot="hero-copy-break"] {
+    display: block;
+  }
+}
+
+@media (min-width: 75rem) {
+  [data-page="stats"] [data-section="hero"] {
+    padding: 128px 40px 24px;
+  }
 }
 
 @media (min-width: 90rem) {
@@ -1494,7 +1515,7 @@
   }
 }
 
-@media (max-width: 74rem) {
+@media (max-width: 80rem) {
   [data-page="stats"] [data-component="container"] {
     border: 0;
   }
@@ -1540,14 +1561,12 @@
     --stats-section-padding: 4rem;
   }
 
-  [data-page="stats"] [data-section="hero"],
   [data-page="stats"] [data-section="chart"],
   [data-page="stats"] [data-section="newsletter"] {
     padding-left: 24px;
     padding-right: 24px;
   }
 
-  [data-page="stats"] [data-section="hero"],
   [data-page="stats"] [data-section="newsletter"] {
     grid-template-columns: 1fr;
   }

+ 16 - 8
packages/stats/app/src/routes/index.tsx

@@ -18,7 +18,7 @@ import {
 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, Show, type JSX } from "solid-js"
+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
@@ -98,21 +98,29 @@ export default function StatsHome() {
 }
 
 function Hero(props: { updatedAt: string | null }) {
+  const [timeZone, setTimeZone] = createSignal("UTC")
+  onMount(() => setTimeZone(Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"))
+
   return (
     <section data-section="hero">
       <p data-slot="hero-meta">
         <svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16">
-          <rect x="3" y="3" width="10" height="10" fill="currentColor" />
-          <rect x="7" y="6.5" width="2" height="4.5" fill="var(--stats-layer-2)" />
-          <rect x="7" y="5" width="2" height="1" fill="var(--stats-layer-2)" />
+          <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M13 13H3V3H13V13ZM6.46777 6.81641V7.81641H7.5791V11.3721H8.5791V6.81641H6.46777ZM7.30078 4.62891V5.62891H8.85645V4.62891H7.30078Z"
+            fill="currentColor"
+          />
         </svg>
-        <span>{props.updatedAt ? `Updated ${formatUpdatedAt(props.updatedAt)}` : "No rows yet"}</span>
+        <span>{props.updatedAt ? `Updated ${formatUpdatedAt(props.updatedAt, timeZone())}` : "No rows yet"}</span>
       </p>
       <div data-slot="hero-canvas">
         <div data-slot="hero-pattern" aria-hidden="true" />
         <h1>Model Stats</h1>
         <p data-slot="hero-copy">
-          See which models are winning real usage, how the mix shifts over time, and where momentum is moving each week.
+          See which models are winning real usage, how the mix{" "}
+          <br data-slot="hero-copy-break" />
+          shifts over time, and where momentum is moving each week.
         </p>
       </div>
     </section>
@@ -154,7 +162,7 @@ function EmptyState(props: { title: string; description: string }) {
   )
 }
 
-function formatUpdatedAt(value: string) {
+function formatUpdatedAt(value: string, timeZone: string) {
   const date = new Date(value)
   if (Number.isNaN(date.getTime())) return "just now"
   return new Intl.DateTimeFormat("en", {
@@ -162,7 +170,7 @@ function formatUpdatedAt(value: string) {
     day: "numeric",
     hour: "numeric",
     minute: "2-digit",
-    timeZone: "UTC",
+    timeZone,
     timeZoneName: "short",
   }).format(date)
 }