Code — how this page is built
Under the hood
The actual source of the components on this page, read straight from the repo. Copy it, read it, or open it on GitHub.
WeatherRouletteGameview on GitHub ↗
Picks a random city from a bundled lat/lng list, fetches current weather from the keyless Open-Meteo API, and maps the WMO code to a label + emoji.
'use client';
import * as React from 'react';
import { Win98Button } from '@/components/ui/win-98-button';
const CITIES: [string, number, number][] = [
['Reykjavík', 64.15, -21.95], ['Tromsø', 69.65, 18.96], ['Lagos', 6.52, 3.38],
['Cairo', 30.04, 31.24], ['Nairobi', -1.29, 36.82], ['Cape Town', -33.92, 18.42],
['Mumbai', 19.08, 72.88], ['Bangkok', 13.76, 100.5], ['Singapore', 1.35, 103.82],
['Tokyo', 35.68, 139.69], ['Ulaanbaatar', 47.89, 106.91], ['Sydney', -33.87, 151.21],
['Auckland', -36.85, 174.76], ['Honolulu', 21.31, -157.86], ['Anchorage', 61.22, -149.9],
['Mexico City', 19.43, -99.13], ['Lima', -12.05, -77.04], ['La Paz', -16.5, -68.15],
['Buenos Aires', -34.6, -58.38], ['Ushuaia', -54.8, -68.3], ['Rey', 35.59, 51.43],
['Yakutsk', 62.03, 129.73], ['Berlin', 52.52, 13.4], ['Marrakesh', 31.63, -7.99],
];
const WMO: Record<number, [string, string]> = {
0: ['Clear sky', '☀️'], 1: ['Mainly clear', '🌤️'], 2: ['Partly cloudy', '⛅'], 3: ['Overcast', '☁️'],
45: ['Fog', '🌫️'], 48: ['Rime fog', '🌫️'], 51: ['Light drizzle', '🌦️'], 53: ['Drizzle', '🌦️'],
55: ['Heavy drizzle', '🌦️'], 61: ['Light rain', '🌧️'], 63: ['Rain', '🌧️'], 65: ['Heavy rain', '🌧️'],
71: ['Light snow', '🌨️'], 73: ['Snow', '🌨️'], 75: ['Heavy snow', '❄️'], 77: ['Snow grains', '❄️'],
80: ['Showers', '🌦️'], 81: ['Showers', '🌧️'], 82: ['Violent showers', '⛈️'],
85: ['Snow showers', '🌨️'], 86: ['Snow showers', '❄️'], 95: ['Thunderstorm', '⛈️'],
96: ['Thunderstorm', '⛈️'], 99: ['Thunderstorm', '⛈️'],
};
export function WeatherRouletteGame() {
const [data, setData] = React.useState<{ city: string; temp: number; wind: number; code: number } | null>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(false);
const spin = React.useCallback(() => {
setLoading(true);
const [city, lat, lng] = CITIES[Math.floor(Math.random() * CITIES.length)];
fetch(`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}¤t_weather=true`)
.then((r) => r.json())
.then((d) => {
const c = d.current_weather;
setData({ city, temp: c.temperature, wind: c.windspeed, code: c.weathercode });
setError(false);
})
.catch(() => setError(true))
.finally(() => setLoading(false));
}, []);
React.useEffect(() => { spin(); }, [spin]);
const [label, emoji] = data ? WMO[data.code] ?? ['Unknown', '🌍'] : ['', ''];
return (
<div className="flex w-full max-w-sm flex-col items-center gap-6 text-center">
{error ? (
<div className="font-mono text-sm opacity-60">weather unavailable — try again</div>
) : loading || !data ? (
<div className="font-mono text-sm opacity-50">spinning the globe…</div>
) : (
<>
<div className="text-8xl">{emoji}</div>
<div>
<div className="text-3xl font-bold">{data.city}</div>
<div className="mt-1 text-lg opacity-80">
{Math.round(data.temp)}°C · {label}
</div>
<div className="mt-1 font-mono text-[12px] opacity-55">wind {Math.round(data.wind)} km/h</div>
</div>
</>
)}
<Win98Button onClick={spin} disabled={loading} className="h-10 min-w-32 px-7 text-sm">
Spin again
</Win98Button>
<div className="font-mono text-[11px] uppercase tracking-[0.12em] opacity-45">
live weather, somewhere random on Earth
</div>
</div>
);
}