Skip to content

Commit ddf9c3a

Browse files
r4topunkclaude
andcommitted
feat(stake): aplica o redesign na página /stake
O protótipo /stake/preview vira a página real. A rota de protótipo e o design lab foram removidos; os componentes subiram para src/components/stake/ e a spec para docs/design/. Estrutura: hero centrado, e daí um card por seção — rider, taxas, posições, prova social, subnet — empilhados no fundo da página. Não há mais uma ilha única envolvendo tudo. Light + dark: o chrome segue o tema do site (tokens em stake-ui.ts); dourado como texto vira amber-700 no claro, onde #f7c948 é ilegível. A arte (palco do rider, palco do orbit) fica dark fixa nos dois temas — os cut-outs e o SVG são desenhados para fundo escuro. Seletor: o CharacterSelector de produção continua sendo o picker. A ficha de skills é coluna a partir de lg e painel sobre o rider abaixo disso, onde a coluna só teria como empilhar embaixo da arte. Roster em uma linha com scroll horizontal — sete riders nunca deixam um órfão sob uma fileira cheia. Orbit: passa a renderizar em qualquer largura (já tinha o braço mobile pronto, faltava ligar) e abre centrado, em vez de na margem vazia. r4to: cut-out novo com chroma key aplicado e clipe alpha. O clipe toca no clique, uma vez, e congela no último frame — no palco e no nó do orbit. Nome em minúsculo nos dois locales. Milestones da subnet viram dados; os rótulos foram para o i18n, que corrige uma lista inteira em inglês no meio da página pt-BR. StakeAdminPanel sai da página pública (mostra um card de diagnóstico para qualquer não-admin conectado) e ganha /stake/admin, noindex. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent c4706b3 commit ddf9c3a

35 files changed

Lines changed: 1483 additions & 2385 deletions

docs/INDEX.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This is the canonical entry point for project documentation. Everything below sh
1515
- `docs/architecture/exploration-progress.md`
1616
- `docs/architecture/refactor-audit.md`
1717
- `docs/i18n/tone-brief.md` — PT-BR translation tone brief
18+
- `docs/design/stake-visual-system.md` — /stake visual system: panel/row/accent tokens (`src/components/stake/stake-ui.ts`), section-header pattern, copy rules, page composition
1819

1920
## Features
2021

src/components/stake/preview/DESIGN-SPEC.md renamed to docs/design/stake-visual-system.md

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
1-
# /stake/preview — visual direction v3 (committed)
1+
# /stake — visual system
2+
3+
> **Status: finalized 2026-08-07 and applied to the production `/stake` and
4+
> `/stake/[rider]` routes.** The `/stake/preview` prototype route is gone; the
5+
> tokens described below live in `src/components/stake/stake-ui.ts` and the page
6+
> is composed by `src/components/stake/StakePageContent.tsx`.
7+
>
8+
> Two decisions changed on the way in, and the sections below are read with them
9+
> in mind:
10+
>
11+
> - **The rider picker is the production `CharacterSelector`.** The prototype's
12+
> `RiderSelect` fork was rejected, so the "RiderSelect" section at the end is
13+
> history, not spec — the arcade stage, the stat meters and the roster grid all
14+
> stayed.
15+
> - **Chrome follows the site theme.** Every `white/xx` alpha quoted below became
16+
> a `foreground` / `muted-foreground` equivalent so the page reads in light mode
17+
> too. The one committed-dark surface left is `ORBIT_STAGE`, the panel the orbit
18+
> SVG is drawn on (it is artwork lit for a dark stage, like the rider cut-outs).
19+
> Gold and Morpheus green ship as light/dark pairs (`GOLD_TEXT`, `MOR_TEXT`,
20+
> `MOR_FILL`) because the bright values are unreadable on a light card.
21+
> - The message namespace is `stake.page.*` (it was `stake.preview.*`).
222
323
Pass 3. Inputs: the triple visual evaluation (grades 6/5/5) and the owner's
424
anti-slop taste rules. Everything here is decided; no toggles for these choices.
525

6-
## System tokens (preview-config.ts)
26+
## System tokens (stake-ui.ts)
727

8-
- `PANEL` = `rounded-2xl border border-white/[0.08] bg-white/[0.06]` — raised from
9-
0.03: the site's own `--card` step is ~2× what we shipped.
10-
- Rows inside a panel: NO sub-cards. Transparent rows separated by ONE hairline
11-
(`divide-y divide-white/[0.06]` on the container). Never border-t AND border-b.
28+
- `CARD` = `rounded-2xl border bg-card text-card-foreground shadow-sm` — one
29+
section, one card, stacked on the page background. (Supersedes the prototype's
30+
`PANEL` foreground-alpha recipe, which existed to step panels above a wrapping
31+
island; the island is gone, so sections use the site's own card token.)
32+
- Rows inside a card: NO sub-cards. Transparent rows separated by ONE hairline
33+
(`divide-y` on the container). Never border-t AND border-b.
1234
- `MUTED` `text-white/50`, `MICRO` `text-white/35` unchanged.
1335
- GOLD unchanged, still the only UI accent. **Color rule tightened:** every key
1436
number / earned yield renders gold; Morpheus green appears ONLY on the subnet
@@ -26,7 +48,7 @@ optional one-line desc (`text-sm text-white/50 max-w-prose`). The rhythm comes
2648
from consistency, not numbering. No uppercase-tracking eyebrows anywhere in
2749
preview files.
2850

29-
## Copy rules (both locales, stake.preview.* namespace)
51+
## Copy rules (both locales, `stake.page.*` namespace)
3052

3153
- **Zero em-dashes (``) and zero en-dash separators** in any user-visible
3254
preview string. Restructure with period, comma, colon or parentheses.
@@ -43,14 +65,17 @@ preview files.
4365
Builder subnet, which funds what the DAO ships. Your MOR stays yours and
4466
unlocks 7 days after you stake."
4567

46-
## Page composition (StakePreview)
68+
## Page composition (StakePageContent)
4769

4870
- Page container `max-w-6xl` (match the site shell); narrow measure only on
4971
paragraphs (`max-w-2xl`).
50-
- **h1 + hero paragraph live OUTSIDE the island**, so the page opens exactly like
51-
Treasury/Auctions. The island starts at the rider section as an explicit stage:
52-
`bg-white/[0.02] rounded-2xl ring-1 ring-white/[0.06] shadow-xl mx-0 px-4 py-8
53-
sm:px-6` (all breakpoints rounded; no -mx bleeds).
72+
- **h1 + hero paragraph sit on the page background**, so the page opens exactly
73+
like Treasury/Auctions. Below them, **every section is its own `CARD`** in a
74+
`space-y-6` stack — the prototype's single wrapping island was replaced by
75+
per-section cards at the owner's request (2026-08-07). The rates strip keeps
76+
slimmer padding than the other cards so global rates weigh less than the
77+
user's own money; PositionsHub and SubnetSection render their own cards
78+
because they own their headers (PositionsHub self-suppresses).
5479
- Social proof: **orbit graph is desktop-only** (`hidden md:block`); below `md`
5580
render the ranked list (BackerList) instead. The `?orbit=list` param now only
5681
affects desktop.

messages/en/stake.json

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"subtitle": "Pick your rider",
44
"intro": "Choose a character to represent you. Use the thumbnails, arrows, or arrow keys to switch.",
55
"chooseLabel": "Choose your character",
6-
"selected": "Selected",
76
"selectedRider": "{name} is ready to drop in.",
87
"stakeCta": "Stake with {name}",
98
"stakeToast": "Staked with {name}",
@@ -21,6 +20,7 @@
2120
"disclaimer": "Your deposit stays yours and is withdrawable anytime. Rates vary; this isn't financial advice.",
2221
"prev": "Previous rider",
2322
"next": "Next rider",
23+
"playClip": "Play {name}'s clip",
2424
"statusTitle": "Status",
2525
"ethStaking": "ETH staking",
2626
"usdcLending": "USDC lending",
@@ -46,7 +46,7 @@
4646
"tagline": "Frontend flow, backend grind."
4747
},
4848
"r4to": {
49-
"name": "R4to",
49+
"name": "r4to",
5050
"tagline": "Onchain from the first push."
5151
},
5252
"pamtech": {
@@ -226,14 +226,15 @@
226226
"claimHint": "To claim, open the reward box (bottom-right) once the MOR unlock date has passed — it bridges your MOR to Arbitrum and splits it to your wallet.",
227227
"powerLockNote": "A far-off MOR unlock means a longer power-lock was chosen at deposit (higher APR / power factor) — the MOR stays locked until that date."
228228
},
229-
"preview": {
229+
"page": {
230230
"hero": "Back a Gnars rider. Your deposit stays yours and keeps earning. You keep half the yield; the other half backs your rider and the treasury.",
231-
"rider": {
232-
"rosterLabel": "Rider roster",
233-
"hint": "Use ← → to switch riders",
234-
"position": "{current} / {total}"
235-
},
236231
"ratesTitle": "Current rates",
232+
"rateHelpLabel": "About the {asset} rate on {venue}",
233+
"rateHelp": {
234+
"morphoUsdc": "Annual rate, paid in USDC. No lock: withdraw whenever you want.",
235+
"morpheusSteth": "Estimated annual rate, paid in MOR. Your stETH unlocks 7 days after you stake.",
236+
"morpheusUsdc": "Estimated annual rate, paid in MOR. Your USDC unlocks 7 days after you stake."
237+
},
237238
"ratesLoading": "loading rates…",
238239
"ratesNa": "n/a",
239240
"socialTitle": "Who is already backing",
@@ -246,7 +247,6 @@
246247
"positions": {
247248
"title": "Your positions",
248249
"desc": "Everything you have staked, and when you can touch it.",
249-
"sampleData": "sample data",
250250
"backsSubnet": "backs the subnet",
251251
"unlocks": "unlocks {date}",
252252
"venue": {
@@ -270,7 +270,14 @@
270270
"desc": "This one pays you nothing. Staked MOR powers the Gnars Builder subnet, which funds what the DAO ships. Your MOR stays yours and unlocks 7 days after you stake.",
271271
"morStaked": "MOR staked",
272272
"goal": "goal {n, number}",
273-
"cta": "Back the subnet"
273+
"cta": "Back the subnet",
274+
"milestones": {
275+
"live": "Subnet live: Gnars is a Morpheus Builder",
276+
"backers10": "First 10 backers on board",
277+
"10k": "{n, number} MOR staked: featured stakers wall",
278+
"50k": "{n, number} MOR staked: community drop",
279+
"100k": "{n, number} MOR staked: IRL Gnars event"
280+
}
274281
},
275282
"backers": {
276283
"loading": "loading the flow…",

messages/pt-br/stake.json

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"subtitle": "Escolha seu rider",
44
"intro": "Escolha um personagem para te representar. Use as miniaturas, as setas ou as teclas de seta para alternar.",
55
"chooseLabel": "Escolha seu personagem",
6-
"selected": "Selecionado",
76
"selectedRider": "{name} está pronto para descer.",
87
"stakeCta": "Fazer stake com {name}",
98
"stakeToast": "Stake feito com {name}",
@@ -21,6 +20,7 @@
2120
"disclaimer": "Seu depósito continua seu e pode ser sacado quando quiser. As taxas variam; isto não é aconselhamento financeiro.",
2221
"prev": "Rider anterior",
2322
"next": "Próximo rider",
23+
"playClip": "Reproduzir o clipe de {name}",
2424
"statusTitle": "Status",
2525
"ethStaking": "Staking de ETH",
2626
"usdcLending": "Lending de USDC",
@@ -46,7 +46,7 @@
4646
"tagline": "Flow no frontend, ralação no backend."
4747
},
4848
"r4to": {
49-
"name": "R4to",
49+
"name": "r4to",
5050
"tagline": "Onchain desde o primeiro empurrão."
5151
},
5252
"pamtech": {
@@ -226,14 +226,15 @@
226226
"claimHint": "Pra sacar, abra a caixa de recompensa (canto inferior direito) depois que a data de unlock do MOR passar — ela leva o MOR pra Arbitrum e divide na sua carteira.",
227227
"powerLockNote": "Um unlock do MOR distante significa que um power-lock maior foi escolhido no depósito (APR / power factor mais alto) — o MOR fica travado até essa data."
228228
},
229-
"preview": {
229+
"page": {
230230
"hero": "Apoie um rider da Gnars. Seu depósito continua seu e rendendo. Metade do rendimento fica com você; a outra metade banca seu rider e o tesouro.",
231-
"rider": {
232-
"rosterLabel": "Elenco de riders",
233-
"hint": "Use ← → pra trocar de rider",
234-
"position": "{current} / {total}"
235-
},
236231
"ratesTitle": "Taxas atuais",
232+
"rateHelpLabel": "Sobre a taxa de {asset} na {venue}",
233+
"rateHelp": {
234+
"morphoUsdc": "Taxa anual, paga em USDC. Sem lock: saque quando quiser.",
235+
"morpheusSteth": "Taxa anual estimada, paga em MOR. Seu stETH libera 7 dias depois do stake.",
236+
"morpheusUsdc": "Taxa anual estimada, paga em MOR. Seu USDC libera 7 dias depois do stake."
237+
},
237238
"ratesLoading": "carregando as taxas…",
238239
"ratesNa": "n/d",
239240
"socialTitle": "Quem já está apoiando",
@@ -246,7 +247,6 @@
246247
"positions": {
247248
"title": "Suas posições",
248249
"desc": "Tudo que você tem em stake, e quando dá pra mexer nisso.",
249-
"sampleData": "dados de exemplo",
250250
"backsSubnet": "sustenta a subnet",
251251
"unlocks": "libera em {date}",
252252
"venue": {
@@ -270,7 +270,14 @@
270270
"desc": "Esse aqui não te paga nada. O MOR em stake sustenta a subnet Gnars Builder, que financia o que a DAO entrega. Seu MOR continua seu e libera 7 dias depois do stake.",
271271
"morStaked": "MOR em stake",
272272
"goal": "meta {n, number}",
273-
"cta": "Apoiar a subnet"
273+
"cta": "Apoiar a subnet",
274+
"milestones": {
275+
"live": "Subnet no ar: a Gnars virou Builder na Morpheus",
276+
"backers10": "Primeiros 10 apoiadores a bordo",
277+
"10k": "{n, number} MOR em stake: mural dos apoiadores",
278+
"50k": "{n, number} MOR em stake: drop pra comunidade",
279+
"100k": "{n, number} MOR em stake: evento Gnars presencial"
280+
}
274281
},
275282
"backers": {
276283
"loading": "carregando o fluxo…",

public/stake/cutout/r4to.png

69.8 KB
Loading

public/stake/video/r4to.webm

1.26 MB
Binary file not shown.

src/app/[locale]/stake/[rider]/page.tsx

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import type { Metadata } from "next";
2-
import { notFound } from "next/navigation";
32
import { getTranslations, setRequestLocale } from "next-intl/server";
4-
import { CharacterSelector } from "@/components/stake/CharacterSelector";
5-
import { StakeAdminPanel } from "@/components/stake/StakeAdminPanel";
6-
import { StakeOrbit } from "@/components/stake/StakeOrbit";
7-
import { MorLootbox } from "@/components/stake/MorLootbox";
8-
import { MorpheusStakeWidget } from "@/components/stake/MorpheusStakeWidget";
9-
import { RIDER_LIST, getRider } from "@/lib/gnars-vaults";
10-
import { STAKE_MINIAPP_EMBED_CONFIG } from "@/lib/miniapp-config";
3+
import { notFound } from "next/navigation";
4+
import { StakePageContent } from "@/components/stake/StakePageContent";
115
import { BASE_URL } from "@/lib/config";
6+
import { getRider, RIDER_LIST, type RiderId } from "@/lib/gnars-vaults";
7+
import { STAKE_MINIAPP_EMBED_CONFIG } from "@/lib/miniapp-config";
128

139
// Shareable per-rider entry point: /stake/vlad opens the roster on that rider,
1410
// so a supporter can link straight to the athlete they're backing.
@@ -58,7 +54,10 @@ export async function generateMetadata({
5854
button: {
5955
...STAKE_MINIAPP_EMBED_CONFIG.button,
6056
title: `Stake with ${name}`,
61-
action: { ...STAKE_MINIAPP_EMBED_CONFIG.button.action, url: `${BASE_URL}/stake/${rider}` },
57+
action: {
58+
...STAKE_MINIAPP_EMBED_CONFIG.button.action,
59+
url: `${BASE_URL}/stake/${rider}`,
60+
},
6261
},
6362
}),
6463
},
@@ -74,25 +73,13 @@ export default async function StakeRiderPage({
7473
setRequestLocale(locale);
7574
if (!getRider(rider)) notFound();
7675

77-
const t = await getTranslations("stake");
78-
7976
return (
8077
<div className="py-10">
81-
<div className="mx-auto max-w-4xl space-y-8">
82-
<div className="space-y-2 text-center">
83-
<p className="text-sm font-semibold uppercase tracking-widest text-muted-foreground">
84-
{t("subtitle")}
85-
</p>
86-
<h1 className="text-4xl font-black tracking-tight sm:text-5xl">{t("title")}</h1>
87-
<p className="mx-auto max-w-xl text-muted-foreground">{t("intro")}</p>
88-
</div>
89-
90-
<CharacterSelector initialRider={rider} />
91-
<StakeOrbit />
92-
<StakeAdminPanel />
78+
{/* max-w-6xl matches the site shell (Treasury, Auctions). The narrow measure
79+
that reading text needs is applied by the paragraphs themselves. */}
80+
<div className="mx-auto max-w-6xl">
81+
<StakePageContent initialRider={rider as RiderId} />
9382
</div>
94-
<MorLootbox />
95-
<MorpheusStakeWidget />
9683
</div>
9784
);
9885
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { Metadata } from "next";
2+
import { setRequestLocale } from "next-intl/server";
3+
import { StakeAdminPanel } from "@/components/stake/StakeAdminPanel";
4+
5+
// Home for the sponsorship-vault deploy tool.
6+
//
7+
// It used to render at the bottom of /stake, which is now a public marketing +
8+
// staking surface — an owner tool (3 wallet txs and a Safe batch) does not belong
9+
// on it, and for a connected non-admin the panel prints a diagnostic card with
10+
// their own addresses. Taking it off that page left the only UI that can deploy a
11+
// rider's Morpho V2 vault unreachable, so it gets its own address instead: adding
12+
// a rider still has a path through the UI, and nobody lands here by accident.
13+
//
14+
// A static segment beats the sibling `[rider]` dynamic route, and no rider is
15+
// called "admin", so this steals nothing. The panel keeps its own isVaultAdmin
16+
// gate — this route is not an access control, just a place to stand.
17+
18+
export const metadata: Metadata = {
19+
title: "Sponsorship vaults",
20+
robots: { index: false, follow: false },
21+
};
22+
23+
export default async function StakeAdminPage({ params }: { params: Promise<{ locale: string }> }) {
24+
const { locale } = await params;
25+
setRequestLocale(locale);
26+
27+
return (
28+
<div className="py-10">
29+
<div className="mx-auto max-w-3xl space-y-4">
30+
{/* The panel renders nothing at all until a wallet is connected, so the
31+
route needs one line of its own — otherwise an owner who lands here
32+
signed out gets a blank page and cannot tell it from a broken one.
33+
English like the panel it introduces: this whole tool is untranslated. */}
34+
<header className="space-y-1">
35+
<h1 className="text-2xl font-bold tracking-tight">Sponsorship vaults</h1>
36+
<p className="text-sm text-muted-foreground">
37+
Owner tool. Connect a SOPA Safe owner wallet to deploy a rider&apos;s vault.
38+
</p>
39+
</header>
40+
<StakeAdminPanel />
41+
</div>
42+
</div>
43+
);
44+
}

src/app/[locale]/stake/page.tsx

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import type { Metadata } from "next";
22
import { getTranslations, setRequestLocale } from "next-intl/server";
3-
import { CharacterSelector } from "@/components/stake/CharacterSelector";
4-
import { StakeAdminPanel } from "@/components/stake/StakeAdminPanel";
5-
import { StakeOrbit } from "@/components/stake/StakeOrbit";
6-
import { StakePositions } from "@/components/stake/StakePositions";
7-
import { MorLootbox } from "@/components/stake/MorLootbox";
8-
import { MorpheusStakeWidget } from "@/components/stake/MorpheusStakeWidget";
3+
import { StakePageContent } from "@/components/stake/StakePageContent";
94
import { STAKE_MINIAPP_EMBED_CONFIG } from "@/lib/miniapp-config";
105

116
export async function generateMetadata({
@@ -51,28 +46,13 @@ export default async function StakePage({ params }: { params: Promise<{ locale:
5146
const { locale } = await params;
5247
setRequestLocale(locale);
5348

54-
const t = await getTranslations("stake");
55-
5649
return (
5750
<div className="py-10">
58-
<div className="mx-auto max-w-4xl space-y-8">
59-
<div className="space-y-2 text-center">
60-
<p className="text-sm font-semibold uppercase tracking-widest text-muted-foreground">
61-
{t("subtitle")}
62-
</p>
63-
<h1 className="text-4xl font-black tracking-tight sm:text-5xl">{t("title")}</h1>
64-
<p className="mx-auto max-w-xl text-muted-foreground">{t("intro")}</p>
65-
</div>
66-
67-
<div id="stake-start" className="scroll-mt-24">
68-
<CharacterSelector />
69-
</div>
70-
<StakeOrbit />
71-
<StakePositions />
72-
<StakeAdminPanel />
51+
{/* max-w-6xl matches the site shell (Treasury, Auctions). The narrow measure
52+
that reading text needs is applied by the paragraphs themselves. */}
53+
<div className="mx-auto max-w-6xl">
54+
<StakePageContent />
7355
</div>
74-
<MorLootbox />
75-
<MorpheusStakeWidget />
7656
</div>
7757
);
7858
}

0 commit comments

Comments
 (0)