diff --git a/package.json b/package.json index f2e79709dc..5fecf69bab 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dev:desktop": "bun --cwd packages/desktop dev", "dev:web": "bun --cwd packages/app dev", "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", - "dev:stats": "bun sst shell --stage=dev -- bun run --cwd packages/stats/app dev", + "dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev", "dev:storybook": "bun --cwd packages/storybook storybook", "lint": "oxlint", "typecheck": "bun turbo typecheck", diff --git a/packages/stats/app/src/routes/index.css b/packages/stats/app/src/routes/index.css index 2e292346d8..36179ebf2b 100644 --- a/packages/stats/app/src/routes/index.css +++ b/packages/stats/app/src/routes/index.css @@ -27,6 +27,7 @@ display: flex; flex-direction: column; gap: 4rem; + overflow-x: clip; padding-bottom: 5rem; background: var(--stats-bg); } @@ -39,6 +40,8 @@ } [data-page="stats"] [data-component="container"] { + box-sizing: border-box; + width: 100%; max-width: 67.5rem; margin: 0 auto; border-left: 1px solid var(--stats-line); @@ -49,74 +52,245 @@ position: sticky; top: 0; z-index: 10; - display: flex; - align-items: center; - justify-content: space-between; - height: 80px; - min-height: 80px; - padding: 24px var(--stats-page-padding); - color: var(--stats-muted); + box-sizing: border-box; + width: 100%; + color: var(--stats-text); background: var(--stats-bg); - border-bottom: 1px solid var(--stats-line); font-family: "IBM Plex Mono", var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); } +[data-page="stats"] [data-component="top"] * { + box-sizing: border-box; +} + +[data-page="stats"] [data-slot="header-bar"] { + display: flex; + align-items: center; + gap: 16px; + min-height: 72px; + padding: 20px 20px 20px 24px; + overflow: hidden; +} + [data-page="stats"] [data-component="top"] a { - color: var(--stats-text); - font-size: 14px; - line-height: 18px; text-decoration: none; } [data-page="stats"] [data-component="top"] a:hover { - text-decoration: underline; - text-underline-offset: 4px; + text-decoration: none; } [data-page="stats"] [data-slot="brand"] { + flex: 0 0 auto; + min-width: 0; display: flex; align-items: center; + margin-right: auto; + color: var(--stats-text); } -[data-page="stats"] [data-slot="brand"] img { - width: auto; - height: 34px; +[data-page="stats"] [data-slot="stats-wordmark"] { + display: flex; + flex-shrink: 0; + align-items: center; + gap: 12px; } -[data-page="stats"] [data-slot="logo dark"] { +[data-page="stats"] [data-slot="brand-mark"] { + display: block; + width: 19px; + height: 24px; +} + +[data-page="stats"] [data-slot="brand-label"] { + display: block; + width: 50.851px; + height: 14px; +} + +[data-page="stats"] [data-component="section-nav"] { display: none; + align-items: center; + justify-content: center; + min-width: 0; } -[data-page="stats"] [data-component="nav-desktop"] ul { +[data-page="stats"] [data-component="section-nav"] ul { display: flex; align-items: center; - gap: 32px; margin: 0; padding: 0; list-style: none; } -[data-page="stats"] [data-component="nav-desktop"] a span { - color: var(--stats-faint); -} - -[data-page="stats"] [data-component="nav-desktop"] [data-slot="cta-button"] { +[data-page="stats"] [data-component="section-nav"] a { display: flex; align-items: center; + justify-content: center; + height: 32px; + padding: 0 15px; + color: var(--stats-muted); + font-size: 13px; + line-height: 1; + white-space: nowrap; +} + +[data-page="stats"] [data-component="section-nav"] a:hover { + color: var(--stats-text); +} + +[data-page="stats"] [data-slot="header-actions"] { + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: flex-end; gap: 8px; - padding: 8px 16px 8px 10px; - border-radius: 4px; - background: var(--color-background-strong); - color: var(--color-text-inverted); +} + +[data-page="stats"] [data-slot="header-button"], +[data-page="stats"] [data-slot="menu-button"] { + position: relative; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + height: 32px; + overflow: hidden; + margin: 0; + border: 0; + border-radius: 0; + appearance: none; + font: inherit; + font-size: 13px; + line-height: 1.1; + cursor: pointer; +} + +[data-page="stats"] [data-slot="header-button"]::before, +[data-page="stats"] [data-slot="menu-button"]::before { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 16px; + pointer-events: none; + content: ""; + background: linear-gradient(180deg, #ffffff00 0%, #ffffff12 100%); +} + +[data-page="stats"] [data-slot="header-button"] { + padding: 0 12px; +} + +[data-page="stats"] [data-slot="header-button"] strong, +[data-page="stats"] [data-slot="header-button"] span, +[data-page="stats"] [data-slot="menu-button"] svg { + position: relative; + z-index: 1; +} + +[data-page="stats"] [data-slot="header-button"] strong { font-weight: 500; white-space: nowrap; } -[data-page="stats"] [data-component="nav-desktop"] [data-slot="cta-button"]:hover { - background: var(--color-background-strong-hover); - text-decoration: none; +[data-page="stats"] [data-slot="header-button"][data-variant="neutral"] { + display: none; + gap: 6px; + color: #161616; + background: #ffffff; + box-shadow: + 0 0 0 0 #00000024, + 0 0 0 0.5px #00000024, + 0 1px 1.5px 0 #0000001a; +} + +[data-page="stats"] [data-slot="header-button"][data-variant="neutral"] span { + color: #5c5c5c; + font-weight: 400; + font-variant-numeric: tabular-nums; +} + +[data-page="stats"] [data-slot="header-button"][data-variant="contrast"] { + gap: 8px; + color: #ffffff; + background: #242424; + box-shadow: + 0 0 0 0 #00000000, + 0 0 0 0.5px #3a3a3a, + 0 1px 1.5px 0 #00000033, + inset 0 -1px 2px 0 #0000000f, + inset 0 1px 2px 0 #ffffff24; +} + +[data-page="stats"] [data-slot="menu-button"] { + display: inline-flex; + width: 32px; + padding: 0; + color: #3a3a3a; + background: #ffffff; + box-shadow: + 0 0 0 0 #00000024, + 0 0 0 0.5px #00000024, + 0 1px 1.5px 0 #0000001a; +} + +[data-page="stats"] [data-slot="header-button"]:focus-visible, +[data-page="stats"] [data-slot="menu-button"]:focus-visible, +[data-page="stats"] [data-component="section-nav"] a:focus-visible, +[data-page="stats"] [data-slot="mobile-menu-item"]:focus-visible, +[data-page="stats"] [data-slot="brand"]:focus-visible { + outline: 2px solid var(--stats-accent); + outline-offset: 2px; +} + +[data-page="stats"] [data-slot="mobile-menu"] { + position: fixed; + top: 72px; + right: 0; + bottom: 0; + left: 0; + z-index: 9; + display: none; + overflow: auto; + background: var(--stats-bg); + border-top: 1px solid var(--stats-line); +} + +[data-page="stats"] [data-menu-open="true"] [data-slot="mobile-menu"]:not([hidden]) { + display: block; +} + +[data-page="stats"] [data-slot="mobile-menu-item"] { + display: flex; + align-items: center; + gap: 12px; + min-height: 65px; + padding: 24px; + color: var(--stats-text); + border-bottom: 1px solid var(--stats-line); + font-size: 13px; + line-height: 16px; + white-space: nowrap; +} + +[data-page="stats"] [data-slot="mobile-menu-item"]:hover { + color: var(--stats-text); + background: var(--stats-layer); +} + +[data-page="stats"] [data-slot="mobile-menu-item"] strong { + font-weight: 400; +} + +[data-page="stats"] [data-slot="mobile-menu-item"] span { + min-width: 0; + overflow: hidden; + color: var(--stats-muted); + font-variant-numeric: tabular-nums; + text-overflow: ellipsis; } [data-page="stats"] [data-component="footer"] { @@ -1168,12 +1342,27 @@ 0 2px 4px #0000003d; } - [data-page="stats"] [data-slot="logo light"] { - display: none; + [data-page="stats"] [data-slot="header-button"][data-variant="neutral"], + [data-page="stats"] [data-slot="menu-button"] { + color: #fafafa; + background: #ffffff0f; + box-shadow: + 0 -0.5px 0 0 #ffffff33, + 0 0 0 0.5px #ffffff33, + 0 1px 2px 0 #00000066; } - [data-page="stats"] [data-slot="logo dark"] { - display: block; + [data-page="stats"] [data-slot="header-button"][data-variant="neutral"] span { + color: #aeaeae; + } + + [data-page="stats"] [data-slot="header-button"][data-variant="contrast"] { + color: #ffffff; + background: #5c5c5c; + box-shadow: + 0 -0.5px 0 0 #ffffff4d, + 0 0 0 0.5px #ffffff66, + 0 1px 2px 0 #00000066; } } @@ -1183,21 +1372,46 @@ } } +@media (min-width: 48rem) { + [data-page="stats"] [data-slot="header-button"][data-variant="neutral"] { + display: inline-flex; + } +} + +@media (min-width: 75rem) { + [data-page="stats"] [data-slot="header-bar"] { + gap: 32px; + } + + [data-page="stats"] [data-slot="brand"], + [data-page="stats"] [data-component="section-nav"], + [data-page="stats"] [data-slot="header-actions"] { + flex: 1 1 0; + } + + [data-page="stats"] [data-slot="brand"] { + margin-right: 0; + } + + [data-page="stats"] [data-component="section-nav"] { + display: flex; + } + + [data-page="stats"] [data-slot="menu-button"] { + display: none; + } + + [data-page="stats"] [data-slot="mobile-menu"] { + display: none !important; + } +} + @media (max-width: 58rem) { [data-page="stats"] { --stats-page-padding: 24px; --stats-section-padding: 4rem; } - [data-page="stats"] [data-component="top"] { - padding-left: 24px; - padding-right: 24px; - } - - [data-page="stats"] [data-component="nav-desktop"] ul { - gap: 18px; - } - [data-page="stats"] [data-section="hero"], [data-page="stats"] [data-section="chart"], [data-page="stats"] [data-section="newsletter"] { @@ -1301,10 +1515,6 @@ } @media (max-width: 40rem) { - [data-page="stats"] [data-component="nav-desktop"] li:not(:last-child) { - display: none; - } - [data-page="stats"] [data-component="footer"], [data-page="stats"] [data-component="legal"] { flex-wrap: wrap; diff --git a/packages/stats/app/src/routes/index.tsx b/packages/stats/app/src/routes/index.tsx index 01c02d4717..0492a23b6a 100644 --- a/packages/stats/app/src/routes/index.tsx +++ b/packages/stats/app/src/routes/index.tsx @@ -14,14 +14,19 @@ import { import { runtime } from "@opencode-ai/stats-core/runtime" import { createAsync, query } from "@solidjs/router" import { scaleBand, scaleLinear } from "d3-scale" -import { createMemo, createSignal, For, Show, type JSX } from "solid-js" +import { createEffect, createMemo, createSignal, For, onCleanup, Show, type JSX } from "solid-js" import { getRequestEvent } from "solid-js/web" -import logoDark from "../asset/logo-ornate-dark.svg" -import logoLight from "../asset/logo-ornate-light.svg" 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 headerLinks = [ + { href: "#top-models", label: "Top Models" }, + { href: "#leaderboard", label: "Leaderboard" }, + { href: "#market-share", label: "Market Share" }, + { href: "#token-cost", label: "Token Cost" }, + { href: "#session-cost", label: "Session Cost" }, +] as const const usageColors = ["#ff5d64", "#ff8a00", "#8bef00", "#12c8b3", "#18c7dc", "#6c7dff", "#9d73f7"] const marketColors = ["#ed6aff", "#a684ff", "#7c86ff", "#51a2ff", "#00d3f2", "#00d5be", "#00bc7d", "#9ae600", "#ffb900"] const countryPositions = [ @@ -115,9 +120,9 @@ function StatsLoading() { ) } -function ChartSection(props: { title: string; description?: string; controls?: JSX.Element; children: JSX.Element }) { +function ChartSection(props: { id?: string; title: string; description?: string; controls?: JSX.Element; children: JSX.Element }) { return ( -
+

{props.title}

@@ -158,7 +163,7 @@ function UsageSection(props: { data: StatsHomeData["usage"] }) { const data = createMemo(() => props.data[product()][range()]) return ( - + usageTotal(item) > 0)} fallback={} @@ -394,6 +399,7 @@ function LeaderboardSection(props: { data: StatsHomeData["leaderboard"] }) { return ( @@ -476,7 +482,7 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) { const activeDay = createMemo(() => data()[selectedIndex()]) return ( - + } @@ -572,7 +578,7 @@ function TokenCostSection(props: { data: StatsHomeData["tokenCost"] }) { const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(data().length - 1, 0))) return ( - + 0} fallback={ @@ -661,7 +667,7 @@ function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) { const selectedIndex = createMemo(() => Math.min(activeIndex(), Math.max(data().length - 1, 0))) return ( - + 0} fallback={ @@ -886,47 +892,122 @@ function Newsletter() { } function Header() { + const [menuOpen, setMenuOpen] = createSignal(false) + const [menuViewport, setMenuViewport] = createSignal(false) + + createEffect(() => { + if (typeof window === "undefined") return + const media = window.matchMedia("(max-width: 74.999rem)") + const update = () => setMenuViewport(media.matches) + update() + media.addEventListener("change", update) + onCleanup(() => media.removeEventListener("change", update)) + }) + + createEffect(() => { + if (!menuOpen()) return + if (!menuViewport()) return + if (typeof document === "undefined") return + const page = document.querySelector('[data-page="stats"]') + const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth + const htmlOverflow = document.documentElement.style.overflow + const pagePaddingRight = page?.style.paddingRight + const bodyOverflow = document.body.style.overflow + document.documentElement.style.overflow = "hidden" + if (scrollbarWidth > 0 && page) page.style.paddingRight = `${scrollbarWidth}px` + document.body.style.overflow = "hidden" + onCleanup(() => { + document.documentElement.style.overflow = htmlOverflow + if (page && pagePaddingRight !== undefined) page.style.paddingRight = pagePaddingRight + document.body.style.overflow = bodyOverflow + }) + }) + return ( -
- - OpenCode - OpenCode - - -
+ + ) +} + +function StatsWordmark() { + return ( + ) }