mirror of
https://github.com/anomalyco/opencode.git
synced 2026-06-01 06:11:30 +00:00
feat(stats): match theme icons to figma (#29893)
This commit is contained in:
parent
46836beec9
commit
76dbe10ad7
4 changed files with 336 additions and 14 deletions
1
packages/stats/AGENTS.md
Normal file
1
packages/stats/AGENTS.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
To start the stats site locally, run `bun dev:stats` from the repo root.
|
||||
|
|
@ -1,6 +1,17 @@
|
|||
// @refresh reload
|
||||
import { createHandler, StartServer } from "@solidjs/start/server"
|
||||
|
||||
const statsThemePreloadScript = `;(function () {
|
||||
var preference = "system"
|
||||
try {
|
||||
var stored = localStorage.getItem("opencode:stats-theme")
|
||||
if (stored === "dark" || stored === "light" || stored === "system") preference = stored
|
||||
} catch (_) {}
|
||||
document.documentElement.dataset.statsTheme = preference
|
||||
if (preference === "system") document.documentElement.style.removeProperty("color-scheme")
|
||||
else document.documentElement.style.setProperty("color-scheme", preference)
|
||||
})()`
|
||||
|
||||
export default createHandler(
|
||||
() => (
|
||||
<StartServer
|
||||
|
|
@ -9,6 +20,7 @@ export default createHandler(
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script id="stats-theme-preload-script">{statsThemePreloadScript}</script>
|
||||
{assets}
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
}
|
||||
|
||||
[data-page="stats"] {
|
||||
color-scheme: light;
|
||||
--color-background: #ffffff;
|
||||
--color-background-weak: #fafafa;
|
||||
--color-background-weak-hover: #eeeeee;
|
||||
|
|
@ -61,6 +62,7 @@
|
|||
--stats-text: #161616;
|
||||
--stats-muted: #5c5c5c;
|
||||
--stats-faint: #808080;
|
||||
--stats-theme-icon-active: #3a3a3a;
|
||||
--stats-accent: #3b5cf6;
|
||||
--stats-accent-text: #6c7dff;
|
||||
--stats-bar-idle: #d4d4d4;
|
||||
|
|
@ -440,6 +442,7 @@
|
|||
}
|
||||
|
||||
[data-page="stats"] [data-slot="subscribe-button"]:focus-visible,
|
||||
[data-page="stats"] [data-slot="theme-option"]: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);
|
||||
|
|
@ -640,6 +643,54 @@
|
|||
gap: 24px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="theme-toggle"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
padding: 1px;
|
||||
background: var(--stats-layer-2);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="theme-option"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
color: var(--stats-faint);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="theme-icon"] {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="theme-option"]:hover {
|
||||
color: var(--stats-text);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="theme-option"][aria-pressed="true"] {
|
||||
background: var(--stats-bg);
|
||||
color: var(--stats-theme-icon-active);
|
||||
box-shadow:
|
||||
0 0 0 0 #0000,
|
||||
0 0 0 0.5px #0000001f,
|
||||
0 1px 2px -1px #00000014,
|
||||
0 2px 4px 0 #0000000a;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="status"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -2114,8 +2165,123 @@
|
|||
width: fit-content;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"],
|
||||
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) {
|
||||
color-scheme: dark;
|
||||
--color-background: #161616;
|
||||
--color-background-weak: #242424;
|
||||
--color-background-weak-hover: #303030;
|
||||
--color-background-strong: #ffffff;
|
||||
--color-background-strong-hover: #eeeeee;
|
||||
--color-text: #d4d4d4;
|
||||
--color-text-weak: #808080;
|
||||
--color-text-strong: #ffffff;
|
||||
--color-text-inverted: #161616;
|
||||
--color-border-weak: #ffffff1a;
|
||||
--stats-bg: #161616;
|
||||
--stats-layer: #242424;
|
||||
--stats-layer-2: #303030;
|
||||
--stats-line: #ffffff1a;
|
||||
--stats-line-strong: #ffffff33;
|
||||
--stats-text: #ffffff;
|
||||
--stats-muted: #d4d4d4;
|
||||
--stats-faint: #808080;
|
||||
--stats-theme-icon-active: #fafafa;
|
||||
--stats-bar-idle: #303030;
|
||||
--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-theme="dark"] [data-component="chart-tooltip"],
|
||||
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="chart-tooltip"] {
|
||||
background: #242424f2;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-section="top-models"] [data-component="chart-tooltip"],
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-section="top-models"]
|
||||
[data-component="chart-tooltip"] {
|
||||
background: #242424f2;
|
||||
box-shadow:
|
||||
0 0 0 0.5px #ffffff24,
|
||||
0 8px 16px #0000003d,
|
||||
0 4px 8px #00000052;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-section="top-models"] [data-component="chart-tooltip"] > span,
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-section="top-models"]
|
||||
[data-component="chart-tooltip"]
|
||||
> span {
|
||||
color: var(--stats-faint);
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-component="leader-card"][data-size="compact"],
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="leader-card"][data-size="compact"] {
|
||||
box-shadow:
|
||||
0 0 0 0.5px #ffffff1f,
|
||||
0 1px 2px -1px #00000052,
|
||||
0 2px 4px #0000003d;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover,
|
||||
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible,
|
||||
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="leader-card"]:hover,
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="leader-card"]:focus-visible {
|
||||
background: #303030;
|
||||
box-shadow:
|
||||
0 0 0 0.5px #ffffff24,
|
||||
0 6px 16px #0000003d,
|
||||
0 2px 6px #00000052;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-slot="header-button"][data-variant="neutral"],
|
||||
[data-page="stats"][data-theme="dark"] [data-slot="menu-button"],
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-slot="header-button"][data-variant="neutral"],
|
||||
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [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-theme="dark"] [data-slot="header-button"][data-variant="neutral"] span,
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-slot="header-button"][data-variant="neutral"]
|
||||
span {
|
||||
color: #aeaeae;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-slot="header-button"][data-variant="contrast"],
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[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;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
[data-page="stats"] {
|
||||
:root:not([data-stats-theme="light"]) [data-page="stats"]:not([data-theme="light"]) {
|
||||
color-scheme: dark;
|
||||
--color-background: #161616;
|
||||
--color-background-weak: #242424;
|
||||
--color-background-weak-hover: #303030;
|
||||
|
|
@ -2134,6 +2300,7 @@
|
|||
--stats-text: #ffffff;
|
||||
--stats-muted: #d4d4d4;
|
||||
--stats-faint: #808080;
|
||||
--stats-theme-icon-active: #fafafa;
|
||||
--stats-bar-idle: #303030;
|
||||
--stats-dot: #303030;
|
||||
--stats-hero-muted: #808080;
|
||||
|
|
@ -2143,11 +2310,14 @@
|
|||
--stats-logo-stroke: #211e1e;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="chart-tooltip"] {
|
||||
:root:not([data-stats-theme="light"]) [data-page="stats"]:not([data-theme="light"]) [data-component="chart-tooltip"] {
|
||||
background: #242424f2;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-section="top-models"]
|
||||
[data-component="chart-tooltip"] {
|
||||
background: #242424f2;
|
||||
box-shadow:
|
||||
0 0 0 0.5px #ffffff24,
|
||||
|
|
@ -2155,19 +2325,29 @@
|
|||
0 4px 8px #00000052;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-section="top-models"] [data-component="chart-tooltip"] > span {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-section="top-models"]
|
||||
[data-component="chart-tooltip"]
|
||||
> span {
|
||||
color: var(--stats-faint);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="leader-card"][data-size="compact"] {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="leader-card"][data-size="compact"] {
|
||||
box-shadow:
|
||||
0 0 0 0.5px #ffffff1f,
|
||||
0 1px 2px -1px #00000052,
|
||||
0 2px 4px #0000003d;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="leader-card"]:hover,
|
||||
[data-page="stats"] [data-component="leader-card"]:focus-visible {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="leader-card"]:hover,
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="leader-card"]:focus-visible {
|
||||
background: #303030;
|
||||
box-shadow:
|
||||
0 0 0 0.5px #ffffff24,
|
||||
|
|
@ -2175,8 +2355,10 @@
|
|||
0 2px 6px #00000052;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="header-button"][data-variant="neutral"],
|
||||
[data-page="stats"] [data-slot="menu-button"] {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-slot="header-button"][data-variant="neutral"],
|
||||
:root:not([data-stats-theme="light"]) [data-page="stats"]:not([data-theme="light"]) [data-slot="menu-button"] {
|
||||
color: #fafafa;
|
||||
background: #ffffff0f;
|
||||
box-shadow:
|
||||
|
|
@ -2185,11 +2367,16 @@
|
|||
0 1px 2px 0 #00000066;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="header-button"][data-variant="neutral"] span {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-slot="header-button"][data-variant="neutral"]
|
||||
span {
|
||||
color: #aeaeae;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="header-button"][data-variant="contrast"] {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-slot="header-button"][data-variant="contrast"] {
|
||||
color: #ffffff;
|
||||
background: #5c5c5c;
|
||||
box-shadow:
|
||||
|
|
|
|||
|
|
@ -67,10 +67,18 @@ const usageColors = [
|
|||
"#ff6467",
|
||||
]
|
||||
const marketColors = ["#ed6aff", "#a684ff", "#7c86ff", "#51a2ff", "#00d3f2", "#00d5be", "#00bc7d", "#9ae600", "#ffb900"]
|
||||
const themePreferences = ["dark", "light", "system"] as const
|
||||
const themePreferenceLabels = {
|
||||
dark: "Dark",
|
||||
light: "Light",
|
||||
system: "System",
|
||||
} as const
|
||||
const themeStorageKey = "opencode:stats-theme"
|
||||
|
||||
type UsageProduct = (typeof products)[number]
|
||||
type TokenProduct = (typeof tokenProducts)[number]
|
||||
type UsageRange = (typeof ranges)[number]
|
||||
type ThemePreference = (typeof themePreferences)[number]
|
||||
|
||||
const getData = query(async () => {
|
||||
"use server"
|
||||
|
|
@ -104,9 +112,24 @@ export default function StatsHome() {
|
|||
const statsUnfurlUrl = new URL(statsUnfurlRankings, statsHomeUrl).toString()
|
||||
const data = createAsync(() => getData())
|
||||
const githubStars = createAsync(() => getGitHubStars())
|
||||
const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
|
||||
const updateThemePreference = (preference: ThemePreference) => {
|
||||
applyThemePreference(preference)
|
||||
setThemePreference(preference)
|
||||
if (typeof window === "undefined") return
|
||||
window.localStorage.setItem(themeStorageKey, preference)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (typeof window === "undefined") return
|
||||
const preference = window.localStorage.getItem(themeStorageKey)
|
||||
const nextPreference = isThemePreference(preference) ? preference : "system"
|
||||
applyThemePreference(nextPreference)
|
||||
setThemePreference(nextPreference)
|
||||
})
|
||||
|
||||
return (
|
||||
<main data-page="stats">
|
||||
<main data-page="stats" data-theme={themePreference()}>
|
||||
<Title>{statsHomeTitle}</Title>
|
||||
<Meta name="description" content={statsHomeDescription} />
|
||||
<Link rel="canonical" href={statsHomeUrl} />
|
||||
|
|
@ -145,12 +168,26 @@ export default function StatsHome() {
|
|||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<Footer />
|
||||
<Footer themePreference={themePreference()} onThemePreferenceChange={updateThemePreference} />
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function isThemePreference(value: string | null): value is ThemePreference {
|
||||
return value === "dark" || value === "light" || value === "system"
|
||||
}
|
||||
|
||||
function applyThemePreference(preference: ThemePreference) {
|
||||
if (typeof document === "undefined") return
|
||||
document.documentElement.dataset.statsTheme = preference
|
||||
if (preference === "system") {
|
||||
document.documentElement.style.removeProperty("color-scheme")
|
||||
return
|
||||
}
|
||||
document.documentElement.style.setProperty("color-scheme", preference)
|
||||
}
|
||||
|
||||
function Hero(props: { updatedAt: string | null }) {
|
||||
const [timeZone, setTimeZone] = createSignal("UTC")
|
||||
const [previousTimeZone, setPreviousTimeZone] = createSignal("UTC")
|
||||
|
|
@ -1421,7 +1458,10 @@ function OpenCodeMark() {
|
|||
)
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
function Footer(props: {
|
||||
themePreference: ThemePreference
|
||||
onThemePreferenceChange: (preference: ThemePreference) => void
|
||||
}) {
|
||||
const [subscribeOpen, setSubscribeOpen] = createSignal(false)
|
||||
const modelStats = [
|
||||
{ href: "#top-models", label: "Top Models" },
|
||||
|
|
@ -1466,6 +1506,22 @@ function Footer() {
|
|||
<span>© 2026 Anomaly Innovations Inc.</span>
|
||||
<span data-slot="status">All systems Operational</span>
|
||||
</div>
|
||||
<div data-slot="theme-toggle" role="group" aria-label="Theme">
|
||||
<For each={themePreferences}>
|
||||
{(preference) => (
|
||||
<button
|
||||
data-slot="theme-option"
|
||||
type="button"
|
||||
aria-label={themePreferenceLabels[preference]}
|
||||
aria-pressed={props.themePreference === preference ? "true" : "false"}
|
||||
title={themePreferenceLabels[preference]}
|
||||
onClick={() => props.onThemePreferenceChange(preference)}
|
||||
>
|
||||
<ThemePreferenceIcon preference={preference} />
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={subscribeOpen()}>
|
||||
<SubscribeModal onClose={() => setSubscribeOpen(false)} />
|
||||
|
|
@ -1474,6 +1530,72 @@ function Footer() {
|
|||
)
|
||||
}
|
||||
|
||||
function ThemePreferenceIcon(props: { preference: ThemePreference }) {
|
||||
return (
|
||||
<svg data-slot="theme-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<Show
|
||||
when={props.preference === "dark"}
|
||||
fallback={
|
||||
<Show
|
||||
when={props.preference === "light"}
|
||||
fallback={
|
||||
<>
|
||||
<rect x="1.5552" y="2.4448" width="12.8896" height="8.8888" fill="currentColor" opacity="0.3" />
|
||||
<svg
|
||||
x="1.0552"
|
||||
y="1.9446"
|
||||
width="13.8889"
|
||||
height="12.5325"
|
||||
viewBox="0 0 13.8889 12.5325"
|
||||
preserveAspectRatio="none"
|
||||
overflow="visible"
|
||||
>
|
||||
<path
|
||||
d="M4.05559 12.0555C4.72936 11.8431 5.72492 11.6111 6.94448 11.6111M6.94448 11.6111C7.65114 11.6111 8.66981 11.6893 9.83336 12.0555M6.94448 11.6111L6.94448 9.38888M13.3889 0.5H0.500102C0.500102 0.5 0.500017 1.29594 0.500017 2.27778V7.61112C0.500017 8.59298 0.500007 9.38889 0.500007 9.38889H13.3889C13.3889 9.38889 13.3889 8.59298 13.3889 7.61112V2.27778C13.3889 1.29594 13.3889 0.5 13.3889 0.5Z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<svg
|
||||
x="0.6102"
|
||||
y="0.6102"
|
||||
width="14.7778"
|
||||
height="14.7778"
|
||||
viewBox="0 0 14.7778 14.7778"
|
||||
preserveAspectRatio="none"
|
||||
overflow="visible"
|
||||
>
|
||||
<path
|
||||
d="M7.38889 0.5V1.38889M12.26 2.51782L11.6315 3.14627M14.2778 7.38892H13.3889M12.26 12.26L11.6315 11.6316M7.38889 14.2778V13.3889M2.51778 12.26L3.14622 11.6316M0.5 7.38892H1.38889M2.51778 2.51782L3.14622 3.14627M7.38888 11.1666C9.47528 11.1666 11.1667 9.47526 11.1667 7.38886C11.1667 5.30245 9.47528 3.61108 7.38888 3.61108C5.30247 3.61108 3.6111 5.30245 3.6111 7.38886C3.6111 9.47526 5.30247 11.1666 7.38888 11.1666Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<svg
|
||||
x="2.0549"
|
||||
y="1.742"
|
||||
width="12.3867"
|
||||
height="12.3971"
|
||||
viewBox="0 0 12.3867 12.3971"
|
||||
preserveAspectRatio="none"
|
||||
overflow="visible"
|
||||
>
|
||||
<path
|
||||
d="M9.05556 8.39711C6.37067 8.39711 4.19444 6.22089 4.19444 3.536C4.19444 2.48445 4.53122 1.51456 5.09822 0.71889C2.48178 1.20733 0.5 3.49944 0.5 6.25822C0.5 9.37244 3.02467 11.8971 6.13889 11.8971C8.76156 11.8971 10.9596 10.1036 11.5903 7.67844C10.8514 8.13189 9.98578 8.39711 9.05556 8.39711Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</Show>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function SubscribeModal(props: { onClose: () => void }) {
|
||||
const [status, setStatus] = createSignal<"idle" | "pending" | "success" | "error">("idle")
|
||||
const [message, setMessage] = createSignal("")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue