wayfarer-weather/README.md
Cayden Schwechten 6250d23297 init
2026-10-31 21:20:16 -05:00

10 KiB
Raw Blame History

Wayfarer

A black-and-gold weather station: pixel-art skies, a heart-based comfort meter, hourly and 14-day forecasts you can switch between, an animated radar map, official alerts and air quality, and a switchable data source. Built with Next.js 16 (App Router), TypeScript, Tailwind CSS v4 and Leaflet.

Run it

npm install
cp .env.example .env.local   # add whichever keys you have
npm run dev                  # http://localhost:3000

With no keys at all, everything except the OpenWeather map layers still works, on Open-Meteo.

What it shows

Section What's in it
Alerts Official NWS watches and warnings for US places; provider alerts elsewhere (WeatherAPI only). Each opens to show the full text.
Hero Temperature, conditions, today's high/low, a precipitation outlook for the next 24 h, comfort hearts.
Conditions Feels like, dew point, humidity, wind with direction and gusts, UV index, air quality (US AQI + PM2.5), pressure, visibility, cloud cover, precipitation, sun times, moon phase.
Hourly 24 h / 48 h / 7-day chart (temperature, feels like, chance of precipitation) with a crosshair readout of any hour, or a full table.
Daily Up to 14 days with a range bar per day. Tap a day for precipitation, wind, UV, humidity, sunrise, sunset and daylight.
Radar and maps Animated radar (worldwide or high-detail US), plus OpenWeather precipitation, cloud, temperature, wind and pressure maps.

A reading a provider doesn't offer is simply hidden. Nothing shows a fake zero.

Providers and what each delivers

Provider Env vars Hourly Daily Notes
Open-Meteo none 7 days, 1 h steps 14 days Free, no key. The deepest data here.
OpenWeather OPENWEATHER_API_KEY, OPENWEATHER_PLAN 5 days, 3 h steps 5 days free plan. No dew point or UV.
48 h, 1 h steps 8+ days onecall plan (One Call 4.0). Separate subscription, 1,000 calls/day free.
WeatherAPI WEATHERAPI_API_KEY, WEATHERAPI_PLAN up to 3 days 3 days (14 paid) Includes its own alerts.
Tomorrow.io TOMORROW_API_KEY 5 days 6 days Free plan: 25 calls/hour. Responses are cached for 10 minutes.

Every provider also gets these, from free sources:

  • Air quality and time zone: Open-Meteo's air-quality API (CAMS data), so the AQI scale is the same whichever provider you pick.
  • US alerts: the National Weather Service. Set NWS_CONTACT to an email or URL, because NWS asks apps to identify themselves.
  • Moon phase: calculated locally.

Radar sources

Layer Source Coverage Frames Limits
Radar RainViewer Worldwide Last hour, 10-min steps Max zoom 7, past frames only, personal/educational use only (since Jan 2026)
US radar NWS NEXRAD via Iowa State IEM US only Last 50 min, 5-min steps Shared academic service: fine for personal use, not for thousands of users
OpenWeather × 5 OpenWeather map tiles Worldwide Current only Needs OPENWEATHER_API_KEY; tiles are proxied by /api/tiles so the key stays secret

US places open on the US radar layer automatically. The basemap is CARTO's dark style; set CARTO_API_KEY (free at carto.com/basemaps/apikey) so /api/tiles can strip CARTO's watermark while keeping the key server-side.

How it fits together

Browser                          Server (Next.js route handlers)               Outside
───────                          ───────────────────────────────               ───────
WeatherApp ── /api/providers ──▶ which keys exist + each plan's limits
           ── /api/geocode   ──▶ lib/weather/geocode.ts ─────────────────────▶ OpenWeather / Open-Meteo
           ── /api/weather   ──▶ in parallel:
                                   providers/<name>     (required)  ──────────▶ chosen provider
                                   extras/air-quality   (optional)  ──────────▶ Open-Meteo AQ
                                   extras/nws-alerts    (optional)  ──────────▶ api.weather.gov
RadarPanel ── /api/radar     ──▶ RainViewer frame index ─────────────────────▶ RainViewer
           ── /api/tiles/…   ──▶ OpenWeather + CARTO tile proxy ─────────────▶ OpenWeather / CARTO CDN
           ── NEXRAD + RainViewer tiles load straight from their servers (no keys involved)

If an optional source fails, its section just disappears; only a failed forecast shows an error.

  • lib/weather/types.ts: the one normalized shape. Units are metric, times are UTC, and missing readings are null.
  • lib/weather/providers/: one adapter per provider. OpenWeather has a folder with free.ts and onecall.ts.
  • lib/weather/extras/: air quality and NWS alerts.
  • lib/radar/layers.ts: every map layer and how to load its frames.
  • lib/display.ts: unit conversion, time-zone-aware formatting, UV/AQI labels, moon phase, precipitation outlook.
  • components/forecast/: ForecastPanel (the switch), HourlyView + HourlyChart, DailyView.
  • components/radar/: RadarPanel (controls) and RadarMap (Leaflet, browser-only).
  • components/ui/ChoiceChips.tsx: the chip group used for providers, views, ranges and map layers.

Adding a provider

  1. Write lib/weather/providers/foo.ts exporting a WeatherProvider, with getForecast() and limits().
  2. Add "foo" to ProviderId in types.ts.
  3. Register it in providers/index.ts, then add its key to .env.example.

Styling (Tailwind v4)

There is no tailwind.config.js; v4 is configured in CSS.

  • app/globals.css holds the theme tokens in @theme. Colors like --color-gold become bg-gold, text-gold and so on, and the fonts become font-display, font-pixel and font-sans. It also holds one custom utility, chamfer (the cut corners), Leaflet control overrides, and a few base rules.
  • Every other style is a utility class in the component that uses it. Layout is mobile-first, with sm: (640px and up) for the desktop arrangement.
  • lib/styles.ts holds the class strings used by more than one component.
  • To resize a frame's corners, add [--cut:22px] to that Slot's className. Put fixed heights on the Slot itself, not its inner element, which is already h-full.
  • The Leaflet overrides sit outside any @layer and are prefixed with .leaflet-container. Leaflet's own CSS is unlayered and loads later, so anything weaker loses.
  • The hourly chart is hand-written SVG with no chart library. Temperature and chance of precipitation sit in separate bands on one time axis rather than sharing two y-axes.

Mobile

  • Uses viewport-fit=cover with safe-area insets, 44px tap targets and 16px inputs (so iOS doesn't zoom on focus).
  • Chips, the hourly chart and the forecast strip scroll sideways; the page itself never does.
  • The map ignores the scroll wheel, so scrolling the page doesn't zoom it; use pinch or the +/− buttons.
  • "Use my location" requires HTTPS on phones. To test on your Android over LAN, use a Vercel preview deploy.
  • Radar autoplay and the floating icon stop when the OS asks for reduced motion.
  • Vercel: import the repo and add your environment variables (OPENWEATHER_API_KEY, CARTO_API_KEY, NWS_CONTACT, plus any optional provider keys) in Project Settings.
  • PM2: npm run build && pm2 start npm --name wayfarer -- start.
  • CARTO domain lock: once deployed, restrict CARTO_API_KEY to your production domain at dashboard.basemaps.carto.com. All /api/* routes also reject cross-site Sec-Fetch-Site: cross-site hotlinking automatically.
  • Built-in attributions: SiteFooter, PlaceSearch, AlertsBanner, and RadarMap render the required live backlinks and license credits for OpenWeather (CC BY-SA 4.0 / ODbL), Open-Meteo (CC BY 4.0), Copernicus Atmosphere Monitoring Service (CAMS), GeoNames (CC BY 4.0), WeatherAPI.com, Tomorrow.io, OpenStreetMap, CARTO, RainViewer, Iowa State IEM, and NOAA/NWS.
  • Non-commercial vs. commercial publishing:
    • Free, ad-free personal / portfolio site: compliant across every source out of the box.
    • Commercial site (ads, paid tiers, or business use): OpenWeather, WeatherAPI.com, NWS, and CARTO (CARTO_API_KEY includes 1M commercial tiles/month) permit commercial use within plan quotas, whereas Open-Meteo (forecast, geocoding, air quality), RainViewer, and Tomorrow.io free tiers are non-commercial only and require paid commercial plans or disabling before monetizing.