mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 22:32:28 +00:00
docs: redesign Go pricing graph with horizontal bars and inline request labels
Improve visual clarity of request limits on the Go pricing page by replacing dot-based chart with animated horizontal bars that directly show model names and exact request counts. Add proper OpenGraph and Twitter Card meta tags for better social sharing discovery.
This commit is contained in:
@@ -246,6 +246,8 @@ export const dict = {
|
||||
"zen.privacy.exceptionsLink": "following exceptions",
|
||||
|
||||
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
||||
"go.meta.description":
|
||||
"Go is a $10/month subscription with generous 5-hour request limits for GLM-5, Kimi K2.5, and MiniMax M2.5.",
|
||||
"go.hero.title": "Low cost coding models for everyone",
|
||||
"go.hero.body":
|
||||
"Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.",
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes go-graph-bar {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
[data-page="go"] {
|
||||
--color-background: hsl(0, 20%, 99%);
|
||||
--color-background-weak: hsl(0, 8%, 97%);
|
||||
@@ -424,13 +431,78 @@ body {
|
||||
|
||||
[data-component="limit-graph"] {
|
||||
margin: 0 auto;
|
||||
max-width: calc(100% - (var(--padding) * 2));
|
||||
width: calc(100% - 120px);
|
||||
max-width: calc(100% - 120px);
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 18px 18px 56px;
|
||||
padding: 58px var(--padding) 56px;
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
[data-slot="plot"] {
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
margin-left: -40px;
|
||||
}
|
||||
|
||||
[data-slot="ylabels"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-slot="ylabels"] [data-ylabel] {
|
||||
position: absolute;
|
||||
left: var(--x);
|
||||
top: var(--y);
|
||||
transform: translate(-100%, -50%);
|
||||
color: var(--color-text-strong);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-slot="pills"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
|
||||
[data-item] {
|
||||
position: absolute;
|
||||
left: var(--x);
|
||||
top: var(--y);
|
||||
transform: translate(12px, -50%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
height: 20px;
|
||||
padding: 0 8px;
|
||||
border-radius: 2px;
|
||||
max-width: calc(100% - 12px);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
box-sizing: border-box;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-name] {
|
||||
color: var(--color-text);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-value] {
|
||||
color: var(--color-text-strong);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="plot-labels"] {
|
||||
@@ -451,8 +523,7 @@ body {
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 720 / 220;
|
||||
height: 220px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -479,13 +550,44 @@ body {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
[data-row],
|
||||
[data-val] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&[data-visible] [data-row],
|
||||
&[data-visible] [data-val] {
|
||||
opacity: 1;
|
||||
transition: opacity 240ms ease;
|
||||
transition-delay: var(--d, 0ms);
|
||||
}
|
||||
|
||||
[data-stub] {
|
||||
stroke: var(--color-border);
|
||||
stroke-width: 2;
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
[data-bar] {
|
||||
transform-box: fill-box;
|
||||
transform-origin: left center;
|
||||
opacity: 0;
|
||||
transform: scaleX(0.02);
|
||||
fill: var(--color-go-2);
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
[data-bar][data-kind="free"] {
|
||||
fill: var(--color-text-strong);
|
||||
}
|
||||
|
||||
[data-val] {
|
||||
fill: var(--color-text-strong);
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
[data-range] {
|
||||
stroke: var(--color-text-strong);
|
||||
stroke-width: 2;
|
||||
@@ -542,6 +644,17 @@ 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;
|
||||
animation-delay: var(--d, 0ms);
|
||||
}
|
||||
|
||||
&[data-visible] [data-slot="pills"] [data-item] {
|
||||
opacity: 1;
|
||||
transition: opacity 240ms ease;
|
||||
transition-delay: var(--d, 0ms);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
[data-animate="line"] {
|
||||
stroke-dashoffset: 0;
|
||||
@@ -552,34 +665,49 @@ body {
|
||||
transform: none;
|
||||
animation: none;
|
||||
}
|
||||
[data-bar] {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
animation: none;
|
||||
}
|
||||
[data-row],
|
||||
[data-val] {
|
||||
opacity: 1;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
[data-slot="pills"] [data-item] {
|
||||
opacity: 1;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
figcaption {
|
||||
margin-top: 34px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-slot="caption-row"] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
[data-slot="caption-left"] {
|
||||
display: grid;
|
||||
display: flex;
|
||||
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;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
[data-slot="caption-meta"] {
|
||||
display: contents;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
[data-slot="caption-label"] {
|
||||
@@ -587,8 +715,6 @@ body {
|
||||
font-weight: 650;
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
[data-slot="caption-link"] {
|
||||
@@ -596,73 +722,6 @@ body {
|
||||
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"] {
|
||||
@@ -671,35 +730,8 @@ body {
|
||||
}
|
||||
|
||||
@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;
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { config } from "~/config"
|
||||
import { getLastSeenWorkspaceID } from "../workspace/common"
|
||||
import { IconMiniMax, IconZai } from "~/component/icon"
|
||||
import { useI18n } from "~/context/i18n"
|
||||
@@ -49,24 +50,50 @@ function LimitsGraph(props: { href: string }) {
|
||||
{ id: "kimi", name: "Kimi K2.5", req: 1850, d: "240ms" },
|
||||
{ id: "minimax", name: "MiniMax M2.5", req: 20000, d: "360ms" },
|
||||
]
|
||||
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 left = 40
|
||||
const right = 60
|
||||
const top = 18
|
||||
const bottom = 44
|
||||
const plot = w - left - right
|
||||
|
||||
const ratio = (n: number) => n / free
|
||||
const rmax = Math.max(1, ...models.map((m) => ratio(m.req)))
|
||||
const log = (n: number) => Math.log10(Math.max(n, 1))
|
||||
const x = (r: number) => left + (log(r) / log(rmax)) * plot
|
||||
const base = 24
|
||||
const p = 2.2
|
||||
const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base)
|
||||
const start = (x(1) / w) * 100
|
||||
|
||||
const yFree = 74
|
||||
const yGo = 134
|
||||
const ticks = [1, 2, 5, 10, 25, 50, 100].filter((t) => t <= rmax)
|
||||
const y = (n: number) => `${(n / h) * 100}%`
|
||||
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 bh = 8
|
||||
const gap = 16
|
||||
const step = bh + gap
|
||||
const sep = bh + 40
|
||||
const fy = top + 22
|
||||
const gy = (i: number) => fy + sep + step * i
|
||||
const my = models.length < 2 ? gy(0) : (gy(0) + gy(models.length - 1)) / 2
|
||||
const px = (n: number) => `${(n / w) * 100}%`
|
||||
const py = (n: number) => `${(n / h) * 100}%`
|
||||
const lx = px(left - 16)
|
||||
|
||||
return (
|
||||
<figure
|
||||
@@ -77,52 +104,81 @@ function LimitsGraph(props: { href: string }) {
|
||||
style={{ "--start": `${start}%` } as any}
|
||||
>
|
||||
<div data-slot="plot">
|
||||
<svg viewBox={`0 0 ${w} ${h}`} role="img" aria-hidden="true">
|
||||
<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) => (
|
||||
<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>
|
||||
{i18n.t("go.graph.tick", { n: t })}
|
||||
</text>
|
||||
{labels.has(t) ? (
|
||||
<text x={x(t)} y={h - 18} text-anchor="middle" data-tick>
|
||||
{i18n.t("go.graph.tick", { n: t })}
|
||||
</text>
|
||||
) : null}
|
||||
</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>
|
||||
<line x1={left} y1={top} x2={left} y2={h - bottom} data-stub />
|
||||
|
||||
<g data-slot="bars">
|
||||
<g style={{ "--d": "0ms" } as any}>
|
||||
<rect x={left} y={fy - bh / 2} width={Math.max(0, x(1) - left)} height={bh} data-bar data-kind="free" />
|
||||
</g>
|
||||
|
||||
<g data-slot="go">
|
||||
<line x1={x(1)} y1={yGo} x2={x(ratio(models[0]!.req))} y2={yGo} data-range data-animate="line" />
|
||||
<line
|
||||
x1={x(ratio(models[0]!.req))}
|
||||
y1={yGo}
|
||||
x2={x(ratio(models[2]!.req))}
|
||||
y2={yGo}
|
||||
data-range
|
||||
data-animate="line"
|
||||
/>
|
||||
<For each={models}>
|
||||
{(m) => (
|
||||
{(m, i) => (
|
||||
<g style={{ "--d": m.d } as any}>
|
||||
<circle cx={x(ratio(m.req))} cy={yGo} r={5.5} data-point data-kind="go" data-model={m.id} />
|
||||
<rect
|
||||
x={left}
|
||||
y={gy(i()) - bh / 2}
|
||||
width={Math.max(0, x(ratio(m.req)) - left)}
|
||||
height={bh}
|
||||
data-bar
|
||||
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}>
|
||||
<div data-slot="ylabels" aria-hidden="true">
|
||||
<span data-ylabel style={{ "--x": lx, "--y": py(fy) } as any}>
|
||||
{i18n.t("go.graph.free")}
|
||||
</span>
|
||||
<span data-row-label style={{ "--y": y(yGo) } as any}>
|
||||
<span data-ylabel style={{ "--x": lx, "--y": py(my) } as any}>
|
||||
{i18n.t("go.graph.go")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div data-slot="pills" aria-hidden="true">
|
||||
<span data-item data-kind="free" style={{ "--x": px(x(1)), "--y": py(fy), "--d": "0ms" } as any}>
|
||||
<span data-name>{i18n.t("go.graph.free")}</span>
|
||||
<span data-value>{free.toLocaleString()}</span>
|
||||
</span>
|
||||
<For each={models}>
|
||||
{(m, i) => (
|
||||
<span
|
||||
data-item
|
||||
data-kind="go"
|
||||
data-model={m.id}
|
||||
style={{ "--x": px(x(ratio(m.req))), "--y": py(gy(i())), "--d": m.d } as any}
|
||||
>
|
||||
<span data-name>{m.name}</span>
|
||||
<span data-value>{m.req.toLocaleString()}</span>
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<figcaption>
|
||||
@@ -134,22 +190,6 @@ function LimitsGraph(props: { href: string }) {
|
||||
{i18n.t("go.graph.usageLimits")}
|
||||
</a>
|
||||
</div>
|
||||
<div data-slot="legend">
|
||||
<span data-item>
|
||||
<i data-dot data-kind="free" />
|
||||
<span data-name>{i18n.t("go.graph.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.req.toLocaleString()}</span>
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</figcaption>
|
||||
@@ -165,9 +205,17 @@ export default function Home() {
|
||||
<main data-page="go">
|
||||
{/*<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />*/}
|
||||
<Title>{i18n.t("go.title")}</Title>
|
||||
<Meta name="description" content={i18n.t("go.meta.description")} />
|
||||
<LocaleLinks path="/go" />
|
||||
<Meta property="og:image" content="/social-share-zen.png" />
|
||||
<Meta name="twitter:image" content="/social-share-zen.png" />
|
||||
<Meta property="og:type" content="website" />
|
||||
<Meta property="og:url" content={`${config.baseUrl}${language.route("/go")}`} />
|
||||
<Meta property="og:title" content={i18n.t("go.title")} />
|
||||
<Meta property="og:description" content={i18n.t("go.meta.description")} />
|
||||
<Meta property="og:image" content="/social-share-black.png" />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={i18n.t("go.title")} />
|
||||
<Meta name="twitter:description" content={i18n.t("go.meta.description")} />
|
||||
<Meta name="twitter:image" content="/social-share-black.png" />
|
||||
<Meta name="opencode:auth" content={loggedin() ? "true" : "false"} />
|
||||
|
||||
<div data-component="container">
|
||||
|
||||
Reference in New Issue
Block a user