Quellcode durchsuchen

feat(stats): polish rankings page

Adam vor 2 Monaten
Ursprung
Commit
0d67d659fc

+ 1 - 0
bun.lock

@@ -639,6 +639,7 @@
         "effect": "catalog:",
         "nitro": "3.0.1-alpha.1",
         "solid-js": "catalog:",
+        "sst": "catalog:",
         "vite": "catalog:",
       },
       "devDependencies": {

+ 1 - 0
packages/stats/app/package.json

@@ -21,6 +21,7 @@
     "d3-scale": "4.0.2",
     "effect": "catalog:",
     "nitro": "3.0.1-alpha.1",
+    "sst": "catalog:",
     "solid-js": "catalog:",
     "vite": "catalog:"
   },

+ 10 - 0
packages/stats/app/src/resource.d.ts

@@ -0,0 +1,10 @@
+import "sst/resource"
+
+declare module "sst/resource" {
+  export interface Resource {
+    EMAILOCTOPUS_API_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+  }
+}

+ 29 - 0
packages/stats/app/src/routes/api/newsletter.ts

@@ -0,0 +1,29 @@
+import { Resource } from "sst/resource"
+
+const listId = "8b9bb82c-9d5f-11f0-975f-0df6fd1e4945"
+
+export async function POST(event: { request: Request }) {
+  const contentType = event.request.headers.get("content-type") ?? ""
+  if (!contentType.includes("multipart/form-data") && !contentType.includes("application/x-www-form-urlencoded")) {
+    return Response.json({ error: "Email address is required" }, { status: 400 })
+  }
+
+  const form = await event.request.formData()
+  const emailAddress = form.get("email")
+  if (typeof emailAddress !== "string" || emailAddress.trim().length === 0) {
+    return Response.json({ error: "Email address is required" }, { status: 400 })
+  }
+
+  const response = await fetch(`https://api.emailoctopus.com/lists/${listId}/contacts`, {
+    method: "PUT",
+    headers: {
+      Authorization: `Bearer ${Resource.EMAILOCTOPUS_API_KEY.value}`,
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify({
+      email_address: emailAddress.trim(),
+    }),
+  })
+  if (!response.ok) return Response.json({ error: "Failed to subscribe" }, { status: 502 })
+  return Response.json({ success: true })
+}

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

@@ -67,6 +67,9 @@
   --stats-dot: #d4d4d4;
   --stats-hero-muted: #5c5c5c;
   --stats-hero-pattern: #eeeeee;
+  --stats-logo-bg: #161616;
+  --stats-logo-fill: #454545;
+  --stats-logo-stroke: #e2e2e2;
   --stats-page-padding: 5rem;
   --stats-section-padding: 6rem;
   min-height: 100vh;
@@ -343,7 +346,7 @@
   gap: 56px;
   box-sizing: border-box;
   min-height: 0;
-  padding: 112px 0 24px;
+  padding: 112px clamp(32px, 4vw, 48px) 24px;
   color: var(--stats-text);
   font-family:
     "IBM Plex Mono",
@@ -364,9 +367,10 @@
   color: var(--stats-text);
 }
 
-[data-page="stats"] [data-slot="footer-mark"] [data-slot="brand-mark"] {
-  width: 24px;
-  height: 30px;
+[data-page="stats"] [data-slot="footer-mark"] [data-slot="opencode-mark"] {
+  display: block;
+  width: 40px;
+  height: 40px;
 }
 
 [data-page="stats"] [data-slot="footer-column"] {
@@ -415,10 +419,19 @@
   justify-content: center;
   width: fit-content;
   height: 28px;
+  margin: 0;
   padding: 0 12px;
   border: 1px solid var(--stats-line);
+  border-radius: 0;
+  appearance: none;
   background: var(--stats-bg);
   color: var(--stats-text) !important;
+  font: inherit;
+  font-size: 11px;
+  font-weight: 500;
+  line-height: 1;
+  text-decoration: none;
+  cursor: pointer;
 }
 
 [data-page="stats"] [data-slot="subscribe-button"]:hover {
@@ -426,6 +439,176 @@
   background: var(--stats-layer);
 }
 
+[data-page="stats"] [data-slot="subscribe-button"]:focus-visible,
+[data-page="stats"] [data-component="subscribe-modal"] button:focus-visible,
+[data-page="stats"] [data-component="subscribe-modal"] input:focus-visible {
+  outline: 2px solid var(--stats-accent);
+  outline-offset: 2px;
+}
+
+[data-page="stats"] [data-component="subscribe-modal"] {
+  position: fixed;
+  inset: 0;
+  z-index: 80;
+  display: grid;
+  place-items: center;
+  box-sizing: border-box;
+  padding: 8px;
+  color: var(--stats-text);
+  font-family:
+    "IBM Plex Mono",
+    var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
+}
+
+[data-page="stats"] [data-slot="modal-scrim"] {
+  position: absolute;
+  inset: 0;
+  background: #00000066;
+  backdrop-filter: blur(2px);
+}
+
+[data-page="stats"] [data-slot="modal-panel"] {
+  position: relative;
+  z-index: 1;
+  width: min(374px, calc(100vw - 16px));
+  overflow: hidden;
+  background: var(--stats-layer);
+  box-shadow:
+    0 0 0 0.5px #00000024,
+    0 16px 32px #0000001f,
+    0 8px 16px #00000014;
+}
+
+[data-page="stats"] [data-slot="modal-brand"] {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 122px;
+  background: #242424;
+  color: #ffffff;
+}
+
+[data-page="stats"] [data-slot="modal-logo"] {
+  display: block;
+  width: 234px;
+  height: 42px;
+}
+
+[data-page="stats"] [data-slot="modal-close"] {
+  position: absolute;
+  top: 0;
+  right: 0;
+  display: grid;
+  place-items: center;
+  width: 32px;
+  height: 32px;
+  margin: 0;
+  padding: 0;
+  border: 0;
+  border-radius: 0;
+  appearance: none;
+  background: transparent;
+  color: #ffffff;
+  cursor: pointer;
+}
+
+[data-page="stats"] [data-slot="modal-close"]:hover {
+  background: #ffffff1a;
+}
+
+[data-page="stats"] [data-slot="modal-body"] {
+  display: grid;
+  gap: 24px;
+  padding: 24px 12px 12px;
+  background: var(--stats-layer);
+}
+
+[data-page="stats"] [data-slot="modal-intro"] {
+  display: grid;
+  gap: 8px;
+  text-align: center;
+}
+
+[data-page="stats"] [data-slot="modal-intro"] h2 {
+  color: var(--stats-text);
+  font-size: 16px;
+  font-weight: 500;
+  line-height: 24px;
+  letter-spacing: 0;
+}
+
+[data-page="stats"] [data-slot="modal-intro"] p {
+  color: var(--stats-muted);
+  font-size: 16px;
+  font-weight: 400;
+  line-height: 24px;
+}
+
+[data-page="stats"] [data-slot="subscribe-form"] {
+  display: grid;
+  gap: 8px;
+}
+
+[data-page="stats"] [data-slot="subscribe-form"] input,
+[data-page="stats"] [data-slot="subscribe-form"] button {
+  box-sizing: border-box;
+  width: 100%;
+  height: 40px;
+  margin: 0;
+  border-radius: 0;
+  appearance: none;
+  font: inherit;
+  font-size: 13px;
+  line-height: 1;
+}
+
+[data-page="stats"] [data-slot="subscribe-form"] input {
+  padding: 0 12px;
+  border: 1px solid var(--stats-line);
+  background: var(--stats-bg);
+  color: var(--stats-text);
+}
+
+[data-page="stats"] [data-slot="subscribe-form"] input::placeholder {
+  color: var(--stats-faint);
+}
+
+[data-page="stats"] [data-slot="subscribe-form"] button {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 0;
+  background: #242424;
+  color: #ffffff;
+  font-weight: 500;
+  cursor: pointer;
+}
+
+[data-page="stats"] [data-slot="subscribe-form"] button:disabled {
+  cursor: progress;
+  opacity: 0.7;
+}
+
+[data-page="stats"] [data-slot="subscribe-feedback"]:empty {
+  display: none;
+}
+
+[data-page="stats"] [data-slot="subscribe-feedback"] p {
+  text-align: center;
+  font-size: 11px;
+  font-weight: 500;
+  line-height: 16px;
+}
+
+[data-page="stats"] [data-slot="subscribe-feedback"] p[data-state="success"] {
+  color: #198b43;
+}
+
+[data-page="stats"] [data-slot="subscribe-feedback"] p[data-state="error"] {
+  color: #b82d35;
+}
+
 [data-page="stats"] [data-slot="footer-pattern"] {
   height: 16px;
   overflow: hidden;
@@ -470,30 +653,6 @@
   background: #198b43;
 }
 
-[data-page="stats"] [data-slot="theme-toggle"] {
-  display: flex;
-  align-items: center;
-  height: 20px;
-  padding: 2px;
-  background: var(--stats-layer);
-}
-
-[data-page="stats"] [data-slot="theme-toggle"] button {
-  display: grid;
-  place-items: center;
-  width: 18px;
-  height: 16px;
-  padding: 0;
-  border-radius: 0;
-  color: var(--stats-muted);
-  background: transparent;
-}
-
-[data-page="stats"] [data-slot="theme-toggle"] button[data-active="true"] {
-  color: var(--stats-text);
-  background: var(--stats-bg);
-}
-
 [data-page="stats"] [data-component="content"] a {
   color: var(--stats-text);
   text-decoration: none;
@@ -1373,6 +1532,10 @@
   font-weight: 600;
 }
 
+[data-page="stats"] [data-component="section-bridge"] {
+  display: none;
+}
+
 [data-page="stats"] [data-slot="section-title"] {
   max-width: 1200px;
   margin-bottom: 40px;
@@ -1779,7 +1942,7 @@
   background: transparent;
   color: var(--stats-text);
   font-size: 11px;
-  line-height: 1;
+  line-height: 16px;
   text-align: left;
 }
 
@@ -1804,6 +1967,7 @@
   overflow: hidden;
   color: var(--stats-muted);
   font-weight: 400;
+  line-height: 16px;
   text-overflow: ellipsis;
   white-space: nowrap;
 }
@@ -1907,7 +2071,7 @@
 }
 
 [data-page="stats"] [data-component="live-filter"] {
-  display: flex;
+  display: none;
   align-items: center;
   gap: 6px;
   padding: 0;
@@ -1960,6 +2124,9 @@
     --stats-dot: #303030;
     --stats-hero-muted: #808080;
     --stats-hero-pattern: #303030;
+    --stats-logo-bg: #f1ecec;
+    --stats-logo-fill: #b7b1b1;
+    --stats-logo-stroke: #211e1e;
   }
 
   [data-page="stats"] [data-component="chart-tooltip"] {
@@ -2040,6 +2207,13 @@
   }
 }
 
+@media (min-width: 90rem) {
+  [data-page="stats"] [data-component="footer"] {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+
 @media (max-width: 74rem) {
   [data-page="stats"] [data-section="top-models"],
   [data-page="stats"] [data-section="leaderboard"],

+ 124 - 46
packages/stats/app/src/routes/index.tsx

@@ -5,6 +5,7 @@ import ibmPlexMonoRegularLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/
 import ibmPlexMonoMediumLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-Medium-Latin1.woff2?url"
 import ibmPlexMonoSemiBoldLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-SemiBold-Latin1.woff2?url"
 import ibmPlexMonoBoldLatin1 from "@ibm/plex/IBM-Plex-Mono/fonts/split/woff2/IBMPlexMono-Bold-Latin1.woff2?url"
+import opencodeWordmarkDark from "../asset/logo-ornate-dark.svg"
 import {
   getStatsHomeData,
   type LeaderboardEntry,
@@ -1318,7 +1319,18 @@ function StatsMark() {
   )
 }
 
+function OpenCodeMark() {
+  return (
+    <svg data-slot="opencode-mark" width="40" height="40" viewBox="0 0 40 40" fill="none" aria-hidden="true">
+      <path d="M40 40H0V0H40V40Z" fill="var(--stats-logo-bg)" />
+      <path d="M26 29H14V17H26V29Z" fill="var(--stats-logo-fill)" />
+      <path d="M26 11H14V29H26V11ZM32 35H8V5H32V35Z" fill="var(--stats-logo-stroke)" />
+    </svg>
+  )
+}
+
 function Footer() {
+  const [subscribeOpen, setSubscribeOpen] = createSignal(false)
   const modelStats = [
     { href: "#top-models", label: "Top Models" },
     { href: "#leaderboard", label: "Leaderboard" },
@@ -1333,17 +1345,17 @@ function Footer() {
   const connect = [
     { href: "mailto:hello@opencode.ai", label: "Contact us" },
     { href: "https://opencode.ai/discord", label: "Community" },
-    { href: "https://x.com/opencode_ai", label: "X" },
-    { href: "https://github.com/sst/opencode", label: "GitHub" },
-    { href: "https://youtube.com/@opencode-ai", label: "YouTube" },
+    { href: "https://x.com/opencode", label: "X" },
+    { href: "https://github.com/anomalyco/opencode", label: "GitHub" },
+    { href: "https://www.youtube.com/@anomaly-co", label: "YouTube" },
   ]
 
   return (
     <footer data-component="footer">
       <SectionBridge label="SESSION COST" href="#session-cost" />
       <div data-slot="footer-grid">
-        <a data-slot="footer-mark" href="/" aria-label="OpenCode home">
-          <StatsMark />
+        <a data-slot="footer-mark" href="https://opencode.ai" aria-label="OpenCode home">
+          <OpenCodeMark />
         </a>
         <FooterColumn title="Model Stats" links={modelStats} />
         <FooterColumn title="Legal" links={legal} />
@@ -1351,9 +1363,9 @@ function Footer() {
         <div data-slot="footer-column">
           <h2>Newsletter</h2>
           <p>Be the first to know about new releases.</p>
-          <a data-slot="subscribe-button" href="https://opencode.ai/">
+          <button data-slot="subscribe-button" type="button" onClick={() => setSubscribeOpen(true)}>
             Subscribe
-          </a>
+          </button>
         </div>
       </div>
       <div data-slot="footer-pattern" aria-hidden="true" />
@@ -1362,19 +1374,114 @@ function Footer() {
           <span>© 2026 Anomaly Innovations Inc.</span>
           <span data-slot="status">All systems Operational</span>
         </div>
-        <div data-slot="theme-toggle" aria-label="Theme preference">
-          <button type="button" aria-label="Use dark theme">
-            <MoonIcon />
-          </button>
-          <button type="button" aria-label="Use light theme">
-            <SunIcon />
-          </button>
-          <button type="button" aria-label="Use system theme" data-active="true">
-            <MonitorIcon />
+      </div>
+      <Show when={subscribeOpen()}>
+        <SubscribeModal onClose={() => setSubscribeOpen(false)} />
+      </Show>
+    </footer>
+  )
+}
+
+function SubscribeModal(props: { onClose: () => void }) {
+  const [status, setStatus] = createSignal<"idle" | "pending" | "success" | "error">("idle")
+  const [message, setMessage] = createSignal("")
+  let input: HTMLInputElement | undefined
+
+  onMount(() => {
+    if (typeof document === "undefined") return
+    const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : undefined
+    const htmlOverflow = document.documentElement.style.overflow
+    const bodyOverflow = document.body.style.overflow
+    document.documentElement.style.overflow = "hidden"
+    document.body.style.overflow = "hidden"
+    const focusTimeout = window.setTimeout(() => input?.focus(), 0)
+    const onKeyDown = (event: KeyboardEvent) => {
+      if (event.key === "Escape") props.onClose()
+    }
+    document.addEventListener("keydown", onKeyDown)
+    onCleanup(() => {
+      window.clearTimeout(focusTimeout)
+      document.documentElement.style.overflow = htmlOverflow
+      document.body.style.overflow = bodyOverflow
+      document.removeEventListener("keydown", onKeyDown)
+      activeElement?.focus()
+    })
+  })
+
+  return (
+    <div data-component="subscribe-modal" role="dialog" aria-modal="true" aria-labelledby="subscribe-title">
+      <div data-slot="modal-scrim" aria-hidden="true" onClick={props.onClose} />
+      <div data-slot="modal-panel">
+        <div data-slot="modal-brand">
+          <img data-slot="modal-logo" src={opencodeWordmarkDark} alt="OpenCode" />
+          <button data-slot="modal-close" type="button" aria-label="Close newsletter signup" onClick={props.onClose}>
+            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
+              <path d="M4.44 4.44L11.56 11.56M11.56 4.44L4.44 11.56" stroke="currentColor" />
+            </svg>
           </button>
         </div>
+        <div data-slot="modal-body">
+          <div data-slot="modal-intro">
+            <h2 id="subscribe-title">OpenCode Newsletter</h2>
+            <p>
+              Be the first to know
+              <br />
+              about new releases.
+            </p>
+          </div>
+          <form
+            data-slot="subscribe-form"
+            method="post"
+            onSubmit={(event) => {
+              event.preventDefault()
+              setStatus("pending")
+              setMessage("")
+              fetch("/api/newsletter", {
+                method: "POST",
+                body: new FormData(event.currentTarget),
+              }).then(
+                async (response) => {
+                  if (response.ok) {
+                    event.currentTarget.reset()
+                    setStatus("success")
+                    return
+                  }
+                  setMessage(await newsletterErrorMessage(response))
+                  setStatus("error")
+                },
+                () => {
+                  setMessage("Failed to subscribe")
+                  setStatus("error")
+                },
+              )
+            }}
+          >
+            <input ref={input} type="email" name="email" placeholder="Email address" required />
+            <button type="submit" disabled={status() === "pending"}>
+              {status() === "pending" ? "Subscribing..." : "Subscribe"}
+            </button>
+          </form>
+          <div data-slot="subscribe-feedback" aria-live="polite">
+            <Show when={status() === "success"}>
+              <p data-state="success">You're subscribed.</p>
+            </Show>
+            <Show when={status() === "error"}>
+              <p data-state="error">{message()}</p>
+            </Show>
+          </div>
+        </div>
       </div>
-    </footer>
+    </div>
+  )
+}
+
+function newsletterErrorMessage(response: Response) {
+  return response.json().then(
+    (body: unknown) =>
+      body && typeof body === "object" && "error" in body && typeof body.error === "string"
+        ? body.error
+        : "Failed to subscribe",
+    () => "Failed to subscribe",
   )
 }
 
@@ -1394,32 +1501,3 @@ function FooterColumn(props: { title: string; links: { href: string; label: stri
     </div>
   )
 }
-
-function MoonIcon() {
-  return (
-    <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
-      <path d="M8.8 7.9A4.3 4.3 0 0 1 4.1 3.2A3.9 3.9 0 1 0 8.8 7.9Z" stroke="currentColor" />
-    </svg>
-  )
-}
-
-function SunIcon() {
-  return (
-    <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
-      <path
-        d="M6 3.5V2M6 10V8.5M8.5 6H10M2 6H3.5M7.75 4.25L8.8 3.2M3.2 8.8L4.25 7.75M4.25 4.25L3.2 3.2M8.8 8.8L7.75 7.75"
-        stroke="currentColor"
-      />
-      <circle cx="6" cy="6" r="1.7" stroke="currentColor" />
-    </svg>
-  )
-}
-
-function MonitorIcon() {
-  return (
-    <svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
-      <path d="M2 3H10V8H2V3Z" stroke="currentColor" />
-      <path d="M4.5 10H7.5M6 8V10" stroke="currentColor" />
-    </svg>
-  )
-}