| 1 | --- |
| 2 | interface Props { |
| 3 | weather: string; |
| 4 | leadingSeparator?: boolean; |
| 5 | } |
| 6 | |
| 7 | const { weather, leadingSeparator } = Astro.props; |
| 8 | |
| 9 | type WeatherCategory = |
| 10 | | "storm" |
| 11 | | "sleet" |
| 12 | | "snow" |
| 13 | | "rain" |
| 14 | | "fog" |
| 15 | | "partlyCloudy" |
| 16 | | "cloudy" |
| 17 | | "clear" |
| 18 | | "unknown"; |
| 19 | |
| 20 | const categoryRules: { category: WeatherCategory; keywords: string[] }[] = [ |
| 21 | { category: "storm", keywords: ["thunder", "tstorm", "storm"] }, |
| 22 | { category: "sleet", keywords: ["sleet", "freez", "frzg", "mix"] }, |
| 23 | { category: "snow", keywords: ["snow", "flurr"] }, |
| 24 | { category: "rain", keywords: ["rain", "shower", "drizzle"] }, |
| 25 | { category: "fog", keywords: ["fog", "mist", "haze"] }, |
| 26 | { category: "partlyCloudy", keywords: ["partly", "variable"] }, |
| 27 | { category: "cloudy", keywords: ["overcast", "cloud"] }, |
| 28 | { category: "clear", keywords: ["clear", "sunny", "sun", "fair"] }, |
| 29 | ]; |
| 30 | |
| 31 | function categorize(conditions: string): WeatherCategory { |
| 32 | const c = conditions.toLowerCase(); |
| 33 | for (const rule of categoryRules) { |
| 34 | for (const kw of rule.keywords) { |
| 35 | if (c.includes(kw)) return rule.category; |
| 36 | } |
| 37 | } |
| 38 | return "unknown"; |
| 39 | } |
| 40 | |
| 41 | const weatherIcons: Partial<Record<WeatherCategory, string>> = { |
| 42 | partlyCloudy: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M164 76a71.9 71.9 0 0 0-22.14 3.48A51.8 51.8 0 0 0 129 63.83l11.56-16.51a4 4 0 0 0-6.56-4.59l-11.55 16.51A52 52 0 0 0 96 52c-1.71 0-3.4.09-5.06.25l-3.5-19.85a4 4 0 0 0-7.88 1.39l3.5 19.84A52.2 52.2 0 0 0 55.85 71L39.32 59.42A4 4 0 0 0 34.73 66l16.53 11.54A51.63 51.63 0 0 0 44 104c0 1.69.09 3.37.25 5l-19.85 3.5a4 4 0 0 0 .69 7.94a4 4 0 0 0 .7-.06l19.85-3.5A52.1 52.1 0 0 0 54 134.6A48 48 0 0 0 84 220h80a72 72 0 0 0 0-144M52 104a44 44 0 0 1 82.33-21.61a72.23 72.23 0 0 0-38.82 43A48.3 48.3 0 0 0 84 124a47.76 47.76 0 0 0-23.4 6.11A44 44 0 0 1 52 104m112 108H84a40 40 0 1 1 9.43-78.88A71.6 71.6 0 0 0 92 143.77a4 4 0 0 0 8 .46a64.3 64.3 0 0 1 2-12.67c0-.12.07-.24.09-.36A64.06 64.06 0 1 1 164 212"/></svg>`, |
| 43 | clear: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M124 40v-8a4 4 0 0 1 8 0v8a4 4 0 0 1-8 0m64 88a60 60 0 1 1-60-60a60.07 60.07 0 0 1 60 60m-8 0a52 52 0 1 0-52 52a52.06 52.06 0 0 0 52-52M61.17 66.83a4 4 0 0 0 5.66-5.66l-8-8a4 4 0 0 0-5.66 5.66Zm0 122.34l-8 8a4 4 0 0 0 5.66 5.66l8-8a4 4 0 0 0-5.66-5.66m136-136l-8 8a4 4 0 0 0 5.66 5.66l8-8a4 4 0 1 0-5.66-5.66m-2.34 136a4 4 0 0 0-5.66 5.66l8 8a4 4 0 0 0 5.66-5.66ZM40 124h-8a4 4 0 0 0 0 8h8a4 4 0 0 0 0-8m88 88a4 4 0 0 0-4 4v8a4 4 0 0 0 8 0v-8a4 4 0 0 0-4-4m96-88h-8a4 4 0 0 0 0 8h8a4 4 0 0 0 0-8"/></svg>`, |
| 44 | cloudy: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M160 44a84.11 84.11 0 0 0-76.41 49.12A60.7 60.7 0 0 0 72 92a60 60 0 0 0 0 120h88a84 84 0 0 0 0-168m0 160H72a52 52 0 1 1 8.55-103.3A83.7 83.7 0 0 0 76 128a4 4 0 0 0 8 0a76 76 0 1 1 76 76"/></svg>`, |
| 45 | storm: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M156 20a72.19 72.19 0 0 0-68.49 49.39A48 48 0 1 0 76 164h44.94l-20.37 33.94A4 4 0 0 0 104 204h32.94l-20.37 33.94a4 4 0 0 0 6.86 4.12l24-40A4 4 0 0 0 144 196h-32.94l19.2-32H156a72 72 0 0 0 0-144m0 136H76a40 40 0 1 1 9.43-78.88A71.6 71.6 0 0 0 84 87.77a4 4 0 0 0 8 .46A64.06 64.06 0 1 1 156 156"/></svg>`, |
| 46 | rain: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="m155.33 194.22l-32 48a4 4 0 1 1-6.66-4.44l32-48a4 4 0 0 1 6.66 4.44M228 92a72.08 72.08 0 0 1-72 72h-25.86l-30.81 46.22a4 4 0 1 1-6.66-4.44L120.53 164H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 92m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 156h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
| 47 | snow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M84 196a8 8 0 1 1-8-8a8 8 0 0 1 8 8m32 8a8 8 0 1 0 8 8a8 8 0 0 0-8-8m48-16a8 8 0 1 0 8 8a8 8 0 0 0-8-8m-96 40a8 8 0 1 0 8 8a8 8 0 0 0-8-8m88 0a8 8 0 1 0 8 8a8 8 0 0 0-8-8m72-136a72.08 72.08 0 0 1-72 72H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 92m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 156h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
| 48 | sleet: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M84 196a8 8 0 1 1-8-8a8 8 0 0 1 8 8m32 8a8 8 0 1 0 8 8a8 8 0 0 0-8-8m48-16a8 8 0 1 0 8 8a8 8 0 0 0-8-8m-96 40a8 8 0 1 0 8 8a8 8 0 0 0-8-8m88 0a8 8 0 1 0 8 8a8 8 0 0 0-8-8m72-136a72.08 72.08 0 0 1-72 72H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 92m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 156h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
| 49 | fog: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M120 204H72a4 4 0 0 1 0-8h48a4 4 0 0 1 0 8m64-8h-24a4 4 0 0 0 0 8h24a4 4 0 0 0 0-8m-24 32h-56a4 4 0 0 0 0 8h56a4 4 0 0 0 0-8m68-128a72.08 72.08 0 0 1-72 72H76a48 48 0 1 1 11.51-94.61A72.08 72.08 0 0 1 228 100m-8 0a64.06 64.06 0 0 0-128-3.77a4 4 0 0 1-8-.46a71.6 71.6 0 0 1 1.42-10.65A40 40 0 1 0 76 164h80a64.07 64.07 0 0 0 64-64"/></svg>`, |
| 50 | }; |
| 51 | |
| 52 | const parts = weather.split(",").map((p) => p.trim()); |
| 53 | |
| 54 | const valid = parts.length >= 4; |
| 55 | |
| 56 | const qualifierRE = |
| 57 | /^(slight chance|chance|isolated|scattered|numerous|widespread|patchy|areas|periods|occasional|frequent)\s+(of\s+)?/i; |
| 58 | const trailingQualifierRE = /\s+(likely)$/i; |
| 59 | |
| 60 | const conditions = valid |
| 61 | ? parts[0].replace(qualifierRE, "").replace(trailingQualifierRE, "").trim() |
| 62 | : ""; |
| 63 | const temperature = valid ? `${parts[1]}°F` : ""; |
| 64 | const location = valid ? `${parts[2]}, ${parts[3]}` : ""; |
| 65 | const icon = valid ? weatherIcons[categorize(conditions)] : undefined; |
| 66 | --- |
| 67 | |
| 68 | {valid && ( |
| 69 | <> |
| 70 | {leadingSeparator && |
| 71 | <span>•</span> |
| 72 | } |
| 73 | <span class="inline-flex items-center gap-1.5"> |
| 74 | {icon && <span class="inline-flex items-center [&>svg]:size-4" set:html={icon} />} |
| 75 | {conditions}{" "}{temperature} |
| 76 | </span> |
| 77 | <span>•</span> |
| 78 | <span class="inline-flex items-center gap-1.5"> |
| 79 | <svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M128 68a36 36 0 1 0 36 36a36 36 0 0 0-36-36m0 64a28 28 0 1 1 28-28a28 28 0 0 1-28 28m0-112a84.09 84.09 0 0 0-84 84c0 30.42 14.17 62.79 41 93.62a250 250 0 0 0 40.73 37.66a4 4 0 0 0 4.58 0A250 250 0 0 0 171 197.62c26.81-30.83 41-63.2 41-93.62a84.09 84.09 0 0 0-84-84m37.1 172.23A254.6 254.6 0 0 1 128 227a254.6 254.6 0 0 1-37.1-34.81C73.15 171.8 52 139.9 52 104a76 76 0 0 1 152 0c0 35.9-21.15 67.8-38.9 88.23"/></svg> |
| 80 | {location} |
| 81 | </span> |
| 82 | </> |
| 83 | )} |