{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "leaderboard-podium",
  "title": "Leaderboard Podium",
  "description": "Top-three React leaderboard podium with crowns, avatars, and ranks for competitive gamification. Leaderboard UI component for highlights and recap screens. Built on shadcn/ui + Tailwind. Open source.",
  "dependencies": [
    "class-variance-authority",
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/trophy/ui/leaderboard-podium.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { Crown } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Types (inlined)\ninterface LeaderboardRanking {\n  userId: string\n  userName: string | null\n  rank: number\n  value: number\n  avatarUrl?: string | null\n}\n\n// Variants\nconst podiumVariants = cva(\"flex items-end justify-center gap-4\", {\n  variants: {\n    size: {\n      sm: \"gap-2\",\n      default: \"gap-4\",\n      lg: \"gap-6\",\n    },\n  },\n  defaultVariants: {\n    size: \"default\",\n  },\n})\n\n// Podium styles for each position\nconst PODIUM_CONFIG = {\n  1: {\n    icon: Crown,\n    color: \"text-rank-1\",\n    bg: \"bg-rank-1/60\",\n    ringColor: \"ring-rank-1/50\",\n    height: \"h-32\",\n    heightSm: \"h-24\",\n    heightLg: \"h-40\",\n  },\n  2: {\n    icon: Crown,\n    color: \"text-rank-2\",\n    bg: \"bg-rank-2/30\",\n    ringColor: \"ring-rank-2/50\",\n    height: \"h-24\",\n    heightSm: \"h-20\",\n    heightLg: \"h-32\",\n  },\n  3: {\n    icon: Crown,\n    color: \"text-rank-3\",\n    bg: \"bg-rank-3/50\",\n    ringColor: \"ring-rank-3/50\",\n    height: \"h-20\",\n    heightSm: \"h-16\",\n    heightLg: \"h-28\",\n  },\n} as const\n\n// Props\ninterface LeaderboardPodiumProps\n  extends\n    React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof podiumVariants> {\n  /** Top 3 rankings (expects at least 1, ideally 3) */\n  rankings: LeaderboardRanking[]\n  /** Show value below name */\n  showValue?: boolean\n  /** Show avatar */\n  showAvatar?: boolean\n  /** Crown badge style variant */\n  medalStyle?: \"classic\" | \"modern\" | \"minimal\"\n}\n\nconst LeaderboardPodium = React.forwardRef<\n  HTMLDivElement,\n  LeaderboardPodiumProps\n>(\n  (\n    {\n      className,\n      size,\n      rankings,\n      showValue = true,\n      showAvatar = true,\n      medalStyle = \"classic\",\n      ...props\n    },\n    ref\n  ) => {\n    // Get top 3, reorder for podium display: 2nd, 1st, 3rd\n    const top3 = rankings.slice(0, 3)\n    const podiumOrder = [\n      top3.find((r) => r.rank === 2),\n      top3.find((r) => r.rank === 1),\n      top3.find((r) => r.rank === 3),\n    ].filter(Boolean) as LeaderboardRanking[]\n\n    if (podiumOrder.length === 0) {\n      return null\n    }\n\n    const avatarSize = {\n      sm: \"h-10 w-10 text-sm\",\n      default: \"h-14 w-14 text-lg\",\n      lg: \"h-20 w-20 text-2xl\",\n    }[size ?? \"default\"]\n\n    const iconSize = {\n      sm: \"h-4 w-4\",\n      default: \"h-5 w-5\",\n      lg: \"h-6 w-6\",\n    }[size ?? \"default\"]\n\n    const textSize = {\n      sm: \"text-xs\",\n      default: \"text-sm\",\n      lg: \"text-base\",\n    }[size ?? \"default\"]\n\n    return (\n      <div\n        ref={ref}\n        className={cn(podiumVariants({ size }), className)}\n        role=\"list\"\n        aria-label=\"Top 3 rankings\"\n        {...props}\n      >\n        {podiumOrder.map((ranking) => {\n          const config = PODIUM_CONFIG[ranking.rank as 1 | 2 | 3]\n          if (!config) return null\n\n          const displayName =\n            ranking.userName || `User ${ranking.userId.slice(0, 6)}`\n          const avatarSrc =\n            ranking.avatarUrl ??\n            `https://i.pravatar.cc/96?u=${encodeURIComponent(ranking.userId)}`\n          const podiumHeight = {\n            sm: config.heightSm,\n            default: config.height,\n            lg: config.heightLg,\n          }[size ?? \"default\"]\n\n          const itemLabel = `Rank ${ranking.rank}: ${displayName}${showValue ? `, ${ranking.value.toLocaleString()}` : \"\"}`\n\n          return (\n            <div\n              key={ranking.userId}\n              role=\"listitem\"\n              aria-label={itemLabel}\n              className=\"flex flex-col items-center\"\n            >\n              {/* Avatar with crown */}\n              <div className=\"relative mb-2\" aria-hidden=\"true\">\n                {showAvatar ? (\n                  <img\n                    src={avatarSrc}\n                    alt={`${displayName} avatar`}\n                    className={cn(\"rounded-full object-cover\", avatarSize)}\n                  />\n                ) : (\n                  <div\n                    className={cn(\n                      \"flex items-center justify-center rounded-full\",\n                      avatarSize,\n                      config.bg\n                    )}\n                  >\n                    <config.icon className={cn(iconSize, config.color)} />\n                  </div>\n                )}\n\n                {/* Crown badge */}\n                {medalStyle !== \"minimal\" && (\n                  <div\n                    className={cn(\n                      \"bg-background absolute -right-1 -bottom-1 flex items-center justify-center rounded-full shadow-sm\",\n                      size === \"sm\"\n                        ? \"h-5 w-5\"\n                        : size === \"lg\"\n                          ? \"h-8 w-8\"\n                          : \"h-6 w-6\"\n                    )}\n                  >\n                    <config.icon\n                      className={cn(\n                        config.color,\n                        size === \"sm\"\n                          ? \"h-3 w-3\"\n                          : size === \"lg\"\n                            ? \"h-5 w-5\"\n                            : \"h-4 w-4\"\n                      )}\n                    />\n                  </div>\n                )}\n              </div>\n\n              {/* Name */}\n              <span\n                className={cn(\n                  \"max-w-20 truncate text-center font-medium\",\n                  textSize\n                )}\n                title={displayName}\n              >\n                {displayName}\n              </span>\n\n              {/* Value */}\n              {showValue && (\n                <span\n                  className={cn(\n                    \"text-muted-foreground tabular-nums\",\n                    size === \"sm\" ? \"text-xs\" : \"text-sm\"\n                  )}\n                >\n                  {ranking.value.toLocaleString()}\n                </span>\n              )}\n\n              {/* Podium block */}\n              <div\n                aria-hidden=\"true\"\n                className={cn(\n                  \"mt-2 w-22 rounded-t-lg\",\n                  size === \"sm\" && \"w-20\",\n                  size === \"lg\" && \"w-24\",\n                  podiumHeight,\n                  config.bg,\n                  medalStyle === \"modern\" && \"rounded-t-xl\"\n                )}\n              >\n                <div\n                  className={cn(\n                    \"flex h-8 items-center justify-center font-bold\",\n                    config.color\n                  )}\n                >\n                  {ranking.rank}\n                </div>\n              </div>\n            </div>\n          )\n        })}\n      </div>\n    )\n  }\n)\nLeaderboardPodium.displayName = \"LeaderboardPodium\"\n\nexport { LeaderboardPodium, podiumVariants }\nexport type { LeaderboardPodiumProps, LeaderboardRanking }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}