feat(stats): match theme icons to figma (#29893)
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
To start the stats site locally, run `bun dev:stats` from the repo root.
|
||||||
@@ -1,6 +1,17 @@
|
|||||||
// @refresh reload
|
// @refresh reload
|
||||||
import { createHandler, StartServer } from "@solidjs/start/server"
|
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(
|
export default createHandler(
|
||||||
() => (
|
() => (
|
||||||
<StartServer
|
<StartServer
|
||||||
@@ -9,6 +20,7 @@ export default createHandler(
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<script id="stats-theme-preload-script">{statsThemePreloadScript}</script>
|
||||||
{assets}
|
{assets}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] {
|
[data-page="stats"] {
|
||||||
|
color-scheme: light;
|
||||||
--color-background: #ffffff;
|
--color-background: #ffffff;
|
||||||
--color-background-weak: #fafafa;
|
--color-background-weak: #fafafa;
|
||||||
--color-background-weak-hover: #eeeeee;
|
--color-background-weak-hover: #eeeeee;
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
--stats-text: #161616;
|
--stats-text: #161616;
|
||||||
--stats-muted: #5c5c5c;
|
--stats-muted: #5c5c5c;
|
||||||
--stats-faint: #808080;
|
--stats-faint: #808080;
|
||||||
|
--stats-theme-icon-active: #3a3a3a;
|
||||||
--stats-accent: #3b5cf6;
|
--stats-accent: #3b5cf6;
|
||||||
--stats-accent-text: #6c7dff;
|
--stats-accent-text: #6c7dff;
|
||||||
--stats-bar-idle: #d4d4d4;
|
--stats-bar-idle: #d4d4d4;
|
||||||
@@ -440,6 +442,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="subscribe-button"]:focus-visible,
|
[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"] button:focus-visible,
|
||||||
[data-page="stats"] [data-component="subscribe-modal"] input:focus-visible {
|
[data-page="stats"] [data-component="subscribe-modal"] input:focus-visible {
|
||||||
outline: 2px solid var(--stats-accent);
|
outline: 2px solid var(--stats-accent);
|
||||||
@@ -640,6 +643,54 @@
|
|||||||
gap: 24px;
|
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"] {
|
[data-page="stats"] [data-slot="status"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -2114,8 +2165,123 @@
|
|||||||
width: fit-content;
|
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) {
|
@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: #161616;
|
||||||
--color-background-weak: #242424;
|
--color-background-weak: #242424;
|
||||||
--color-background-weak-hover: #303030;
|
--color-background-weak-hover: #303030;
|
||||||
@@ -2134,6 +2300,7 @@
|
|||||||
--stats-text: #ffffff;
|
--stats-text: #ffffff;
|
||||||
--stats-muted: #d4d4d4;
|
--stats-muted: #d4d4d4;
|
||||||
--stats-faint: #808080;
|
--stats-faint: #808080;
|
||||||
|
--stats-theme-icon-active: #fafafa;
|
||||||
--stats-bar-idle: #303030;
|
--stats-bar-idle: #303030;
|
||||||
--stats-dot: #303030;
|
--stats-dot: #303030;
|
||||||
--stats-hero-muted: #808080;
|
--stats-hero-muted: #808080;
|
||||||
@@ -2143,11 +2310,14 @@
|
|||||||
--stats-logo-stroke: #211e1e;
|
--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;
|
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;
|
background: #242424f2;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0 0 0.5px #ffffff24,
|
0 0 0 0.5px #ffffff24,
|
||||||
@@ -2155,19 +2325,29 @@
|
|||||||
0 4px 8px #00000052;
|
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);
|
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:
|
box-shadow:
|
||||||
0 0 0 0.5px #ffffff1f,
|
0 0 0 0.5px #ffffff1f,
|
||||||
0 1px 2px -1px #00000052,
|
0 1px 2px -1px #00000052,
|
||||||
0 2px 4px #0000003d;
|
0 2px 4px #0000003d;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-component="leader-card"]:hover,
|
:root:not([data-stats-theme="light"])
|
||||||
[data-page="stats"] [data-component="leader-card"]:focus-visible {
|
[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;
|
background: #303030;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 0 0 0.5px #ffffff24,
|
0 0 0 0.5px #ffffff24,
|
||||||
@@ -2175,8 +2355,10 @@
|
|||||||
0 2px 6px #00000052;
|
0 2px 6px #00000052;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="header-button"][data-variant="neutral"],
|
:root:not([data-stats-theme="light"])
|
||||||
[data-page="stats"] [data-slot="menu-button"] {
|
[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;
|
color: #fafafa;
|
||||||
background: #ffffff0f;
|
background: #ffffff0f;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
@@ -2185,11 +2367,16 @@
|
|||||||
0 1px 2px 0 #00000066;
|
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;
|
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;
|
color: #ffffff;
|
||||||
background: #5c5c5c;
|
background: #5c5c5c;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
|
|||||||
@@ -67,10 +67,18 @@ const usageColors = [
|
|||||||
"#ff6467",
|
"#ff6467",
|
||||||
]
|
]
|
||||||
const marketColors = ["#ed6aff", "#a684ff", "#7c86ff", "#51a2ff", "#00d3f2", "#00d5be", "#00bc7d", "#9ae600", "#ffb900"]
|
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 UsageProduct = (typeof products)[number]
|
||||||
type TokenProduct = (typeof tokenProducts)[number]
|
type TokenProduct = (typeof tokenProducts)[number]
|
||||||
type UsageRange = (typeof ranges)[number]
|
type UsageRange = (typeof ranges)[number]
|
||||||
|
type ThemePreference = (typeof themePreferences)[number]
|
||||||
|
|
||||||
const getData = query(async () => {
|
const getData = query(async () => {
|
||||||
"use server"
|
"use server"
|
||||||
@@ -104,9 +112,24 @@ export default function StatsHome() {
|
|||||||
const statsUnfurlUrl = new URL(statsUnfurlRankings, statsHomeUrl).toString()
|
const statsUnfurlUrl = new URL(statsUnfurlRankings, statsHomeUrl).toString()
|
||||||
const data = createAsync(() => getData())
|
const data = createAsync(() => getData())
|
||||||
const githubStars = createAsync(() => getGitHubStars())
|
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 (
|
return (
|
||||||
<main data-page="stats">
|
<main data-page="stats" data-theme={themePreference()}>
|
||||||
<Title>{statsHomeTitle}</Title>
|
<Title>{statsHomeTitle}</Title>
|
||||||
<Meta name="description" content={statsHomeDescription} />
|
<Meta name="description" content={statsHomeDescription} />
|
||||||
<Link rel="canonical" href={statsHomeUrl} />
|
<Link rel="canonical" href={statsHomeUrl} />
|
||||||
@@ -145,12 +168,26 @@ export default function StatsHome() {
|
|||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer themePreference={themePreference()} onThemePreferenceChange={updateThemePreference} />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</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 }) {
|
function Hero(props: { updatedAt: string | null }) {
|
||||||
const [timeZone, setTimeZone] = createSignal("UTC")
|
const [timeZone, setTimeZone] = createSignal("UTC")
|
||||||
const [previousTimeZone, setPreviousTimeZone] = 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 [subscribeOpen, setSubscribeOpen] = createSignal(false)
|
||||||
const modelStats = [
|
const modelStats = [
|
||||||
{ href: "#top-models", label: "Top Models" },
|
{ href: "#top-models", label: "Top Models" },
|
||||||
@@ -1466,6 +1506,22 @@ function Footer() {
|
|||||||
<span>© 2026 Anomaly Innovations Inc.</span>
|
<span>© 2026 Anomaly Innovations Inc.</span>
|
||||||
<span data-slot="status">All systems Operational</span>
|
<span data-slot="status">All systems Operational</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<Show when={subscribeOpen()}>
|
<Show when={subscribeOpen()}>
|
||||||
<SubscribeModal onClose={() => setSubscribeOpen(false)} />
|
<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 }) {
|
function SubscribeModal(props: { onClose: () => void }) {
|
||||||
const [status, setStatus] = createSignal<"idle" | "pending" | "success" | "error">("idle")
|
const [status, setStatus] = createSignal<"idle" | "pending" | "success" | "error">("idle")
|
||||||
const [message, setMessage] = createSignal("")
|
const [message, setMessage] = createSignal("")
|
||||||
|
|||||||
Reference in New Issue
Block a user