diff --git a/src/components/airdrop/CampaignHero.tsx b/src/components/airdrop/CampaignHero.tsx index 7354c69..166782a 100644 --- a/src/components/airdrop/CampaignHero.tsx +++ b/src/components/airdrop/CampaignHero.tsx @@ -29,33 +29,20 @@ interface StatusData { const MAX_SUPPLY = 1_000_000; -const TIER_META = [ - { key: "bronze" as const, emoji: "🥉", label: "Bronze", cmcRank: 1900 }, - { key: "silver" as const, emoji: "🥈", label: "Silver", cmcRank: 950 }, - { key: "gold" as const, emoji: "🥇", label: "Gold", cmcRank: 400 }, - { key: "diamond" as const, emoji: "💎", label: "Diamond", cmcRank: 250 }, -]; - -type Tier = { - key: string; - emoji: string; - label: string; - cmcRank: number; +const TIER_KEYS = ["bronze", "silver", "gold", "diamond"] as const; + +interface MilestoneRow { fdv: number; pct: number; - reached: boolean; -}; - -const IS_PROD_MODE = process.env.NEXT_PUBLIC_AIRDROP_MODE !== "test"; - -/** Build tier array from API milestones so test/prod config is respected */ -function buildTiers(milestones: StatusData["milestones"]): Tier[] { - return TIER_META.map((m) => { - const ms = milestones[m.key]; - return { ...m, fdv: ms.mcap, pct: ms.pct, reached: ms.reached }; - }); + unlockPlot: number; + poolUsd: number; + burnPct: number; + cmcRank: string | null; + isFull: boolean; } +const CMC_RANKS = ["≈ CMC #1900", "≈ CMC #950", "≈ CMC #400", "≈ CMC #250"]; + /* ─── Helpers ─── */ function useAirdropStatus() { @@ -71,18 +58,12 @@ function useAirdropStatus() { }); } -/** Pool USD at a given milestone: poolAmount * (pct/100) * (fdv / maxSupply). */ -function poolUsdAtTier(tier: Tier, poolAmount: number): number { - return poolAmount * (tier.pct / 100) * (tier.fdv / MAX_SUPPLY); -} - -/** PLOT unlocked at a given milestone. */ -function plotAtTier(tier: Tier, poolAmount: number): number { - return poolAmount * (tier.pct / 100); +function formatCompact(val: number): string { + if (val >= 1_000_000) return `$${(val / 1_000_000).toFixed(1)}M`; + if (val >= 1_000) return `$${(val / 1_000).toFixed(0)}K`; + return `$${val.toFixed(0)}`; } -/* ─── Countdown hook ─── */ - function useCountdown(endDateStr: string) { const [remaining, setRemaining] = useState({ d: 0, h: 0, m: 0, s: 0 }); @@ -106,147 +87,107 @@ function useCountdown(endDateStr: string) { return remaining; } -/* ─── Segmented progress bar ─── */ +function buildMilestoneRows( + milestones: StatusData["milestones"], + poolAmount: number, +): MilestoneRow[] { + return TIER_KEYS.map((key, i) => { + const ms = milestones[key]; + const price = ms.mcap / MAX_SUPPLY; + const unlockPlot = poolAmount * (ms.pct / 100); + return { + fdv: ms.mcap, + pct: ms.pct, + unlockPlot, + poolUsd: unlockPlot * price, + burnPct: 100 - ms.pct, + cmcRank: CMC_RANKS[i] ?? null, + isFull: ms.pct === 100, + }; + }); +} + +function getCurrentBurnState( + currentFdv: number, + milestones: StatusData["milestones"], + poolAmount: number, +): { burnPct: number; distributePct: number; poolUsd: number } { + const entries = TIER_KEYS.map((k) => milestones[k]); + let highestPct = 0; + for (let i = entries.length - 1; i >= 0; i--) { + if (currentFdv >= entries[i].mcap) { + highestPct = entries[i].pct; + break; + } + } + const price = currentFdv / MAX_SUPPLY; + const unlockPlot = poolAmount * (highestPct / 100); + return { + burnPct: 100 - highestPct, + distributePct: highestPct, + poolUsd: unlockPlot * price, + }; +} + +/* ─── Burn Bar ─── */ -/** - * 4-segment progress bar. Each segment represents one milestone tier and - * fills based on log-scale progress between adjacent milestones, so reaching - * Bronze visibly fills the first segment instead of looking like 1% of the bar. - */ -function SegmentedProgressBar({ - tiers, +function BurnBar({ + burnPct, + distributePct, currentFdv, + poolUsd, }: { - tiers: Tier[]; + burnPct: number; + distributePct: number; currentFdv: number; + poolUsd: number; }) { - const segments = useMemo(() => { - return tiers.map((t, i) => { - const lowerFdv = i === 0 ? t.fdv / 10 : tiers[i - 1].fdv; - let fillPct = 0; - if (currentFdv >= t.fdv) { - fillPct = 100; - } else if (currentFdv > lowerFdv) { - const logCur = Math.log10(currentFdv); - const logLow = Math.log10(lowerFdv); - const logHi = Math.log10(t.fdv); - fillPct = ((logCur - logLow) / (logHi - logLow)) * 100; - } - return { ...t, fillPct }; - }); - }, [tiers, currentFdv]); - - const indicatorIdx = segments.findIndex((s) => s.fillPct < 100 && s.fillPct > 0); - const indicatorSegment = - indicatorIdx === -1 - ? segments.findIndex((s) => s.fillPct === 0) - : indicatorIdx; + const isFull = distributePct >= 100; + const isAllBurned = burnPct >= 100; return ( -
- {data.poolAmount.toLocaleString()} PLOT locked in a time-locked contract. - Reach milestone FDV targets and the pool is distributed to point holders. - Miss them and the unreached portion is burned forever. +
+ Grow the market. Or watch it burn.
- - {/* Lock-up proof */} - {data.lockerTx ? ( - - 🔒 View lock-up proof on Basescan - - ) : ( - - 🔒 Lock-up proof: pending - - )}