tiennm99 478e62a085 feat(ui): add a theme switcher and rebuild the button system
Two threads that could not be separated: they share globals.css, page.js
and GameClient.js, and their edits interleave inside those files.

THEME SWITCHING

Light, dark and system, chosen from a control in both headers and
remembered in localStorage. An inline script in the document head
resolves the choice before first paint, so a dark-theme visitor never
sees a white flash.

The dark palette moves out of a prefers-color-scheme media query and
onto a class, because a media query cannot be overridden by an explicit
choice. That also repairs every dark: utility in the app: the variant
keys off a .dark class that nothing had ever set, so rules like
dark:text-green-400 had never once applied.

The palette is declared as :root.dark rather than .dark. A bare class
ties with :root on specificity and wins only by sitting later in the
file, which is fragile: a brand :root block further down had been
silently reverting --surface and the brand tokens to their light values,
leaving the page background white while the cards went dark.

BUTTONS AND ELEVATION

The button primitive was still the shadcn scaffold: a grayscale default
variant and a size scale starting at 36px, below the touch target this
app needs. So every real call site hand-patched the same brand fill and
min-height, ten of them, and drifted apart on padding, weight and
shadow. That drift is what read as unpolished.

The default variant is now the brand fill, sizes start at 44px, and
there is a pressed state, a hover lift, and a loading prop. The
overrides are gone. A shared elevation scale sits behind the Tailwind
shadow utilities, so cards and dialogs agree with buttons about how
raised things are; the dark values carry more opacity and a faint inset
highlight, since shadows read as nothing on a dark ground.

The focus ring was measured at 2.59:1 against the page, under the 3:1
floor for a non-text indicator, and no alpha value clears it at any
lightness. It is now opaque at oklch(0.55), measuring 4.54:1.

HIERARCHY

Submit outranks Skip by size and fill instead of colour alone. Next
Round outweighs Menu. The donate button gives up the only brand fill on
the landing page so choosing a city can have it. City rows become cards
with real elevation and an explicit Play affordance, having previously
been near-invisible against the page. The guess map is framed in a
padded card rather than restyled, so a bright map over dark chrome reads
as deliberate. The score counts up on reveal.
2026-08-30 10:27:54 +07:00
2026-08-17 13:28:24 +07:00
2025-08-07 13:08:06 +07:00
2025-08-07 12:27:13 +07:00
2025-08-07 12:27:13 +07:00
2025-08-04 23:11:11 +07:00
2025-08-07 12:27:13 +07:00
2025-08-07 12:27:13 +07:00
2026-08-17 13:28:24 +07:00

VNGeoGuessr

A GeoGuessr clone focused on Vietnamese locations with accurate boundary detection and anti-cheat security. Play geolocation guessing games across 5 Vietnamese locations using real street-level imagery.

🎮 Features

  • 5 Vietnamese Locations: Ha Noi, Da Nang, Ho Chi Minh, Da Lat, Duc Hoa (Long An)
  • 360° Street View: Mapillary panoramic images with PhotoSphere viewer
  • Anti-Cheat Security: Server-side session management prevents cheating
  • Distance-Based Scoring: 0-5 point system based on accuracy
  • Persistent Leaderboards: Redis-powered top 200 player rankings
  • Accurate Boundaries: Nominatim API + Turf.js for precise city detection

📚 Documentation

For detailed information about this project, see the documentation in /docs/:

🚀 Getting Started

Prerequisites

  • Node.js 18+
  • npm 11+
  • Redis server (for leaderboards)

Development

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Start production server
npm start

# Run linting
npm run lint

Open http://localhost:3000 to play the game.

🏗️ Built With

  • Frontend: Next.js 15 + React 19 + Tailwind CSS 4
  • Street View: Mapillary API + PhotoSphere Viewer
  • Maps: Leaflet + OpenStreetMap
  • Geographic Data: Nominatim API + Turf.js
  • Storage: Redis (leaderboards) + In-memory sessions
  • UI Components: shadcn/ui

🎯 How to Play

  1. Enter your username
  2. Select a Vietnamese location
  3. View the 360° street panorama
  4. Place your guess on the map
  5. Earn points based on accuracy (closer = more points)
  6. Compete for the top 200 leaderboard spots!

📋 Development Notes

  • JavaScript Only: No TypeScript files
  • Individual Parameters: Functions use separate parameters, not object destructuring
  • Server-Side Security: Game coordinates never sent to client
  • Session Management: Automatic cleanup after 30 minutes

🤝 Contributing

This project follows specific development guidelines. Please read the Development Guidelines before contributing.

S
Description
GeoGuessr clone for Vietnamese locations — Mapillary panoramas, Turf.js boundaries, Upstash REST leaderboards
Readme Apache-2.0
14 MiB
0 Stars 1 Watchers 0 Forks
Languages
JavaScript 98.2%
CSS 1.8%