Skip to content

Commit eea2936

Browse files
sktbrdclaude
andcommitted
feat(morpheus): illustrated amplification ladder, flywheel icons, quieter permissionless line
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 3d7b8c7 commit eea2936

4 files changed

Lines changed: 174 additions & 46 deletions

File tree

messages/en/stake.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"morpheusPage": {
33
"eyebrow": "Gnars × Morpheus",
44
"title": "Gnars builds on Morpheus",
5-
"lede": "Gnars — the community-owned action-sports DAO — runs a Builder subnet on Morpheus, the decentralized AI network. Permissionlessly: no official deal, no gatekeeper, just Gnars building on an open protocol and bringing skate, BMX, and surf culture with it.",
5+
"lede": "Gnars — the community-owned action-sports DAO — runs a Builder subnet on Morpheus, the decentralized AI network, building permissionlessly on the open protocol and bringing skate, BMX, and surf culture with it.",
66
"ctaStake": "Stake MOR",
77
"ctaFull": "Every way to stake",
88
"how": {
@@ -19,6 +19,14 @@
1919
"f1": "Your principal stays yours — only your wallet can withdraw it, straight from the contract on Base.",
2020
"f2": "Withdrawals unlock 7 days after your last deposit — each new deposit restarts the lock on your whole position.",
2121
"f3": "Rewards accrue to the subnet — you're backing the builder, not farming yield."
22+
},
23+
"amp": {
24+
"title": "The amplification ladder",
25+
"desc": "Milestones aren't just airdrop buckets. Every rung the community climbs, Gnars puts its own media and its own crew behind Morpheus — real reach, shipped by the DAO itself.",
26+
"liveChip": "{n, number} MOR staked so far",
27+
"unlocked": "unlocked",
28+
"next": "next up",
29+
"upcoming": "on the ladder"
2230
}
2331
},
2432
"title": "Stake or Die",

messages/pt-br/stake.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"morpheusPage": {
33
"eyebrow": "Gnars × Morpheus",
44
"title": "A Gnars constrói na Morpheus",
5-
"lede": "A Gnars — DAO de esportes de ação de propriedade da comunidade — roda uma subnet Builder na Morpheus, a rede de IA descentralizada. De forma permissionless: sem acordo oficial, sem gatekeeper, só a Gnars construindo sobre um protocolo aberto e levando junto a cultura do skate, BMX e surf.",
5+
"lede": "A Gnars — DAO de esportes de ação de propriedade da comunidade — roda uma subnet Builder na Morpheus, a rede de IA descentralizada, construindo de forma permissionless sobre o protocolo aberto e levando junto a cultura do skate, BMX e surf.",
66
"ctaStake": "Fazer stake de MOR",
77
"ctaFull": "Todas as formas de stake",
88
"how": {
@@ -19,6 +19,14 @@
1919
"f1": "Seu principal continua seu — só a sua carteira consegue sacar, direto do contrato na Base.",
2020
"f2": "O saque destrava 7 dias depois do último depósito — cada depósito novo reinicia o lock da posição inteira.",
2121
"f3": "As recompensas vão para a subnet — você está apoiando o builder, não farmando yield."
22+
},
23+
"amp": {
24+
"title": "A escada de amplificação",
25+
"desc": "Os marcos não são só buckets de airdrop. A cada degrau que a comunidade sobe, a Gnars coloca a própria mídia e a própria crew atrás da Morpheus — alcance de verdade, entregue pela própria DAO.",
26+
"liveChip": "{n, number} MOR em stake até agora",
27+
"unlocked": "destravado",
28+
"next": "próximo",
29+
"upcoming": "na escada"
2230
}
2331
},
2432
"title": "Stake or Die",

src/components/stake/MorpheusPageContent.tsx

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,48 @@
1212
// wallet, 7-day lock counted from the LAST deposit, rewards accrue to the
1313
// subnet. Don't add a claim here without reading the contract first.
1414
import { useState } from "react";
15-
import { useTranslations } from "next-intl";
15+
import { useLocale, useTranslations } from "next-intl";
1616
import Image from "next/image";
17+
import {
18+
Clapperboard,
19+
Flag,
20+
Gift,
21+
HandCoins,
22+
MapPin,
23+
Megaphone,
24+
Rocket,
25+
Shirt,
26+
Tv,
27+
} from "lucide-react";
1728
import { GnarsStakeDialog } from "@/components/stake/GnarsStakeDialog";
1829
import { RoadmapSection } from "@/components/stake/RoadmapSection";
1930
import { CARD, CARD_PAD, GOLD, GOLD_CTA, GOLD_INK, MUTED } from "@/components/stake/stake-ui";
2031
import { SubnetSection } from "@/components/stake/SubnetSection";
2132
import { Button } from "@/components/ui/button";
33+
import { useGnarsSubnet } from "@/hooks/use-gnars-subnet";
2234
import { Link } from "@/i18n/navigation";
35+
import { isMilestoneDone, nextMilestone, SUBNET_MILESTONES } from "@/lib/stake-milestones";
36+
import { cn } from "@/lib/utils";
37+
38+
// One icon per flywheel step and per milestone rung. Data-keyed (not positional)
39+
// so a ladder edit in stake-milestones.ts can't silently shift every icon.
40+
const STEP_ICONS = { step1: HandCoins, step2: Gift, step3: Megaphone } as const;
41+
const RUNG_ICONS: Record<string, typeof Tv> = {
42+
"10k": Tv,
43+
"15k": Flag,
44+
"25k": Clapperboard,
45+
"30k": Shirt,
46+
"50k": MapPin,
47+
"100k": Rocket,
48+
};
2349

2450
export function MorpheusPageContent() {
2551
const t = useTranslations("stake.morpheusPage");
52+
const tSub = useTranslations("stake.page.subnet");
53+
const locale = useLocale();
2654
const [stakeOpen, setStakeOpen] = useState(false);
55+
const { totalStaked } = useGnarsSubnet();
56+
const next = nextMilestone(totalStaked);
2757

2858
const steps = ["step1", "step2", "step3"] as const;
2959
const facts = ["f1", "f2", "f3"] as const;
@@ -64,18 +94,93 @@ export function MorpheusPageContent() {
6494
<section className={`${CARD} ${CARD_PAD}`}>
6595
<h2 className="text-lg font-bold tracking-tight">{t("how.title")}</h2>
6696
<ol className="mt-4 grid gap-4 sm:grid-cols-3">
67-
{steps.map((s, i) => (
68-
<li key={s} className="rounded-xl border border-border/60 bg-background/40 p-4">
69-
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-foreground text-xs font-bold text-background">
70-
{i + 1}
71-
</div>
72-
<div className="mt-2.5 text-sm font-semibold">{t(`how.${s}t`)}</div>
73-
<p className={`mt-1 text-xs leading-relaxed ${MUTED}`}>{t(`how.${s}d`)}</p>
74-
</li>
75-
))}
97+
{steps.map((s) => {
98+
const Icon = STEP_ICONS[s];
99+
return (
100+
<li key={s} className="rounded-xl border border-border/60 bg-background/40 p-4">
101+
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-foreground text-background">
102+
<Icon className="h-4.5 w-4.5" aria-hidden />
103+
</div>
104+
<div className="mt-2.5 text-sm font-semibold">{t(`how.${s}t`)}</div>
105+
<p className={`mt-1 text-xs leading-relaxed ${MUTED}`}>{t(`how.${s}d`)}</p>
106+
</li>
107+
);
108+
})}
76109
</ol>
77110
</section>
78111

112+
{/* The amplification ladder, illustrated — the marketing Gnars ships for
113+
Morpheus at each rung, with live unlock state. Morpheus green is the
114+
accent on purpose: this page is the Morpheus page. */}
115+
<section className={`${CARD} ${CARD_PAD} sm:p-8`}>
116+
<div className="flex flex-wrap items-center justify-between gap-3">
117+
<h2 className="text-lg font-bold tracking-tight">{t("amp.title")}</h2>
118+
<span className="rounded-full bg-emerald-500/15 px-3 py-1 text-xs font-semibold text-emerald-600 dark:text-emerald-300">
119+
{t("amp.liveChip", { n: Math.floor(totalStaked) })}
120+
</span>
121+
</div>
122+
<p className={`mt-1.5 max-w-2xl text-sm ${MUTED}`}>{t("amp.desc")}</p>
123+
<ul className="mt-5 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
124+
{SUBNET_MILESTONES.map((m) => {
125+
const done = isMilestoneDone(m, totalStaked);
126+
const isNext = next?.id === m.id;
127+
const Icon = RUNG_ICONS[m.id] ?? Rocket;
128+
return (
129+
<li
130+
key={m.id}
131+
className={cn(
132+
"rounded-2xl border p-4 transition-colors",
133+
done &&
134+
"border-emerald-500/40 bg-gradient-to-br from-emerald-500/[0.12] to-transparent",
135+
isNext &&
136+
"border-amber-500/50 bg-gradient-to-br from-amber-500/[0.08] to-transparent",
137+
!done && !isNext && "border-border/60 bg-background/40",
138+
)}
139+
>
140+
<div className="flex items-center justify-between">
141+
<span
142+
className={cn(
143+
"flex h-10 w-10 items-center justify-center rounded-xl",
144+
done
145+
? "bg-emerald-500 text-white"
146+
: isNext
147+
? "bg-amber-500/90 text-white"
148+
: "border border-border text-muted-foreground",
149+
)}
150+
>
151+
<Icon className="h-5 w-5" aria-hidden />
152+
</span>
153+
<span
154+
className={cn(
155+
"rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider",
156+
done
157+
? "bg-emerald-500/15 text-emerald-600 dark:text-emerald-300"
158+
: isNext
159+
? "bg-amber-500/15 text-amber-600 dark:text-amber-300"
160+
: "text-muted-foreground",
161+
)}
162+
>
163+
{done ? t("amp.unlocked") : isNext ? t("amp.next") : t("amp.upcoming")}
164+
</span>
165+
</div>
166+
<div className="mt-3 text-xl font-black tabular-nums">
167+
{m.amountMor.toLocaleString(locale)}{" "}
168+
<span className="text-xs font-medium text-muted-foreground">MOR</span>
169+
</div>
170+
<div className="mt-1 text-sm font-medium leading-snug">
171+
{tSub(`milestones.${m.id}`)}
172+
</div>
173+
<div
174+
className={`mt-1.5 text-[10px] font-semibold uppercase tracking-wider ${MUTED}`}
175+
>
176+
{tSub(`firmness.${m.firmness}`)}
177+
</div>
178+
</li>
179+
);
180+
})}
181+
</ul>
182+
</section>
183+
79184
{/* Custody facts — every line verified against the contract */}
80185
<section className={`${CARD} ${CARD_PAD}`}>
81186
<h2 className="text-lg font-bold tracking-tight">{t("facts.title")}</h2>
@@ -89,7 +194,7 @@ export function MorpheusPageContent() {
89194
</section>
90195

91196
{/* Live ladder + stake CTA, and the four phases — the production sections */}
92-
<SubnetSection />
197+
<SubnetSection showChecklist={false} />
93198
<RoadmapSection />
94199

95200
<GnarsStakeDialog open={stakeOpen} onOpenChange={setStakeOpen} />

src/components/stake/SubnetSection.tsx

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import { cn } from "@/lib/utils";
6363
const fmtMor = (n: number, locale: string) =>
6464
n.toLocaleString(locale, { maximumFractionDigits: 0 });
6565

66-
export function SubnetSection() {
66+
export function SubnetSection({ showChecklist = true }: { showChecklist?: boolean } = {}) {
6767
const t = useTranslations("stake.page.subnet");
6868
const locale = useLocale();
6969
const { address: you } = useUserAddress();
@@ -186,44 +186,51 @@ export function SubnetSection() {
186186

187187
{/* The ladder needs naming and framing before it is read: without these
188188
two lines it looks like a reward schedule, which is the other axis
189-
entirely and the one the stake dialog's disclaimer contradicts. */}
190-
<h3 className="mt-6 text-sm font-semibold">{t("milestonesTitle")}</h3>
191-
<p className={cn("mt-1 max-w-prose text-xs", MUTED)}>{t("milestonesNote")}</p>
189+
entirely and the one the stake dialog's disclaimer contradicts.
190+
/morpheus passes showChecklist={false}: its illustrated ladder card
191+
carries the same milestones, and printing them twice on one page
192+
reads as filler. */}
193+
{showChecklist ? (
194+
<>
195+
<h3 className="mt-6 text-sm font-semibold">{t("milestonesTitle")}</h3>
196+
<p className={cn("mt-1 max-w-prose text-xs", MUTED)}>{t("milestonesNote")}</p>
192197

193-
<ul className="mt-3 space-y-2">
194-
{SUBNET_MILESTONES.map((m) => {
195-
const done = isMilestoneDone(m, totalStaked);
196-
return (
197-
<li key={m.id} className="flex items-start gap-2.5 text-sm">
198-
{/* Filled = reached, hollow = pending. Colourless on purpose: green
198+
<ul className="mt-3 space-y-2">
199+
{SUBNET_MILESTONES.map((m) => {
200+
const done = isMilestoneDone(m, totalStaked);
201+
return (
202+
<li key={m.id} className="flex items-start gap-2.5 text-sm">
203+
{/* Filled = reached, hollow = pending. Colourless on purpose: green
199204
is spent on the bar and the total, and a green tick here would
200205
make three different greens argue inside one panel. */}
201-
<span
202-
aria-hidden
203-
className={cn(
204-
"mt-1.5 size-1.5 shrink-0 rounded-full",
205-
done ? "bg-foreground/70" : "border border-foreground/30",
206-
)}
207-
/>
208-
<span className={done ? "" : MUTED}>
209-
{t("milestoneRow", { n: m.amountMor, what: t(`milestones.${m.id}`) })}{" "}
210-
{/* Inline rather than a right-aligned column: these labels are
206+
<span
207+
aria-hidden
208+
className={cn(
209+
"mt-1.5 size-1.5 shrink-0 rounded-full",
210+
done ? "bg-foreground/70" : "border border-foreground/30",
211+
)}
212+
/>
213+
<span className={done ? "" : MUTED}>
214+
{t("milestoneRow", { n: m.amountMor, what: t(`milestones.${m.id}`) })}{" "}
215+
{/* Inline rather than a right-aligned column: these labels are
211216
long enough to wrap on a phone, and a pinned column would
212217
squeeze them to two words a line. Not a PILL either — a pill
213218
is a badge about the page, never the state a row is in. */}
214-
<span
215-
className={cn(
216-
"whitespace-nowrap text-[10px] font-semibold tracking-wide uppercase",
217-
MICRO,
218-
)}
219-
>
220-
· {t(`firmness.${m.firmness}`)}
221-
</span>
222-
</span>
223-
</li>
224-
);
225-
})}
226-
</ul>
219+
<span
220+
className={cn(
221+
"whitespace-nowrap text-[10px] font-semibold tracking-wide uppercase",
222+
MICRO,
223+
)}
224+
>
225+
· {t(`firmness.${m.firmness}`)}
226+
</span>
227+
</span>
228+
</li>
229+
);
230+
})}
231+
</ul>
232+
</>
233+
) : null}
227234

228235
<Button
229236
className={cn(GOLD_CTA, "mt-6 w-full sm:w-auto")}

0 commit comments

Comments
 (0)