Five fixes per user feedback on the live chart from #1060
1. Move milestone labels INSIDE the chart, anchored to the LEFT of each vertical line
Currently the 4-line label blocks (TIER / $value / unlocks Y% / ≈ #Z) sit ABOVE the chart in the pad.top area. The user wants them inside the chart area, anchored to the LEFT of each vertical milestone line — same simple style as the ETH benchmark, but mirrored left-of-line instead of right-of-line.
Implementation: each label block uses text-anchor="end" with x = toX(milestonePos) - 6 (6px gap left of the line), y near pad.top + 14. Text extends leftward from the line, fits inside the band that ENDS at that line.
2. Replace tier names "Bronze / Silver / Gold / Diamond" with "Milestone 1 / 2 / 3 / 4"
The user wrote: "the title may confuse users." Drop TIER_NAMES map. Each label now reads MILESTONE 1 / \$1M / unlocks 10% / CMC ≈ #1900. Also drop the bottom row of tier-name X-axis labels — they were redundant with the inside labels in the new layout.
Mobile legend: change tier names to M1 / M2 / M3 / M4 to keep it compact.
3. Add "CMC" prefix to rank lines + footnote
- Rank line:
≈ #1900 → CMC ≈ #1900
- Footnote at chart bottom currently reads
MCap = PLOT price × 1M max supply. Append: · CMC = CoinMarketCap
4. Smooth out the curve kink between milestone 3 and 4
The current curve has a visible slope discontinuity at each milestone knot because the banded log-interp mcapToX is C¹-discontinuous: each band has a different log range (band 0 = 2 decades, band 1 = 1 decade, band 2 = 0.7 decades, band 3 = 0.3 decades), so the slope dx/d(log m) jumps at the knots.
Fix: replace the banded log-interp with a monotone cubic Hermite interpolation in (log10(mcap), X) space using Catmull-Rom slopes at each knot:
slopes[i] = (xs[i+1] - xs[i-1]) / (lms[i+1] - lms[i-1]) // interior
= (xs[1] - xs[0]) / (lms[1] - lms[0]) // first knot
= (xs[i] - xs[i-1]) / (lms[i] - lms[i-1]) // last knot
within segment [t0=lms[i-1], t1=lms[i]]:
u = (t - t0) / (t1 - t0)
X = h00·x0 + h10·dt·m0 + h01·x1 + h11·dt·m1 // standard cubic Hermite basis
This is C¹-continuous everywhere, passes exactly through all 5 milestone knots (so milestones still land at X=0/0.25/0.5/0.75/1.0), and makes the curve appear smooth at the knots without overshoot (the slopes are monotonic increasing for our data, so the spline stays monotonic).
The heartbeat dot uses the same mcapToX, so it still lies exactly on the curve.
5. Match PlotLink's font system
PlotLink uses Geist Mono for .font-mono (declared in src/app/globals.css:56-57). Currently SVG text uses inline fontFamily="monospace" which falls back to system mono. Replace with <g className="font-mono"> wrappers — SVG text inherits the parent's font-family, so the entire chart picks up Geist Mono.
Also drop hardcoded letterSpacing="2" in favor of more measured tracking-wider equivalent, and use Tailwind text size utilities consistently.
Files
src/components/airdrop/CampaignHero.tsx
Acceptance criteria
Reviewer note
Block this PR if it:
- (a) leaves milestone labels above the chart
- (b) keeps "Bronze/Silver/Gold/Diamond" anywhere in the chart UI
- (c) keeps the visible slope discontinuity at any milestone knot
- (d) uses inline
fontFamily="monospace" instead of the project font system
Five fixes per user feedback on the live chart from #1060
1. Move milestone labels INSIDE the chart, anchored to the LEFT of each vertical line
Currently the 4-line label blocks (TIER / $value / unlocks Y% / ≈ #Z) sit ABOVE the chart in the
pad.toparea. The user wants them inside the chart area, anchored to the LEFT of each vertical milestone line — same simple style as the ETH benchmark, but mirrored left-of-line instead of right-of-line.Implementation: each label block uses
text-anchor="end"withx = toX(milestonePos) - 6(6px gap left of the line),ynearpad.top + 14. Text extends leftward from the line, fits inside the band that ENDS at that line.2. Replace tier names "Bronze / Silver / Gold / Diamond" with "Milestone 1 / 2 / 3 / 4"
The user wrote: "the title may confuse users." Drop
TIER_NAMESmap. Each label now readsMILESTONE 1/\$1M/unlocks 10%/CMC ≈ #1900. Also drop the bottom row of tier-name X-axis labels — they were redundant with the inside labels in the new layout.Mobile legend: change tier names to
M1 / M2 / M3 / M4to keep it compact.3. Add "CMC" prefix to rank lines + footnote
≈ #1900→CMC ≈ #1900MCap = PLOT price × 1M max supply. Append:· CMC = CoinMarketCap4. Smooth out the curve kink between milestone 3 and 4
The current curve has a visible slope discontinuity at each milestone knot because the banded log-interp
mcapToXis C¹-discontinuous: each band has a different log range (band 0 = 2 decades, band 1 = 1 decade, band 2 = 0.7 decades, band 3 = 0.3 decades), so the slopedx/d(log m)jumps at the knots.Fix: replace the banded log-interp with a monotone cubic Hermite interpolation in
(log10(mcap), X)space using Catmull-Rom slopes at each knot:This is C¹-continuous everywhere, passes exactly through all 5 milestone knots (so milestones still land at X=0/0.25/0.5/0.75/1.0), and makes the curve appear smooth at the knots without overshoot (the slopes are monotonic increasing for our data, so the spline stays monotonic).
The heartbeat dot uses the same
mcapToX, so it still lies exactly on the curve.5. Match PlotLink's font system
PlotLink uses Geist Mono for
.font-mono(declared insrc/app/globals.css:56-57). Currently SVG text uses inlinefontFamily="monospace"which falls back to system mono. Replace with<g className="font-mono">wrappers — SVG text inherits the parent's font-family, so the entire chart picks up Geist Mono.Also drop hardcoded
letterSpacing="2"in favor of more measuredtracking-widerequivalent, and use Tailwind text size utilities consistently.Files
src/components/airdrop/CampaignHero.tsxAcceptance criteria
MILESTONE N(small caps, bold) /\$value(bold) /unlocks Y%/CMC ≈ #Z.MCap = PLOT price × 1M max supply · CMC = CoinMarketCap.font-monoclass wrapper).M1 / M2 / M3 / M4short labels.Reviewer note
Block this PR if it:
fontFamily="monospace"instead of the project font system