Skip to content

lucasflorian/geonerd

Repository files navigation

Geo Nerd

Geo Nerd is a web-based geography quiz application where players can test their knowledge about world countries, flags, capitals, and map locations. Bilingual (FR/EN), no framework. Live at https://geo-nerd.com.

Tech Stack

  • Core: Vanilla JavaScript (ES modules), HTML5. No framework.
  • Styling: SCSS, compiled to a single app.css via Gulp + Dart Sass + autoprefixer.
  • Bundling: esbuild (entry app/js/app.jsdist/app.js, target: es2020, bundled; minified in production builds, unminified in dev, external sourcemap in both).
  • Build/dev: Gulp 5 + BrowserSync, gulp-file-include for HTML partials.
  • Testing: Vitest unit tests in tests/ (npm test).
  • Animation: GSAP + Draggable (vendored in dist/libs/, loaded via <script>).
  • Audio: Web Audio API synthesized tones (no audio files).
  • Deployment: custom SFTP script (deploy.js) using ssh2-sftp-client.
  • Server: clean/extensionless URLs (/page-name/page-name.html).

Games

Flag Nerd and Capital Nerd come in Classic, Hard and Speed Run; Map Nerd in Classic and Hard; Country Nerd (name a country per letter A–Z) in Classic and Hard; plus a deterministic Daily Challenge (Classic/Hard, 20 questions, same for everyone each day). See app/js/games/.

Project Structure

  • /app: source files (edit here).
    • /app/js
      • app.js: entry point; loads data, sets window.geoNerdApp, instantiates per-page modules.
      • /games: one file per game mode (BaseGame.js, BaseQuizGame.js, BaseSpeedRun.js + implementations).
      • /utils: LanguageManager.js (i18n), StringUtils, ArrayUtils, ScoreUtils, MapInteraction, SoundManager, FeedbackEffects.
      • /services: GeoNerdNavigation.js (routing), StorageService.js, MobileMenu.js.
      • /pages: page controllers (HomeCards, Scores, Training).
    • /app/pages: HTML templates + partials/ (uses gulp-file-include). Contains world-countries.svg.
    • /app/scss: styling — top-level partials (_variables, _fonts, _layout, _titles) + pages/ + partials/; entry app.scss.
    • /app/data: country JSON. /app/img, /app/fonts, /app/libs, /app/favicon: static assets.
  • /dist: fully generated build output — do not edit. app.js/app.css (esbuild/sass), HTML (fileInclude), and all assets copied from app/ (the assets task).

Note: UI translations live in app/data/i18n/{fr,en}.json (imported and bundled by esbuild, not served). The countries-*.json files in app/data/ hold country data only.

Key Concepts

Navigation & Routing

SPA-like behavior. GeoNerdNavigation.js handles clean URLs; the server maps /page-name to /page-name.html in dist.

Localization (i18n)

LanguageManager.js is the i18n core. UI strings live in app/data/i18n/{fr,en}.json; they are import-ed and inlined into the bundle by esbuild at build time, so t(key) resolves synchronously (no runtime fetch). Adding a language = add a JSON file with the same key tree + import it.

  • Language is URL-derived: paths under /en/... are English, everything else French. The FR and EN builds are produced separately — dist/*.html and dist/en/*.html — via gulp-file-include with a lang context variable.
  • HTML hooks: data-i18n (textContent), data-i18n-html (innerHTML), data-i18n-placeholder (placeholder attr); translatePage() runs on load. LanguageManager also applies French typography (non-breaking spaces before !?:;).
  • SEO meta: per-page <title>/description/OG/Twitter/canonical are baked into the static HTML at build by the gulp injectMeta task (it resolves the __META_TITLE__ / __META_DESCRIPTION__ tokens in _header.html from pages.<slugCamelCase> in the i18n JSON, so crawlers and social unfurlers that don't run JS get them), then re-confirmed client-side by updateMetaTags() (which also handles language switches). Source of truth stays the i18n JSON.
  • Country data is language-specific: data/countries-{lang}.json (full set) and data/countries-easy-{lang}.json (reduced set used by Map games).
  • localStorage keys are language-prefixed ({lang}.{key}) by default, so in-flight game state is per language; shared keys (those ending in .best, .gamesPlayed, .cumulativeScore, plus geonerd.muted) are stored unprefixed so best scores and cumulative stats persist across FR↔EN.

Map System (Map Nerd)

  • Inline SVG (app/pages/world-countries.svg); playable countries carry the .onu class, others are dimmed.
  • MapNerdClassic.js (point-and-click) vs MapNerdHard.js (point-then-type).
  • Zoom/pan via GSAP (MapInteraction.js).

Deployment

npm run deploy builds and pushes /dist to the server via SFTP. Connection details live in the git-ignored sftp-config.json (see sftp-config-sample.json).

Development Workflows

  • npm install: install dependencies.
  • npx gulp: build + watch + live-reload. BrowserSync proxies the geonerd.local vhost, so you need a local web server serving dist/ mapped to geonerd.local in /etc/hosts.
  • npx gulp build: production build into dist/ (no watch/server).
  • npm run deploy: build then push dist/ to production via SFTP.
  • npm test: run the Vitest unit tests (in tests/).
  • Versioning: npm run release -- <major|minor|patch|X.Y.Z> (runs release.js) bumps package.json, inserts a dated CHANGELOG.md skeleton, commits Release X.Y.Z and creates the matching tag. The version is injected into the HTML (@@version) as a ?v= cache-buster on app.js/app.css, so bump it on every deploy.

About

Various geography games / Divers jeux de géographie

Topics

Resources

Stars

Watchers

Forks

Contributors