diff --git a/src/pages/LeaderboardPage.jsx b/src/pages/LeaderboardPage.jsx
index 3bf8dd9..3352f38 100644
--- a/src/pages/LeaderboardPage.jsx
+++ b/src/pages/LeaderboardPage.jsx
@@ -1,4 +1,5 @@
import React, { useState, useMemo } from 'react';
+import { motion } from 'framer-motion';
import { useMockStore } from '../data/mockStore';
import {
Trophy, TrendingUp, DollarSign, Target, Crown, ShieldOff,
@@ -16,10 +17,12 @@ const rankBadge = (i) =>
'text-zinc-500';
// ── Podium (shared) ──────────────────────────────────────────────────────────
-const TopPodium = ({ data, metricKey, formatVal, accentColor = 'border-yellow-400' }) => {
+const TopPodium = ({ data, metricKey, numberFormat = {}, accentColor = 'border-yellow-400' }) => {
if (data.length < 3) return null;
const [first, second, third] = data;
- const Step = ({ agent, rank, height, color, glow }) => (
+ // Bars grow from a shared baseline at a constant speed, so the shortest (bronze)
+ // finishes first, then silver, and the tallest (gold) lands last.
+ const Step = ({ agent, rank, heightPx, color, glow }) => (
@@ -33,20 +36,27 @@ const TopPodium = ({ data, metricKey, formatVal, accentColor = 'border-yellow-40
{agent.name}
- {formatVal(agent[metricKey])}
+
-
+
);
return (
-
-
-
-
+ // Fixed min-height reserves the full podium space so the shared baseline
+ // never shifts while the bars animate — they grow upward from the floor.
+
+
+
+
);
};
@@ -137,13 +147,6 @@ const LeaderboardPage = () => {
}).sort((a, b) => b[metric] - a[metric]);
}, [agents, salesHistory, timeframe, metric]);
- const formatSales = (val, m) => {
- if (m === 'revenue') return `$${val.toLocaleString('en-US')}`;
- if (m === 'volume') return `${val} Deals`;
- if (m === 'winRate') return `${val.toFixed(1)}%`;
- return val;
- };
-
// ── Canvasser leaderboard data ───────────────────────────────────────────
// Single source of truth: canvasserHistory records, filtered by date window.
// verified = records with status 'verified' OR 'converted' (converted is also verified)
@@ -170,11 +173,6 @@ const LeaderboardPage = () => {
}).sort((a, b) => b[cvMetric] - a[cvMetric]);
}, [agents, canvasserHistory, cvTimeframe, cvMetric]);
- const formatCv = (val, m) => {
- if (m === 'convRate') return `${val.toFixed(1)}%`;
- return `${val}`;
- };
-
// ── Permission guard ─────────────────────────────────────────────────────
if (!can('leaderboard', 'view')) {
return (
@@ -259,7 +257,11 @@ const LeaderboardPage = () => {
formatSales(v, metric)}
+ numberFormat={{
+ revenue: { prefix: '$', useLocaleString: true },
+ volume: { suffix: ' Deals' },
+ winRate: { suffix: '%', decimals: 1 },
+ }[metric]}
accentColor="border-yellow-400"
/>
@@ -350,7 +352,11 @@ const LeaderboardPage = () => {
formatCv(v, cvMetric)}
+ numberFormat={{
+ verified: {},
+ total: {},
+ convRate: { suffix: '%', decimals: 1 },
+ }[cvMetric]}
accentColor="border-emerald-400"
/>