From cdf36ecc3e15922ed2813e5a0371164b0fccf23b Mon Sep 17 00:00:00 2001 From: project7 Date: Mon, 4 May 2026 13:25:42 +0900 Subject: [PATCH] Replace milestone cards with MCap progress chart (#1035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the 4 static milestone cards with an SVG area chart showing current MCap progress toward $100M. Features: accent gradient fill, heartbeat animated dot at current position, vertical dashed milestone markers, desktop labels inline above chart, mobile letter markers (A/B/C/D) with legend grid below. Linear scale, responsive width. Bump version: 1.5.0 → 1.6.0 Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/components/airdrop/CampaignHero.tsx | 200 +++++++++++++++++++----- 2 files changed, 164 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 4a727a5..564267c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "1.5.0", + "version": "1.6.0", "private": true, "workspaces": [ "packages/*" diff --git a/src/components/airdrop/CampaignHero.tsx b/src/components/airdrop/CampaignHero.tsx index e221501..2c54a0b 100644 --- a/src/components/airdrop/CampaignHero.tsx +++ b/src/components/airdrop/CampaignHero.tsx @@ -24,13 +24,15 @@ interface StatusData { lockerTx: string | null; } -const MILESTONE_CARDS = [ - { mcap: 1_000_000, label: "$1M", cmcRank: "#1900", pct: 10, key: "bronze" as const }, - { mcap: 10_000_000, label: "$10M", cmcRank: "#950", pct: 30, key: "silver" as const }, - { mcap: 50_000_000, label: "$50M", cmcRank: "#400", pct: 50, key: "gold" as const }, - { mcap: 100_000_000, label: "$100M", cmcRank: "#250", pct: 100, key: "diamond" as const }, +const MILESTONES = [ + { mcap: 1_000_000, label: "$1M", cmcRank: "≈ #1900", pct: 10, letter: "A" }, + { mcap: 10_000_000, label: "$10M", cmcRank: "≈ #950", pct: 30, letter: "B" }, + { mcap: 50_000_000, label: "$50M", cmcRank: "≈ #400", pct: 50, letter: "C" }, + { mcap: 100_000_000, label: "$100M", cmcRank: "≈ #250", pct: 100, letter: "D" }, ]; +const MAX_MCAP = 100_000_000; + /* ─── Helpers ─── */ function useAirdropStatus() { @@ -69,6 +71,160 @@ function useCountdown(endDateStr: string) { return remaining; } +/* ─── MCap Chart ─── */ + +function MCapChart({ currentFdv }: { currentFdv: number }) { + const progress = Math.min(currentFdv / MAX_MCAP, 1); + const svgW = 600; + const svgH = 80; + const pad = { left: 0, right: 0 }; + const chartW = svgW - pad.left - pad.right; + const fillX = pad.left + progress * chartW; + + return ( +
+ {/* Desktop labels above chart */} +
+ {MILESTONES.map((ms) => { + const x = (ms.mcap / MAX_MCAP) * 100; + return ( +
+
{ms.label}
+
unlocks {ms.pct}%
+
{ms.cmcRank}
+
+ ); + })} +
+ + {/* SVG chart */} + 0 ? `$${(currentFdv / 1_000_000).toFixed(2)}M` : "$0"} of $100M`} + > + + + + + + + + {/* Unfilled area */} + + + {/* Filled area */} + {progress > 0 && ( + + )} + + {/* Milestone vertical dashed lines */} + {MILESTONES.map((ms) => { + const mx = pad.left + (ms.mcap / MAX_MCAP) * chartW; + return ( + + ); + })} + + {/* Mobile letter markers */} + {MILESTONES.map((ms) => { + const mx = pad.left + (ms.mcap / MAX_MCAP) * chartW; + return ( + + {ms.letter} + + ); + })} + + {/* Current MCap line */} + {progress > 0 && ( + + )} + + + {/* Heartbeat dot — positioned via CSS over the SVG */} +
+
+ + + + +
+
+ + {/* Scale labels */} +
+ $0 + $50M + $100M +
+ + {/* Mobile legend */} +
+ {MILESTONES.map((ms) => ( +
+ {ms.letter} +
+
{ms.label}
+
unlocks {ms.pct}%
+
{ms.cmcRank}
+
+
+ ))} +
+
+ ); +} + /* ─── Main component ─── */ export function CampaignHero() { @@ -147,38 +303,8 @@ export function CampaignHero() { )} - {/* ── Milestone cards ── */} -
- {MILESTONE_CARDS.map((ms) => { - const reached = data.currentFdv >= ms.mcap; - return ( -
-
- {reached ? ( - - ) : ( - - )} -
-
- {ms.label} -
-
- ≈ CMC {ms.cmcRank} -
-
unlocks
-
- {ms.pct}% -
-
- ); - })} -
+ {/* ── MCap progress chart ── */} + {/* ── Participant count ── */}