diff --git a/src/app/components/LeaderboardList.js b/src/app/components/LeaderboardList.js index d5c1964..f337832 100644 --- a/src/app/components/LeaderboardList.js +++ b/src/app/components/LeaderboardList.js @@ -3,7 +3,20 @@ import { Medal } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; -import { formatDistance, getDistanceColor } from '../../lib/game'; +import { formatDistance, calculateScore } from '../../lib/game'; + +// Keyed by the round score the distance would earn, so the tint tracks the +// scoring ladder instead of re-typing its thresholds. +const DISTANCE_COLORS = { + 5: 'text-green-700 dark:text-green-300', + 4: 'text-blue-700 dark:text-blue-300', + 3: 'text-yellow-700 dark:text-yellow-300', + 2: 'text-orange-700 dark:text-orange-300', +}; + +function getDistanceColor(distance) { + return DISTANCE_COLORS[calculateScore(distance)] ?? 'text-red-700 dark:text-red-300'; +} function getScoreColor(score) { if (score >= 50) return 'text-purple-700 dark:text-purple-300'; diff --git a/src/lib/game.js b/src/lib/game.js index d7f70e4..6759a19 100644 --- a/src/lib/game.js +++ b/src/lib/game.js @@ -17,14 +17,21 @@ export function calculateDistance(lat1, lon1, lat2, lon2) { } } +// Distance ceiling in meters -> points. The single scoring ladder; anything +// that bands a result (colors, labels, the scoring table on the home page) +// should derive from calculateScore rather than re-typing these thresholds. +export const SCORE_BANDS = [ + { maxMeters: 50, points: 5 }, + { maxMeters: 100, points: 4 }, + { maxMeters: 200, points: 3 }, + { maxMeters: 500, points: 2 }, + { maxMeters: 1000, points: 1 }, +]; + // Calculate score based on distance (0-5 points scale) export function calculateScore(distance) { - if (distance <= 50) return 5; - if (distance <= 100) return 4; - if (distance <= 200) return 3; - if (distance <= 500) return 2; - if (distance <= 1000) return 1; - return 0; + const band = SCORE_BANDS.find((entry) => distance <= entry.maxMeters); + return band ? band.points : 0; } // Format distance for display @@ -35,32 +42,3 @@ export function formatDistance(distance) { return `${(distance / 1000).toFixed(2)}km`; } } - - -// Get or set username from localStorage -export function getUsername() { - if (typeof window === 'undefined') return null; - return localStorage.getItem('vngeoguessr_username'); -} - -export function setUsername(username) { - if (typeof window === 'undefined') return; - localStorage.setItem('vngeoguessr_username', username); -} - -// Get result message based on score -export function getResultMessage(score, distance) { - if (score > 4) return "Excellent! Outstanding guess!"; - if (score > 2) return "Good job! Nice work!"; - if (score > 0) return "Not bad! Keep trying!"; - return "Nice try! Better luck next time!"; -} - -// Get distance color for leaderboard display -export function getDistanceColor(distance) { - if (distance <= 50) return 'text-green-700 dark:text-green-300'; - if (distance <= 100) return 'text-blue-700 dark:text-blue-300'; - if (distance <= 200) return 'text-yellow-700 dark:text-yellow-300'; - if (distance <= 500) return 'text-orange-700 dark:text-orange-300'; - return 'text-red-700 dark:text-red-300'; -} diff --git a/src/lib/pano-index.js b/src/lib/pano-index.js index ed28bea..f0144ba 100644 --- a/src/lib/pano-index.js +++ b/src/lib/pano-index.js @@ -6,7 +6,7 @@ // replaces asking the Graph API what is near a random point, which fails // outright in dense districts and takes seconds where it works. // -// SERVER-SIDE ONLY. These files are ~25MB of exact answers; importing them from +// SERVER-SIDE ONLY. These files are ~28MB of exact answers; importing them from // anywhere the browser can reach hands every round's solution to the player. // src/lib/regions.js is the client-safe view of the same tree and deliberately // imports nothing from here -- tests/regions.test.js enforces that boundary. diff --git a/src/lib/username.js b/src/lib/username.js new file mode 100644 index 0000000..69baf05 --- /dev/null +++ b/src/lib/username.js @@ -0,0 +1,15 @@ +// Player name in localStorage, mirroring theme.js: one module owns one +// browser-storage concern, so server code importing lib/game.js for scoring +// never touches window. + +export const USERNAME_STORAGE_KEY = 'vngeoguessr_username'; + +export function getUsername() { + if (typeof window === 'undefined') return null; + return localStorage.getItem(USERNAME_STORAGE_KEY); +} + +export function setUsername(username) { + if (typeof window === 'undefined') return; + localStorage.setItem(USERNAME_STORAGE_KEY, username); +}