docs: replace Go landing page video with interactive limits graph

Users can now see a clear visual comparison of request limits between Free tier and Go tier across all available models, making it easier to understand the value of a Go subscription at a glance.
This commit is contained in:
David Hill
2026-03-04 15:21:07 +00:00
parent ad56338108
commit 7c215c0d02
3 changed files with 446 additions and 13 deletions
+3 -2
View File
@@ -251,11 +251,12 @@ export const dict = {
"go.cta.start": "Subscribe to Go - $10/mo", "go.cta.start": "Subscribe to Go - $10/mo",
"go.pricing.body": "Use with any agent. Top up credit if needed. Cancel any time.", "go.pricing.body": "Use with any agent. Top up credit if needed. Cancel any time.",
"go.graph.free": "Free",
"go.graph.go": "Go",
"go.problem.title": "What problem is Go solving?", "go.problem.title": "What problem is Go solving?",
"go.problem.body": "go.problem.body":
"We're focused on bringing the OpenCode experience to as many people as possible. OpenCode Go is a low cost ($10/month) subscription designed to bring agentic coding to programmers around the world. It provides generous limits and reliable access to the most capable open source models.", "We're focused on bringing the OpenCode experience to as many people as possible. OpenCode Go is a low cost ($10/month) subscription designed to bring agentic coding to programmers around the world. It provides generous limits and reliable access to the most capable open source models.",
"go.problem.subtitle": "go.problem.subtitle": " ",
" ",
"go.problem.item1": "Low cost subscription pricing", "go.problem.item1": "Low cost subscription pricing",
"go.problem.item2": "Generous limits and reliable access", "go.problem.item2": "Generous limits and reliable access",
"go.problem.item3": "Built for as many programmers as possible", "go.problem.item3": "Built for as many programmers as possible",
+304 -3
View File
@@ -8,6 +8,19 @@
} }
} }
@keyframes go-graph-line {
to {
stroke-dashoffset: 0;
}
}
@keyframes go-graph-point {
to {
opacity: 1;
transform: scale(1);
}
}
[data-page="go"] { [data-page="go"] {
--color-background: hsl(0, 20%, 99%); --color-background: hsl(0, 20%, 99%);
--color-background-weak: hsl(0, 8%, 97%); --color-background-weak: hsl(0, 8%, 97%);
@@ -16,6 +29,10 @@
--color-background-interactive: hsl(62, 84%, 88%); --color-background-interactive: hsl(62, 84%, 88%);
--color-background-interactive-weaker: hsl(64, 74%, 95%); --color-background-interactive-weaker: hsl(64, 74%, 95%);
--color-go-1: hsl(61.9, 82.6%, 77.5%);
--color-go-2: hsl(62.4, 78.6%, 56.1%);
--color-go-3: hsl(62.1, 100%, 39.8%);
--color-text: hsl(0, 1%, 39%); --color-text: hsl(0, 1%, 39%);
--color-text-weak: hsl(0, 1%, 74%); --color-text-weak: hsl(0, 1%, 74%);
--color-text-weaker: hsl(30, 2%, 81%); --color-text-weaker: hsl(30, 2%, 81%);
@@ -37,6 +54,10 @@
--color-background-interactive: hsl(62, 100%, 90%); --color-background-interactive: hsl(62, 100%, 90%);
--color-background-interactive-weaker: hsl(60, 20%, 8%); --color-background-interactive-weaker: hsl(60, 20%, 8%);
--color-go-1: hsl(62.1, 64.9%, 25.7%);
--color-go-2: hsl(61.7, 46.4%, 53.9%);
--color-go-3: hsl(61.9, 100%, 50%);
--color-text: hsl(0, 4%, 71%); --color-text: hsl(0, 4%, 71%);
--color-text-weak: hsl(0, 2%, 49%); --color-text-weak: hsl(0, 2%, 49%);
--color-text-weaker: hsl(0, 3%, 28%); --color-text-weaker: hsl(0, 3%, 28%);
@@ -381,13 +402,293 @@ body {
[data-component="comparison"] { [data-component="comparison"] {
border-top: 1px solid var(--color-border-weak); border-top: 1px solid var(--color-border-weak);
video { padding: 0;
background:
radial-gradient(1200px 400px at 15% 0%, rgba(0, 0, 0, 0.035), transparent 55%),
radial-gradient(900px 320px at 85% 15%, rgba(0, 0, 0, 0.02), transparent 60%), var(--color-background-weak);
@media (prefers-color-scheme: dark) {
background:
radial-gradient(1200px 400px at 15% 0%, rgba(255, 255, 255, 0.03), transparent 55%),
radial-gradient(900px 320px at 85% 15%, rgba(255, 255, 255, 0.02), transparent 60%),
var(--color-background-weak);
}
[data-component="limit-graph"] {
margin: 0 auto;
max-width: calc(100% - (var(--padding) * 2));
border: none;
background: transparent;
padding: 18px 18px 56px;
[data-slot="plot"] {
position: relative;
}
[data-slot="plot-labels"] {
position: absolute;
inset: 0;
pointer-events: none;
}
[data-row-label] {
position: absolute;
left: 0;
top: var(--y);
transform: translateY(-50%);
color: var(--color-text-strong);
font-size: 16px;
font-weight: 700;
}
svg {
width: 100%; width: 100%;
height: auto; height: auto;
max-width: none; aspect-ratio: 720 / 220;
max-height: none;
display: block; display: block;
} }
[data-grid] {
stroke: var(--color-border);
stroke-width: 1;
opacity: 0.6;
}
[data-tick] {
fill: var(--color-text-weak);
font-size: 12px;
}
[data-row] {
fill: var(--color-text-strong);
font-size: 13px;
font-weight: 600;
}
[data-stub] {
stroke: var(--color-border);
stroke-width: 2;
stroke-linecap: round;
opacity: 0.55;
}
[data-range] {
stroke: var(--color-text-strong);
stroke-width: 2;
stroke-linecap: round;
opacity: 0.65;
}
[data-point] {
vector-effect: non-scaling-stroke;
stroke-width: 1;
transform-box: fill-box;
transform-origin: center;
}
[data-point][data-kind="free"] {
fill: var(--color-background);
stroke: var(--color-text-strong);
}
[data-point][data-kind="go"] {
fill: var(--color-background-interactive);
stroke: var(--color-text-strong);
}
[data-point][data-kind="go"][data-model="glm"] {
fill: var(--color-go-1);
}
[data-point][data-kind="go"][data-model="kimi"] {
fill: var(--color-go-2);
}
[data-point][data-kind="go"][data-model="minimax"] {
fill: var(--color-go-3);
}
[data-animate="line"] {
stroke-dasharray: 900;
stroke-dashoffset: 900;
}
&[data-visible] [data-animate="line"] {
animation: go-graph-line 1000ms cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
animation-delay: 80ms;
}
[data-point] {
opacity: 0;
transform: scale(0.85);
}
&[data-visible] [data-point] {
animation: go-graph-point 520ms cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
animation-delay: var(--d, 0ms);
}
@media (prefers-reduced-motion: reduce) {
[data-animate="line"] {
stroke-dashoffset: 0;
animation: none;
}
[data-point] {
opacity: 1;
transform: none;
animation: none;
}
}
figcaption {
margin-top: 34px;
display: flex;
flex-direction: column;
gap: 10px;
font-size: 13px;
}
[data-slot="caption-row"] {
display: flex;
width: 100%;
}
[data-slot="caption-left"] {
display: grid;
width: 100%;
grid-template-columns: var(--start, 16.9%) minmax(0, 1fr);
grid-template-rows: auto auto;
align-items: center;
column-gap: 0;
row-gap: 0;
min-width: 0;
}
[data-slot="caption-meta"] {
display: contents;
}
[data-slot="caption-label"] {
color: var(--color-text-strong);
font-weight: 650;
white-space: nowrap;
line-height: 1;
grid-column: 1;
grid-row: 1;
}
[data-slot="caption-link"] {
color: var(--color-text-strong);
text-decoration-thickness: 1px;
width: fit-content;
line-height: 1;
grid-column: 1;
grid-row: 2;
align-self: start;
}
[data-slot="legend"] {
display: flex;
width: 100%;
flex-wrap: nowrap;
gap: 10px;
min-width: 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding-bottom: 8px;
margin-left: -12px;
grid-column: 2;
grid-row: 1;
align-self: center;
[data-item] {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: 8px;
border: 1px solid var(--color-border-weak);
background: var(--color-background);
padding: 6px 10px;
border-radius: 999px;
max-width: 100%;
}
[data-dot] {
width: 10px;
height: 10px;
border-radius: 999px;
display: inline-block;
border: 1px solid var(--color-text-strong);
background: var(--color-background);
flex: 0 0 auto;
}
[data-dot][data-kind="go"] {
background: var(--color-background-interactive);
}
[data-dot][data-kind="go"][data-model="glm"] {
background: var(--color-go-1);
}
[data-dot][data-kind="go"][data-model="kimi"] {
background: var(--color-go-2);
}
[data-dot][data-kind="go"][data-model="minimax"] {
background: var(--color-go-3);
}
[data-name] {
color: var(--color-text);
white-space: nowrap;
}
[data-value] {
color: var(--color-text-strong);
font-weight: 600;
white-space: nowrap;
}
}
[data-slot="caption-note"] {
color: var(--color-text-weak);
font-size: 12px;
}
@media (max-width: 56.25rem) {
[data-slot="caption-left"] {
grid-template-columns: var(--start, 16.9%) minmax(0, 1fr);
grid-template-rows: auto auto;
align-items: start;
}
[data-slot="legend"] {
grid-column: 2;
grid-row: 1;
}
[data-slot="caption-meta"] {
display: flex;
gap: 24px;
align-items: baseline;
grid-column: 2;
grid-row: 2;
margin-top: 12px;
}
[data-slot="caption-label"] {
grid-column: auto;
grid-row: auto;
}
[data-slot="caption-link"] {
grid-column: auto;
grid-row: auto;
align-self: baseline;
}
}
}
} }
[data-slot="section-title"] { [data-slot="section-title"] {
+136 -5
View File
@@ -1,11 +1,10 @@
import "./index.css" import "./index.css"
import { createAsync, query, redirect } from "@solidjs/router" import { createAsync, query, redirect } from "@solidjs/router"
import { Title, Meta } from "@solidjs/meta" import { Title, Meta } from "@solidjs/meta"
import { For, createSignal, onCleanup, onMount } from "solid-js"
//import { HttpHeader } from "@solidjs/start" //import { HttpHeader } from "@solidjs/start"
import goLogoLight from "../../asset/go-ornate-light.svg" import goLogoLight from "../../asset/go-ornate-light.svg"
import goLogoDark from "../../asset/go-ornate-dark.svg" import goLogoDark from "../../asset/go-ornate-dark.svg"
import compareVideo from "../../asset/lander/opencode-comparison-min.mp4"
import compareVideoPoster from "../../asset/lander/opencode-comparison-poster.png"
import avatarDax from "../../asset/lander/avatar-dax.png" import avatarDax from "../../asset/lander/avatar-dax.png"
import avatarJay from "../../asset/lander/avatar-jay.png" import avatarJay from "../../asset/lander/avatar-jay.png"
import avatarFrank from "../../asset/lander/avatar-frank.png" import avatarFrank from "../../asset/lander/avatar-frank.png"
@@ -28,6 +27,137 @@ const checkLoggedIn = query(async () => {
if (workspaceID) throw redirect(`/workspace/${workspaceID}`) if (workspaceID) throw redirect(`/workspace/${workspaceID}`)
}, "checkLoggedIn.get") }, "checkLoggedIn.get")
function LimitsGraph(props: { href: string; labels: { free: string; go: string } }) {
let root!: HTMLElement
const [visible, setVisible] = createSignal(false)
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 free = 200 * 30
const models = [
{ id: "glm", name: "GLM-5", month: 5750, d: "120ms" },
{ id: "kimi", name: "Kimi K2.5", month: 9250, d: "240ms" },
{ id: "minimax", name: "MiniMax M2.5", month: 100000, d: "360ms" },
]
const scale = 18
const ratio = (n: number) => n / free
const w = 720
const h = 220
const left = 88
const right = 24
const top = 22
const bottom = 46
const plot = w - left - right
const x = (r: number) => left + (Math.min(r, scale) / scale) * plot
const start = (x(1) / w) * 100
const yFree = 74
const yGo = 134
const ticks = [1, 2, 5, 10, 15]
const y = (n: number) => `${(n / h) * 100}%`
return (
<figure
data-component="limit-graph"
aria-label="Requests per month: Free vs Go"
data-visible={visible() ? "" : undefined}
ref={root}
style={{ "--start": `${start}%` } as any}
>
<div data-slot="plot">
<svg viewBox={`0 0 ${w} ${h}`} role="img" aria-hidden="true">
<g data-slot="grid">
<For each={ticks}>
{(t) => (
<g>
<line x1={x(t)} y1={top} x2={x(t)} y2={h - bottom} data-grid />
<text x={x(t)} y={h - 18} text-anchor="middle" data-tick>
{t}x
</text>
</g>
)}
</For>
</g>
<g data-slot="free" style={{ "--d": "0ms" } as any}>
<circle cx={x(1)} cy={yFree} r={5.5} data-point data-kind="free" />
</g>
<g data-slot="go">
<line
x1={x(ratio(models[0]!.month))}
y1={yGo}
x2={x(ratio(models[2]!.month))}
y2={yGo}
data-range
data-animate="line"
/>
<For each={models}>
{(m) => (
<g style={{ "--d": m.d } as any}>
<circle cx={x(ratio(m.month))} cy={yGo} r={5.5} data-point data-kind="go" data-model={m.id} />
</g>
)}
</For>
</g>
</svg>
<div data-slot="plot-labels">
<span data-row-label style={{ "--y": y(yFree) } as any}>
{props.labels.free}
</span>
<span data-row-label style={{ "--y": y(yGo) } as any}>
{props.labels.go}
</span>
</div>
</div>
<figcaption>
<div data-slot="caption-row">
<div data-slot="caption-left">
<div data-slot="caption-meta">
<span data-slot="caption-label">Requests/month</span>
<a data-slot="caption-link" href={props.href}>
Usage limits
</a>
</div>
<div data-slot="legend">
<span data-item>
<i data-dot data-kind="free" />
<span data-name>Free</span>
<span data-value>{free.toLocaleString()}</span>
</span>
<For each={models}>
{(m) => (
<span data-item>
<i data-dot data-kind="go" data-model={m.id} />
<span data-name>{m.name}</span>
<span data-value>{m.month.toLocaleString()}</span>
</span>
)}
</For>
</div>
</div>
</div>
</figcaption>
</figure>
)
}
export default function Home() { export default function Home() {
const loggedin = createAsync(() => checkLoggedIn()) const loggedin = createAsync(() => checkLoggedIn())
const i18n = useI18n() const i18n = useI18n()
@@ -144,9 +274,10 @@ export default function Home() {
</section> </section>
<section data-component="comparison"> <section data-component="comparison">
<video src={compareVideo} autoplay playsinline loop muted preload="auto" poster={compareVideoPoster}> <LimitsGraph
{i18n.t("common.videoUnsupported")} href={language.route("/docs/go/#usage-limits")}
</video> labels={{ free: i18n.t("go.graph.free"), go: i18n.t("go.graph.go") }}
/>
</section> </section>
<section data-component="problem"> <section data-component="problem">