Skip to content
Personal

Weather Dashboard

A frontend-only weather lookup app, built to demonstrate one thing above all: keeping concerns separated, file by file, with no exceptions.

Role
Design & development, solo
Year
2026
Stack
Vanilla JS (ES modules), Open-Meteo API
Status
Complete

Search a city, see its current weather — that's the entire brief. No accounts, no forecasts, no backend, no API key to protect (Open-Meteo is free and keyless). What the project is actually built to demonstrate is discipline: five small modules, each with exactly one job, none of them reaching into another's responsibility.

What makes it work

  • Every technical failure becomes one friendly sentence. A dropped connection, a bad response, unexpected data — all caught in api.js and turned into "Unable to connect. Please check your internet connection and try again." before it ever reaches the rest of the app. A city that genuinely isn't found gets its own distinct message, since that's a valid result, not a technical failure.
  • A real race condition, found and fixed. Clicking two recent-search chips in quick succession could show the wrong city, since nothing stopped two searches from overlapping. Fixed by tracking in-flight state and disabling the search button, input, and chips together while a search is running.

Design at a glance

  • bg · #eef2f7
  • surface · #ffffff
  • accent · #2f6fed
  • text · #1c2331

System font stack throughout, mobile-first with breakpoints at 600px and 900px.

Built to last

  • Four single-responsibility modules (api.js, weatherFormatter.js, storage.js, app.js) — none touches another's job, and only app.js touches the DOM
  • Recent searches persist via localStorage, capped at 5, case-insensitive de-duplication
  • Last-searched city restores automatically on reload; a genuine EN/FR toggle throughout

Want an app this disciplined about where every line of logic lives?

See it live Start a project
For developers — the full technical breakdown

01. Strict separation of concerns, enforced end-to-end

Problem
Small frontend-only apps tend to let API calls, formatting, storage, and DOM updates blur together in one file as they grow.
Choice
Four modules, each with exactly one job: api.js talks to Open-Meteo and nothing else, weatherFormatter.js turns data into display strings with no DOM or network access, storage.js only reads/writes localStorage, and app.js is the sole file allowed to touch the DOM.
Result
Any of the three non-DOM modules could be unit-tested or swapped independently — none of them needs a browser to run.

02. A found-in-testing race condition between overlapping searches

Problem
Clicking a second recent-search chip before the first search resolved could let the earlier request's response land last, showing the wrong city.
Choice
Added an in-progress flag that disables the search button, input, and all recent-search chips together for the duration of a request.
Result
Two overlapping searches can no longer race each other — verified by deliberately firing rapid successive searches during the production-polish milestone.

Accessibility & performance

  • All API/city data inserted via textContent, never innerHTML — no injection path from a city name or API response
  • :focus-visible states on the language toggle; visible focus throughout
  • prefers-reduced-motion respected for state transitions
  • Defensive localStorage reads — corrupted or tampered data falls back to an empty list instead of crashing the app

What I’d do differently

No forecast, only current conditions — a deliberate scope cut to keep the separation-of-concerns demonstration tight. A v2 forecast view would need a fifth module rather than growing weatherFormatter.js past its one job.

Stack

  • Vanilla JavaScript (ES modules)
  • Open-Meteo API (geocoding & weather, keyless)
  • localStorage