use-weather-forecast 0.1.110 → 0.1.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,6 +72,10 @@ This package no longer uses ipinfo.io. Instead:
72
72
  no mixed-content issues, though ipapi.co's free tier is rate-limited (1,000
73
73
  requests/day) — deploy the worker and pass `geoEndpoint` for higher-volume or
74
74
  production use.
75
+ - A failed lookup is **repeated** before it throws: `getClientLocation` tries three
76
+ times by default, waiting 500ms, then 1s, so a single rate-limited or cold-start
77
+ response doesn't take the whole forecast down. Tune it with the third argument:
78
+ `getClientLocation(geoEndpoint, ip, { attempts: 5, retryDelay: 250 })`.
75
79
 
76
80
  ### Deploying the geo worker
77
81
 
@@ -96,3 +100,6 @@ in non-browser environments (SSR) or when `localStorage` is unavailable/full.
96
100
 
97
101
  - Open-Meteo powers the forecast data.
98
102
  - Cloudflare's `request.cf` and ipapi.co power IP geolocation (see above).
103
+ - HTTP requests go through [`grab-url`](https://www.npmjs.com/package/grab-url), the
104
+ repo-wide client, rather than raw `fetch`. It is the package's only runtime
105
+ dependency.
@@ -1,2 +1,20 @@
1
1
  import type { WeatherLocation } from '../types';
2
- export declare function getClientLocation(geoEndpoint?: string, ip?: string): Promise<WeatherLocation>;
2
+ export type GeolocationOptions = {
3
+ /** default=3 How many times the lookup is tried before the error is thrown. */
4
+ attempts?: number;
5
+ /** default=500 Milliseconds to wait before the second try; each further wait grows by that much. */
6
+ retryDelay?: number;
7
+ };
8
+ /**
9
+ * Resolve the caller's location, either from a deployed geo worker or from
10
+ * ipapi.co directly.
11
+ *
12
+ * A failed lookup is repeated with a growing delay and only the last error is
13
+ * thrown, so a single rate-limited or cold-started response no longer takes
14
+ * the whole forecast down with it.
15
+ *
16
+ * @param geoEndpoint URL of a deployed geo worker; omit to use ipapi.co.
17
+ * @param ip Look up this IP instead of the caller's own.
18
+ * @param options How often to repeat the lookup, and how long to wait between tries.
19
+ */
20
+ export declare function getClientLocation(geoEndpoint?: string, ip?: string, options?: GeolocationOptions): Promise<WeatherLocation>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared JSON transport for the package's upstreams (the geo worker, ipapi.co
3
+ * and Open-Meteo).
4
+ *
5
+ * `grab-url` is the HTTP client used across this repo: it parses the JSON,
6
+ * applies a timeout and can repeat a failed request itself. Unlike `fetch` it
7
+ * resolves rather than rejects when a request fails, handing back the message
8
+ * on `.error`, so every call site has to inspect the payload -- that check
9
+ * lives here so the callers can just `await` a typed body.
10
+ */
11
+ export type GrabJsonOptions = {
12
+ headers?: Record<string, string>;
13
+ /** default=0 Times grab-url repeats a request that failed at the transport level. */
14
+ retryAttempts?: number;
15
+ /** default=15 Seconds before the request is aborted. */
16
+ timeout?: number;
17
+ };
18
+ /**
19
+ * Request a JSON body, throwing a labelled error for any failure.
20
+ *
21
+ * @param url Full URL to request.
22
+ * @param label Prefix for the thrown message, e.g. `ipapi.co lookup`.
23
+ * @param options Transport options passed through to grab-url.
24
+ */
25
+ export declare function grabJson<T>(url: string, label: string, options?: GrabJsonOptions): Promise<T>;
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -86,29 +96,44 @@ function clearWeatherForecastCache() {
86
96
  }
87
97
  }
88
98
 
89
- // src/api/geolocation.ts
90
- var DEFAULT_IP_API_URL = "https://ipapi.co";
91
- async function getClientLocation(geoEndpoint, ip) {
92
- if (geoEndpoint) {
93
- const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;
94
- const res2 = await fetch(url);
95
- if (!res2.ok) throw new Error(`Geolocation worker lookup failed: ${res2.status}`);
96
- const data2 = await res2.json();
97
- return {
98
- city: data2.city,
99
- region: data2.region,
100
- country: data2.country,
101
- timezone: data2.timezone,
102
- latitude: Number(data2.latitude),
103
- longitude: Number(data2.longitude)
104
- };
99
+ // src/api/http.ts
100
+ var import_grab_url = __toESM(require("grab-url"), 1);
101
+ function describeFailure(error) {
102
+ return error.replace(/^HTTP error:\s*/i, "").trim() || "unknown error";
103
+ }
104
+ async function grabJson(url, label, options = {}) {
105
+ const { headers, retryAttempts = 0, timeout = 15 } = options;
106
+ const data = await (0, import_grab_url.default)(url, { headers, retryAttempts, timeout });
107
+ if (typeof data?.error === "string") {
108
+ throw new Error(`${label} failed: ${describeFailure(data.error)}`);
105
109
  }
106
- const res = await fetch(`${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ""}json/`);
107
- if (!res.ok) throw new Error(`ipapi.co lookup failed: ${res.status}`);
108
- const data = await res.json();
109
- if (data.error) {
110
- throw new Error(`ipapi.co lookup failed: ${data.reason ?? "unknown error"}`);
110
+ if (data?.error === true) {
111
+ throw new Error(`${label} failed: ${data.reason ?? "unknown error"}`);
111
112
  }
113
+ if (!data) throw new Error(`${label} failed: empty response`);
114
+ return data;
115
+ }
116
+
117
+ // src/api/geolocation.ts
118
+ var DEFAULT_IP_API_URL = "https://ipapi.co";
119
+ var DEFAULT_ATTEMPTS = 3;
120
+ var DEFAULT_RETRY_DELAY_MS = 500;
121
+ var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
122
+ async function lookupViaWorker(geoEndpoint, ip) {
123
+ const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;
124
+ const data = await grabJson(url, "Geolocation worker lookup");
125
+ return {
126
+ city: data.city,
127
+ region: data.region,
128
+ country: data.country,
129
+ timezone: data.timezone,
130
+ latitude: Number(data.latitude),
131
+ longitude: Number(data.longitude)
132
+ };
133
+ }
134
+ async function lookupViaIpApi(ip) {
135
+ const url = `${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ""}json/`;
136
+ const data = await grabJson(url, "ipapi.co lookup");
112
137
  return {
113
138
  city: data.city,
114
139
  region: data.region,
@@ -118,6 +143,20 @@ async function getClientLocation(geoEndpoint, ip) {
118
143
  longitude: Number(data.longitude)
119
144
  };
120
145
  }
146
+ async function getClientLocation(geoEndpoint, ip, options = {}) {
147
+ const attempts = Math.max(1, Math.trunc(options.attempts ?? DEFAULT_ATTEMPTS));
148
+ const retryDelay = Math.max(0, options.retryDelay ?? DEFAULT_RETRY_DELAY_MS);
149
+ let lastError;
150
+ for (let attempt = 1; attempt <= attempts; attempt++) {
151
+ try {
152
+ return geoEndpoint ? await lookupViaWorker(geoEndpoint, ip) : await lookupViaIpApi(ip);
153
+ } catch (error) {
154
+ lastError = error;
155
+ if (attempt < attempts && retryDelay > 0) await wait(retryDelay * attempt);
156
+ }
157
+ }
158
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
159
+ }
121
160
 
122
161
  // src/api/forecast.ts
123
162
  function buildUrl(latitude, longitude, options) {
@@ -171,11 +210,10 @@ async function getWeatherForecast(options = {}) {
171
210
  const url = buildUrl(location.latitude, location.longitude, { ...options, location });
172
211
  const cached = readCachedForecast(url);
173
212
  if (cached) return cached;
174
- const response = await fetch(url, { headers: { "Content-Type": "application/json" } });
175
- if (!response.ok) {
176
- throw new Error(`Weather request failed: ${response.status} ${response.statusText}`);
177
- }
178
- const data = await response.json();
213
+ const data = await grabJson(url, "Weather request", {
214
+ headers: { "Content-Type": "application/json" },
215
+ retryAttempts: 1
216
+ });
179
217
  if (!data?.current || !data?.hourly || !data?.daily) {
180
218
  throw new Error("Invalid weather response");
181
219
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/weatherCodes.ts","../src/lib/cache.ts","../src/api/geolocation.ts","../src/api/forecast.ts","../src/hooks/useWeatherForecast.ts","../src/icons/BaseIcon.tsx","../src/icons/weather-icons.tsx","../src/icons/WeatherIcon.tsx","../src/components/WeatherForecast.tsx"],"sourcesContent":["export * from './types';\nexport * from './weatherCodes';\nexport { clearWeatherForecastCache } from './lib/cache';\nexport * from './api/geolocation';\nexport * from './api/forecast';\nexport * from './hooks/useWeatherForecast';\nexport * from './icons/WeatherIcon';\nexport * from './components/WeatherForecast';\n","import type { WeatherCondition } from './types';\n\nexport function getWeatherIcon(code: number, isDay = 1): WeatherCondition {\n if (code === 0) return 'sun';\n if ([1, 2].includes(code)) return isDay ? 'cloud-sun' : 'clouds';\n if (code === 3) return 'clouds';\n if ([45, 48].includes(code)) return 'cloud-fog';\n if ([51, 53, 55, 56, 57].includes(code)) return 'cloud-drizzle';\n if ([61, 63].includes(code)) return 'cloud-showers';\n if ([65, 66, 67, 80, 81, 82].includes(code)) return 'cloud-showers-heavy';\n if ([71, 73, 75, 77, 85, 86].includes(code)) return 'cloud-snow';\n if (code === 95) return 'cloud-bolt';\n if ([96, 99].includes(code)) return 'cloud-hail';\n return 'clouds';\n}\n","const CACHE_PREFIX = 'weather-forecast-cache:';\nconst CACHE_TTL_MS = 30 * 60 * 1000;\n\ntype CacheEntry<T> = {\n timestamp: number;\n data: T;\n};\n\nfunction getStorage(): Storage | null {\n if (typeof window === 'undefined' || !window.localStorage) return null;\n return window.localStorage;\n}\n\n/** Returns the cached value for `key` if it was written within the last 30 minutes. */\nexport function readCachedForecast<T>(key: string): T | null {\n const storage = getStorage();\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(CACHE_PREFIX + key);\n if (!raw) return null;\n\n const entry = JSON.parse(raw) as CacheEntry<T>;\n if (Date.now() - entry.timestamp > CACHE_TTL_MS) {\n storage.removeItem(CACHE_PREFIX + key);\n return null;\n }\n\n return entry.data;\n } catch {\n return null;\n }\n}\n\nexport function writeCachedForecast<T>(key: string, data: T): void {\n const storage = getStorage();\n if (!storage) return;\n\n try {\n const entry: CacheEntry<T> = { timestamp: Date.now(), data };\n storage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));\n } catch {\n // Storage full or unavailable (e.g. private browsing) — safe to ignore,\n // the next call will simply hit the network again.\n }\n}\n\n/** Clears all cached forecasts. Mainly useful for tests/debugging. */\nexport function clearWeatherForecastCache(): void {\n const storage = getStorage();\n if (!storage) return;\n\n for (let i = storage.length - 1; i >= 0; i--) {\n const key = storage.key(i);\n if (key?.startsWith(CACHE_PREFIX)) storage.removeItem(key);\n }\n}\n","import type { WeatherLocation } from '../types';\n\nconst DEFAULT_IP_API_URL = 'https://ipapi.co';\n\ntype IpApiResponse = {\n city?: string;\n region?: string;\n country_name?: string;\n timezone?: string;\n latitude?: number;\n longitude?: number;\n error?: boolean;\n reason?: string;\n};\n\ntype GeoWorkerResponse = {\n city?: string;\n region?: string;\n country?: string;\n timezone?: string;\n latitude?: number | string;\n longitude?: number | string;\n};\n\nexport async function getClientLocation(geoEndpoint?: string, ip?: string): Promise<WeatherLocation> {\n if (geoEndpoint) {\n const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;\n const res = await fetch(url);\n if (!res.ok) throw new Error(`Geolocation worker lookup failed: ${res.status}`);\n const data: GeoWorkerResponse = await res.json();\n\n return {\n city: data.city,\n region: data.region,\n country: data.country,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n }\n\n const res = await fetch(`${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ''}json/`);\n if (!res.ok) throw new Error(`ipapi.co lookup failed: ${res.status}`);\n const data: IpApiResponse = await res.json();\n\n if (data.error) {\n throw new Error(`ipapi.co lookup failed: ${data.reason ?? 'unknown error'}`);\n }\n\n return {\n city: data.city,\n region: data.region,\n country: data.country_name,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n}\n","import type { WeatherForecastData, WeatherForecastOptions, WeatherLocation } from '../types';\nimport { getWeatherIcon } from '../weatherCodes';\nimport { getClientLocation } from './geolocation';\nimport { readCachedForecast, writeCachedForecast } from '../lib/cache';\n\nfunction buildUrl(latitude: number, longitude: number, options: WeatherForecastOptions) {\n const params = new URLSearchParams({\n latitude: String(latitude),\n longitude: String(longitude),\n timezone: options.location?.timezone || 'auto',\n temperature_unit: options.temperatureUnit || 'fahrenheit',\n wind_speed_unit: options.windSpeedUnit || 'mph',\n forecast_days: String(options.forecastDays ?? 5),\n forecast_hours: String(options.forecastHours ?? 24),\n current: [\n 'temperature_2m',\n 'weather_code',\n 'is_day',\n 'rain',\n 'showers',\n 'snowfall',\n 'wind_speed_10m',\n ].join(','),\n hourly: [\n 'temperature_2m',\n 'weather_code',\n 'precipitation_probability',\n 'rain',\n 'showers',\n 'snowfall',\n ].join(','),\n daily: [\n 'temperature_2m_max',\n 'temperature_2m_min',\n 'weather_code',\n 'precipitation_probability_max',\n 'precipitation_sum',\n 'wind_speed_10m_max',\n ].join(','),\n });\n\n return `https://api.open-meteo.com/v1/forecast?${params.toString()}`;\n}\n\nexport async function getWeatherForecast(\n options: WeatherForecastOptions = {}\n): Promise<WeatherForecastData> {\n let location: WeatherLocation;\n\n if (typeof options.latitude === 'number' && typeof options.longitude === 'number') {\n location = {\n ...options.location,\n latitude: options.latitude,\n longitude: options.longitude,\n };\n } else {\n location = await getClientLocation(options.geoEndpoint, options.ip);\n }\n\n const url = buildUrl(location.latitude, location.longitude, { ...options, location });\n\n const cached = readCachedForecast<WeatherForecastData>(url);\n if (cached) return cached;\n\n const response = await fetch(url, { headers: { 'Content-Type': 'application/json' } });\n\n if (!response.ok) {\n throw new Error(`Weather request failed: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json();\n\n if (!data?.current || !data?.hourly || !data?.daily) {\n throw new Error('Invalid weather response');\n }\n\n const result: WeatherForecastData = {\n location: { ...location, timezone: data.timezone || location.timezone },\n current: {\n time: data.current.time,\n temperature: Math.round(data.current.temperature_2m),\n weatherCode: data.current.weather_code,\n icon: getWeatherIcon(data.current.weather_code, data.current.is_day),\n isDay: data.current.is_day,\n rain: data.current.rain,\n showers: data.current.showers,\n snowfall: data.current.snowfall,\n windSpeed: data.current.wind_speed_10m,\n },\n hourly: data.hourly.time.map((time: string, index: number) => ({\n time,\n temperature: Math.round(data.hourly.temperature_2m[index]),\n weatherCode: data.hourly.weather_code[index],\n precipitationProbability: data.hourly.precipitation_probability?.[index],\n rain: data.hourly.rain?.[index],\n showers: data.hourly.showers?.[index],\n snowfall: data.hourly.snowfall?.[index],\n icon: getWeatherIcon(data.hourly.weather_code[index]),\n })),\n daily: data.daily.time.map((date: string, index: number) => ({\n date,\n min: Math.round(data.daily.temperature_2m_min[index]),\n max: Math.round(data.daily.temperature_2m_max[index]),\n weatherCode: data.daily.weather_code[index],\n precipitationProbabilityMax: data.daily.precipitation_probability_max?.[index],\n precipitationSum: data.daily.precipitation_sum?.[index],\n windSpeedMax: data.daily.wind_speed_10m_max?.[index],\n icon: getWeatherIcon(data.daily.weather_code[index]),\n })),\n };\n\n writeCachedForecast(url, result);\n return result;\n}\n","import { useEffect, useState } from 'react';\nimport type { WeatherForecastData, WeatherForecastOptions } from '../types';\nimport { getWeatherForecast } from '../api/forecast';\n\nexport function useWeatherForecast(options: WeatherForecastOptions = {}) {\n const [data, setData] = useState<WeatherForecastData | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let active = true;\n setLoading(true);\n setError(null);\n\n getWeatherForecast(options)\n .then((result) => { if (active) setData(result); })\n .catch((err) => { if (active) setError(err instanceof Error ? err : new Error('Unknown error')); })\n .finally(() => { if (active) setLoading(false); });\n\n return () => { active = false; };\n }, [\n options.latitude,\n options.longitude,\n options.geoEndpoint,\n options.ip,\n options.forecastDays,\n options.forecastHours,\n options.temperatureUnit,\n options.windSpeedUnit,\n options.location?.timezone,\n ]);\n\n return { data, error, loading };\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\n\nexport function BaseIcon({ title, children, ...props }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n role={title ? 'img' : 'presentation'}\n aria-hidden={title ? undefined : true}\n aria-label={title}\n {...props}\n >\n {title ? <title>{title}</title> : null}\n {children}\n </svg>\n );\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\nimport { BaseIcon } from './BaseIcon';\n\nexport const CloudBoltIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M7 18a4.815 4.815 0 0 1-3.327-1.318A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 0 7h-1m-4.75-5L11 17.5h3.375l-2.25 4.5\" /></BaseIcon>\n);\nexport const CloudDrizzleIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 18v1m-3-4v1m6-1v1m2 3v1M7 19v1m-3-3.034a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudFogIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M5 21h7m4 0h3M8 17h8\" /></BaseIcon>\n);\nexport const CloudHailIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 17.45v.1m-1.5 3.9v.1m10.5-4.1v.1m-1.5 3.9v.1m-3-4.1v.1m-1.5 3.9v.1m10.475-4.575A3.5 3.5 0 0 0 19 11h-1c.146-.65.148-1.32.006-1.97a4.784 4.784 0 0 0-.831-1.823 5.36 5.36 0 0 0-1.544-1.398 5.964 5.964 0 0 0-2.02-.759 6.224 6.224 0 0 0-2.19-.006 5.971 5.971 0 0 0-2.024.749C8.157 6.533 7.295 7.687 7 9a4.815 4.815 0 0 0-3.327 1.318A4.403 4.403 0 0 0 2.295 13.5c0 1.194.496 2.338 1.378 3.182.105.1.214.195.327.284m4.5.534a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm10.5-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-3-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z\" /></BaseIcon>\n);\nexport const CloudShowersIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M12 13v2m0 3v2m4-5v2m0 3v2m-8-7v2m0 3v2\" /></BaseIcon>\n);\nexport const CloudShowersHeavyIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M16 15v6m-8-6v6m4-8v6\" /></BaseIcon>\n);\nexport const CloudSleetIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M13.5 17.45a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm.5 3.9a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm-6.327-4.868.692-.723-.692.723ZM7 9v1a1 1 0 0 0 .976-.781L7 9Zm2.397-3.207.512.858-.512-.858ZM18 11l-.976-.219A1 1 0 0 0 18 12v-1Zm2.768 5.268a1 1 0 0 0 1.414 1.414l-.707-.707-.707-.707ZM3.379 17.75a1 1 0 0 0 1.242-1.567L4 16.966l-.62.784Zm5.55-.379a1 1 0 0 0-1.857-.742L8 17l.928.371ZM5.071 21.63a1 1 0 0 0 1.856.742L6 22l-.928-.371Zm13.857-4.258a1 1 0 0 0-1.857-.742L18 17l.928.371Zm-3.857 4.258a1 1 0 0 0 1.857.742L16 22l-.928-.371ZM13 17.5h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2a1.5 1.5 0 0 0 1.5 1.5v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2a1.5 1.5 0 0 0-1.5-1.5v1Zm0 .45h-1v.1h2v-.1h-1Zm-1 4.05h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2A1.5 1.5 0 0 0 11 23v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2A1.5 1.5 0 0 0 11 20v1Zm0 .45h-1v.1h2v-.1h-1Zm-7.327-4.768.692-.723a3.403 3.403 0 0 1-1.07-2.459h-2c0 1.473.613 2.877 1.687 3.905l.691-.723ZM2.295 13.5h1c0-.914.38-1.8 1.07-2.46l-.692-.722-.69-.723A5.403 5.403 0 0 0 1.294 13.5h1Zm1.378-3.182.692.723A3.815 3.815 0 0 1 7 10V8c-1.499 0-2.945.57-4.018 1.595l.691.723ZM7 9l.976.219c.23-1.024.91-1.956 1.933-2.568l-.512-.858-.513-.859c-1.456.87-2.5 2.245-2.86 3.847L7 9Zm2.397-3.207.512.858a4.972 4.972 0 0 1 1.686-.622l-.173-.985-.174-.985a6.971 6.971 0 0 0-2.364.875l.513.859Zm2.025-.749.173.985a5.223 5.223 0 0 1 1.837.005l.18-.984.178-.984a7.223 7.223 0 0 0-2.542-.007l.174.985Zm2.19.006-.18.984c.606.11 1.177.326 1.681.631l.518-.856.518-.855a6.963 6.963 0 0 0-2.359-.888l-.179.984Zm2.02.76-.519.855c.505.305.93.693 1.257 1.136l.805-.594.805-.594a6.361 6.361 0 0 0-1.831-1.659l-.518.855Zm1.543 1.397-.805.594c.326.442.549.932.66 1.441l.976-.212.978-.213a5.786 5.786 0 0 0-1.005-2.204l-.804.594Zm.831 1.823-.977.212c.111.509.11 1.031-.005 1.539L18 11l.976.219c.178-.792.18-1.609.008-2.402l-.978.213ZM18 11v1h1v-2h-1v1Zm1 0v1a2.5 2.5 0 0 1 1.768.732l.707-.707.707-.707A4.5 4.5 0 0 0 19 10v1Zm2.475 1.025-.707.707A2.5 2.5 0 0 1 21.5 14.5h2a4.5 4.5 0 0 0-1.318-3.182l-.707.707ZM22.5 14.5h-1a2.5 2.5 0 0 1-.732 1.768l.707.707.707.707A4.5 4.5 0 0 0 23.5 14.5h-1ZM4 16.966l.62-.784a3.684 3.684 0 0 1-.255-.223l-.692.723-.69.723c.126.121.259.237.396.345L4 16.966ZM8 17l-.928-.371-2 5L6 22l.928.371 2-5L8 17Zm10 0-.928-.371-2 5L16 22l.928.371 2-5L18 17Z\" /></BaseIcon>\n);\nexport const CloudSnowIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 15h2m-1-1v2m5-1h2m-1-1v2M5 21h2m-1-1v2m5-2h2m-1-1v2m5 0h2m-1-1v2M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudSunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M10.201 19.997c-1.977 0-3.58-1.544-3.58-3.448 0-1.903 1.603-3.446 3.58-3.446.303-1.355 1.38-2.461 2.827-2.902 1.445-.44 3.042-.148 4.186.77 1.144.915 1.663 2.312 1.361 3.667h.761c1.472 0 2.664 1.2 2.664 2.68A2.672 2.672 0 0 1 19.336 20h-9.134m2.66-12.934a4.166 4.166 0 0 0-2.677-1.732m2.677 1.732 2.744-1.892m-2.744 1.892c.55.796.802 1.754.723 2.709m-3.4-4.44a4.167 4.167 0 0 0-3.118.667m3.118-.668.602-3.278m-3.72 3.946A4.167 4.167 0 0 0 5.334 8.68m1.733-2.678L5.174 3.26m.16 5.42c-.2 1.088.041 2.21.669 3.12m-.669-3.12-3.278-.601m3.947 3.72-2.744 1.893m2.744-1.893a4.167 4.167 0 0 0 2.174 1.607\" /></BaseIcon>\n);\nexport const CloudsIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8.958 21.05C6.772 21.05 5 19.344 5 17.24s1.772-3.81 3.958-3.81c.335-1.498 1.525-2.72 3.124-3.207 1.598-.487 3.363-.164 4.628.85 1.264 1.011 1.837 2.556 1.504 4.053h.842A2.953 2.953 0 0 1 22 18.09a2.954 2.954 0 0 1-2.945 2.964H8.958M18 8c-.5-2.5-3.532-3.401-5.5-2.5a3.117 3.117 0 0 0-2.11-2.312c-1.352-.411-2.845-.138-3.915.72-1.07.855-1.554 2.161-1.273 3.428h-.711A2.498 2.498 0 0 0 2 9.843c0 1.094.695 2.023 1.666 2.366\" /></BaseIcon>\n);\nexport const SnowflakeIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"m9.25 4-1 2H6m-2.25 4L5 12m0 0-1.25 2M5 12h3m-3 0H2.5m17.75-2L19 12l1.25 2M6 18h2.25l1 2M14 9h-4m4 0 3-5m-3 5 2 3m-6-3L7 4m3 5-2 3m0 0 2 3m0 0-3 5m3-5h4m2-3-2 3m2-3h5.5M14 15l3 5M14.75 4l1 2H18m0 12h-2.25l-1 2\" /></BaseIcon>\n);\nexport const SunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 4V2m0 20v-2m10-8h-2M4 12H2m15.657-5.657 1.414-1.414M4.93 19.07l1.414-1.414m12.728 1.414-1.414-1.414M6.343 6.343 4.93 4.929M7 12a5 5 0 1 0 10 0 5 5 0 0 0-10 0Z\" /></BaseIcon>\n);\nexport const WindIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2\" /></BaseIcon>\n);\n","import React from 'react';\nimport type { IconProps, WeatherCondition } from '../types';\nimport {\n CloudBoltIcon,\n CloudDrizzleIcon,\n CloudFogIcon,\n CloudHailIcon,\n CloudShowersHeavyIcon,\n CloudShowersIcon,\n CloudSleetIcon,\n CloudSnowIcon,\n CloudSunIcon,\n CloudsIcon,\n SnowflakeIcon,\n SunIcon,\n} from './weather-icons';\n\nconst iconMap: Record<WeatherCondition, React.ComponentType<IconProps>> = {\n sun: SunIcon,\n 'cloud-sun': CloudSunIcon,\n clouds: CloudsIcon,\n 'cloud-fog': CloudFogIcon,\n 'cloud-drizzle': CloudDrizzleIcon,\n 'cloud-showers': CloudShowersIcon,\n 'cloud-showers-heavy': CloudShowersHeavyIcon,\n 'cloud-sleet': CloudSleetIcon,\n 'cloud-snow': CloudSnowIcon,\n snowflake: SnowflakeIcon,\n 'cloud-bolt': CloudBoltIcon,\n 'cloud-hail': CloudHailIcon,\n};\n\nexport function WeatherIcon({ condition, ...props }: IconProps & { condition: WeatherCondition }) {\n const Icon = iconMap[condition] || CloudsIcon;\n return <Icon {...props} />;\n}\n","import React, { useEffect, useState } from 'react';\nimport { useWeatherForecast } from '../hooks/useWeatherForecast';\nimport type { DailyWeather, TemperatureUnit, WeatherForecastOptions, WindSpeedUnit } from '../types';\nimport { WeatherIcon } from '../icons/WeatherIcon';\nimport { WindIcon } from '../icons/weather-icons';\n\ntype Props = WeatherForecastOptions & {\n className?: string;\n style?: React.CSSProperties;\n compact?: boolean;\n};\n\nconst WIND_SPEED_UNIT_LABELS: Record<WindSpeedUnit, string> = {\n kmh: 'km/h',\n mph: 'mph',\n ms: 'm/s',\n kn: 'kn',\n};\n\n// Format a real instant in a specific IANA timezone, falling back to the\n// browser's local zone if the timezone string is missing or invalid.\nfunction formatInZone(instant: Date, timezone: string | undefined, options: Intl.DateTimeFormatOptions): string {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined }).format(instant);\n } catch {\n return new Intl.DateTimeFormat([], options).format(instant);\n }\n}\n\n// The clock is shown with the hour and minute large and the seconds (plus any\n// AM/PM marker) trailing in a smaller, dimmer size, so split it into those two\n// halves. Falls back to one undivided string if the locale's parts are not\n// where we expect them.\nfunction clockParts(instant: Date, timezone: string | undefined): { main: string; tail: string } {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: '2-digit', second: '2-digit' };\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n const parts = format.formatToParts(instant);\n const part = (type: Intl.DateTimeFormatPartTypes) => parts.find((p) => p.type === type)?.value;\n const hour = part('hour');\n const minute = part('minute');\n const second = part('second');\n if (!hour || !minute || !second) return { main: format.format(instant), tail: '' };\n const dayPeriod = part('dayPeriod');\n return { main: `${hour}:${minute}`, tail: `:${second}${dayPeriod ? ` ${dayPeriod}` : ''}` };\n}\n\n// Just the zone abbreviation (e.g. \"GMT+2\"), so it can be shown apart from the\n// clock itself.\nfunction zoneLabel(instant: Date, timezone: string | undefined): string {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', timeZoneName: 'short' };\n try {\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n return format.formatToParts(instant).find((part) => part.type === 'timeZoneName')?.value ?? '';\n } catch {\n return '';\n }\n}\n\n// Daily dates arrive as plain \"YYYY-MM-DD\" calendar days; `new Date(...)` would\n// read those as UTC midnight and roll back a day in western timezones, so build\n// the date from its parts and let it stay a local calendar day.\nfunction parseCalendarDate(date: string): Date {\n const [year, month, day] = date.split('-').map(Number);\n if (!year || !month || !day) return new Date(date);\n return new Date(year, month - 1, day);\n}\n\n// The upcoming days sit in narrow columns in the card, so they are labelled\n// with the abbreviated weekday (\"Fri\") rather than a full name or \"Tomorrow\".\nfunction upcomingDayLabel(date: string): string {\n return parseCalendarDate(date).toLocaleDateString([], { weekday: 'short' });\n}\n\n// Daily totals that no longer have a column of their own in the card layout are\n// kept as hover text so the detail is still reachable.\nfunction upcomingDayDetail(day: DailyWeather, windSpeedUnitLabel: string): string | undefined {\n const details: string[] = [];\n if (day.precipitationSum !== undefined) details.push(`${day.precipitationSum.toFixed(1)} mm`);\n if (day.windSpeedMax !== undefined) details.push(`${day.windSpeedMax.toFixed(1)} ${windSpeedUnitLabel} wind`);\n return details.length > 0 ? details.join(' · ') : undefined;\n}\n\nconst styles = {\n root: {\n fontFamily: 'system-ui, sans-serif',\n border: '1px solid #e5e7eb',\n borderRadius: 12,\n padding: 16,\n background: '#fff',\n color: '#111827',\n maxWidth: 640,\n } as React.CSSProperties,\n row: { display: 'flex', alignItems: 'center', gap: 8 } as React.CSSProperties,\n hours: { display: 'flex', gap: 12, overflowX: 'auto', paddingBottom: 4 } as React.CSSProperties,\n hourCard: { minWidth: 72, textAlign: 'center' as const, padding: 8, borderRadius: 8, background: '#f9fafb' },\n dayRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, padding: '8px 0', borderBottom: '1px solid #f3f4f6' } as React.CSSProperties,\n // Fluid: fills whatever container it is dropped into (e.g. the full\n // sidebar/column width on desktop), with generous, even padding. Current\n // conditions sit on the left and the upcoming days on the right; the two\n // halves wrap into a stack when the container gets narrow.\n compactRoot: {\n fontFamily: 'system-ui, sans-serif',\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'wrap',\n alignItems: 'stretch',\n gap: 24,\n padding: '20px 24px',\n borderRadius: 16,\n width: '100%',\n boxSizing: 'border-box',\n } as React.CSSProperties,\n // Current conditions form two spread-out rows down the left side of the\n // card (rather than five stacked lines), so the card stays short and each\n // row uses the full available width.\n compactBody: { display: 'flex', flexDirection: 'column', justifyContent: 'space-between', gap: 20, flex: '1.6 1 240px', minWidth: 0 } as React.CSSProperties,\n compactRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '12px 16px' } as React.CSSProperties,\n compactCity: { fontSize: 15, fontWeight: 700, lineHeight: 1.2 } as React.CSSProperties,\n compactHeader: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 12 } as React.CSSProperties,\n compactTemp: { fontSize: 32, fontWeight: 600, lineHeight: 1 } as React.CSSProperties,\n compactTime: { fontSize: 26, fontWeight: 600, lineHeight: 1.1 } as React.CSSProperties,\n compactSeconds: { fontSize: 13, fontWeight: 500, opacity: 0.6, marginLeft: 2 } as React.CSSProperties,\n compactDate: { fontSize: 11, opacity: 0.7, marginTop: 5 } as React.CSSProperties,\n compactStats: { display: 'flex', flexWrap: 'wrap', gap: '8px 18px', fontSize: 12, opacity: 0.9 } as React.CSSProperties,\n // Three equal columns on the right side of the card, each stacking\n // weekday / icon / high-low, vertically centered against the current\n // conditions and nudged over behind a divider so the two halves read as\n // separate sections.\n upcoming: {\n display: 'grid',\n gridTemplateColumns: 'repeat(3, 1fr)',\n alignContent: 'center',\n gap: 14,\n flex: '1 1 190px',\n minWidth: 0,\n paddingLeft: 22,\n borderLeft: '1px solid currentColor',\n borderLeftColor: 'rgba(128,128,128,0.25)',\n fontSize: 11,\n } as React.CSSProperties,\n upcomingDay: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 5, textAlign: 'center' } as React.CSSProperties,\n upcomingDayName: { opacity: 0.7, whiteSpace: 'nowrap' } as React.CSSProperties,\n upcomingTemps: { whiteSpace: 'nowrap' } as React.CSSProperties,\n compactStat: { display: 'flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' } as React.CSSProperties,\n unitSwitch: { display: 'inline-flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' } as React.CSSProperties,\n rain: { color: '#2563eb', whiteSpace: 'nowrap' } as React.CSSProperties,\n tz: { fontSize: 12, opacity: 0.7, marginTop: 2 } as React.CSSProperties,\n list: { display: 'flex', flexDirection: 'column' } as React.CSSProperties,\n};\n\n// Precipitation chance is only surfaced once it represents a meaningful risk.\nconst RAIN_RISK_THRESHOLD = 2;\n\nfunction RainBadge({ probability, compact }: { probability?: number; compact?: boolean }) {\n if (probability === undefined || probability === null || probability <= RAIN_RISK_THRESHOLD) return null;\n return <span style={styles.rain}>{compact ? ` · ${probability}%` : ` · ${probability}% rain`}</span>;\n}\n\n/**\n * Renders one location's forecast: a live current time (ticking, in the\n * location's own timezone), a clickable degF / degC unit switch, high/low and\n * precipitation for the next hours and days.\n */\nfunction SingleWeatherForecast(props: Props) {\n // Temperature unit is local state so the degF / degC switch can toggle it;\n // it is seeded from (and re-synced to) the temperatureUnit prop.\n const [unit, setUnit] = useState<TemperatureUnit>(props.temperatureUnit ?? 'fahrenheit');\n useEffect(() => {\n setUnit(props.temperatureUnit ?? 'fahrenheit');\n }, [props.temperatureUnit]);\n\n const { data, loading, error } = useWeatherForecast({ ...props, temperatureUnit: unit });\n\n // A ticking clock so the displayed current time stays accurate to the second.\n const [now, setNow] = useState(() => new Date());\n useEffect(() => {\n const id = setInterval(() => setNow(new Date()), 1000);\n return () => clearInterval(id);\n }, []);\n\n const toggleUnit = () => setUnit((u) => (u === 'celsius' ? 'fahrenheit' : 'celsius'));\n\n const renderUnitSwitch = (fontSize: number) => (\n <span\n role=\"button\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n toggleUnit();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggleUnit();\n }\n }}\n title=\"Switch temperature units\"\n aria-label={`Switch temperature units, currently ${unit === 'celsius' ? 'Celsius' : 'Fahrenheit'}`}\n style={{ ...styles.unitSwitch, fontSize }}\n >\n <span style={{ fontWeight: unit === 'fahrenheit' ? 700 : 400, opacity: unit === 'fahrenheit' ? 1 : 0.45 }}>&deg;F</span>\n <span style={{ opacity: 0.4, margin: '0 4px' }}>|</span>\n <span style={{ fontWeight: unit === 'celsius' ? 700 : 400, opacity: unit === 'celsius' ? 1 : 0.45 }}>&deg;C</span>\n </span>\n );\n\n // Keep showing the last forecast while a unit switch refetches in the\n // background; only suppress output on the very first load.\n if (loading && !data) return null;\n if (error) return <div className={props.className} style={props.style}>Error: {error.message}</div>;\n if (!data) return <div className={props.className} style={props.style}>No forecast available.</div>;\n\n const timezone = data.location?.timezone;\n const currentDate = formatInZone(now, timezone, { weekday: 'short', month: 'short', day: 'numeric' });\n const currentTime = formatInZone(now, timezone, { hour: 'numeric', minute: '2-digit', second: '2-digit', timeZoneName: 'short' });\n // The compact layout shows the clock at a larger size, so the zone label is\n // split off onto the smaller date line to keep the time on one line.\n const currentClock = clockParts(now, timezone);\n const currentZone = zoneLabel(now, timezone);\n\n if (props.compact) {\n const today = data.daily[0];\n const windSpeedUnitLabel = WIND_SPEED_UNIT_LABELS[props.windSpeedUnit ?? 'mph'];\n // Today is already summarised in the card's own stats line, so the\n // upcoming-days section starts at tomorrow and shows the next three days.\n const upcomingDays = data.daily.slice(1, 4);\n\n return (\n <div className={props.className} style={{ ...styles.compactRoot, ...props.style }}>\n <div style={styles.compactBody}>\n <div style={styles.compactRow}>\n <div style={styles.compactCity}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </div>\n\n <div style={styles.compactHeader}>\n <WeatherIcon condition={data.current.icon} width={30} height={30} title=\"Current weather\" />\n <strong style={styles.compactTemp}>{data.current.temperature}</strong>\n {renderUnitSwitch(13)}\n </div>\n </div>\n\n <div style={styles.compactRow}>\n <div>\n <div style={styles.compactTime}>\n {currentClock.main}\n {currentClock.tail && <span style={styles.compactSeconds}>{currentClock.tail}</span>}\n </div>\n <div style={styles.compactDate}>\n {currentDate}\n {currentZone ? ` · ${currentZone}` : ''}\n </div>\n </div>\n\n <div style={styles.compactStats}>\n {today && (\n <span style={styles.compactStat}>\n <WeatherIcon condition={today.icon} width={16} height={16} title=\"Today's high and low\" />\n {today.max}&deg; / {today.min}&deg;\n <RainBadge probability={today.precipitationProbabilityMax} compact />\n </span>\n )}\n <span style={styles.compactStat}>\n <WindIcon width={16} height={16} title=\"Wind speed\" />\n {data.current.windSpeed !== undefined ? `${Math.round(data.current.windSpeed)} ${windSpeedUnitLabel}` : '—'}\n </span>\n </div>\n </div>\n </div>\n\n {upcomingDays.length > 0 && (\n <div style={styles.upcoming}>\n {upcomingDays.map((day) => (\n <div key={day.date} style={styles.upcomingDay} title={upcomingDayDetail(day, windSpeedUnitLabel)}>\n <span style={styles.upcomingDayName}>{upcomingDayLabel(day.date)}</span>\n <WeatherIcon condition={day.icon} width={14} height={14} />\n <span style={styles.upcomingTemps}>\n <strong>{day.max}&deg;</strong>\n <span style={{ opacity: 0.6 }}> / {day.min}&deg;</span>\n </span>\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={{ ...styles.rain, fontSize: 10 }}>{day.precipitationProbabilityMax}%</span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div className={props.className} style={{ ...styles.root, ...props.style }}>\n <div>\n <h3 style={{ margin: 0, fontSize: 20 }}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </h3>\n <div style={styles.tz}>\n {currentDate} &middot; {currentTime}\n {timezone ? ` · ${timezone}` : ''}\n </div>\n <div style={{ ...styles.row, marginTop: 8 }}>\n <WeatherIcon condition={data.current.icon} width={28} height={28} title=\"Current weather\" />\n <strong style={{ fontSize: 28 }}>{data.current.temperature}</strong>\n {renderUnitSwitch(14)}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next hours</h4>\n <div style={styles.hours}>\n {data.hourly.slice(0, props.forecastHours ?? 12).map((hour) => (\n <div key={hour.time} style={styles.hourCard}>\n <div style={{ fontSize: 12 }}>{new Date(hour.time).toLocaleTimeString([], { hour: 'numeric' })}</div>\n <WeatherIcon condition={hour.icon} width={20} height={20} style={{ margin: '6px auto' }} />\n <div>{hour.temperature}&deg;</div>\n {hour.precipitationProbability !== undefined && hour.precipitationProbability > 0 && (\n <div style={{ ...styles.rain, fontSize: 12 }}>{hour.precipitationProbability}%</div>\n )}\n </div>\n ))}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next days</h4>\n <div>\n {data.daily.map((day) => (\n <div key={day.date} style={styles.dayRow}>\n <div style={{ width: 120 }}>{new Date(day.date).toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' })}</div>\n <WeatherIcon condition={day.icon} width={20} height={20} />\n <div>\n {day.min}&deg; / {day.max}&deg;\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={styles.rain}> &middot; {day.precipitationProbabilityMax}%</span>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Weather forecast widget. When `locations` is provided, one forecast is\n * rendered per location; otherwise a single auto-detected forecast is shown.\n */\nexport function WeatherForecast(props: Props) {\n const { locations, ...rest } = props;\n\n if (locations && locations.length > 0) {\n const gap = props.compact ? 8 : 16;\n return (\n <div style={{ ...styles.list, gap }}>\n {locations.map((loc, index) => (\n <SingleWeatherForecast\n key={`${loc.label ?? ''}:${loc.latitude ?? ''}:${loc.longitude ?? ''}:${index}`}\n {...rest}\n latitude={typeof loc.latitude === 'number' ? loc.latitude : undefined}\n longitude={typeof loc.longitude === 'number' ? loc.longitude : undefined}\n location={{ city: loc.label, timezone: loc.timezone }}\n />\n ))}\n </div>\n );\n }\n\n return <SingleWeatherForecast {...rest} />;\n}\n"],"mappings":"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","names":["res","data","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/weatherCodes.ts","../src/lib/cache.ts","../src/api/http.ts","../src/api/geolocation.ts","../src/api/forecast.ts","../src/hooks/useWeatherForecast.ts","../src/icons/BaseIcon.tsx","../src/icons/weather-icons.tsx","../src/icons/WeatherIcon.tsx","../src/components/WeatherForecast.tsx"],"sourcesContent":["export * from './types';\nexport * from './weatherCodes';\nexport { clearWeatherForecastCache } from './lib/cache';\nexport * from './api/geolocation';\nexport * from './api/forecast';\nexport * from './hooks/useWeatherForecast';\nexport * from './icons/WeatherIcon';\nexport * from './components/WeatherForecast';\n","import type { WeatherCondition } from './types';\n\nexport function getWeatherIcon(code: number, isDay = 1): WeatherCondition {\n if (code === 0) return 'sun';\n if ([1, 2].includes(code)) return isDay ? 'cloud-sun' : 'clouds';\n if (code === 3) return 'clouds';\n if ([45, 48].includes(code)) return 'cloud-fog';\n if ([51, 53, 55, 56, 57].includes(code)) return 'cloud-drizzle';\n if ([61, 63].includes(code)) return 'cloud-showers';\n if ([65, 66, 67, 80, 81, 82].includes(code)) return 'cloud-showers-heavy';\n if ([71, 73, 75, 77, 85, 86].includes(code)) return 'cloud-snow';\n if (code === 95) return 'cloud-bolt';\n if ([96, 99].includes(code)) return 'cloud-hail';\n return 'clouds';\n}\n","const CACHE_PREFIX = 'weather-forecast-cache:';\nconst CACHE_TTL_MS = 30 * 60 * 1000;\n\ntype CacheEntry<T> = {\n timestamp: number;\n data: T;\n};\n\nfunction getStorage(): Storage | null {\n if (typeof window === 'undefined' || !window.localStorage) return null;\n return window.localStorage;\n}\n\n/** Returns the cached value for `key` if it was written within the last 30 minutes. */\nexport function readCachedForecast<T>(key: string): T | null {\n const storage = getStorage();\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(CACHE_PREFIX + key);\n if (!raw) return null;\n\n const entry = JSON.parse(raw) as CacheEntry<T>;\n if (Date.now() - entry.timestamp > CACHE_TTL_MS) {\n storage.removeItem(CACHE_PREFIX + key);\n return null;\n }\n\n return entry.data;\n } catch {\n return null;\n }\n}\n\nexport function writeCachedForecast<T>(key: string, data: T): void {\n const storage = getStorage();\n if (!storage) return;\n\n try {\n const entry: CacheEntry<T> = { timestamp: Date.now(), data };\n storage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));\n } catch {\n // Storage full or unavailable (e.g. private browsing) — safe to ignore,\n // the next call will simply hit the network again.\n }\n}\n\n/** Clears all cached forecasts. Mainly useful for tests/debugging. */\nexport function clearWeatherForecastCache(): void {\n const storage = getStorage();\n if (!storage) return;\n\n for (let i = storage.length - 1; i >= 0; i--) {\n const key = storage.key(i);\n if (key?.startsWith(CACHE_PREFIX)) storage.removeItem(key);\n }\n}\n","import grab from 'grab-url';\n\n/**\n * Shared JSON transport for the package's upstreams (the geo worker, ipapi.co\n * and Open-Meteo).\n *\n * `grab-url` is the HTTP client used across this repo: it parses the JSON,\n * applies a timeout and can repeat a failed request itself. Unlike `fetch` it\n * resolves rather than rejects when a request fails, handing back the message\n * on `.error`, so every call site has to inspect the payload -- that check\n * lives here so the callers can just `await` a typed body.\n */\nexport type GrabJsonOptions = {\n headers?: Record<string, string>;\n /** default=0 Times grab-url repeats a request that failed at the transport level. */\n retryAttempts?: number;\n /** default=15 Seconds before the request is aborted. */\n timeout?: number;\n};\n\n/**\n * Both upstreams can answer HTTP 200 with an error flag instead of data:\n * ipapi.co does it for rate limits, Open-Meteo for a malformed query.\n */\ntype ErrorPayload = { error?: boolean | string; reason?: string };\n\n/**\n * grab-url reports a failed request as `HTTP error: 429 Too Many Requests`.\n * That prefix is noise once the message is nested under `<label> failed:`,\n * so drop it and keep the status.\n */\nfunction describeFailure(error: string): string {\n return error.replace(/^HTTP error:\\s*/i, '').trim() || 'unknown error';\n}\n\n/**\n * Request a JSON body, throwing a labelled error for any failure.\n *\n * @param url Full URL to request.\n * @param label Prefix for the thrown message, e.g. `ipapi.co lookup`.\n * @param options Transport options passed through to grab-url.\n */\nexport async function grabJson<T>(\n url: string,\n label: string,\n options: GrabJsonOptions = {}\n): Promise<T> {\n const { headers, retryAttempts = 0, timeout = 15 } = options;\n\n const data = (await grab(url, { headers, retryAttempts, timeout })) as\n | (T & ErrorPayload)\n | null\n | undefined;\n\n if (typeof data?.error === 'string') {\n throw new Error(`${label} failed: ${describeFailure(data.error)}`);\n }\n\n if (data?.error === true) {\n throw new Error(`${label} failed: ${data.reason ?? 'unknown error'}`);\n }\n\n if (!data) throw new Error(`${label} failed: empty response`);\n\n return data as T;\n}\n","import type { WeatherLocation } from '../types';\nimport { grabJson } from './http';\n\nconst DEFAULT_IP_API_URL = 'https://ipapi.co';\n\n/**\n * ipapi.co's free tier rate-limits by IP and answers a 200 carrying\n * `{ error: true, reason: 'RateLimited' }`, and a geo worker can drop a\n * request while it cold-starts. Both clear on their own, so the lookup is\n * repeated before the error reaches the caller.\n */\nconst DEFAULT_ATTEMPTS = 3;\nconst DEFAULT_RETRY_DELAY_MS = 500;\n\nexport type GeolocationOptions = {\n /** default=3 How many times the lookup is tried before the error is thrown. */\n attempts?: number;\n /** default=500 Milliseconds to wait before the second try; each further wait grows by that much. */\n retryDelay?: number;\n};\n\ntype IpApiResponse = {\n city?: string;\n region?: string;\n country_name?: string;\n timezone?: string;\n latitude?: number;\n longitude?: number;\n error?: boolean;\n reason?: string;\n};\n\ntype GeoWorkerResponse = {\n city?: string;\n region?: string;\n country?: string;\n timezone?: string;\n latitude?: number | string;\n longitude?: number | string;\n};\n\nconst wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\nasync function lookupViaWorker(geoEndpoint: string, ip?: string): Promise<WeatherLocation> {\n const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;\n const data = await grabJson<GeoWorkerResponse>(url, 'Geolocation worker lookup');\n\n return {\n city: data.city,\n region: data.region,\n country: data.country,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n}\n\nasync function lookupViaIpApi(ip?: string): Promise<WeatherLocation> {\n const url = `${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ''}json/`;\n const data = await grabJson<IpApiResponse>(url, 'ipapi.co lookup');\n\n return {\n city: data.city,\n region: data.region,\n country: data.country_name,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n}\n\n/**\n * Resolve the caller's location, either from a deployed geo worker or from\n * ipapi.co directly.\n *\n * A failed lookup is repeated with a growing delay and only the last error is\n * thrown, so a single rate-limited or cold-started response no longer takes\n * the whole forecast down with it.\n *\n * @param geoEndpoint URL of a deployed geo worker; omit to use ipapi.co.\n * @param ip Look up this IP instead of the caller's own.\n * @param options How often to repeat the lookup, and how long to wait between tries.\n */\nexport async function getClientLocation(\n geoEndpoint?: string,\n ip?: string,\n options: GeolocationOptions = {}\n): Promise<WeatherLocation> {\n const attempts = Math.max(1, Math.trunc(options.attempts ?? DEFAULT_ATTEMPTS));\n const retryDelay = Math.max(0, options.retryDelay ?? DEFAULT_RETRY_DELAY_MS);\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n return geoEndpoint ? await lookupViaWorker(geoEndpoint, ip) : await lookupViaIpApi(ip);\n } catch (error) {\n lastError = error;\n if (attempt < attempts && retryDelay > 0) await wait(retryDelay * attempt);\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n","import type { WeatherForecastData, WeatherForecastOptions, WeatherLocation } from '../types';\nimport { getWeatherIcon } from '../weatherCodes';\nimport { getClientLocation } from './geolocation';\nimport { grabJson } from './http';\nimport { readCachedForecast, writeCachedForecast } from '../lib/cache';\n\nfunction buildUrl(latitude: number, longitude: number, options: WeatherForecastOptions) {\n const params = new URLSearchParams({\n latitude: String(latitude),\n longitude: String(longitude),\n timezone: options.location?.timezone || 'auto',\n temperature_unit: options.temperatureUnit || 'fahrenheit',\n wind_speed_unit: options.windSpeedUnit || 'mph',\n forecast_days: String(options.forecastDays ?? 5),\n forecast_hours: String(options.forecastHours ?? 24),\n current: [\n 'temperature_2m',\n 'weather_code',\n 'is_day',\n 'rain',\n 'showers',\n 'snowfall',\n 'wind_speed_10m',\n ].join(','),\n hourly: [\n 'temperature_2m',\n 'weather_code',\n 'precipitation_probability',\n 'rain',\n 'showers',\n 'snowfall',\n ].join(','),\n daily: [\n 'temperature_2m_max',\n 'temperature_2m_min',\n 'weather_code',\n 'precipitation_probability_max',\n 'precipitation_sum',\n 'wind_speed_10m_max',\n ].join(','),\n });\n\n return `https://api.open-meteo.com/v1/forecast?${params.toString()}`;\n}\n\nexport async function getWeatherForecast(\n options: WeatherForecastOptions = {}\n): Promise<WeatherForecastData> {\n let location: WeatherLocation;\n\n if (typeof options.latitude === 'number' && typeof options.longitude === 'number') {\n location = {\n ...options.location,\n latitude: options.latitude,\n longitude: options.longitude,\n };\n } else {\n location = await getClientLocation(options.geoEndpoint, options.ip);\n }\n\n const url = buildUrl(location.latitude, location.longitude, { ...options, location });\n\n const cached = readCachedForecast<WeatherForecastData>(url);\n if (cached) return cached;\n\n const data = await grabJson<any>(url, 'Weather request', {\n headers: { 'Content-Type': 'application/json' },\n retryAttempts: 1,\n });\n\n if (!data?.current || !data?.hourly || !data?.daily) {\n throw new Error('Invalid weather response');\n }\n\n const result: WeatherForecastData = {\n location: { ...location, timezone: data.timezone || location.timezone },\n current: {\n time: data.current.time,\n temperature: Math.round(data.current.temperature_2m),\n weatherCode: data.current.weather_code,\n icon: getWeatherIcon(data.current.weather_code, data.current.is_day),\n isDay: data.current.is_day,\n rain: data.current.rain,\n showers: data.current.showers,\n snowfall: data.current.snowfall,\n windSpeed: data.current.wind_speed_10m,\n },\n hourly: data.hourly.time.map((time: string, index: number) => ({\n time,\n temperature: Math.round(data.hourly.temperature_2m[index]),\n weatherCode: data.hourly.weather_code[index],\n precipitationProbability: data.hourly.precipitation_probability?.[index],\n rain: data.hourly.rain?.[index],\n showers: data.hourly.showers?.[index],\n snowfall: data.hourly.snowfall?.[index],\n icon: getWeatherIcon(data.hourly.weather_code[index]),\n })),\n daily: data.daily.time.map((date: string, index: number) => ({\n date,\n min: Math.round(data.daily.temperature_2m_min[index]),\n max: Math.round(data.daily.temperature_2m_max[index]),\n weatherCode: data.daily.weather_code[index],\n precipitationProbabilityMax: data.daily.precipitation_probability_max?.[index],\n precipitationSum: data.daily.precipitation_sum?.[index],\n windSpeedMax: data.daily.wind_speed_10m_max?.[index],\n icon: getWeatherIcon(data.daily.weather_code[index]),\n })),\n };\n\n writeCachedForecast(url, result);\n return result;\n}\n","import { useEffect, useState } from 'react';\nimport type { WeatherForecastData, WeatherForecastOptions } from '../types';\nimport { getWeatherForecast } from '../api/forecast';\n\nexport function useWeatherForecast(options: WeatherForecastOptions = {}) {\n const [data, setData] = useState<WeatherForecastData | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let active = true;\n setLoading(true);\n setError(null);\n\n getWeatherForecast(options)\n .then((result) => { if (active) setData(result); })\n .catch((err) => { if (active) setError(err instanceof Error ? err : new Error('Unknown error')); })\n .finally(() => { if (active) setLoading(false); });\n\n return () => { active = false; };\n }, [\n options.latitude,\n options.longitude,\n options.geoEndpoint,\n options.ip,\n options.forecastDays,\n options.forecastHours,\n options.temperatureUnit,\n options.windSpeedUnit,\n options.location?.timezone,\n ]);\n\n return { data, error, loading };\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\n\nexport function BaseIcon({ title, children, ...props }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n role={title ? 'img' : 'presentation'}\n aria-hidden={title ? undefined : true}\n aria-label={title}\n {...props}\n >\n {title ? <title>{title}</title> : null}\n {children}\n </svg>\n );\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\nimport { BaseIcon } from './BaseIcon';\n\nexport const CloudBoltIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M7 18a4.815 4.815 0 0 1-3.327-1.318A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 0 7h-1m-4.75-5L11 17.5h3.375l-2.25 4.5\" /></BaseIcon>\n);\nexport const CloudDrizzleIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 18v1m-3-4v1m6-1v1m2 3v1M7 19v1m-3-3.034a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudFogIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M5 21h7m4 0h3M8 17h8\" /></BaseIcon>\n);\nexport const CloudHailIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 17.45v.1m-1.5 3.9v.1m10.5-4.1v.1m-1.5 3.9v.1m-3-4.1v.1m-1.5 3.9v.1m10.475-4.575A3.5 3.5 0 0 0 19 11h-1c.146-.65.148-1.32.006-1.97a4.784 4.784 0 0 0-.831-1.823 5.36 5.36 0 0 0-1.544-1.398 5.964 5.964 0 0 0-2.02-.759 6.224 6.224 0 0 0-2.19-.006 5.971 5.971 0 0 0-2.024.749C8.157 6.533 7.295 7.687 7 9a4.815 4.815 0 0 0-3.327 1.318A4.403 4.403 0 0 0 2.295 13.5c0 1.194.496 2.338 1.378 3.182.105.1.214.195.327.284m4.5.534a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm10.5-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-3-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z\" /></BaseIcon>\n);\nexport const CloudShowersIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M12 13v2m0 3v2m4-5v2m0 3v2m-8-7v2m0 3v2\" /></BaseIcon>\n);\nexport const CloudShowersHeavyIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M16 15v6m-8-6v6m4-8v6\" /></BaseIcon>\n);\nexport const CloudSleetIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M13.5 17.45a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm.5 3.9a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm-6.327-4.868.692-.723-.692.723ZM7 9v1a1 1 0 0 0 .976-.781L7 9Zm2.397-3.207.512.858-.512-.858ZM18 11l-.976-.219A1 1 0 0 0 18 12v-1Zm2.768 5.268a1 1 0 0 0 1.414 1.414l-.707-.707-.707-.707ZM3.379 17.75a1 1 0 0 0 1.242-1.567L4 16.966l-.62.784Zm5.55-.379a1 1 0 0 0-1.857-.742L8 17l.928.371ZM5.071 21.63a1 1 0 0 0 1.856.742L6 22l-.928-.371Zm13.857-4.258a1 1 0 0 0-1.857-.742L18 17l.928.371Zm-3.857 4.258a1 1 0 0 0 1.857.742L16 22l-.928-.371ZM13 17.5h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2a1.5 1.5 0 0 0 1.5 1.5v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2a1.5 1.5 0 0 0-1.5-1.5v1Zm0 .45h-1v.1h2v-.1h-1Zm-1 4.05h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2A1.5 1.5 0 0 0 11 23v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2A1.5 1.5 0 0 0 11 20v1Zm0 .45h-1v.1h2v-.1h-1Zm-7.327-4.768.692-.723a3.403 3.403 0 0 1-1.07-2.459h-2c0 1.473.613 2.877 1.687 3.905l.691-.723ZM2.295 13.5h1c0-.914.38-1.8 1.07-2.46l-.692-.722-.69-.723A5.403 5.403 0 0 0 1.294 13.5h1Zm1.378-3.182.692.723A3.815 3.815 0 0 1 7 10V8c-1.499 0-2.945.57-4.018 1.595l.691.723ZM7 9l.976.219c.23-1.024.91-1.956 1.933-2.568l-.512-.858-.513-.859c-1.456.87-2.5 2.245-2.86 3.847L7 9Zm2.397-3.207.512.858a4.972 4.972 0 0 1 1.686-.622l-.173-.985-.174-.985a6.971 6.971 0 0 0-2.364.875l.513.859Zm2.025-.749.173.985a5.223 5.223 0 0 1 1.837.005l.18-.984.178-.984a7.223 7.223 0 0 0-2.542-.007l.174.985Zm2.19.006-.18.984c.606.11 1.177.326 1.681.631l.518-.856.518-.855a6.963 6.963 0 0 0-2.359-.888l-.179.984Zm2.02.76-.519.855c.505.305.93.693 1.257 1.136l.805-.594.805-.594a6.361 6.361 0 0 0-1.831-1.659l-.518.855Zm1.543 1.397-.805.594c.326.442.549.932.66 1.441l.976-.212.978-.213a5.786 5.786 0 0 0-1.005-2.204l-.804.594Zm.831 1.823-.977.212c.111.509.11 1.031-.005 1.539L18 11l.976.219c.178-.792.18-1.609.008-2.402l-.978.213ZM18 11v1h1v-2h-1v1Zm1 0v1a2.5 2.5 0 0 1 1.768.732l.707-.707.707-.707A4.5 4.5 0 0 0 19 10v1Zm2.475 1.025-.707.707A2.5 2.5 0 0 1 21.5 14.5h2a4.5 4.5 0 0 0-1.318-3.182l-.707.707ZM22.5 14.5h-1a2.5 2.5 0 0 1-.732 1.768l.707.707.707.707A4.5 4.5 0 0 0 23.5 14.5h-1ZM4 16.966l.62-.784a3.684 3.684 0 0 1-.255-.223l-.692.723-.69.723c.126.121.259.237.396.345L4 16.966ZM8 17l-.928-.371-2 5L6 22l.928.371 2-5L8 17Zm10 0-.928-.371-2 5L16 22l.928.371 2-5L18 17Z\" /></BaseIcon>\n);\nexport const CloudSnowIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 15h2m-1-1v2m5-1h2m-1-1v2M5 21h2m-1-1v2m5-2h2m-1-1v2m5 0h2m-1-1v2M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudSunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M10.201 19.997c-1.977 0-3.58-1.544-3.58-3.448 0-1.903 1.603-3.446 3.58-3.446.303-1.355 1.38-2.461 2.827-2.902 1.445-.44 3.042-.148 4.186.77 1.144.915 1.663 2.312 1.361 3.667h.761c1.472 0 2.664 1.2 2.664 2.68A2.672 2.672 0 0 1 19.336 20h-9.134m2.66-12.934a4.166 4.166 0 0 0-2.677-1.732m2.677 1.732 2.744-1.892m-2.744 1.892c.55.796.802 1.754.723 2.709m-3.4-4.44a4.167 4.167 0 0 0-3.118.667m3.118-.668.602-3.278m-3.72 3.946A4.167 4.167 0 0 0 5.334 8.68m1.733-2.678L5.174 3.26m.16 5.42c-.2 1.088.041 2.21.669 3.12m-.669-3.12-3.278-.601m3.947 3.72-2.744 1.893m2.744-1.893a4.167 4.167 0 0 0 2.174 1.607\" /></BaseIcon>\n);\nexport const CloudsIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8.958 21.05C6.772 21.05 5 19.344 5 17.24s1.772-3.81 3.958-3.81c.335-1.498 1.525-2.72 3.124-3.207 1.598-.487 3.363-.164 4.628.85 1.264 1.011 1.837 2.556 1.504 4.053h.842A2.953 2.953 0 0 1 22 18.09a2.954 2.954 0 0 1-2.945 2.964H8.958M18 8c-.5-2.5-3.532-3.401-5.5-2.5a3.117 3.117 0 0 0-2.11-2.312c-1.352-.411-2.845-.138-3.915.72-1.07.855-1.554 2.161-1.273 3.428h-.711A2.498 2.498 0 0 0 2 9.843c0 1.094.695 2.023 1.666 2.366\" /></BaseIcon>\n);\nexport const SnowflakeIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"m9.25 4-1 2H6m-2.25 4L5 12m0 0-1.25 2M5 12h3m-3 0H2.5m17.75-2L19 12l1.25 2M6 18h2.25l1 2M14 9h-4m4 0 3-5m-3 5 2 3m-6-3L7 4m3 5-2 3m0 0 2 3m0 0-3 5m3-5h4m2-3-2 3m2-3h5.5M14 15l3 5M14.75 4l1 2H18m0 12h-2.25l-1 2\" /></BaseIcon>\n);\nexport const SunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 4V2m0 20v-2m10-8h-2M4 12H2m15.657-5.657 1.414-1.414M4.93 19.07l1.414-1.414m12.728 1.414-1.414-1.414M6.343 6.343 4.93 4.929M7 12a5 5 0 1 0 10 0 5 5 0 0 0-10 0Z\" /></BaseIcon>\n);\nexport const WindIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2\" /></BaseIcon>\n);\n","import React from 'react';\nimport type { IconProps, WeatherCondition } from '../types';\nimport {\n CloudBoltIcon,\n CloudDrizzleIcon,\n CloudFogIcon,\n CloudHailIcon,\n CloudShowersHeavyIcon,\n CloudShowersIcon,\n CloudSleetIcon,\n CloudSnowIcon,\n CloudSunIcon,\n CloudsIcon,\n SnowflakeIcon,\n SunIcon,\n} from './weather-icons';\n\nconst iconMap: Record<WeatherCondition, React.ComponentType<IconProps>> = {\n sun: SunIcon,\n 'cloud-sun': CloudSunIcon,\n clouds: CloudsIcon,\n 'cloud-fog': CloudFogIcon,\n 'cloud-drizzle': CloudDrizzleIcon,\n 'cloud-showers': CloudShowersIcon,\n 'cloud-showers-heavy': CloudShowersHeavyIcon,\n 'cloud-sleet': CloudSleetIcon,\n 'cloud-snow': CloudSnowIcon,\n snowflake: SnowflakeIcon,\n 'cloud-bolt': CloudBoltIcon,\n 'cloud-hail': CloudHailIcon,\n};\n\nexport function WeatherIcon({ condition, ...props }: IconProps & { condition: WeatherCondition }) {\n const Icon = iconMap[condition] || CloudsIcon;\n return <Icon {...props} />;\n}\n","import React, { useEffect, useState } from 'react';\nimport { useWeatherForecast } from '../hooks/useWeatherForecast';\nimport type { DailyWeather, TemperatureUnit, WeatherForecastOptions, WindSpeedUnit } from '../types';\nimport { WeatherIcon } from '../icons/WeatherIcon';\nimport { WindIcon } from '../icons/weather-icons';\n\ntype Props = WeatherForecastOptions & {\n className?: string;\n style?: React.CSSProperties;\n compact?: boolean;\n};\n\nconst WIND_SPEED_UNIT_LABELS: Record<WindSpeedUnit, string> = {\n kmh: 'km/h',\n mph: 'mph',\n ms: 'm/s',\n kn: 'kn',\n};\n\n// Format a real instant in a specific IANA timezone, falling back to the\n// browser's local zone if the timezone string is missing or invalid.\nfunction formatInZone(instant: Date, timezone: string | undefined, options: Intl.DateTimeFormatOptions): string {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined }).format(instant);\n } catch {\n return new Intl.DateTimeFormat([], options).format(instant);\n }\n}\n\n// The clock is shown with the hour and minute large and the seconds (plus any\n// AM/PM marker) trailing in a smaller, dimmer size, so split it into those two\n// halves. Falls back to one undivided string if the locale's parts are not\n// where we expect them.\nfunction clockParts(instant: Date, timezone: string | undefined): { main: string; tail: string } {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: '2-digit', second: '2-digit' };\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n const parts = format.formatToParts(instant);\n const part = (type: Intl.DateTimeFormatPartTypes) => parts.find((p) => p.type === type)?.value;\n const hour = part('hour');\n const minute = part('minute');\n const second = part('second');\n if (!hour || !minute || !second) return { main: format.format(instant), tail: '' };\n const dayPeriod = part('dayPeriod');\n return { main: `${hour}:${minute}`, tail: `:${second}${dayPeriod ? ` ${dayPeriod}` : ''}` };\n}\n\n// Just the zone abbreviation (e.g. \"GMT+2\"), so it can be shown apart from the\n// clock itself.\nfunction zoneLabel(instant: Date, timezone: string | undefined): string {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', timeZoneName: 'short' };\n try {\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n return format.formatToParts(instant).find((part) => part.type === 'timeZoneName')?.value ?? '';\n } catch {\n return '';\n }\n}\n\n// Daily dates arrive as plain \"YYYY-MM-DD\" calendar days; `new Date(...)` would\n// read those as UTC midnight and roll back a day in western timezones, so build\n// the date from its parts and let it stay a local calendar day.\nfunction parseCalendarDate(date: string): Date {\n const [year, month, day] = date.split('-').map(Number);\n if (!year || !month || !day) return new Date(date);\n return new Date(year, month - 1, day);\n}\n\n// The upcoming days sit in narrow columns in the card, so they are labelled\n// with the abbreviated weekday (\"Fri\") rather than a full name or \"Tomorrow\".\nfunction upcomingDayLabel(date: string): string {\n return parseCalendarDate(date).toLocaleDateString([], { weekday: 'short' });\n}\n\n// Daily totals that no longer have a column of their own in the card layout are\n// kept as hover text so the detail is still reachable.\nfunction upcomingDayDetail(day: DailyWeather, windSpeedUnitLabel: string): string | undefined {\n const details: string[] = [];\n if (day.precipitationSum !== undefined) details.push(`${day.precipitationSum.toFixed(1)} mm`);\n if (day.windSpeedMax !== undefined) details.push(`${day.windSpeedMax.toFixed(1)} ${windSpeedUnitLabel} wind`);\n return details.length > 0 ? details.join(' · ') : undefined;\n}\n\nconst styles = {\n root: {\n fontFamily: 'system-ui, sans-serif',\n border: '1px solid #e5e7eb',\n borderRadius: 12,\n padding: 16,\n background: '#fff',\n color: '#111827',\n maxWidth: 640,\n } as React.CSSProperties,\n row: { display: 'flex', alignItems: 'center', gap: 8 } as React.CSSProperties,\n hours: { display: 'flex', gap: 12, overflowX: 'auto', paddingBottom: 4 } as React.CSSProperties,\n hourCard: { minWidth: 72, textAlign: 'center' as const, padding: 8, borderRadius: 8, background: '#f9fafb' },\n dayRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, padding: '8px 0', borderBottom: '1px solid #f3f4f6' } as React.CSSProperties,\n // Fluid: fills whatever container it is dropped into (e.g. the full\n // sidebar/column width on desktop), with generous, even padding. Current\n // conditions sit on the left and the upcoming days on the right; the two\n // halves wrap into a stack when the container gets narrow.\n compactRoot: {\n fontFamily: 'system-ui, sans-serif',\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'wrap',\n alignItems: 'stretch',\n gap: 24,\n padding: '20px 24px',\n borderRadius: 16,\n width: '100%',\n boxSizing: 'border-box',\n } as React.CSSProperties,\n // Current conditions form two spread-out rows down the left side of the\n // card (rather than five stacked lines), so the card stays short and each\n // row uses the full available width.\n compactBody: { display: 'flex', flexDirection: 'column', justifyContent: 'space-between', gap: 20, flex: '1.6 1 240px', minWidth: 0 } as React.CSSProperties,\n compactRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '12px 16px' } as React.CSSProperties,\n compactCity: { fontSize: 15, fontWeight: 700, lineHeight: 1.2 } as React.CSSProperties,\n compactHeader: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 12 } as React.CSSProperties,\n compactTemp: { fontSize: 32, fontWeight: 600, lineHeight: 1 } as React.CSSProperties,\n compactTime: { fontSize: 26, fontWeight: 600, lineHeight: 1.1 } as React.CSSProperties,\n compactSeconds: { fontSize: 13, fontWeight: 500, opacity: 0.6, marginLeft: 2 } as React.CSSProperties,\n compactDate: { fontSize: 11, opacity: 0.7, marginTop: 5 } as React.CSSProperties,\n compactStats: { display: 'flex', flexWrap: 'wrap', gap: '8px 18px', fontSize: 12, opacity: 0.9 } as React.CSSProperties,\n // Three equal columns on the right side of the card, each stacking\n // weekday / icon / high-low, vertically centered against the current\n // conditions and nudged over behind a divider so the two halves read as\n // separate sections.\n upcoming: {\n display: 'grid',\n gridTemplateColumns: 'repeat(3, 1fr)',\n alignContent: 'center',\n gap: 14,\n flex: '1 1 190px',\n minWidth: 0,\n paddingLeft: 22,\n borderLeft: '1px solid currentColor',\n borderLeftColor: 'rgba(128,128,128,0.25)',\n fontSize: 11,\n } as React.CSSProperties,\n upcomingDay: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 5, textAlign: 'center' } as React.CSSProperties,\n upcomingDayName: { opacity: 0.7, whiteSpace: 'nowrap' } as React.CSSProperties,\n upcomingTemps: { whiteSpace: 'nowrap' } as React.CSSProperties,\n compactStat: { display: 'flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' } as React.CSSProperties,\n unitSwitch: { display: 'inline-flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' } as React.CSSProperties,\n rain: { color: '#2563eb', whiteSpace: 'nowrap' } as React.CSSProperties,\n tz: { fontSize: 12, opacity: 0.7, marginTop: 2 } as React.CSSProperties,\n list: { display: 'flex', flexDirection: 'column' } as React.CSSProperties,\n};\n\n// Precipitation chance is only surfaced once it represents a meaningful risk.\nconst RAIN_RISK_THRESHOLD = 2;\n\nfunction RainBadge({ probability, compact }: { probability?: number; compact?: boolean }) {\n if (probability === undefined || probability === null || probability <= RAIN_RISK_THRESHOLD) return null;\n return <span style={styles.rain}>{compact ? ` · ${probability}%` : ` · ${probability}% rain`}</span>;\n}\n\n/**\n * Renders one location's forecast: a live current time (ticking, in the\n * location's own timezone), a clickable degF / degC unit switch, high/low and\n * precipitation for the next hours and days.\n */\nfunction SingleWeatherForecast(props: Props) {\n // Temperature unit is local state so the degF / degC switch can toggle it;\n // it is seeded from (and re-synced to) the temperatureUnit prop.\n const [unit, setUnit] = useState<TemperatureUnit>(props.temperatureUnit ?? 'fahrenheit');\n useEffect(() => {\n setUnit(props.temperatureUnit ?? 'fahrenheit');\n }, [props.temperatureUnit]);\n\n const { data, loading, error } = useWeatherForecast({ ...props, temperatureUnit: unit });\n\n // A ticking clock so the displayed current time stays accurate to the second.\n const [now, setNow] = useState(() => new Date());\n useEffect(() => {\n const id = setInterval(() => setNow(new Date()), 1000);\n return () => clearInterval(id);\n }, []);\n\n const toggleUnit = () => setUnit((u) => (u === 'celsius' ? 'fahrenheit' : 'celsius'));\n\n const renderUnitSwitch = (fontSize: number) => (\n <span\n role=\"button\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n toggleUnit();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggleUnit();\n }\n }}\n title=\"Switch temperature units\"\n aria-label={`Switch temperature units, currently ${unit === 'celsius' ? 'Celsius' : 'Fahrenheit'}`}\n style={{ ...styles.unitSwitch, fontSize }}\n >\n <span style={{ fontWeight: unit === 'fahrenheit' ? 700 : 400, opacity: unit === 'fahrenheit' ? 1 : 0.45 }}>&deg;F</span>\n <span style={{ opacity: 0.4, margin: '0 4px' }}>|</span>\n <span style={{ fontWeight: unit === 'celsius' ? 700 : 400, opacity: unit === 'celsius' ? 1 : 0.45 }}>&deg;C</span>\n </span>\n );\n\n // Keep showing the last forecast while a unit switch refetches in the\n // background; only suppress output on the very first load.\n if (loading && !data) return null;\n if (error) return <div className={props.className} style={props.style}>Error: {error.message}</div>;\n if (!data) return <div className={props.className} style={props.style}>No forecast available.</div>;\n\n const timezone = data.location?.timezone;\n const currentDate = formatInZone(now, timezone, { weekday: 'short', month: 'short', day: 'numeric' });\n const currentTime = formatInZone(now, timezone, { hour: 'numeric', minute: '2-digit', second: '2-digit', timeZoneName: 'short' });\n // The compact layout shows the clock at a larger size, so the zone label is\n // split off onto the smaller date line to keep the time on one line.\n const currentClock = clockParts(now, timezone);\n const currentZone = zoneLabel(now, timezone);\n\n if (props.compact) {\n const today = data.daily[0];\n const windSpeedUnitLabel = WIND_SPEED_UNIT_LABELS[props.windSpeedUnit ?? 'mph'];\n // Today is already summarised in the card's own stats line, so the\n // upcoming-days section starts at tomorrow and shows the next three days.\n const upcomingDays = data.daily.slice(1, 4);\n\n return (\n <div className={props.className} style={{ ...styles.compactRoot, ...props.style }}>\n <div style={styles.compactBody}>\n <div style={styles.compactRow}>\n <div style={styles.compactCity}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </div>\n\n <div style={styles.compactHeader}>\n <WeatherIcon condition={data.current.icon} width={30} height={30} title=\"Current weather\" />\n <strong style={styles.compactTemp}>{data.current.temperature}</strong>\n {renderUnitSwitch(13)}\n </div>\n </div>\n\n <div style={styles.compactRow}>\n <div>\n <div style={styles.compactTime}>\n {currentClock.main}\n {currentClock.tail && <span style={styles.compactSeconds}>{currentClock.tail}</span>}\n </div>\n <div style={styles.compactDate}>\n {currentDate}\n {currentZone ? ` · ${currentZone}` : ''}\n </div>\n </div>\n\n <div style={styles.compactStats}>\n {today && (\n <span style={styles.compactStat}>\n <WeatherIcon condition={today.icon} width={16} height={16} title=\"Today's high and low\" />\n {today.max}&deg; / {today.min}&deg;\n <RainBadge probability={today.precipitationProbabilityMax} compact />\n </span>\n )}\n <span style={styles.compactStat}>\n <WindIcon width={16} height={16} title=\"Wind speed\" />\n {data.current.windSpeed !== undefined ? `${Math.round(data.current.windSpeed)} ${windSpeedUnitLabel}` : '—'}\n </span>\n </div>\n </div>\n </div>\n\n {upcomingDays.length > 0 && (\n <div style={styles.upcoming}>\n {upcomingDays.map((day) => (\n <div key={day.date} style={styles.upcomingDay} title={upcomingDayDetail(day, windSpeedUnitLabel)}>\n <span style={styles.upcomingDayName}>{upcomingDayLabel(day.date)}</span>\n <WeatherIcon condition={day.icon} width={14} height={14} />\n <span style={styles.upcomingTemps}>\n <strong>{day.max}&deg;</strong>\n <span style={{ opacity: 0.6 }}> / {day.min}&deg;</span>\n </span>\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={{ ...styles.rain, fontSize: 10 }}>{day.precipitationProbabilityMax}%</span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div className={props.className} style={{ ...styles.root, ...props.style }}>\n <div>\n <h3 style={{ margin: 0, fontSize: 20 }}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </h3>\n <div style={styles.tz}>\n {currentDate} &middot; {currentTime}\n {timezone ? ` · ${timezone}` : ''}\n </div>\n <div style={{ ...styles.row, marginTop: 8 }}>\n <WeatherIcon condition={data.current.icon} width={28} height={28} title=\"Current weather\" />\n <strong style={{ fontSize: 28 }}>{data.current.temperature}</strong>\n {renderUnitSwitch(14)}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next hours</h4>\n <div style={styles.hours}>\n {data.hourly.slice(0, props.forecastHours ?? 12).map((hour) => (\n <div key={hour.time} style={styles.hourCard}>\n <div style={{ fontSize: 12 }}>{new Date(hour.time).toLocaleTimeString([], { hour: 'numeric' })}</div>\n <WeatherIcon condition={hour.icon} width={20} height={20} style={{ margin: '6px auto' }} />\n <div>{hour.temperature}&deg;</div>\n {hour.precipitationProbability !== undefined && hour.precipitationProbability > 0 && (\n <div style={{ ...styles.rain, fontSize: 12 }}>{hour.precipitationProbability}%</div>\n )}\n </div>\n ))}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next days</h4>\n <div>\n {data.daily.map((day) => (\n <div key={day.date} style={styles.dayRow}>\n <div style={{ width: 120 }}>{new Date(day.date).toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' })}</div>\n <WeatherIcon condition={day.icon} width={20} height={20} />\n <div>\n {day.min}&deg; / {day.max}&deg;\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={styles.rain}> &middot; {day.precipitationProbabilityMax}%</span>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Weather forecast widget. When `locations` is provided, one forecast is\n * rendered per location; otherwise a single auto-detected forecast is shown.\n */\nexport function WeatherForecast(props: Props) {\n const { locations, ...rest } = props;\n\n if (locations && locations.length > 0) {\n const gap = props.compact ? 8 : 16;\n return (\n <div style={{ ...styles.list, gap }}>\n {locations.map((loc, index) => (\n <SingleWeatherForecast\n key={`${loc.label ?? ''}:${loc.latitude ?? ''}:${loc.longitude ?? ''}:${index}`}\n {...rest}\n latitude={typeof loc.latitude === 'number' ? loc.latitude : undefined}\n longitude={typeof loc.longitude === 'number' ? loc.longitude : undefined}\n location={{ city: loc.label, timezone: loc.timezone }}\n />\n ))}\n </div>\n );\n }\n\n return <SingleWeatherForecast {...rest} />;\n}\n"],"mappings":"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","names":["grab","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime"]}
package/dist/index.js CHANGED
@@ -54,29 +54,44 @@ function clearWeatherForecastCache() {
54
54
  }
55
55
  }
56
56
 
57
- // src/api/geolocation.ts
58
- var DEFAULT_IP_API_URL = "https://ipapi.co";
59
- async function getClientLocation(geoEndpoint, ip) {
60
- if (geoEndpoint) {
61
- const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;
62
- const res2 = await fetch(url);
63
- if (!res2.ok) throw new Error(`Geolocation worker lookup failed: ${res2.status}`);
64
- const data2 = await res2.json();
65
- return {
66
- city: data2.city,
67
- region: data2.region,
68
- country: data2.country,
69
- timezone: data2.timezone,
70
- latitude: Number(data2.latitude),
71
- longitude: Number(data2.longitude)
72
- };
57
+ // src/api/http.ts
58
+ import grab from "grab-url";
59
+ function describeFailure(error) {
60
+ return error.replace(/^HTTP error:\s*/i, "").trim() || "unknown error";
61
+ }
62
+ async function grabJson(url, label, options = {}) {
63
+ const { headers, retryAttempts = 0, timeout = 15 } = options;
64
+ const data = await grab(url, { headers, retryAttempts, timeout });
65
+ if (typeof data?.error === "string") {
66
+ throw new Error(`${label} failed: ${describeFailure(data.error)}`);
73
67
  }
74
- const res = await fetch(`${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ""}json/`);
75
- if (!res.ok) throw new Error(`ipapi.co lookup failed: ${res.status}`);
76
- const data = await res.json();
77
- if (data.error) {
78
- throw new Error(`ipapi.co lookup failed: ${data.reason ?? "unknown error"}`);
68
+ if (data?.error === true) {
69
+ throw new Error(`${label} failed: ${data.reason ?? "unknown error"}`);
79
70
  }
71
+ if (!data) throw new Error(`${label} failed: empty response`);
72
+ return data;
73
+ }
74
+
75
+ // src/api/geolocation.ts
76
+ var DEFAULT_IP_API_URL = "https://ipapi.co";
77
+ var DEFAULT_ATTEMPTS = 3;
78
+ var DEFAULT_RETRY_DELAY_MS = 500;
79
+ var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
80
+ async function lookupViaWorker(geoEndpoint, ip) {
81
+ const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;
82
+ const data = await grabJson(url, "Geolocation worker lookup");
83
+ return {
84
+ city: data.city,
85
+ region: data.region,
86
+ country: data.country,
87
+ timezone: data.timezone,
88
+ latitude: Number(data.latitude),
89
+ longitude: Number(data.longitude)
90
+ };
91
+ }
92
+ async function lookupViaIpApi(ip) {
93
+ const url = `${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ""}json/`;
94
+ const data = await grabJson(url, "ipapi.co lookup");
80
95
  return {
81
96
  city: data.city,
82
97
  region: data.region,
@@ -86,6 +101,20 @@ async function getClientLocation(geoEndpoint, ip) {
86
101
  longitude: Number(data.longitude)
87
102
  };
88
103
  }
104
+ async function getClientLocation(geoEndpoint, ip, options = {}) {
105
+ const attempts = Math.max(1, Math.trunc(options.attempts ?? DEFAULT_ATTEMPTS));
106
+ const retryDelay = Math.max(0, options.retryDelay ?? DEFAULT_RETRY_DELAY_MS);
107
+ let lastError;
108
+ for (let attempt = 1; attempt <= attempts; attempt++) {
109
+ try {
110
+ return geoEndpoint ? await lookupViaWorker(geoEndpoint, ip) : await lookupViaIpApi(ip);
111
+ } catch (error) {
112
+ lastError = error;
113
+ if (attempt < attempts && retryDelay > 0) await wait(retryDelay * attempt);
114
+ }
115
+ }
116
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
117
+ }
89
118
 
90
119
  // src/api/forecast.ts
91
120
  function buildUrl(latitude, longitude, options) {
@@ -139,11 +168,10 @@ async function getWeatherForecast(options = {}) {
139
168
  const url = buildUrl(location.latitude, location.longitude, { ...options, location });
140
169
  const cached = readCachedForecast(url);
141
170
  if (cached) return cached;
142
- const response = await fetch(url, { headers: { "Content-Type": "application/json" } });
143
- if (!response.ok) {
144
- throw new Error(`Weather request failed: ${response.status} ${response.statusText}`);
145
- }
146
- const data = await response.json();
171
+ const data = await grabJson(url, "Weather request", {
172
+ headers: { "Content-Type": "application/json" },
173
+ retryAttempts: 1
174
+ });
147
175
  if (!data?.current || !data?.hourly || !data?.daily) {
148
176
  throw new Error("Invalid weather response");
149
177
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/weatherCodes.ts","../src/lib/cache.ts","../src/api/geolocation.ts","../src/api/forecast.ts","../src/hooks/useWeatherForecast.ts","../src/icons/BaseIcon.tsx","../src/icons/weather-icons.tsx","../src/icons/WeatherIcon.tsx","../src/components/WeatherForecast.tsx"],"sourcesContent":["import type { WeatherCondition } from './types';\n\nexport function getWeatherIcon(code: number, isDay = 1): WeatherCondition {\n if (code === 0) return 'sun';\n if ([1, 2].includes(code)) return isDay ? 'cloud-sun' : 'clouds';\n if (code === 3) return 'clouds';\n if ([45, 48].includes(code)) return 'cloud-fog';\n if ([51, 53, 55, 56, 57].includes(code)) return 'cloud-drizzle';\n if ([61, 63].includes(code)) return 'cloud-showers';\n if ([65, 66, 67, 80, 81, 82].includes(code)) return 'cloud-showers-heavy';\n if ([71, 73, 75, 77, 85, 86].includes(code)) return 'cloud-snow';\n if (code === 95) return 'cloud-bolt';\n if ([96, 99].includes(code)) return 'cloud-hail';\n return 'clouds';\n}\n","const CACHE_PREFIX = 'weather-forecast-cache:';\nconst CACHE_TTL_MS = 30 * 60 * 1000;\n\ntype CacheEntry<T> = {\n timestamp: number;\n data: T;\n};\n\nfunction getStorage(): Storage | null {\n if (typeof window === 'undefined' || !window.localStorage) return null;\n return window.localStorage;\n}\n\n/** Returns the cached value for `key` if it was written within the last 30 minutes. */\nexport function readCachedForecast<T>(key: string): T | null {\n const storage = getStorage();\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(CACHE_PREFIX + key);\n if (!raw) return null;\n\n const entry = JSON.parse(raw) as CacheEntry<T>;\n if (Date.now() - entry.timestamp > CACHE_TTL_MS) {\n storage.removeItem(CACHE_PREFIX + key);\n return null;\n }\n\n return entry.data;\n } catch {\n return null;\n }\n}\n\nexport function writeCachedForecast<T>(key: string, data: T): void {\n const storage = getStorage();\n if (!storage) return;\n\n try {\n const entry: CacheEntry<T> = { timestamp: Date.now(), data };\n storage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));\n } catch {\n // Storage full or unavailable (e.g. private browsing) — safe to ignore,\n // the next call will simply hit the network again.\n }\n}\n\n/** Clears all cached forecasts. Mainly useful for tests/debugging. */\nexport function clearWeatherForecastCache(): void {\n const storage = getStorage();\n if (!storage) return;\n\n for (let i = storage.length - 1; i >= 0; i--) {\n const key = storage.key(i);\n if (key?.startsWith(CACHE_PREFIX)) storage.removeItem(key);\n }\n}\n","import type { WeatherLocation } from '../types';\n\nconst DEFAULT_IP_API_URL = 'https://ipapi.co';\n\ntype IpApiResponse = {\n city?: string;\n region?: string;\n country_name?: string;\n timezone?: string;\n latitude?: number;\n longitude?: number;\n error?: boolean;\n reason?: string;\n};\n\ntype GeoWorkerResponse = {\n city?: string;\n region?: string;\n country?: string;\n timezone?: string;\n latitude?: number | string;\n longitude?: number | string;\n};\n\nexport async function getClientLocation(geoEndpoint?: string, ip?: string): Promise<WeatherLocation> {\n if (geoEndpoint) {\n const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;\n const res = await fetch(url);\n if (!res.ok) throw new Error(`Geolocation worker lookup failed: ${res.status}`);\n const data: GeoWorkerResponse = await res.json();\n\n return {\n city: data.city,\n region: data.region,\n country: data.country,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n }\n\n const res = await fetch(`${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ''}json/`);\n if (!res.ok) throw new Error(`ipapi.co lookup failed: ${res.status}`);\n const data: IpApiResponse = await res.json();\n\n if (data.error) {\n throw new Error(`ipapi.co lookup failed: ${data.reason ?? 'unknown error'}`);\n }\n\n return {\n city: data.city,\n region: data.region,\n country: data.country_name,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n}\n","import type { WeatherForecastData, WeatherForecastOptions, WeatherLocation } from '../types';\nimport { getWeatherIcon } from '../weatherCodes';\nimport { getClientLocation } from './geolocation';\nimport { readCachedForecast, writeCachedForecast } from '../lib/cache';\n\nfunction buildUrl(latitude: number, longitude: number, options: WeatherForecastOptions) {\n const params = new URLSearchParams({\n latitude: String(latitude),\n longitude: String(longitude),\n timezone: options.location?.timezone || 'auto',\n temperature_unit: options.temperatureUnit || 'fahrenheit',\n wind_speed_unit: options.windSpeedUnit || 'mph',\n forecast_days: String(options.forecastDays ?? 5),\n forecast_hours: String(options.forecastHours ?? 24),\n current: [\n 'temperature_2m',\n 'weather_code',\n 'is_day',\n 'rain',\n 'showers',\n 'snowfall',\n 'wind_speed_10m',\n ].join(','),\n hourly: [\n 'temperature_2m',\n 'weather_code',\n 'precipitation_probability',\n 'rain',\n 'showers',\n 'snowfall',\n ].join(','),\n daily: [\n 'temperature_2m_max',\n 'temperature_2m_min',\n 'weather_code',\n 'precipitation_probability_max',\n 'precipitation_sum',\n 'wind_speed_10m_max',\n ].join(','),\n });\n\n return `https://api.open-meteo.com/v1/forecast?${params.toString()}`;\n}\n\nexport async function getWeatherForecast(\n options: WeatherForecastOptions = {}\n): Promise<WeatherForecastData> {\n let location: WeatherLocation;\n\n if (typeof options.latitude === 'number' && typeof options.longitude === 'number') {\n location = {\n ...options.location,\n latitude: options.latitude,\n longitude: options.longitude,\n };\n } else {\n location = await getClientLocation(options.geoEndpoint, options.ip);\n }\n\n const url = buildUrl(location.latitude, location.longitude, { ...options, location });\n\n const cached = readCachedForecast<WeatherForecastData>(url);\n if (cached) return cached;\n\n const response = await fetch(url, { headers: { 'Content-Type': 'application/json' } });\n\n if (!response.ok) {\n throw new Error(`Weather request failed: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json();\n\n if (!data?.current || !data?.hourly || !data?.daily) {\n throw new Error('Invalid weather response');\n }\n\n const result: WeatherForecastData = {\n location: { ...location, timezone: data.timezone || location.timezone },\n current: {\n time: data.current.time,\n temperature: Math.round(data.current.temperature_2m),\n weatherCode: data.current.weather_code,\n icon: getWeatherIcon(data.current.weather_code, data.current.is_day),\n isDay: data.current.is_day,\n rain: data.current.rain,\n showers: data.current.showers,\n snowfall: data.current.snowfall,\n windSpeed: data.current.wind_speed_10m,\n },\n hourly: data.hourly.time.map((time: string, index: number) => ({\n time,\n temperature: Math.round(data.hourly.temperature_2m[index]),\n weatherCode: data.hourly.weather_code[index],\n precipitationProbability: data.hourly.precipitation_probability?.[index],\n rain: data.hourly.rain?.[index],\n showers: data.hourly.showers?.[index],\n snowfall: data.hourly.snowfall?.[index],\n icon: getWeatherIcon(data.hourly.weather_code[index]),\n })),\n daily: data.daily.time.map((date: string, index: number) => ({\n date,\n min: Math.round(data.daily.temperature_2m_min[index]),\n max: Math.round(data.daily.temperature_2m_max[index]),\n weatherCode: data.daily.weather_code[index],\n precipitationProbabilityMax: data.daily.precipitation_probability_max?.[index],\n precipitationSum: data.daily.precipitation_sum?.[index],\n windSpeedMax: data.daily.wind_speed_10m_max?.[index],\n icon: getWeatherIcon(data.daily.weather_code[index]),\n })),\n };\n\n writeCachedForecast(url, result);\n return result;\n}\n","import { useEffect, useState } from 'react';\nimport type { WeatherForecastData, WeatherForecastOptions } from '../types';\nimport { getWeatherForecast } from '../api/forecast';\n\nexport function useWeatherForecast(options: WeatherForecastOptions = {}) {\n const [data, setData] = useState<WeatherForecastData | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let active = true;\n setLoading(true);\n setError(null);\n\n getWeatherForecast(options)\n .then((result) => { if (active) setData(result); })\n .catch((err) => { if (active) setError(err instanceof Error ? err : new Error('Unknown error')); })\n .finally(() => { if (active) setLoading(false); });\n\n return () => { active = false; };\n }, [\n options.latitude,\n options.longitude,\n options.geoEndpoint,\n options.ip,\n options.forecastDays,\n options.forecastHours,\n options.temperatureUnit,\n options.windSpeedUnit,\n options.location?.timezone,\n ]);\n\n return { data, error, loading };\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\n\nexport function BaseIcon({ title, children, ...props }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n role={title ? 'img' : 'presentation'}\n aria-hidden={title ? undefined : true}\n aria-label={title}\n {...props}\n >\n {title ? <title>{title}</title> : null}\n {children}\n </svg>\n );\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\nimport { BaseIcon } from './BaseIcon';\n\nexport const CloudBoltIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M7 18a4.815 4.815 0 0 1-3.327-1.318A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 0 7h-1m-4.75-5L11 17.5h3.375l-2.25 4.5\" /></BaseIcon>\n);\nexport const CloudDrizzleIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 18v1m-3-4v1m6-1v1m2 3v1M7 19v1m-3-3.034a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudFogIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M5 21h7m4 0h3M8 17h8\" /></BaseIcon>\n);\nexport const CloudHailIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 17.45v.1m-1.5 3.9v.1m10.5-4.1v.1m-1.5 3.9v.1m-3-4.1v.1m-1.5 3.9v.1m10.475-4.575A3.5 3.5 0 0 0 19 11h-1c.146-.65.148-1.32.006-1.97a4.784 4.784 0 0 0-.831-1.823 5.36 5.36 0 0 0-1.544-1.398 5.964 5.964 0 0 0-2.02-.759 6.224 6.224 0 0 0-2.19-.006 5.971 5.971 0 0 0-2.024.749C8.157 6.533 7.295 7.687 7 9a4.815 4.815 0 0 0-3.327 1.318A4.403 4.403 0 0 0 2.295 13.5c0 1.194.496 2.338 1.378 3.182.105.1.214.195.327.284m4.5.534a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm10.5-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-3-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z\" /></BaseIcon>\n);\nexport const CloudShowersIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M12 13v2m0 3v2m4-5v2m0 3v2m-8-7v2m0 3v2\" /></BaseIcon>\n);\nexport const CloudShowersHeavyIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M16 15v6m-8-6v6m4-8v6\" /></BaseIcon>\n);\nexport const CloudSleetIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M13.5 17.45a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm.5 3.9a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm-6.327-4.868.692-.723-.692.723ZM7 9v1a1 1 0 0 0 .976-.781L7 9Zm2.397-3.207.512.858-.512-.858ZM18 11l-.976-.219A1 1 0 0 0 18 12v-1Zm2.768 5.268a1 1 0 0 0 1.414 1.414l-.707-.707-.707-.707ZM3.379 17.75a1 1 0 0 0 1.242-1.567L4 16.966l-.62.784Zm5.55-.379a1 1 0 0 0-1.857-.742L8 17l.928.371ZM5.071 21.63a1 1 0 0 0 1.856.742L6 22l-.928-.371Zm13.857-4.258a1 1 0 0 0-1.857-.742L18 17l.928.371Zm-3.857 4.258a1 1 0 0 0 1.857.742L16 22l-.928-.371ZM13 17.5h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2a1.5 1.5 0 0 0 1.5 1.5v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2a1.5 1.5 0 0 0-1.5-1.5v1Zm0 .45h-1v.1h2v-.1h-1Zm-1 4.05h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2A1.5 1.5 0 0 0 11 23v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2A1.5 1.5 0 0 0 11 20v1Zm0 .45h-1v.1h2v-.1h-1Zm-7.327-4.768.692-.723a3.403 3.403 0 0 1-1.07-2.459h-2c0 1.473.613 2.877 1.687 3.905l.691-.723ZM2.295 13.5h1c0-.914.38-1.8 1.07-2.46l-.692-.722-.69-.723A5.403 5.403 0 0 0 1.294 13.5h1Zm1.378-3.182.692.723A3.815 3.815 0 0 1 7 10V8c-1.499 0-2.945.57-4.018 1.595l.691.723ZM7 9l.976.219c.23-1.024.91-1.956 1.933-2.568l-.512-.858-.513-.859c-1.456.87-2.5 2.245-2.86 3.847L7 9Zm2.397-3.207.512.858a4.972 4.972 0 0 1 1.686-.622l-.173-.985-.174-.985a6.971 6.971 0 0 0-2.364.875l.513.859Zm2.025-.749.173.985a5.223 5.223 0 0 1 1.837.005l.18-.984.178-.984a7.223 7.223 0 0 0-2.542-.007l.174.985Zm2.19.006-.18.984c.606.11 1.177.326 1.681.631l.518-.856.518-.855a6.963 6.963 0 0 0-2.359-.888l-.179.984Zm2.02.76-.519.855c.505.305.93.693 1.257 1.136l.805-.594.805-.594a6.361 6.361 0 0 0-1.831-1.659l-.518.855Zm1.543 1.397-.805.594c.326.442.549.932.66 1.441l.976-.212.978-.213a5.786 5.786 0 0 0-1.005-2.204l-.804.594Zm.831 1.823-.977.212c.111.509.11 1.031-.005 1.539L18 11l.976.219c.178-.792.18-1.609.008-2.402l-.978.213ZM18 11v1h1v-2h-1v1Zm1 0v1a2.5 2.5 0 0 1 1.768.732l.707-.707.707-.707A4.5 4.5 0 0 0 19 10v1Zm2.475 1.025-.707.707A2.5 2.5 0 0 1 21.5 14.5h2a4.5 4.5 0 0 0-1.318-3.182l-.707.707ZM22.5 14.5h-1a2.5 2.5 0 0 1-.732 1.768l.707.707.707.707A4.5 4.5 0 0 0 23.5 14.5h-1ZM4 16.966l.62-.784a3.684 3.684 0 0 1-.255-.223l-.692.723-.69.723c.126.121.259.237.396.345L4 16.966ZM8 17l-.928-.371-2 5L6 22l.928.371 2-5L8 17Zm10 0-.928-.371-2 5L16 22l.928.371 2-5L18 17Z\" /></BaseIcon>\n);\nexport const CloudSnowIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 15h2m-1-1v2m5-1h2m-1-1v2M5 21h2m-1-1v2m5-2h2m-1-1v2m5 0h2m-1-1v2M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudSunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M10.201 19.997c-1.977 0-3.58-1.544-3.58-3.448 0-1.903 1.603-3.446 3.58-3.446.303-1.355 1.38-2.461 2.827-2.902 1.445-.44 3.042-.148 4.186.77 1.144.915 1.663 2.312 1.361 3.667h.761c1.472 0 2.664 1.2 2.664 2.68A2.672 2.672 0 0 1 19.336 20h-9.134m2.66-12.934a4.166 4.166 0 0 0-2.677-1.732m2.677 1.732 2.744-1.892m-2.744 1.892c.55.796.802 1.754.723 2.709m-3.4-4.44a4.167 4.167 0 0 0-3.118.667m3.118-.668.602-3.278m-3.72 3.946A4.167 4.167 0 0 0 5.334 8.68m1.733-2.678L5.174 3.26m.16 5.42c-.2 1.088.041 2.21.669 3.12m-.669-3.12-3.278-.601m3.947 3.72-2.744 1.893m2.744-1.893a4.167 4.167 0 0 0 2.174 1.607\" /></BaseIcon>\n);\nexport const CloudsIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8.958 21.05C6.772 21.05 5 19.344 5 17.24s1.772-3.81 3.958-3.81c.335-1.498 1.525-2.72 3.124-3.207 1.598-.487 3.363-.164 4.628.85 1.264 1.011 1.837 2.556 1.504 4.053h.842A2.953 2.953 0 0 1 22 18.09a2.954 2.954 0 0 1-2.945 2.964H8.958M18 8c-.5-2.5-3.532-3.401-5.5-2.5a3.117 3.117 0 0 0-2.11-2.312c-1.352-.411-2.845-.138-3.915.72-1.07.855-1.554 2.161-1.273 3.428h-.711A2.498 2.498 0 0 0 2 9.843c0 1.094.695 2.023 1.666 2.366\" /></BaseIcon>\n);\nexport const SnowflakeIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"m9.25 4-1 2H6m-2.25 4L5 12m0 0-1.25 2M5 12h3m-3 0H2.5m17.75-2L19 12l1.25 2M6 18h2.25l1 2M14 9h-4m4 0 3-5m-3 5 2 3m-6-3L7 4m3 5-2 3m0 0 2 3m0 0-3 5m3-5h4m2-3-2 3m2-3h5.5M14 15l3 5M14.75 4l1 2H18m0 12h-2.25l-1 2\" /></BaseIcon>\n);\nexport const SunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 4V2m0 20v-2m10-8h-2M4 12H2m15.657-5.657 1.414-1.414M4.93 19.07l1.414-1.414m12.728 1.414-1.414-1.414M6.343 6.343 4.93 4.929M7 12a5 5 0 1 0 10 0 5 5 0 0 0-10 0Z\" /></BaseIcon>\n);\nexport const WindIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2\" /></BaseIcon>\n);\n","import React from 'react';\nimport type { IconProps, WeatherCondition } from '../types';\nimport {\n CloudBoltIcon,\n CloudDrizzleIcon,\n CloudFogIcon,\n CloudHailIcon,\n CloudShowersHeavyIcon,\n CloudShowersIcon,\n CloudSleetIcon,\n CloudSnowIcon,\n CloudSunIcon,\n CloudsIcon,\n SnowflakeIcon,\n SunIcon,\n} from './weather-icons';\n\nconst iconMap: Record<WeatherCondition, React.ComponentType<IconProps>> = {\n sun: SunIcon,\n 'cloud-sun': CloudSunIcon,\n clouds: CloudsIcon,\n 'cloud-fog': CloudFogIcon,\n 'cloud-drizzle': CloudDrizzleIcon,\n 'cloud-showers': CloudShowersIcon,\n 'cloud-showers-heavy': CloudShowersHeavyIcon,\n 'cloud-sleet': CloudSleetIcon,\n 'cloud-snow': CloudSnowIcon,\n snowflake: SnowflakeIcon,\n 'cloud-bolt': CloudBoltIcon,\n 'cloud-hail': CloudHailIcon,\n};\n\nexport function WeatherIcon({ condition, ...props }: IconProps & { condition: WeatherCondition }) {\n const Icon = iconMap[condition] || CloudsIcon;\n return <Icon {...props} />;\n}\n","import React, { useEffect, useState } from 'react';\nimport { useWeatherForecast } from '../hooks/useWeatherForecast';\nimport type { DailyWeather, TemperatureUnit, WeatherForecastOptions, WindSpeedUnit } from '../types';\nimport { WeatherIcon } from '../icons/WeatherIcon';\nimport { WindIcon } from '../icons/weather-icons';\n\ntype Props = WeatherForecastOptions & {\n className?: string;\n style?: React.CSSProperties;\n compact?: boolean;\n};\n\nconst WIND_SPEED_UNIT_LABELS: Record<WindSpeedUnit, string> = {\n kmh: 'km/h',\n mph: 'mph',\n ms: 'm/s',\n kn: 'kn',\n};\n\n// Format a real instant in a specific IANA timezone, falling back to the\n// browser's local zone if the timezone string is missing or invalid.\nfunction formatInZone(instant: Date, timezone: string | undefined, options: Intl.DateTimeFormatOptions): string {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined }).format(instant);\n } catch {\n return new Intl.DateTimeFormat([], options).format(instant);\n }\n}\n\n// The clock is shown with the hour and minute large and the seconds (plus any\n// AM/PM marker) trailing in a smaller, dimmer size, so split it into those two\n// halves. Falls back to one undivided string if the locale's parts are not\n// where we expect them.\nfunction clockParts(instant: Date, timezone: string | undefined): { main: string; tail: string } {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: '2-digit', second: '2-digit' };\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n const parts = format.formatToParts(instant);\n const part = (type: Intl.DateTimeFormatPartTypes) => parts.find((p) => p.type === type)?.value;\n const hour = part('hour');\n const minute = part('minute');\n const second = part('second');\n if (!hour || !minute || !second) return { main: format.format(instant), tail: '' };\n const dayPeriod = part('dayPeriod');\n return { main: `${hour}:${minute}`, tail: `:${second}${dayPeriod ? ` ${dayPeriod}` : ''}` };\n}\n\n// Just the zone abbreviation (e.g. \"GMT+2\"), so it can be shown apart from the\n// clock itself.\nfunction zoneLabel(instant: Date, timezone: string | undefined): string {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', timeZoneName: 'short' };\n try {\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n return format.formatToParts(instant).find((part) => part.type === 'timeZoneName')?.value ?? '';\n } catch {\n return '';\n }\n}\n\n// Daily dates arrive as plain \"YYYY-MM-DD\" calendar days; `new Date(...)` would\n// read those as UTC midnight and roll back a day in western timezones, so build\n// the date from its parts and let it stay a local calendar day.\nfunction parseCalendarDate(date: string): Date {\n const [year, month, day] = date.split('-').map(Number);\n if (!year || !month || !day) return new Date(date);\n return new Date(year, month - 1, day);\n}\n\n// The upcoming days sit in narrow columns in the card, so they are labelled\n// with the abbreviated weekday (\"Fri\") rather than a full name or \"Tomorrow\".\nfunction upcomingDayLabel(date: string): string {\n return parseCalendarDate(date).toLocaleDateString([], { weekday: 'short' });\n}\n\n// Daily totals that no longer have a column of their own in the card layout are\n// kept as hover text so the detail is still reachable.\nfunction upcomingDayDetail(day: DailyWeather, windSpeedUnitLabel: string): string | undefined {\n const details: string[] = [];\n if (day.precipitationSum !== undefined) details.push(`${day.precipitationSum.toFixed(1)} mm`);\n if (day.windSpeedMax !== undefined) details.push(`${day.windSpeedMax.toFixed(1)} ${windSpeedUnitLabel} wind`);\n return details.length > 0 ? details.join(' · ') : undefined;\n}\n\nconst styles = {\n root: {\n fontFamily: 'system-ui, sans-serif',\n border: '1px solid #e5e7eb',\n borderRadius: 12,\n padding: 16,\n background: '#fff',\n color: '#111827',\n maxWidth: 640,\n } as React.CSSProperties,\n row: { display: 'flex', alignItems: 'center', gap: 8 } as React.CSSProperties,\n hours: { display: 'flex', gap: 12, overflowX: 'auto', paddingBottom: 4 } as React.CSSProperties,\n hourCard: { minWidth: 72, textAlign: 'center' as const, padding: 8, borderRadius: 8, background: '#f9fafb' },\n dayRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, padding: '8px 0', borderBottom: '1px solid #f3f4f6' } as React.CSSProperties,\n // Fluid: fills whatever container it is dropped into (e.g. the full\n // sidebar/column width on desktop), with generous, even padding. Current\n // conditions sit on the left and the upcoming days on the right; the two\n // halves wrap into a stack when the container gets narrow.\n compactRoot: {\n fontFamily: 'system-ui, sans-serif',\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'wrap',\n alignItems: 'stretch',\n gap: 24,\n padding: '20px 24px',\n borderRadius: 16,\n width: '100%',\n boxSizing: 'border-box',\n } as React.CSSProperties,\n // Current conditions form two spread-out rows down the left side of the\n // card (rather than five stacked lines), so the card stays short and each\n // row uses the full available width.\n compactBody: { display: 'flex', flexDirection: 'column', justifyContent: 'space-between', gap: 20, flex: '1.6 1 240px', minWidth: 0 } as React.CSSProperties,\n compactRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '12px 16px' } as React.CSSProperties,\n compactCity: { fontSize: 15, fontWeight: 700, lineHeight: 1.2 } as React.CSSProperties,\n compactHeader: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 12 } as React.CSSProperties,\n compactTemp: { fontSize: 32, fontWeight: 600, lineHeight: 1 } as React.CSSProperties,\n compactTime: { fontSize: 26, fontWeight: 600, lineHeight: 1.1 } as React.CSSProperties,\n compactSeconds: { fontSize: 13, fontWeight: 500, opacity: 0.6, marginLeft: 2 } as React.CSSProperties,\n compactDate: { fontSize: 11, opacity: 0.7, marginTop: 5 } as React.CSSProperties,\n compactStats: { display: 'flex', flexWrap: 'wrap', gap: '8px 18px', fontSize: 12, opacity: 0.9 } as React.CSSProperties,\n // Three equal columns on the right side of the card, each stacking\n // weekday / icon / high-low, vertically centered against the current\n // conditions and nudged over behind a divider so the two halves read as\n // separate sections.\n upcoming: {\n display: 'grid',\n gridTemplateColumns: 'repeat(3, 1fr)',\n alignContent: 'center',\n gap: 14,\n flex: '1 1 190px',\n minWidth: 0,\n paddingLeft: 22,\n borderLeft: '1px solid currentColor',\n borderLeftColor: 'rgba(128,128,128,0.25)',\n fontSize: 11,\n } as React.CSSProperties,\n upcomingDay: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 5, textAlign: 'center' } as React.CSSProperties,\n upcomingDayName: { opacity: 0.7, whiteSpace: 'nowrap' } as React.CSSProperties,\n upcomingTemps: { whiteSpace: 'nowrap' } as React.CSSProperties,\n compactStat: { display: 'flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' } as React.CSSProperties,\n unitSwitch: { display: 'inline-flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' } as React.CSSProperties,\n rain: { color: '#2563eb', whiteSpace: 'nowrap' } as React.CSSProperties,\n tz: { fontSize: 12, opacity: 0.7, marginTop: 2 } as React.CSSProperties,\n list: { display: 'flex', flexDirection: 'column' } as React.CSSProperties,\n};\n\n// Precipitation chance is only surfaced once it represents a meaningful risk.\nconst RAIN_RISK_THRESHOLD = 2;\n\nfunction RainBadge({ probability, compact }: { probability?: number; compact?: boolean }) {\n if (probability === undefined || probability === null || probability <= RAIN_RISK_THRESHOLD) return null;\n return <span style={styles.rain}>{compact ? ` · ${probability}%` : ` · ${probability}% rain`}</span>;\n}\n\n/**\n * Renders one location's forecast: a live current time (ticking, in the\n * location's own timezone), a clickable degF / degC unit switch, high/low and\n * precipitation for the next hours and days.\n */\nfunction SingleWeatherForecast(props: Props) {\n // Temperature unit is local state so the degF / degC switch can toggle it;\n // it is seeded from (and re-synced to) the temperatureUnit prop.\n const [unit, setUnit] = useState<TemperatureUnit>(props.temperatureUnit ?? 'fahrenheit');\n useEffect(() => {\n setUnit(props.temperatureUnit ?? 'fahrenheit');\n }, [props.temperatureUnit]);\n\n const { data, loading, error } = useWeatherForecast({ ...props, temperatureUnit: unit });\n\n // A ticking clock so the displayed current time stays accurate to the second.\n const [now, setNow] = useState(() => new Date());\n useEffect(() => {\n const id = setInterval(() => setNow(new Date()), 1000);\n return () => clearInterval(id);\n }, []);\n\n const toggleUnit = () => setUnit((u) => (u === 'celsius' ? 'fahrenheit' : 'celsius'));\n\n const renderUnitSwitch = (fontSize: number) => (\n <span\n role=\"button\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n toggleUnit();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggleUnit();\n }\n }}\n title=\"Switch temperature units\"\n aria-label={`Switch temperature units, currently ${unit === 'celsius' ? 'Celsius' : 'Fahrenheit'}`}\n style={{ ...styles.unitSwitch, fontSize }}\n >\n <span style={{ fontWeight: unit === 'fahrenheit' ? 700 : 400, opacity: unit === 'fahrenheit' ? 1 : 0.45 }}>&deg;F</span>\n <span style={{ opacity: 0.4, margin: '0 4px' }}>|</span>\n <span style={{ fontWeight: unit === 'celsius' ? 700 : 400, opacity: unit === 'celsius' ? 1 : 0.45 }}>&deg;C</span>\n </span>\n );\n\n // Keep showing the last forecast while a unit switch refetches in the\n // background; only suppress output on the very first load.\n if (loading && !data) return null;\n if (error) return <div className={props.className} style={props.style}>Error: {error.message}</div>;\n if (!data) return <div className={props.className} style={props.style}>No forecast available.</div>;\n\n const timezone = data.location?.timezone;\n const currentDate = formatInZone(now, timezone, { weekday: 'short', month: 'short', day: 'numeric' });\n const currentTime = formatInZone(now, timezone, { hour: 'numeric', minute: '2-digit', second: '2-digit', timeZoneName: 'short' });\n // The compact layout shows the clock at a larger size, so the zone label is\n // split off onto the smaller date line to keep the time on one line.\n const currentClock = clockParts(now, timezone);\n const currentZone = zoneLabel(now, timezone);\n\n if (props.compact) {\n const today = data.daily[0];\n const windSpeedUnitLabel = WIND_SPEED_UNIT_LABELS[props.windSpeedUnit ?? 'mph'];\n // Today is already summarised in the card's own stats line, so the\n // upcoming-days section starts at tomorrow and shows the next three days.\n const upcomingDays = data.daily.slice(1, 4);\n\n return (\n <div className={props.className} style={{ ...styles.compactRoot, ...props.style }}>\n <div style={styles.compactBody}>\n <div style={styles.compactRow}>\n <div style={styles.compactCity}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </div>\n\n <div style={styles.compactHeader}>\n <WeatherIcon condition={data.current.icon} width={30} height={30} title=\"Current weather\" />\n <strong style={styles.compactTemp}>{data.current.temperature}</strong>\n {renderUnitSwitch(13)}\n </div>\n </div>\n\n <div style={styles.compactRow}>\n <div>\n <div style={styles.compactTime}>\n {currentClock.main}\n {currentClock.tail && <span style={styles.compactSeconds}>{currentClock.tail}</span>}\n </div>\n <div style={styles.compactDate}>\n {currentDate}\n {currentZone ? ` · ${currentZone}` : ''}\n </div>\n </div>\n\n <div style={styles.compactStats}>\n {today && (\n <span style={styles.compactStat}>\n <WeatherIcon condition={today.icon} width={16} height={16} title=\"Today's high and low\" />\n {today.max}&deg; / {today.min}&deg;\n <RainBadge probability={today.precipitationProbabilityMax} compact />\n </span>\n )}\n <span style={styles.compactStat}>\n <WindIcon width={16} height={16} title=\"Wind speed\" />\n {data.current.windSpeed !== undefined ? `${Math.round(data.current.windSpeed)} ${windSpeedUnitLabel}` : '—'}\n </span>\n </div>\n </div>\n </div>\n\n {upcomingDays.length > 0 && (\n <div style={styles.upcoming}>\n {upcomingDays.map((day) => (\n <div key={day.date} style={styles.upcomingDay} title={upcomingDayDetail(day, windSpeedUnitLabel)}>\n <span style={styles.upcomingDayName}>{upcomingDayLabel(day.date)}</span>\n <WeatherIcon condition={day.icon} width={14} height={14} />\n <span style={styles.upcomingTemps}>\n <strong>{day.max}&deg;</strong>\n <span style={{ opacity: 0.6 }}> / {day.min}&deg;</span>\n </span>\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={{ ...styles.rain, fontSize: 10 }}>{day.precipitationProbabilityMax}%</span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div className={props.className} style={{ ...styles.root, ...props.style }}>\n <div>\n <h3 style={{ margin: 0, fontSize: 20 }}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </h3>\n <div style={styles.tz}>\n {currentDate} &middot; {currentTime}\n {timezone ? ` · ${timezone}` : ''}\n </div>\n <div style={{ ...styles.row, marginTop: 8 }}>\n <WeatherIcon condition={data.current.icon} width={28} height={28} title=\"Current weather\" />\n <strong style={{ fontSize: 28 }}>{data.current.temperature}</strong>\n {renderUnitSwitch(14)}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next hours</h4>\n <div style={styles.hours}>\n {data.hourly.slice(0, props.forecastHours ?? 12).map((hour) => (\n <div key={hour.time} style={styles.hourCard}>\n <div style={{ fontSize: 12 }}>{new Date(hour.time).toLocaleTimeString([], { hour: 'numeric' })}</div>\n <WeatherIcon condition={hour.icon} width={20} height={20} style={{ margin: '6px auto' }} />\n <div>{hour.temperature}&deg;</div>\n {hour.precipitationProbability !== undefined && hour.precipitationProbability > 0 && (\n <div style={{ ...styles.rain, fontSize: 12 }}>{hour.precipitationProbability}%</div>\n )}\n </div>\n ))}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next days</h4>\n <div>\n {data.daily.map((day) => (\n <div key={day.date} style={styles.dayRow}>\n <div style={{ width: 120 }}>{new Date(day.date).toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' })}</div>\n <WeatherIcon condition={day.icon} width={20} height={20} />\n <div>\n {day.min}&deg; / {day.max}&deg;\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={styles.rain}> &middot; {day.precipitationProbabilityMax}%</span>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Weather forecast widget. When `locations` is provided, one forecast is\n * rendered per location; otherwise a single auto-detected forecast is shown.\n */\nexport function WeatherForecast(props: Props) {\n const { locations, ...rest } = props;\n\n if (locations && locations.length > 0) {\n const gap = props.compact ? 8 : 16;\n return (\n <div style={{ ...styles.list, gap }}>\n {locations.map((loc, index) => (\n <SingleWeatherForecast\n key={`${loc.label ?? ''}:${loc.latitude ?? ''}:${loc.longitude ?? ''}:${index}`}\n {...rest}\n latitude={typeof loc.latitude === 'number' ? loc.latitude : undefined}\n longitude={typeof loc.longitude === 'number' ? loc.longitude : undefined}\n location={{ city: loc.label, timezone: loc.timezone }}\n />\n ))}\n </div>\n );\n }\n\n return <SingleWeatherForecast {...rest} />;\n}\n"],"mappings":"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","names":["res","data","jsx","jsx","useEffect","useState","jsx","jsxs","useState","useEffect"]}
1
+ {"version":3,"sources":["../src/weatherCodes.ts","../src/lib/cache.ts","../src/api/http.ts","../src/api/geolocation.ts","../src/api/forecast.ts","../src/hooks/useWeatherForecast.ts","../src/icons/BaseIcon.tsx","../src/icons/weather-icons.tsx","../src/icons/WeatherIcon.tsx","../src/components/WeatherForecast.tsx"],"sourcesContent":["import type { WeatherCondition } from './types';\n\nexport function getWeatherIcon(code: number, isDay = 1): WeatherCondition {\n if (code === 0) return 'sun';\n if ([1, 2].includes(code)) return isDay ? 'cloud-sun' : 'clouds';\n if (code === 3) return 'clouds';\n if ([45, 48].includes(code)) return 'cloud-fog';\n if ([51, 53, 55, 56, 57].includes(code)) return 'cloud-drizzle';\n if ([61, 63].includes(code)) return 'cloud-showers';\n if ([65, 66, 67, 80, 81, 82].includes(code)) return 'cloud-showers-heavy';\n if ([71, 73, 75, 77, 85, 86].includes(code)) return 'cloud-snow';\n if (code === 95) return 'cloud-bolt';\n if ([96, 99].includes(code)) return 'cloud-hail';\n return 'clouds';\n}\n","const CACHE_PREFIX = 'weather-forecast-cache:';\nconst CACHE_TTL_MS = 30 * 60 * 1000;\n\ntype CacheEntry<T> = {\n timestamp: number;\n data: T;\n};\n\nfunction getStorage(): Storage | null {\n if (typeof window === 'undefined' || !window.localStorage) return null;\n return window.localStorage;\n}\n\n/** Returns the cached value for `key` if it was written within the last 30 minutes. */\nexport function readCachedForecast<T>(key: string): T | null {\n const storage = getStorage();\n if (!storage) return null;\n\n try {\n const raw = storage.getItem(CACHE_PREFIX + key);\n if (!raw) return null;\n\n const entry = JSON.parse(raw) as CacheEntry<T>;\n if (Date.now() - entry.timestamp > CACHE_TTL_MS) {\n storage.removeItem(CACHE_PREFIX + key);\n return null;\n }\n\n return entry.data;\n } catch {\n return null;\n }\n}\n\nexport function writeCachedForecast<T>(key: string, data: T): void {\n const storage = getStorage();\n if (!storage) return;\n\n try {\n const entry: CacheEntry<T> = { timestamp: Date.now(), data };\n storage.setItem(CACHE_PREFIX + key, JSON.stringify(entry));\n } catch {\n // Storage full or unavailable (e.g. private browsing) — safe to ignore,\n // the next call will simply hit the network again.\n }\n}\n\n/** Clears all cached forecasts. Mainly useful for tests/debugging. */\nexport function clearWeatherForecastCache(): void {\n const storage = getStorage();\n if (!storage) return;\n\n for (let i = storage.length - 1; i >= 0; i--) {\n const key = storage.key(i);\n if (key?.startsWith(CACHE_PREFIX)) storage.removeItem(key);\n }\n}\n","import grab from 'grab-url';\n\n/**\n * Shared JSON transport for the package's upstreams (the geo worker, ipapi.co\n * and Open-Meteo).\n *\n * `grab-url` is the HTTP client used across this repo: it parses the JSON,\n * applies a timeout and can repeat a failed request itself. Unlike `fetch` it\n * resolves rather than rejects when a request fails, handing back the message\n * on `.error`, so every call site has to inspect the payload -- that check\n * lives here so the callers can just `await` a typed body.\n */\nexport type GrabJsonOptions = {\n headers?: Record<string, string>;\n /** default=0 Times grab-url repeats a request that failed at the transport level. */\n retryAttempts?: number;\n /** default=15 Seconds before the request is aborted. */\n timeout?: number;\n};\n\n/**\n * Both upstreams can answer HTTP 200 with an error flag instead of data:\n * ipapi.co does it for rate limits, Open-Meteo for a malformed query.\n */\ntype ErrorPayload = { error?: boolean | string; reason?: string };\n\n/**\n * grab-url reports a failed request as `HTTP error: 429 Too Many Requests`.\n * That prefix is noise once the message is nested under `<label> failed:`,\n * so drop it and keep the status.\n */\nfunction describeFailure(error: string): string {\n return error.replace(/^HTTP error:\\s*/i, '').trim() || 'unknown error';\n}\n\n/**\n * Request a JSON body, throwing a labelled error for any failure.\n *\n * @param url Full URL to request.\n * @param label Prefix for the thrown message, e.g. `ipapi.co lookup`.\n * @param options Transport options passed through to grab-url.\n */\nexport async function grabJson<T>(\n url: string,\n label: string,\n options: GrabJsonOptions = {}\n): Promise<T> {\n const { headers, retryAttempts = 0, timeout = 15 } = options;\n\n const data = (await grab(url, { headers, retryAttempts, timeout })) as\n | (T & ErrorPayload)\n | null\n | undefined;\n\n if (typeof data?.error === 'string') {\n throw new Error(`${label} failed: ${describeFailure(data.error)}`);\n }\n\n if (data?.error === true) {\n throw new Error(`${label} failed: ${data.reason ?? 'unknown error'}`);\n }\n\n if (!data) throw new Error(`${label} failed: empty response`);\n\n return data as T;\n}\n","import type { WeatherLocation } from '../types';\nimport { grabJson } from './http';\n\nconst DEFAULT_IP_API_URL = 'https://ipapi.co';\n\n/**\n * ipapi.co's free tier rate-limits by IP and answers a 200 carrying\n * `{ error: true, reason: 'RateLimited' }`, and a geo worker can drop a\n * request while it cold-starts. Both clear on their own, so the lookup is\n * repeated before the error reaches the caller.\n */\nconst DEFAULT_ATTEMPTS = 3;\nconst DEFAULT_RETRY_DELAY_MS = 500;\n\nexport type GeolocationOptions = {\n /** default=3 How many times the lookup is tried before the error is thrown. */\n attempts?: number;\n /** default=500 Milliseconds to wait before the second try; each further wait grows by that much. */\n retryDelay?: number;\n};\n\ntype IpApiResponse = {\n city?: string;\n region?: string;\n country_name?: string;\n timezone?: string;\n latitude?: number;\n longitude?: number;\n error?: boolean;\n reason?: string;\n};\n\ntype GeoWorkerResponse = {\n city?: string;\n region?: string;\n country?: string;\n timezone?: string;\n latitude?: number | string;\n longitude?: number | string;\n};\n\nconst wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));\n\nasync function lookupViaWorker(geoEndpoint: string, ip?: string): Promise<WeatherLocation> {\n const url = ip ? `${geoEndpoint}?ip=${encodeURIComponent(ip)}` : geoEndpoint;\n const data = await grabJson<GeoWorkerResponse>(url, 'Geolocation worker lookup');\n\n return {\n city: data.city,\n region: data.region,\n country: data.country,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n}\n\nasync function lookupViaIpApi(ip?: string): Promise<WeatherLocation> {\n const url = `${DEFAULT_IP_API_URL}/${ip ? `${encodeURIComponent(ip)}/` : ''}json/`;\n const data = await grabJson<IpApiResponse>(url, 'ipapi.co lookup');\n\n return {\n city: data.city,\n region: data.region,\n country: data.country_name,\n timezone: data.timezone,\n latitude: Number(data.latitude),\n longitude: Number(data.longitude),\n };\n}\n\n/**\n * Resolve the caller's location, either from a deployed geo worker or from\n * ipapi.co directly.\n *\n * A failed lookup is repeated with a growing delay and only the last error is\n * thrown, so a single rate-limited or cold-started response no longer takes\n * the whole forecast down with it.\n *\n * @param geoEndpoint URL of a deployed geo worker; omit to use ipapi.co.\n * @param ip Look up this IP instead of the caller's own.\n * @param options How often to repeat the lookup, and how long to wait between tries.\n */\nexport async function getClientLocation(\n geoEndpoint?: string,\n ip?: string,\n options: GeolocationOptions = {}\n): Promise<WeatherLocation> {\n const attempts = Math.max(1, Math.trunc(options.attempts ?? DEFAULT_ATTEMPTS));\n const retryDelay = Math.max(0, options.retryDelay ?? DEFAULT_RETRY_DELAY_MS);\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= attempts; attempt++) {\n try {\n return geoEndpoint ? await lookupViaWorker(geoEndpoint, ip) : await lookupViaIpApi(ip);\n } catch (error) {\n lastError = error;\n if (attempt < attempts && retryDelay > 0) await wait(retryDelay * attempt);\n }\n }\n\n throw lastError instanceof Error ? lastError : new Error(String(lastError));\n}\n","import type { WeatherForecastData, WeatherForecastOptions, WeatherLocation } from '../types';\nimport { getWeatherIcon } from '../weatherCodes';\nimport { getClientLocation } from './geolocation';\nimport { grabJson } from './http';\nimport { readCachedForecast, writeCachedForecast } from '../lib/cache';\n\nfunction buildUrl(latitude: number, longitude: number, options: WeatherForecastOptions) {\n const params = new URLSearchParams({\n latitude: String(latitude),\n longitude: String(longitude),\n timezone: options.location?.timezone || 'auto',\n temperature_unit: options.temperatureUnit || 'fahrenheit',\n wind_speed_unit: options.windSpeedUnit || 'mph',\n forecast_days: String(options.forecastDays ?? 5),\n forecast_hours: String(options.forecastHours ?? 24),\n current: [\n 'temperature_2m',\n 'weather_code',\n 'is_day',\n 'rain',\n 'showers',\n 'snowfall',\n 'wind_speed_10m',\n ].join(','),\n hourly: [\n 'temperature_2m',\n 'weather_code',\n 'precipitation_probability',\n 'rain',\n 'showers',\n 'snowfall',\n ].join(','),\n daily: [\n 'temperature_2m_max',\n 'temperature_2m_min',\n 'weather_code',\n 'precipitation_probability_max',\n 'precipitation_sum',\n 'wind_speed_10m_max',\n ].join(','),\n });\n\n return `https://api.open-meteo.com/v1/forecast?${params.toString()}`;\n}\n\nexport async function getWeatherForecast(\n options: WeatherForecastOptions = {}\n): Promise<WeatherForecastData> {\n let location: WeatherLocation;\n\n if (typeof options.latitude === 'number' && typeof options.longitude === 'number') {\n location = {\n ...options.location,\n latitude: options.latitude,\n longitude: options.longitude,\n };\n } else {\n location = await getClientLocation(options.geoEndpoint, options.ip);\n }\n\n const url = buildUrl(location.latitude, location.longitude, { ...options, location });\n\n const cached = readCachedForecast<WeatherForecastData>(url);\n if (cached) return cached;\n\n const data = await grabJson<any>(url, 'Weather request', {\n headers: { 'Content-Type': 'application/json' },\n retryAttempts: 1,\n });\n\n if (!data?.current || !data?.hourly || !data?.daily) {\n throw new Error('Invalid weather response');\n }\n\n const result: WeatherForecastData = {\n location: { ...location, timezone: data.timezone || location.timezone },\n current: {\n time: data.current.time,\n temperature: Math.round(data.current.temperature_2m),\n weatherCode: data.current.weather_code,\n icon: getWeatherIcon(data.current.weather_code, data.current.is_day),\n isDay: data.current.is_day,\n rain: data.current.rain,\n showers: data.current.showers,\n snowfall: data.current.snowfall,\n windSpeed: data.current.wind_speed_10m,\n },\n hourly: data.hourly.time.map((time: string, index: number) => ({\n time,\n temperature: Math.round(data.hourly.temperature_2m[index]),\n weatherCode: data.hourly.weather_code[index],\n precipitationProbability: data.hourly.precipitation_probability?.[index],\n rain: data.hourly.rain?.[index],\n showers: data.hourly.showers?.[index],\n snowfall: data.hourly.snowfall?.[index],\n icon: getWeatherIcon(data.hourly.weather_code[index]),\n })),\n daily: data.daily.time.map((date: string, index: number) => ({\n date,\n min: Math.round(data.daily.temperature_2m_min[index]),\n max: Math.round(data.daily.temperature_2m_max[index]),\n weatherCode: data.daily.weather_code[index],\n precipitationProbabilityMax: data.daily.precipitation_probability_max?.[index],\n precipitationSum: data.daily.precipitation_sum?.[index],\n windSpeedMax: data.daily.wind_speed_10m_max?.[index],\n icon: getWeatherIcon(data.daily.weather_code[index]),\n })),\n };\n\n writeCachedForecast(url, result);\n return result;\n}\n","import { useEffect, useState } from 'react';\nimport type { WeatherForecastData, WeatherForecastOptions } from '../types';\nimport { getWeatherForecast } from '../api/forecast';\n\nexport function useWeatherForecast(options: WeatherForecastOptions = {}) {\n const [data, setData] = useState<WeatherForecastData | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let active = true;\n setLoading(true);\n setError(null);\n\n getWeatherForecast(options)\n .then((result) => { if (active) setData(result); })\n .catch((err) => { if (active) setError(err instanceof Error ? err : new Error('Unknown error')); })\n .finally(() => { if (active) setLoading(false); });\n\n return () => { active = false; };\n }, [\n options.latitude,\n options.longitude,\n options.geoEndpoint,\n options.ip,\n options.forecastDays,\n options.forecastHours,\n options.temperatureUnit,\n options.windSpeedUnit,\n options.location?.timezone,\n ]);\n\n return { data, error, loading };\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\n\nexport function BaseIcon({ title, children, ...props }: IconProps & { children: React.ReactNode }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n role={title ? 'img' : 'presentation'}\n aria-hidden={title ? undefined : true}\n aria-label={title}\n {...props}\n >\n {title ? <title>{title}</title> : null}\n {children}\n </svg>\n );\n}\n","import React from 'react';\nimport type { IconProps } from '../types';\nimport { BaseIcon } from './BaseIcon';\n\nexport const CloudBoltIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M7 18a4.815 4.815 0 0 1-3.327-1.318A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 0 7h-1m-4.75-5L11 17.5h3.375l-2.25 4.5\" /></BaseIcon>\n);\nexport const CloudDrizzleIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 18v1m-3-4v1m6-1v1m2 3v1M7 19v1m-3-3.034a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudFogIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M5 21h7m4 0h3M8 17h8\" /></BaseIcon>\n);\nexport const CloudHailIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 17.45v.1m-1.5 3.9v.1m10.5-4.1v.1m-1.5 3.9v.1m-3-4.1v.1m-1.5 3.9v.1m10.475-4.575A3.5 3.5 0 0 0 19 11h-1c.146-.65.148-1.32.006-1.97a4.784 4.784 0 0 0-.831-1.823 5.36 5.36 0 0 0-1.544-1.398 5.964 5.964 0 0 0-2.02-.759 6.224 6.224 0 0 0-2.19-.006 5.971 5.971 0 0 0-2.024.749C8.157 6.533 7.295 7.687 7 9a4.815 4.815 0 0 0-3.327 1.318A4.403 4.403 0 0 0 2.295 13.5c0 1.194.496 2.338 1.378 3.182.105.1.214.195.327.284m4.5.534a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm10.5-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-3-4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-1.5 4a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z\" /></BaseIcon>\n);\nexport const CloudShowersIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M12 13v2m0 3v2m4-5v2m0 3v2m-8-7v2m0 3v2\" /></BaseIcon>\n);\nexport const CloudShowersHeavyIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662M16 15v6m-8-6v6m4-8v6\" /></BaseIcon>\n);\nexport const CloudSleetIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M13.5 17.45a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm.5 3.9a1 1 0 1 0-2 0h2Zm-2 .1a1 1 0 1 0 2 0h-2Zm-6.327-4.868.692-.723-.692.723ZM7 9v1a1 1 0 0 0 .976-.781L7 9Zm2.397-3.207.512.858-.512-.858ZM18 11l-.976-.219A1 1 0 0 0 18 12v-1Zm2.768 5.268a1 1 0 0 0 1.414 1.414l-.707-.707-.707-.707ZM3.379 17.75a1 1 0 0 0 1.242-1.567L4 16.966l-.62.784Zm5.55-.379a1 1 0 0 0-1.857-.742L8 17l.928.371ZM5.071 21.63a1 1 0 0 0 1.856.742L6 22l-.928-.371Zm13.857-4.258a1 1 0 0 0-1.857-.742L18 17l.928.371Zm-3.857 4.258a1 1 0 0 0 1.857.742L16 22l-.928-.371ZM13 17.5h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2a1.5 1.5 0 0 0 1.5 1.5v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2a1.5 1.5 0 0 0-1.5-1.5v1Zm0 .45h-1v.1h2v-.1h-1Zm-1 4.05h-1a.5.5 0 0 1 .5-.5v2a1.5 1.5 0 0 0 1.5-1.5h-1Zm-.5.5v-1a.5.5 0 0 1 .5.5h-2A1.5 1.5 0 0 0 11 23v-1Zm-.5-.5h1a.5.5 0 0 1-.5.5v-2a1.5 1.5 0 0 0-1.5 1.5h1Zm.5-.5v1a.5.5 0 0 1-.5-.5h2A1.5 1.5 0 0 0 11 20v1Zm0 .45h-1v.1h2v-.1h-1Zm-7.327-4.768.692-.723a3.403 3.403 0 0 1-1.07-2.459h-2c0 1.473.613 2.877 1.687 3.905l.691-.723ZM2.295 13.5h1c0-.914.38-1.8 1.07-2.46l-.692-.722-.69-.723A5.403 5.403 0 0 0 1.294 13.5h1Zm1.378-3.182.692.723A3.815 3.815 0 0 1 7 10V8c-1.499 0-2.945.57-4.018 1.595l.691.723ZM7 9l.976.219c.23-1.024.91-1.956 1.933-2.568l-.512-.858-.513-.859c-1.456.87-2.5 2.245-2.86 3.847L7 9Zm2.397-3.207.512.858a4.972 4.972 0 0 1 1.686-.622l-.173-.985-.174-.985a6.971 6.971 0 0 0-2.364.875l.513.859Zm2.025-.749.173.985a5.223 5.223 0 0 1 1.837.005l.18-.984.178-.984a7.223 7.223 0 0 0-2.542-.007l.174.985Zm2.19.006-.18.984c.606.11 1.177.326 1.681.631l.518-.856.518-.855a6.963 6.963 0 0 0-2.359-.888l-.179.984Zm2.02.76-.519.855c.505.305.93.693 1.257 1.136l.805-.594.805-.594a6.361 6.361 0 0 0-1.831-1.659l-.518.855Zm1.543 1.397-.805.594c.326.442.549.932.66 1.441l.976-.212.978-.213a5.786 5.786 0 0 0-1.005-2.204l-.804.594Zm.831 1.823-.977.212c.111.509.11 1.031-.005 1.539L18 11l.976.219c.178-.792.18-1.609.008-2.402l-.978.213ZM18 11v1h1v-2h-1v1Zm1 0v1a2.5 2.5 0 0 1 1.768.732l.707-.707.707-.707A4.5 4.5 0 0 0 19 10v1Zm2.475 1.025-.707.707A2.5 2.5 0 0 1 21.5 14.5h2a4.5 4.5 0 0 0-1.318-3.182l-.707.707ZM22.5 14.5h-1a2.5 2.5 0 0 1-.732 1.768l.707.707.707.707A4.5 4.5 0 0 0 23.5 14.5h-1ZM4 16.966l.62-.784a3.684 3.684 0 0 1-.255-.223l-.692.723-.69.723c.126.121.259.237.396.345L4 16.966ZM8 17l-.928-.371-2 5L6 22l.928.371 2-5L8 17Zm10 0-.928-.371-2 5L16 22l.928.371 2-5L18 17Z\" /></BaseIcon>\n);\nexport const CloudSnowIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8 15h2m-1-1v2m5-1h2m-1-1v2M5 21h2m-1-1v2m5-2h2m-1-1v2m5 0h2m-1-1v2M4 16.966a4.63 4.63 0 0 1-.327-.284A4.403 4.403 0 0 1 2.295 13.5c0-1.194.496-2.338 1.378-3.182A4.815 4.815 0 0 1 7 9c.295-1.313 1.157-2.466 2.397-3.207a5.971 5.971 0 0 1 2.025-.749 6.224 6.224 0 0 1 2.19.006c.721.131 1.408.39 2.02.76a5.36 5.36 0 0 1 1.543 1.397c.407.552.69 1.172.831 1.823.142.65.14 1.32-.006 1.97h1a3.5 3.5 0 0 1 1.5 6.662\" /></BaseIcon>\n);\nexport const CloudSunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M10.201 19.997c-1.977 0-3.58-1.544-3.58-3.448 0-1.903 1.603-3.446 3.58-3.446.303-1.355 1.38-2.461 2.827-2.902 1.445-.44 3.042-.148 4.186.77 1.144.915 1.663 2.312 1.361 3.667h.761c1.472 0 2.664 1.2 2.664 2.68A2.672 2.672 0 0 1 19.336 20h-9.134m2.66-12.934a4.166 4.166 0 0 0-2.677-1.732m2.677 1.732 2.744-1.892m-2.744 1.892c.55.796.802 1.754.723 2.709m-3.4-4.44a4.167 4.167 0 0 0-3.118.667m3.118-.668.602-3.278m-3.72 3.946A4.167 4.167 0 0 0 5.334 8.68m1.733-2.678L5.174 3.26m.16 5.42c-.2 1.088.041 2.21.669 3.12m-.669-3.12-3.278-.601m3.947 3.72-2.744 1.893m2.744-1.893a4.167 4.167 0 0 0 2.174 1.607\" /></BaseIcon>\n);\nexport const CloudsIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M8.958 21.05C6.772 21.05 5 19.344 5 17.24s1.772-3.81 3.958-3.81c.335-1.498 1.525-2.72 3.124-3.207 1.598-.487 3.363-.164 4.628.85 1.264 1.011 1.837 2.556 1.504 4.053h.842A2.953 2.953 0 0 1 22 18.09a2.954 2.954 0 0 1-2.945 2.964H8.958M18 8c-.5-2.5-3.532-3.401-5.5-2.5a3.117 3.117 0 0 0-2.11-2.312c-1.352-.411-2.845-.138-3.915.72-1.07.855-1.554 2.161-1.273 3.428h-.711A2.498 2.498 0 0 0 2 9.843c0 1.094.695 2.023 1.666 2.366\" /></BaseIcon>\n);\nexport const SnowflakeIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"m9.25 4-1 2H6m-2.25 4L5 12m0 0-1.25 2M5 12h3m-3 0H2.5m17.75-2L19 12l1.25 2M6 18h2.25l1 2M14 9h-4m4 0 3-5m-3 5 2 3m-6-3L7 4m3 5-2 3m0 0 2 3m0 0-3 5m3-5h4m2-3-2 3m2-3h5.5M14 15l3 5M14.75 4l1 2H18m0 12h-2.25l-1 2\" /></BaseIcon>\n);\nexport const SunIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M12 4V2m0 20v-2m10-8h-2M4 12H2m15.657-5.657 1.414-1.414M4.93 19.07l1.414-1.414m12.728 1.414-1.414-1.414M6.343 6.343 4.93 4.929M7 12a5 5 0 1 0 10 0 5 5 0 0 0-10 0Z\" /></BaseIcon>\n);\nexport const WindIcon = (props: IconProps) => (\n <BaseIcon {...props}><path d=\"M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2\" /></BaseIcon>\n);\n","import React from 'react';\nimport type { IconProps, WeatherCondition } from '../types';\nimport {\n CloudBoltIcon,\n CloudDrizzleIcon,\n CloudFogIcon,\n CloudHailIcon,\n CloudShowersHeavyIcon,\n CloudShowersIcon,\n CloudSleetIcon,\n CloudSnowIcon,\n CloudSunIcon,\n CloudsIcon,\n SnowflakeIcon,\n SunIcon,\n} from './weather-icons';\n\nconst iconMap: Record<WeatherCondition, React.ComponentType<IconProps>> = {\n sun: SunIcon,\n 'cloud-sun': CloudSunIcon,\n clouds: CloudsIcon,\n 'cloud-fog': CloudFogIcon,\n 'cloud-drizzle': CloudDrizzleIcon,\n 'cloud-showers': CloudShowersIcon,\n 'cloud-showers-heavy': CloudShowersHeavyIcon,\n 'cloud-sleet': CloudSleetIcon,\n 'cloud-snow': CloudSnowIcon,\n snowflake: SnowflakeIcon,\n 'cloud-bolt': CloudBoltIcon,\n 'cloud-hail': CloudHailIcon,\n};\n\nexport function WeatherIcon({ condition, ...props }: IconProps & { condition: WeatherCondition }) {\n const Icon = iconMap[condition] || CloudsIcon;\n return <Icon {...props} />;\n}\n","import React, { useEffect, useState } from 'react';\nimport { useWeatherForecast } from '../hooks/useWeatherForecast';\nimport type { DailyWeather, TemperatureUnit, WeatherForecastOptions, WindSpeedUnit } from '../types';\nimport { WeatherIcon } from '../icons/WeatherIcon';\nimport { WindIcon } from '../icons/weather-icons';\n\ntype Props = WeatherForecastOptions & {\n className?: string;\n style?: React.CSSProperties;\n compact?: boolean;\n};\n\nconst WIND_SPEED_UNIT_LABELS: Record<WindSpeedUnit, string> = {\n kmh: 'km/h',\n mph: 'mph',\n ms: 'm/s',\n kn: 'kn',\n};\n\n// Format a real instant in a specific IANA timezone, falling back to the\n// browser's local zone if the timezone string is missing or invalid.\nfunction formatInZone(instant: Date, timezone: string | undefined, options: Intl.DateTimeFormatOptions): string {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined }).format(instant);\n } catch {\n return new Intl.DateTimeFormat([], options).format(instant);\n }\n}\n\n// The clock is shown with the hour and minute large and the seconds (plus any\n// AM/PM marker) trailing in a smaller, dimmer size, so split it into those two\n// halves. Falls back to one undivided string if the locale's parts are not\n// where we expect them.\nfunction clockParts(instant: Date, timezone: string | undefined): { main: string; tail: string } {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: '2-digit', second: '2-digit' };\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n const parts = format.formatToParts(instant);\n const part = (type: Intl.DateTimeFormatPartTypes) => parts.find((p) => p.type === type)?.value;\n const hour = part('hour');\n const minute = part('minute');\n const second = part('second');\n if (!hour || !minute || !second) return { main: format.format(instant), tail: '' };\n const dayPeriod = part('dayPeriod');\n return { main: `${hour}:${minute}`, tail: `:${second}${dayPeriod ? ` ${dayPeriod}` : ''}` };\n}\n\n// Just the zone abbreviation (e.g. \"GMT+2\"), so it can be shown apart from the\n// clock itself.\nfunction zoneLabel(instant: Date, timezone: string | undefined): string {\n const options: Intl.DateTimeFormatOptions = { hour: 'numeric', timeZoneName: 'short' };\n try {\n const format = (() => {\n try {\n return new Intl.DateTimeFormat([], { ...options, timeZone: timezone || undefined });\n } catch {\n return new Intl.DateTimeFormat([], options);\n }\n })();\n return format.formatToParts(instant).find((part) => part.type === 'timeZoneName')?.value ?? '';\n } catch {\n return '';\n }\n}\n\n// Daily dates arrive as plain \"YYYY-MM-DD\" calendar days; `new Date(...)` would\n// read those as UTC midnight and roll back a day in western timezones, so build\n// the date from its parts and let it stay a local calendar day.\nfunction parseCalendarDate(date: string): Date {\n const [year, month, day] = date.split('-').map(Number);\n if (!year || !month || !day) return new Date(date);\n return new Date(year, month - 1, day);\n}\n\n// The upcoming days sit in narrow columns in the card, so they are labelled\n// with the abbreviated weekday (\"Fri\") rather than a full name or \"Tomorrow\".\nfunction upcomingDayLabel(date: string): string {\n return parseCalendarDate(date).toLocaleDateString([], { weekday: 'short' });\n}\n\n// Daily totals that no longer have a column of their own in the card layout are\n// kept as hover text so the detail is still reachable.\nfunction upcomingDayDetail(day: DailyWeather, windSpeedUnitLabel: string): string | undefined {\n const details: string[] = [];\n if (day.precipitationSum !== undefined) details.push(`${day.precipitationSum.toFixed(1)} mm`);\n if (day.windSpeedMax !== undefined) details.push(`${day.windSpeedMax.toFixed(1)} ${windSpeedUnitLabel} wind`);\n return details.length > 0 ? details.join(' · ') : undefined;\n}\n\nconst styles = {\n root: {\n fontFamily: 'system-ui, sans-serif',\n border: '1px solid #e5e7eb',\n borderRadius: 12,\n padding: 16,\n background: '#fff',\n color: '#111827',\n maxWidth: 640,\n } as React.CSSProperties,\n row: { display: 'flex', alignItems: 'center', gap: 8 } as React.CSSProperties,\n hours: { display: 'flex', gap: 12, overflowX: 'auto', paddingBottom: 4 } as React.CSSProperties,\n hourCard: { minWidth: 72, textAlign: 'center' as const, padding: 8, borderRadius: 8, background: '#f9fafb' },\n dayRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, padding: '8px 0', borderBottom: '1px solid #f3f4f6' } as React.CSSProperties,\n // Fluid: fills whatever container it is dropped into (e.g. the full\n // sidebar/column width on desktop), with generous, even padding. Current\n // conditions sit on the left and the upcoming days on the right; the two\n // halves wrap into a stack when the container gets narrow.\n compactRoot: {\n fontFamily: 'system-ui, sans-serif',\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'wrap',\n alignItems: 'stretch',\n gap: 24,\n padding: '20px 24px',\n borderRadius: 16,\n width: '100%',\n boxSizing: 'border-box',\n } as React.CSSProperties,\n // Current conditions form two spread-out rows down the left side of the\n // card (rather than five stacked lines), so the card stays short and each\n // row uses the full available width.\n compactBody: { display: 'flex', flexDirection: 'column', justifyContent: 'space-between', gap: 20, flex: '1.6 1 240px', minWidth: 0 } as React.CSSProperties,\n compactRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: '12px 16px' } as React.CSSProperties,\n compactCity: { fontSize: 15, fontWeight: 700, lineHeight: 1.2 } as React.CSSProperties,\n compactHeader: { display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: 12 } as React.CSSProperties,\n compactTemp: { fontSize: 32, fontWeight: 600, lineHeight: 1 } as React.CSSProperties,\n compactTime: { fontSize: 26, fontWeight: 600, lineHeight: 1.1 } as React.CSSProperties,\n compactSeconds: { fontSize: 13, fontWeight: 500, opacity: 0.6, marginLeft: 2 } as React.CSSProperties,\n compactDate: { fontSize: 11, opacity: 0.7, marginTop: 5 } as React.CSSProperties,\n compactStats: { display: 'flex', flexWrap: 'wrap', gap: '8px 18px', fontSize: 12, opacity: 0.9 } as React.CSSProperties,\n // Three equal columns on the right side of the card, each stacking\n // weekday / icon / high-low, vertically centered against the current\n // conditions and nudged over behind a divider so the two halves read as\n // separate sections.\n upcoming: {\n display: 'grid',\n gridTemplateColumns: 'repeat(3, 1fr)',\n alignContent: 'center',\n gap: 14,\n flex: '1 1 190px',\n minWidth: 0,\n paddingLeft: 22,\n borderLeft: '1px solid currentColor',\n borderLeftColor: 'rgba(128,128,128,0.25)',\n fontSize: 11,\n } as React.CSSProperties,\n upcomingDay: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 5, textAlign: 'center' } as React.CSSProperties,\n upcomingDayName: { opacity: 0.7, whiteSpace: 'nowrap' } as React.CSSProperties,\n upcomingTemps: { whiteSpace: 'nowrap' } as React.CSSProperties,\n compactStat: { display: 'flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' } as React.CSSProperties,\n unitSwitch: { display: 'inline-flex', alignItems: 'center', cursor: 'pointer', userSelect: 'none' } as React.CSSProperties,\n rain: { color: '#2563eb', whiteSpace: 'nowrap' } as React.CSSProperties,\n tz: { fontSize: 12, opacity: 0.7, marginTop: 2 } as React.CSSProperties,\n list: { display: 'flex', flexDirection: 'column' } as React.CSSProperties,\n};\n\n// Precipitation chance is only surfaced once it represents a meaningful risk.\nconst RAIN_RISK_THRESHOLD = 2;\n\nfunction RainBadge({ probability, compact }: { probability?: number; compact?: boolean }) {\n if (probability === undefined || probability === null || probability <= RAIN_RISK_THRESHOLD) return null;\n return <span style={styles.rain}>{compact ? ` · ${probability}%` : ` · ${probability}% rain`}</span>;\n}\n\n/**\n * Renders one location's forecast: a live current time (ticking, in the\n * location's own timezone), a clickable degF / degC unit switch, high/low and\n * precipitation for the next hours and days.\n */\nfunction SingleWeatherForecast(props: Props) {\n // Temperature unit is local state so the degF / degC switch can toggle it;\n // it is seeded from (and re-synced to) the temperatureUnit prop.\n const [unit, setUnit] = useState<TemperatureUnit>(props.temperatureUnit ?? 'fahrenheit');\n useEffect(() => {\n setUnit(props.temperatureUnit ?? 'fahrenheit');\n }, [props.temperatureUnit]);\n\n const { data, loading, error } = useWeatherForecast({ ...props, temperatureUnit: unit });\n\n // A ticking clock so the displayed current time stays accurate to the second.\n const [now, setNow] = useState(() => new Date());\n useEffect(() => {\n const id = setInterval(() => setNow(new Date()), 1000);\n return () => clearInterval(id);\n }, []);\n\n const toggleUnit = () => setUnit((u) => (u === 'celsius' ? 'fahrenheit' : 'celsius'));\n\n const renderUnitSwitch = (fontSize: number) => (\n <span\n role=\"button\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n toggleUnit();\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggleUnit();\n }\n }}\n title=\"Switch temperature units\"\n aria-label={`Switch temperature units, currently ${unit === 'celsius' ? 'Celsius' : 'Fahrenheit'}`}\n style={{ ...styles.unitSwitch, fontSize }}\n >\n <span style={{ fontWeight: unit === 'fahrenheit' ? 700 : 400, opacity: unit === 'fahrenheit' ? 1 : 0.45 }}>&deg;F</span>\n <span style={{ opacity: 0.4, margin: '0 4px' }}>|</span>\n <span style={{ fontWeight: unit === 'celsius' ? 700 : 400, opacity: unit === 'celsius' ? 1 : 0.45 }}>&deg;C</span>\n </span>\n );\n\n // Keep showing the last forecast while a unit switch refetches in the\n // background; only suppress output on the very first load.\n if (loading && !data) return null;\n if (error) return <div className={props.className} style={props.style}>Error: {error.message}</div>;\n if (!data) return <div className={props.className} style={props.style}>No forecast available.</div>;\n\n const timezone = data.location?.timezone;\n const currentDate = formatInZone(now, timezone, { weekday: 'short', month: 'short', day: 'numeric' });\n const currentTime = formatInZone(now, timezone, { hour: 'numeric', minute: '2-digit', second: '2-digit', timeZoneName: 'short' });\n // The compact layout shows the clock at a larger size, so the zone label is\n // split off onto the smaller date line to keep the time on one line.\n const currentClock = clockParts(now, timezone);\n const currentZone = zoneLabel(now, timezone);\n\n if (props.compact) {\n const today = data.daily[0];\n const windSpeedUnitLabel = WIND_SPEED_UNIT_LABELS[props.windSpeedUnit ?? 'mph'];\n // Today is already summarised in the card's own stats line, so the\n // upcoming-days section starts at tomorrow and shows the next three days.\n const upcomingDays = data.daily.slice(1, 4);\n\n return (\n <div className={props.className} style={{ ...styles.compactRoot, ...props.style }}>\n <div style={styles.compactBody}>\n <div style={styles.compactRow}>\n <div style={styles.compactCity}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </div>\n\n <div style={styles.compactHeader}>\n <WeatherIcon condition={data.current.icon} width={30} height={30} title=\"Current weather\" />\n <strong style={styles.compactTemp}>{data.current.temperature}</strong>\n {renderUnitSwitch(13)}\n </div>\n </div>\n\n <div style={styles.compactRow}>\n <div>\n <div style={styles.compactTime}>\n {currentClock.main}\n {currentClock.tail && <span style={styles.compactSeconds}>{currentClock.tail}</span>}\n </div>\n <div style={styles.compactDate}>\n {currentDate}\n {currentZone ? ` · ${currentZone}` : ''}\n </div>\n </div>\n\n <div style={styles.compactStats}>\n {today && (\n <span style={styles.compactStat}>\n <WeatherIcon condition={today.icon} width={16} height={16} title=\"Today's high and low\" />\n {today.max}&deg; / {today.min}&deg;\n <RainBadge probability={today.precipitationProbabilityMax} compact />\n </span>\n )}\n <span style={styles.compactStat}>\n <WindIcon width={16} height={16} title=\"Wind speed\" />\n {data.current.windSpeed !== undefined ? `${Math.round(data.current.windSpeed)} ${windSpeedUnitLabel}` : '—'}\n </span>\n </div>\n </div>\n </div>\n\n {upcomingDays.length > 0 && (\n <div style={styles.upcoming}>\n {upcomingDays.map((day) => (\n <div key={day.date} style={styles.upcomingDay} title={upcomingDayDetail(day, windSpeedUnitLabel)}>\n <span style={styles.upcomingDayName}>{upcomingDayLabel(day.date)}</span>\n <WeatherIcon condition={day.icon} width={14} height={14} />\n <span style={styles.upcomingTemps}>\n <strong>{day.max}&deg;</strong>\n <span style={{ opacity: 0.6 }}> / {day.min}&deg;</span>\n </span>\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={{ ...styles.rain, fontSize: 10 }}>{day.precipitationProbabilityMax}%</span>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n }\n\n return (\n <div className={props.className} style={{ ...styles.root, ...props.style }}>\n <div>\n <h3 style={{ margin: 0, fontSize: 20 }}>\n {data.location?.city || 'Current location'}\n {data.location?.region ? `, ${data.location.region}` : ''}\n </h3>\n <div style={styles.tz}>\n {currentDate} &middot; {currentTime}\n {timezone ? ` · ${timezone}` : ''}\n </div>\n <div style={{ ...styles.row, marginTop: 8 }}>\n <WeatherIcon condition={data.current.icon} width={28} height={28} title=\"Current weather\" />\n <strong style={{ fontSize: 28 }}>{data.current.temperature}</strong>\n {renderUnitSwitch(14)}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next hours</h4>\n <div style={styles.hours}>\n {data.hourly.slice(0, props.forecastHours ?? 12).map((hour) => (\n <div key={hour.time} style={styles.hourCard}>\n <div style={{ fontSize: 12 }}>{new Date(hour.time).toLocaleTimeString([], { hour: 'numeric' })}</div>\n <WeatherIcon condition={hour.icon} width={20} height={20} style={{ margin: '6px auto' }} />\n <div>{hour.temperature}&deg;</div>\n {hour.precipitationProbability !== undefined && hour.precipitationProbability > 0 && (\n <div style={{ ...styles.rain, fontSize: 12 }}>{hour.precipitationProbability}%</div>\n )}\n </div>\n ))}\n </div>\n </div>\n\n <div style={{ marginTop: 20 }}>\n <h4 style={{ margin: '0 0 10px 0' }}>Next days</h4>\n <div>\n {data.daily.map((day) => (\n <div key={day.date} style={styles.dayRow}>\n <div style={{ width: 120 }}>{new Date(day.date).toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' })}</div>\n <WeatherIcon condition={day.icon} width={20} height={20} />\n <div>\n {day.min}&deg; / {day.max}&deg;\n {day.precipitationProbabilityMax !== undefined && day.precipitationProbabilityMax > RAIN_RISK_THRESHOLD && (\n <span style={styles.rain}> &middot; {day.precipitationProbabilityMax}%</span>\n )}\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Weather forecast widget. When `locations` is provided, one forecast is\n * rendered per location; otherwise a single auto-detected forecast is shown.\n */\nexport function WeatherForecast(props: Props) {\n const { locations, ...rest } = props;\n\n if (locations && locations.length > 0) {\n const gap = props.compact ? 8 : 16;\n return (\n <div style={{ ...styles.list, gap }}>\n {locations.map((loc, index) => (\n <SingleWeatherForecast\n key={`${loc.label ?? ''}:${loc.latitude ?? ''}:${loc.longitude ?? ''}:${index}`}\n {...rest}\n latitude={typeof loc.latitude === 'number' ? loc.latitude : undefined}\n longitude={typeof loc.longitude === 'number' ? loc.longitude : undefined}\n location={{ city: loc.label, timezone: loc.timezone }}\n />\n ))}\n </div>\n );\n }\n\n return <SingleWeatherForecast {...rest} />;\n}\n"],"mappings":"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","names":["jsx","jsx","useEffect","useState","jsx","jsxs","useState","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-weather-forecast",
3
- "version": "0.1.110",
3
+ "version": "0.1.112",
4
4
  "description": "React weather forecast component using Open-Meteo and IP geolocation",
5
5
  "keywords": [
6
6
  "react",
@@ -33,6 +33,9 @@
33
33
  "LICENSE"
34
34
  ],
35
35
  "sideEffects": false,
36
+ "dependencies": {
37
+ "grab-url": "^1.6.22"
38
+ },
36
39
  "peerDependencies": {
37
40
  "react": "^18 || ^19",
38
41
  "react-dom": "^18 || ^19"