feat(console): animate Go usage allowances and bonuses (#46055)

This commit is contained in:
Kit Langton 2026-08-28 22:22:28 -04:00 committed by GitHub
parent df35e842f5
commit 62a2f0e617
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 528 additions and 215 deletions

View file

@ -178,6 +178,7 @@
"@upstash/redis": "1.38.0",
"chart.js": "4.5.1",
"nitro": "3.0.1-alpha.1",
"number-flow": "0.6.2",
"solid-js": "catalog:",
"solid-list": "0.3.0",
"solid-stripe": "0.8.1",
@ -3651,6 +3652,8 @@
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="],
@ -4565,6 +4568,8 @@
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"number-flow": ["number-flow@0.6.2", "", { "dependencies": { "esm-env": "^1.1.4" } }, "sha512-MCnImG4Q5vPwhSXnov56nOuyyKn6LC+Qd7II1UiKc+ACRtug5iAtn0+CwXNxM38AC5lSowEY+oYEtZX2qMnUyw=="],
"nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],

View file

@ -29,6 +29,7 @@
"@upstash/redis": "1.38.0",
"chart.js": "4.5.1",
"nitro": "3.0.1-alpha.1",
"number-flow": "0.6.2",
"solid-js": "catalog:",
"solid-list": "0.3.0",
"solid-stripe": "0.8.1",

View file

@ -0,0 +1,267 @@
import { For, createSignal, onCleanup, onMount } from "solid-js"
import { useI18n } from "~/context/i18n"
import { RollingNumber } from "./rolling-number"
export function LimitsGraph(props: { href: string }) {
let root!: HTMLElement
const [visible, setVisible] = createSignal(false)
const [boosted, setBoosted] = createSignal(false)
const [promoted, setPromoted] = createSignal<string[]>([])
let timer: ReturnType<typeof setTimeout> | undefined
const i18n = useI18n()
onMount(() => {
const motion = window.matchMedia("(prefers-reduced-motion: reduce)")
const finish = () => {
if (!motion.matches) return
clearTimeout(timer)
setVisible(true)
setBoosted(true)
setPromoted(bonuses.map((model) => model.id))
}
motion.addEventListener("change", finish)
onCleanup(() => {
clearTimeout(timer)
motion.removeEventListener("change", finish)
})
if (motion.matches) return finish()
if (typeof IntersectionObserver === "undefined") return setVisible(true)
const observer = new IntersectionObserver(
(entries) => {
const entry = entries[0]
if (!entry?.isIntersecting || entry.intersectionRatio < 0.35) return
setVisible(true)
observer.disconnect()
},
{ threshold: 0.35 },
)
observer.observe(root)
onCleanup(() => observer.disconnect())
})
const baseline = 100
const graph = [
{ id: "kimi-k3", name: "Kimi K3", req: 110 },
{ id: "grok-4.6", name: "Grok 4.6", req: 169 },
{ id: "hy4-preview", name: "Hy4 preview", req: 1350 },
{ id: "gpt-5.6-luna", name: "GPT 5.6 Luna", req: 2050 },
{ id: "glm-5.3-flash", name: "GLM-5.3-Flash", req: 3160, baseReq: 1580, bonus: "2x usage" },
{ id: "minimax-m3", name: "MiniMax M3", req: 3200 },
{ id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300 },
{ id: "qwen3.8-flash", name: "Qwen3.8 Flash", req: 5400 },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7600 },
{ id: "longcat-2.0", name: "LongCat-2.0", req: 11400 },
{ id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100 },
{ id: "hy3", name: "Hy3", req: 34400, baseReq: 4300, bonus: "8x usage" },
{ id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", req: 45300, edge: true },
].map((model, index) => ({ ...model, d: `${50 + index * 25}ms` }))
const bonuses = graph.filter((model) => model.baseReq)
const w = 1040
const chartW = 720
const left = 40
const right = 60
const top = 18
const bottom = 44
const plot = chartW - left - right
const infiniteX = w - 180
const ratio = (n: number) => n / baseline
const rmax = Math.max(1, ...graph.filter((m) => !("infinite" in m)).map((m) => ratio(m.req)))
const log = (n: number) => Math.log10(Math.max(n, 1))
const base = 24
const p = 2.2
const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base)
const ticks = [1, 5, 10, 25, 50, 100, 250].filter((t) => t <= rmax)
const labels = (() => {
const set = new Set<number>()
let last = -Infinity
for (const t of ticks) {
if (t === 1) {
set.add(t)
last = x(t)
continue
}
const pos = x(t)
if (pos - last < 44) continue
set.add(t)
last = pos
}
return set
})()
const shown = ticks.filter((t) => labels.has(t))
const bh = 8
const gap = 20
const step = bh + gap
const gy = (i: number) => top + 22 + step * i
const h = gy(graph.length - 1) + bottom
const my = graph.length < 2 ? gy(0) : (gy(0) + gy(graph.length - 1)) / 2
const px = (n: number) => `${(n / w) * 100}%`
const py = (n: number) => `${(n / h) * 100}%`
const lx = px(left - 16)
const ty = py(h - 18)
const timing = () => {
const style = getComputedStyle(root)
return {
duration: Number.parseFloat(style.getPropertyValue("--bonus-duration")),
easing: style.getPropertyValue("--spring-easing").trim(),
spinEasing: style.getPropertyValue("--digit-easing").trim(),
}
}
return (
<figure
data-component="limit-graph"
aria-label={i18n.t("go.graph.label")}
data-visible={visible() ? "" : undefined}
data-boosted={boosted() ? "" : undefined}
ref={root}
onAnimationStart={(event) => {
if (!(event.target instanceof SVGElement) || event.animationName !== "go-graph-reveal") return
if (event.target.hasAttribute("data-stage-end") && !boosted()) {
const duration = Number.parseFloat(getComputedStyle(root).getPropertyValue("--reveal-duration"))
timer = setTimeout(() => setBoosted(true), duration * 0.6)
return
}
if (event.target.dataset.animate !== "bonus") return
const model = event.target.dataset.model
if (!model) return
setPromoted((current) => [...current, model])
}}
>
<div data-slot="plot">
<svg
viewBox={`0 0 ${w} ${h}`}
preserveAspectRatio="none"
role="img"
aria-hidden="true"
style={{ height: `${h}px` }}
>
<g data-slot="grid">
<For each={ticks}>
{(t, i) => (
<line x1={x(t)} y1={h - bottom} x2={x(t)} y2={top} data-grid style={{ "--d": `${i() * 100}ms` }} />
)}
</For>
</g>
<line x1={left} y1={h - bottom} x2={left} y2={top} data-stub />
<g data-slot="bars">
<For each={graph}>
{(m, i) => (
<>
<rect
data-animate="bar"
data-model={m.id}
data-stage-end={i() === graph.length - 1 ? "" : undefined}
style={{ "--d": m.d }}
x={left}
y={gy(i()) - bh / 2}
width={Math.max(0, ("infinite" in m ? infiniteX : x(ratio(m.baseReq ?? m.req))) - left)}
height={bh}
data-bar
data-kind={"infinite" in m ? "infinite" : "go"}
/>
{m.baseReq && (
<rect
data-animate="bonus"
data-model={m.id}
style={{ "--bonus-delay": `${bonuses.indexOf(m) * 60}ms` }}
x={x(ratio(m.baseReq)) + 2}
y={gy(i()) - bh / 2}
width={Math.max(0, x(ratio(m.req)) - x(ratio(m.baseReq)) - 2)}
height={bh}
data-bar
data-kind="promo"
/>
)}
</>
)}
</For>
</g>
</svg>
<div data-slot="ylabels" aria-hidden="true">
<span data-ylabel style={{ "--x": lx, "--y": py(my) } as any}>
{i18n.t("go.graph.go")}
</span>
</div>
<div data-slot="xlabels" aria-hidden="true">
<For each={shown}>
{(t) => (
<span
data-xlabel
data-tick={t}
style={{ "--x": px(x(t)), "--y": ty, "--d": `${ticks.indexOf(t) * 100}ms` }}
>
{i18n.t("go.graph.tick", { n: t })}
</span>
)}
</For>
</div>
<div data-slot="pills">
<For each={graph}>
{(m, i) => (
<span
data-item
data-kind="go"
data-model={m.id}
data-edge={"edge" in m ? "" : undefined}
data-infinite={"infinite" in m ? "" : undefined}
data-promo={m.baseReq ? "" : undefined}
style={{
"--x": px("infinite" in m ? infiniteX : x(ratio(m.baseReq ?? m.req))),
"--y": py(gy(i())),
"--d": m.d,
"--bonus-delay": `${Math.max(0, bonuses.indexOf(m)) * 60}ms`,
"--travel": `${"infinite" in m ? 0 : ((x(ratio(m.req)) - x(ratio(m.baseReq ?? m.req))) / w) * 100}cqw`,
}}
>
<span data-label>
{!("infinite" in m) && m.baseReq ? (
<RollingNumber
value={promoted().includes(m.id) ? m.req : m.baseReq}
target={m.req}
timing={timing}
/>
) : (
<span data-value>{"infinite" in m ? "\u221e" : m.req.toLocaleString()}</span>
)}
<span data-name>{m.name}</span>
{m.id === "muse-spark-1.2-contributor" && (
<span data-regions>
(
<a href="https://ai.developer.meta.com/legal/geographic-use-policy">
{i18n.t("go.graph.limitedRegions")}
</a>
)
</span>
)}
{"infinite" in m && <span data-limited>({i18n.t("go.graph.limitedTime")})</span>}
</span>
{"bonus" in m && <span data-bonus>{m.bonus}</span>}
</span>
)}
</For>
</div>
</div>
<figcaption>
<div data-slot="caption-row">
<div data-slot="caption-left">
<div data-slot="caption-meta">
<span data-slot="caption-label">{i18n.t("go.graph.label")}</span>
<a data-slot="caption-link" href={props.href}>
{i18n.t("go.graph.usageLimits")}
</a>
</div>
</div>
</div>
</figcaption>
</figure>
)
}

View file

@ -0,0 +1,58 @@
import NumberFlow from "number-flow"
import { continuous } from "number-flow/plugins"
import { createEffect, onCleanup, onMount } from "solid-js"
export function RollingNumber(props: {
value: number
target: number
timing: () => { duration: number; easing: string; spinEasing: string }
}) {
let root!: HTMLSpanElement
// Keep the server-rendered text stable while the custom element owns its contents.
const initial = props.value.toLocaleString()
const growing = props.target.toLocaleString().length > initial.length
onMount(() => {
if (new Intl.NumberFormat().resolvedOptions().numberingSystem !== "latn") {
createEffect(() => {
root.textContent = props.value.toLocaleString()
})
return
}
const flow = new NumberFlow()
flow.format = { useGrouping: true, maximumFractionDigits: 0 }
flow.trend = 1
if (growing) flow.plugins = [continuous]
flow.opacityTiming = { duration: 180, easing: "ease-out" }
const motion = window.matchMedia("(prefers-reduced-motion: reduce)")
const updateMotion = () => {
flow.animated = !motion.matches
}
updateMotion()
motion.addEventListener("change", updateMotion)
onCleanup(() => motion.removeEventListener("change", updateMotion))
root.replaceChildren(flow)
createEffect(() => {
const value = props.value
if (flow.value === value) return
const timing = props.timing()
flow.transformTiming = { duration: timing.duration, easing: timing.easing }
flow.spinTiming = {
duration: timing.duration,
easing: growing ? "cubic-bezier(0.45, 0, 0.55, 1)" : timing.spinEasing,
}
flow.update(value)
})
})
return (
<span
data-value
ref={root}
style={{ "min-width": `${props.target.toLocaleString().length}ch`, "text-align": "right" }}
>
{initial}
</span>
)
}

View file

@ -267,7 +267,7 @@ export const dict = {
"go.graph.free": "Free",
"go.graph.freePill": "Big Pickle and free models",
"go.graph.go": "Go",
"go.graph.label": "Requests per 5 hour",
"go.graph.label": "Requests / 5 hours",
"go.graph.limitedRegions": "limited regions",
"go.graph.limitedTime": "limited time",
"go.graph.tick": "{{n}}x",

View file

@ -21,10 +21,40 @@
}
}
@keyframes go-graph-bar {
@keyframes go-graph-reveal {
to {
clip-path: inset(0 0 0 0) fill-box;
}
}
@keyframes go-graph-label {
to {
mask-position: 0 0;
opacity: 1;
transform: scaleX(1);
}
}
@keyframes go-graph-grid {
to {
mask-position: 0 100%;
}
}
@keyframes go-graph-heat {
from {
filter: brightness(var(--arrival-brightness));
}
to {
filter: brightness(1);
}
}
@keyframes go-graph-bonus-heat {
from {
color: var(--color-go-3);
}
to {
color: var(--color-text-weak);
}
}
@ -477,6 +507,48 @@ body {
}
[data-component="limit-graph"] {
--reveal-duration: 950ms;
--bonus-duration: 1500ms;
--digit-easing: cubic-bezier(0.22, 1, 0.36, 1);
--grid-duration: 2000ms;
--grid-easing: cubic-bezier(0.22, 1, 0.36, 1);
--arrival-brightness: 1.15;
--heat-duration: 1600ms;
--bar-delay: 240ms;
/* Critically damped response, shared by the bars, labels, and rolling digits. */
--spring-easing: linear(
0,
0.02991,
0.10078,
0.19179,
0.28962,
0.38611,
0.47651,
0.55839,
0.63079,
0.69365,
0.74748,
0.79306,
0.83131,
0.86315,
0.8895,
0.91117,
0.92892,
0.94339,
0.95515,
0.96467,
0.97236,
0.97855,
0.98352,
0.98751,
0.9907,
0.99324,
0.99527,
0.99689,
0.99817,
0.99919,
1
);
margin: 0 auto;
width: calc(100% - 120px);
max-width: calc(100% - 120px);
@ -491,6 +563,7 @@ body {
}
[data-slot="plot"] {
container-type: inline-size;
position: relative;
overflow: visible;
width: 100%;
@ -515,6 +588,7 @@ body {
}
[data-slot="xlabels"] [data-xlabel] {
opacity: 0;
position: absolute;
left: var(--x);
top: var(--y);
@ -551,11 +625,26 @@ body {
}
}
[data-slot="xlabels"] [data-xlabel],
[data-slot="pills"] [data-label],
[data-bonus] {
mask-image: linear-gradient(to right, #000 40%, transparent 60%);
mask-size: 250% 100%;
mask-position: 100% 0;
mask-repeat: no-repeat;
}
[data-slot="pills"] {
position: absolute;
inset: 0;
pointer-events: none;
[data-label] {
display: inline-flex;
align-items: center;
gap: 8px;
}
[data-item] {
position: absolute;
left: var(--x);
@ -573,7 +662,6 @@ body {
font-size: 13px;
line-height: 20px;
box-sizing: border-box;
opacity: 0;
}
@media (max-width: 60rem) {
@ -605,10 +693,16 @@ body {
[data-value] {
color: var(--color-text-strong);
font-weight: 600;
font-variant-numeric: tabular-nums;
white-space: nowrap;
number-flow {
line-height: 1;
}
}
[data-bonus] {
opacity: 0;
color: var(--color-text-weak);
font-size: 12px;
font-weight: 400;
@ -685,11 +779,22 @@ body {
opacity: 0.55;
}
[data-grid],
[data-stub] {
mask-image: linear-gradient(to top, #000 48%, transparent 52%);
mask-size: 100% 250%;
mask-position: 0 0;
mask-repeat: no-repeat;
mask-origin: stroke-box;
mask-clip: stroke-box;
}
[data-animate="bar"],
[data-animate="bonus"] {
clip-path: inset(0 100% 0 0) fill-box;
}
[data-bar] {
transform-box: fill-box;
transform-origin: left center;
opacity: 0;
transform: scaleX(0.02);
fill: var(--bar-go);
stroke: none;
}
@ -760,18 +865,74 @@ body {
animation-delay: var(--d, 0ms);
}
&[data-visible] [data-bar] {
animation: go-graph-bar 560ms cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
&[data-visible] [data-grid],
&[data-visible] [data-stub] {
animation:
go-graph-grid var(--grid-duration) var(--grid-easing) forwards,
go-graph-heat var(--heat-duration) linear backwards;
animation-delay: var(--d, 0ms), calc(var(--d, 0ms) + var(--grid-duration));
}
&[data-visible] [data-slot="xlabels"] [data-xlabel] {
animation: go-graph-label 300ms ease-out forwards;
animation-delay: var(--d, 0ms);
}
&[data-visible] [data-slot="pills"] [data-item] {
opacity: 1;
transition: opacity 240ms ease;
transition-delay: var(--d, 0ms);
&[data-visible] [data-animate="bar"] {
animation:
go-graph-reveal var(--reveal-duration) var(--spring-easing) forwards,
go-graph-heat var(--heat-duration) linear backwards;
animation-delay:
calc(var(--bar-delay) + var(--d, 0ms)), calc(var(--bar-delay) + var(--d, 0ms) + var(--reveal-duration));
}
&[data-visible] [data-slot="pills"] [data-label] {
animation: go-graph-label 400ms ease-out forwards;
animation-delay: calc(var(--bar-delay) + var(--d, 0ms) + var(--reveal-duration) * 0.4);
}
&[data-boosted] [data-animate="bonus"] {
animation:
go-graph-reveal var(--bonus-duration) var(--spring-easing) forwards,
go-graph-heat var(--heat-duration) linear backwards;
animation-delay: var(--bonus-delay), calc(var(--bonus-delay) + var(--bonus-duration));
}
&[data-boosted] [data-slot="pills"] [data-item][data-promo] {
translate: var(--travel) 0;
transition: translate var(--bonus-duration) var(--spring-easing) var(--bonus-delay);
@media (max-width: 60rem) {
&[data-edge] {
translate: none;
transition: none;
}
}
}
&[data-boosted] [data-bonus] {
animation:
go-graph-label 400ms ease-out forwards,
go-graph-bonus-heat 3000ms linear backwards;
animation-delay: var(--bonus-delay);
}
@media (prefers-reduced-motion: reduce) {
[data-grid],
[data-stub],
&[data-visible] [data-grid],
&[data-visible] [data-stub] {
mask-image: none;
animation: none;
}
[data-slot="xlabels"] [data-xlabel],
&[data-visible] [data-slot="xlabels"] [data-xlabel] {
opacity: 1;
mask-image: none;
animation: none;
}
[data-animate="line"] {
stroke-dashoffset: 0;
animation: none;
@ -781,9 +942,12 @@ body {
transform: none;
animation: none;
}
[data-bar] {
[data-animate="bar"],
&[data-visible] [data-animate="bar"],
[data-animate="bonus"],
&[data-boosted] [data-animate="bonus"] {
opacity: 1;
transform: none;
clip-path: none;
animation: none;
}
[data-row],
@ -792,8 +956,16 @@ body {
transition: none;
}
[data-slot="pills"] [data-item] {
[data-slot="pills"] [data-label],
&[data-visible] [data-slot="pills"] [data-label],
[data-bonus],
&[data-boosted] [data-bonus] {
opacity: 1;
mask-image: none;
animation: none;
}
&[data-boosted] [data-slot="pills"] [data-item][data-promo] {
transition: none;
}
}
@ -886,6 +1058,12 @@ body {
flex-wrap: wrap;
justify-content: flex-start;
[data-label] {
max-width: 100%;
flex-wrap: wrap;
gap: 3px 8px;
}
&[data-model="muse-spark-1.2-contributor"] {
transform: translateY(11px);

View file

@ -1,7 +1,7 @@
import "./index.css"
import { createAsync, query } from "@solidjs/router"
import { Title, Meta } from "@solidjs/meta"
import { For, createMemo, createSignal, onCleanup, onMount } from "solid-js"
import { For, createMemo } from "solid-js"
//import { HttpHeader } from "@solidjs/start"
import goLogoLight from "../../asset/go-ornate-light.svg"
import goLogoDark from "../../asset/go-ornate-dark.svg"
@ -10,6 +10,7 @@ import { Faq } from "~/component/faq"
import { Legal } from "~/component/legal"
import { Footer } from "~/component/footer"
import { Header } from "~/component/header"
import { LimitsGraph } from "~/component/limits-graph"
import { config } from "~/config"
import { getLastSeenWorkspaceID } from "../workspace/common"
import { IconMiniMax, IconMiMo, IconZai, IconAlibaba, IconDeepSeek } from "~/component/icon"
@ -49,203 +50,6 @@ const models = [
{ name: "Hy3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" },
] as const
function LimitsGraph(props: { href: string }) {
let root!: HTMLElement
const [visible, setVisible] = createSignal(false)
const i18n = useI18n()
onMount(() => {
if (typeof IntersectionObserver === "undefined") return setVisible(true)
const observer = new IntersectionObserver(
(entries) => {
const entry = entries[0]
if (!entry?.isIntersecting) return
setVisible(true)
observer.disconnect()
},
{ threshold: 0.35 },
)
observer.observe(root)
onCleanup(() => observer.disconnect())
})
const baseline = 100
const graph = [
{ id: "kimi-k3", name: "Kimi K3", req: 110, d: "50ms" },
{ id: "grok-4.6", name: "Grok 4.6", req: 169, d: "75ms" },
{ id: "hy4-preview", name: "Hy4 preview", req: 1350, d: "90ms" },
{ id: "gpt-5.6-luna", name: "GPT 5.6 Luna", req: 2050, d: "290ms" },
{ id: "glm-5.3-flash", name: "GLM-5.3-Flash", req: 3160, baseReq: 1580, bonus: "2x usage", d: "100ms" },
{ id: "minimax-m3", name: "MiniMax M3", req: 3200, d: "210ms" },
{ id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300, d: "300ms" },
{ id: "qwen3.8-flash", name: "Qwen3.8 Flash", req: 5400, d: "315ms" },
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7600, d: "330ms" },
{ id: "longcat-2.0", name: "LongCat-2.0", req: 11400, d: "335ms" },
{ id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100, d: "340ms" },
{ id: "hy3", name: "Hy3", req: 34400, baseReq: 4300, bonus: "8x usage", d: "320ms" },
{ id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", req: 45300, edge: true, d: "360ms" },
]
const w = 1040
const chartW = 720
const left = 40
const right = 60
const top = 18
const bottom = 44
const plot = chartW - left - right
const infiniteX = w - 180
const ratio = (n: number) => n / baseline
const rmax = Math.max(1, ...graph.filter((m) => !("infinite" in m)).map((m) => ratio(m.req)))
const log = (n: number) => Math.log10(Math.max(n, 1))
const base = 24
const p = 2.2
const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base)
const ticks = [1, 5, 10, 25, 50, 100, 250].filter((t) => t <= rmax)
const labels = (() => {
const set = new Set<number>()
let last = -Infinity
for (const t of ticks) {
if (t === 1) {
set.add(t)
last = x(t)
continue
}
const pos = x(t)
if (pos - last < 44) continue
set.add(t)
last = pos
}
return set
})()
const shown = ticks.filter((t) => labels.has(t))
const bh = 8
const gap = 20
const step = bh + gap
const gy = (i: number) => top + 22 + step * i
const h = gy(graph.length - 1) + bottom
const my = graph.length < 2 ? gy(0) : (gy(0) + gy(graph.length - 1)) / 2
const px = (n: number) => `${(n / w) * 100}%`
const py = (n: number) => `${(n / h) * 100}%`
const lx = px(left - 16)
const ty = py(h - 18)
return (
<figure
data-component="limit-graph"
aria-label={i18n.t("go.graph.label")}
data-visible={visible() ? "" : undefined}
ref={root}
>
<div data-slot="plot">
<svg
viewBox={`0 0 ${w} ${h}`}
preserveAspectRatio="none"
role="img"
aria-hidden="true"
style={{ height: `${h}px` }}
>
<g data-slot="grid">
<For each={ticks}>{(t) => <line x1={x(t)} y1={top} x2={x(t)} y2={h - bottom} data-grid />}</For>
</g>
<line x1={left} y1={top} x2={left} y2={h - bottom} data-stub />
<g data-slot="bars">
<For each={graph}>
{(m, i) => (
<g data-model={m.id} style={{ "--d": m.d } as any}>
<rect
x={left}
y={gy(i()) - bh / 2}
width={Math.max(0, ("infinite" in m ? infiniteX : x(ratio(m.baseReq ?? m.req))) - left)}
height={bh}
data-bar
data-kind={"infinite" in m ? "infinite" : "go"}
data-model={m.id}
/>
{m.baseReq && (
<rect
x={x(ratio(m.baseReq)) + 2}
y={gy(i()) - bh / 2}
width={Math.max(0, x(ratio(m.req)) - x(ratio(m.baseReq)) - 2)}
height={bh}
data-bar
data-kind="promo"
data-model={m.id}
/>
)}
</g>
)}
</For>
</g>
</svg>
<div data-slot="ylabels" aria-hidden="true">
<span data-ylabel style={{ "--x": lx, "--y": py(my) } as any}>
{i18n.t("go.graph.go")}
</span>
</div>
<div data-slot="xlabels" aria-hidden="true">
<For each={shown}>
{(t) => (
<span data-xlabel data-tick={t} style={{ "--x": px(x(t)), "--y": ty } as any}>
{i18n.t("go.graph.tick", { n: t })}
</span>
)}
</For>
</div>
<div data-slot="pills">
<For each={graph}>
{(m, i) => (
<span
data-item
data-kind="go"
data-model={m.id}
data-edge={"edge" in m ? "" : undefined}
data-infinite={"infinite" in m ? "" : undefined}
style={
{ "--x": px("infinite" in m ? infiniteX : x(ratio(m.req))), "--y": py(gy(i())), "--d": m.d } as any
}
>
<span data-value>{"infinite" in m ? "∞" : m.req.toLocaleString()}</span>
<span data-name>{m.name}</span>
{m.id === "muse-spark-1.2-contributor" && (
<span data-regions>
(
<a href="https://ai.developer.meta.com/legal/geographic-use-policy">
{i18n.t("go.graph.limitedRegions")}
</a>
)
</span>
)}
{"infinite" in m && <span data-limited>({i18n.t("go.graph.limitedTime")})</span>}
{"bonus" in m && <span data-bonus>{m.bonus}</span>}
</span>
)}
</For>
</div>
</div>
<figcaption>
<div data-slot="caption-row">
<div data-slot="caption-left">
<div data-slot="caption-meta">
<span data-slot="caption-label">{i18n.t("go.graph.label")}</span>
<a data-slot="caption-link" href={props.href}>
{i18n.t("go.graph.usageLimits")}
</a>
</div>
</div>
</div>
</figcaption>
</figure>
)
}
export default function Home() {
const workspaceID = createAsync(() => checkLoggedIn())
const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/go` : "/auth"))