diff --git a/package-lock.json b/package-lock.json index 0ef38ed..f131c00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "plotlink", - "version": "1.2.1", + "version": "1.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "plotlink", - "version": "1.2.1", + "version": "1.2.2", "workspaces": [ "packages/*" ], diff --git a/package.json b/package.json index 3ea014c..0a8bbd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "1.2.1", + "version": "1.2.2", "private": true, "workspaces": [ "packages/*" diff --git a/src/components/airdrop/MilestoneTrack.tsx b/src/components/airdrop/MilestoneTrack.tsx index 95be367..05e5d1c 100644 --- a/src/components/airdrop/MilestoneTrack.tsx +++ b/src/components/airdrop/MilestoneTrack.tsx @@ -19,12 +19,14 @@ interface StatusData { /* ─── Chart tier definitions (presentation layer) ─── */ const MAX_SUPPLY = 1_000_000; -const CHART_TIERS = [ - { label: "Bronze", emoji: "\uD83E\uDD49", fdv: 1_000_000, poolPct: 10 }, - { label: "Silver", emoji: "\uD83E\uDD48", fdv: 10_000_000, poolPct: 30 }, - { label: "Gold", emoji: "\uD83E\uDD47", fdv: 50_000_000, poolPct: 50 }, - { label: "Diamond", emoji: "\uD83D\uDC8E", fdv: 100_000_000, poolPct: 100 }, -] as const; + +/** Visual presentation per tier; FDV and poolPct come from API/config. */ +const TIER_PRESENTATION = [ + { key: "bronze" as const, label: "Bronze", emoji: "\uD83E\uDD49" }, + { key: "silver" as const, label: "Silver", emoji: "\uD83E\uDD48" }, + { key: "gold" as const, label: "Gold", emoji: "\uD83E\uDD47" }, + { key: "diamond" as const, label: "Diamond", emoji: "\uD83D\uDC8E" }, +]; /** Pool USD value at a given FDV milestone: poolAmount * (pct/100) * (fdv / maxSupply) */ function poolUsdAt(fdv: number, poolPct: number, poolAmount: number): number { @@ -47,14 +49,18 @@ const PAD = { top: 40, right: 20, bottom: 50, left: 65 }; const CHART_W = SVG_W - PAD.left - PAD.right; const CHART_H = SVG_H - PAD.top - PAD.bottom; -// Log scale helpers — FDV axis from 10k to 200M -const LOG_MIN = Math.log10(10_000); -const LOG_MAX = Math.log10(200_000_000); +// Log scale helpers — FDV axis bounds derived from the active milestones so +// test mode (e.g. $7K bronze) and prod mode (e.g. $1M bronze) both render. +function computeFdvBounds(milestones: ChartMilestone[]): { logMin: number; logMax: number; floor: number } { + const minFdv = Math.max(milestones[0].fdv / 100, 1); + const maxFdv = milestones[milestones.length - 1].fdv * 2; + return { logMin: Math.log10(minFdv), logMax: Math.log10(maxFdv), floor: minFdv }; +} -function fdvToX(fdv: number): number { +function fdvToX(fdv: number, logMin: number, logMax: number, floor: number): number { if (fdv <= 0) return PAD.left; - const logVal = Math.log10(Math.max(fdv, 10_000)); - const t = (logVal - LOG_MIN) / (LOG_MAX - LOG_MIN); + const logVal = Math.log10(Math.max(fdv, floor)); + const t = (logVal - logMin) / (logMax - logMin); return PAD.left + t * CHART_W; } @@ -65,12 +71,12 @@ function usdToY(usd: number, yMax: number): number { /* ─── Path builders ─── */ -function buildAreaPath(milestones: ChartMilestone[], yMax: number): string { +function buildAreaPath(milestones: ChartMilestone[], yMax: number, logMin: number, logMax: number, floor: number): string { const baseline = usdToY(0, yMax); - let path = `M ${fdvToX(10_000)} ${baseline}`; + let path = `M ${fdvToX(floor, logMin, logMax, floor)} ${baseline}`; let prevY = baseline; for (const m of milestones) { - const x = fdvToX(m.fdv); + const x = fdvToX(m.fdv, logMin, logMax, floor); const y = usdToY(m.poolUsd, yMax); path += ` L ${x} ${prevY} L ${x} ${y}`; prevY = y; @@ -80,15 +86,15 @@ function buildAreaPath(milestones: ChartMilestone[], yMax: number): string { return path; } -function buildLinePath(milestones: ChartMilestone[], yMax: number): string { +function buildLinePath(milestones: ChartMilestone[], yMax: number, logMin: number, logMax: number, floor: number): string { let path = ""; let prevY = usdToY(0, yMax); for (let i = 0; i < milestones.length; i++) { const m = milestones[i]; - const x = fdvToX(m.fdv); + const x = fdvToX(m.fdv, logMin, logMax, floor); const y = usdToY(m.poolUsd, yMax); if (i === 0) { - path = `M ${fdvToX(10_000)} ${prevY} L ${x} ${prevY} L ${x} ${y}`; + path = `M ${fdvToX(floor, logMin, logMax, floor)} ${prevY} L ${x} ${prevY} L ${x} ${y}`; } else { path += ` L ${x} ${prevY} L ${x} ${y}`; } @@ -113,11 +119,17 @@ export function MilestoneTrack() { const milestones: ChartMilestone[] = useMemo( () => - CHART_TIERS.map((t) => ({ - ...t, - poolUsd: poolUsdAt(t.fdv, t.poolPct, data?.poolAmount ?? 50_000), - })), - [data?.poolAmount], + TIER_PRESENTATION.map((t) => { + const ms = data?.milestones?.[t.key]; + return { + label: t.label, + emoji: t.emoji, + fdv: ms?.mcap ?? 0, + poolPct: ms?.pct ?? 0, + poolUsd: poolUsdAt(ms?.mcap ?? 0, ms?.pct ?? 0, data?.poolAmount ?? 0), + }; + }), + [data?.milestones, data?.poolAmount], ); if (isLoading || !data) { @@ -128,6 +140,8 @@ export function MilestoneTrack() { ); } + const { logMin, logMax, floor } = computeFdvBounds(milestones); + // Y-axis max with 10% headroom above Diamond const yMax = milestones[milestones.length - 1].poolUsd * 1.1; @@ -152,11 +166,11 @@ export function MilestoneTrack() { const currentPoolUsd = currentZone > 0 ? milestones[currentZone - 1].poolUsd : 0; - const dotX = fdvToX(Math.max(currentFdv, 10_000)); + const dotX = fdvToX(Math.max(currentFdv, floor), logMin, logMax, floor); const dotY = usdToY(currentPoolUsd, yMax); - const areaPath = buildAreaPath(milestones, yMax); - const linePath = buildLinePath(milestones, yMax); + const areaPath = buildAreaPath(milestones, yMax, logMin, logMax, floor); + const linePath = buildLinePath(milestones, yMax, logMin, logMax, floor); return (