120 lines
10 KiB
Markdown
120 lines
10 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
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.
|
||
|
||
## Deploy & Legal Compliance
|
||
|
||
- **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.
|