Files
weather/app/public/alert-info.js
T
Nate EmmertandClaude Sonnet 5.5 72a4a3fe38
Publish Container / publish (push) Successful in 2m44s
Alerts tab: fix hurricane/tropical storm bugs; rich alert cards matching the dashboard
Hurricane / tropical storm fixes
- NHC intensity is in knots: show real mph (80 kt = 92 mph) instead of mislabelled 'mph'
- Real Saffir-Simpson category from wind (was always cat 1); subtropical, post-tropical and
  potential-cyclone types no longer shown as 'depression'; each storm shows movement
  (direction + mph, previously a non-existent field), distance from you, update time, advisory link
- Forecast track points coloured by their own forecast strength (not the current class)
- Map click handlers registered once (popups stacked on every refresh); overlays wait for the
  map to settle instead of silently not drawing while tiles load; stale renders superseded
- Subtropical/tropical advice added to alert guidance; escaped storm names

Alert cards (active alerts only)
- What it's for, area, window, time remaining, plain-language meaning, what to do, and a
  Full details expander (headline, text, instructions, severity/urgency/certainty, issuer)
- No longer drops alerts beyond 6 per severity group; expired alerts are filtered out
- Shared plain-language alert glossary (alert-info.js, 64 products incl. tropical and surge)
  now used by both the app and the dashboard history

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 14:00:41 -04:00

102 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Shared plain-language alert helpers (used by the weather app and the station dashboard) */
(() => {
'use strict';
const GLOSSARY = {
'heat advisory': 'Dangerously hot conditions expected. Limit time outdoors, drink plenty of water, and check on children, older adults and pets.',
'excessive heat watch': 'Conditions are favorable for life-threatening heat in the next day or two. Plan ahead.',
'excessive heat warning': 'Life-threatening heat is occurring or imminent. Avoid outdoor activity and stay cool.',
'extreme heat watch': 'Conditions are favorable for life-threatening heat in the next day or two. Plan ahead.',
'extreme heat warning': 'Life-threatening heat is occurring or imminent. Avoid outdoor activity and stay cool.',
'severe thunderstorm watch': 'Storms capable of damaging wind or large hail are possible in the area. Stay alert and be ready to take shelter.',
'severe thunderstorm warning': 'A severe storm with damaging wind and/or large hail is happening or imminent. Move indoors away from windows.',
'tornado watch': 'Conditions are favorable for tornadoes. Stay alert and know where you would take shelter.',
'tornado warning': 'A tornado is occurring or imminent. Take shelter immediately in the lowest, most interior room.',
'special weather statement': 'A short notice about a significant but non-warning weather event, often a strong storm, gusty winds, dense fog or sudden changes.',
'flood watch': 'Flooding is possible. Be prepared to move to higher ground and avoid low-lying areas.',
'flood advisory': 'Minor flooding is occurring or expected. Avoid flooded roads and low spots.',
'flood warning': 'Flooding is happening or imminent. Move to higher ground and never drive through flood water.',
'flash flood watch': 'Flash flooding is possible. Be ready to move to higher ground quickly.',
'flash flood warning': 'Flash flooding is occurring or imminent. Move to higher ground now.',
'wind advisory': 'Sustained or gusty winds that can blow around unsecured objects and make driving difficult for high-profile vehicles.',
'high wind watch': 'Damaging winds are possible. Secure outdoor objects.',
'high wind warning': 'Damaging winds are occurring or imminent. Secure objects and avoid travel if possible.',
'freeze watch': 'A freeze is possible. Protect sensitive plants, pets and exposed pipes.',
'freeze warning': 'Temperatures at or below freezing are expected. Protect plants, pets and exposed pipes.',
'frost advisory': 'Frost is expected and can damage sensitive plants.',
'hard freeze warning': 'Severe freezing temperatures are expected. Protect plants, pets and pipes.',
'winter storm watch': 'Heavy snow and/or ice is possible. Prepare for hazardous travel.',
'winter storm warning': 'Heavy snow and/or ice is occurring or imminent. Travel may be dangerous or impossible.',
'winter weather advisory': 'Snow, sleet or freezing rain will cause slippery roads and travel difficulty.',
'ice storm warning': 'Significant ice accumulation is expected. Power outages and dangerous travel are likely.',
'blizzard warning': 'Heavy snow with strong winds and very low visibility. Avoid travel.',
'wind chill advisory': 'Dangerously cold wind chills are expected. Dress in layers and limit time outside.',
'wind chill warning': 'Life-threatening wind chills are expected. Frostbite can occur quickly.',
'dense fog advisory': 'Fog is reducing visibility to a quarter mile or less. Slow down and use low-beam headlights.',
'dense smoke advisory': 'Smoke is reducing visibility and air quality.',
'air quality alert': 'Air quality is unhealthy for some or all people. Limit prolonged outdoor exertion.',
'red flag warning': 'Dry, windy and warm conditions create a high wildfire risk. Avoid open flames and outdoor burning.',
'fire weather watch': 'Conditions may soon create a high wildfire risk.',
'rip current statement': 'Dangerous rip currents at the beach. Stay out of the water or swim near a lifeguard.',
'coastal flood advisory': 'Minor coastal flooding is expected.',
'coastal flood warning': 'Significant coastal flooding is occurring or imminent.',
'small craft advisory': 'Winds or seas hazardous to small boats.',
'tropical storm watch': 'Tropical storm conditions are possible within about 48 hours.',
'tropical storm warning': 'Tropical storm conditions are expected within about 36 hours.',
'hurricane watch': 'Hurricane conditions are possible within about 48 hours.',
'hurricane warning': 'Hurricane conditions are expected within about 36 hours.',
'hazardous weather outlook': 'An outlook of possible hazardous weather in the next several days.',
'severe weather statement': 'An update on an ongoing severe storm warning.',
'flood statement': 'An update on an ongoing flood warning.',
'storm surge watch': 'Life-threatening storm surge flooding is possible within about 48 hours. Know your evacuation zone and be ready to leave.',
'storm surge warning': 'Life-threatening storm surge flooding is expected within about 36 hours. Follow evacuation orders from local officials immediately.',
'hurricane local statement': 'Local details on a hurricane threat: expected winds, rain, surge and tornado risk for your area, and what officials are advising.',
'tropical cyclone statement': 'Local details on a tropical storm or hurricane threat for your area.',
'tropical depression watch': 'A tropical depression may affect the area; heavy rain and gusty wind are possible.',
'extreme wind warning': 'Extreme, hurricane-force winds (115+ mph) are about to hit. Take shelter now in the strongest part of a sturdy building.',
'hurricane force wind watch': 'Hurricane-force winds are possible over marine areas. Stay in port.',
'hurricane force wind warning': 'Hurricane-force winds are expected over marine areas. Dangerous for all vessels.',
'storm warning': 'Strong marine winds (48–63 knots) expected. Dangerous for all vessels.',
'gale warning': 'Strong marine winds (34–47 knots) expected. Hazardous for small and large vessels.',
'gale watch': 'Gale-force marine winds are possible.',
'special marine warning': 'Short-lived, hazardous marine weather (strong winds, waterspouts, hail) is happening. Seek safe harbor.',
'high surf advisory': 'Large breaking waves make swimming and surfing dangerous.',
'high surf warning': 'Very large breaking waves are expected: dangerous and life-threatening at the shore.',
'beach hazards statement': 'Hazards at the beach such as rip currents or high surf. Swim near a lifeguard.',
'lake wind advisory': 'Gusty lake winds can create hazardous waves for small boats.',
'dust storm warning': 'Blowing dust is reducing visibility to near zero. Pull off the road and turn off your lights.',
'tornado emergency': 'A confirmed large and destructive tornado is moving toward a populated area. Take cover immediately.',
'flash flood emergency': 'Catastrophic, life-threatening flash flooding is happening. Move to higher ground now.',
};
function meaning(event) {
const e = String(event || '').toLowerCase().trim();
if (GLOSSARY[e]) return GLOSSARY[e];
if (e.endsWith('watch')) return 'A Watch: conditions are favorable for this hazard. Stay alert and be ready to act.';
if (e.endsWith('warning')) return 'A Warning: this hazard is happening or imminent. Take action now.';
if (e.endsWith('advisory')) return 'An Advisory: conditions may cause inconvenience or danger if you are not careful.';
if (e.endsWith('statement')) return 'An informational statement about developing or ongoing weather.';
return '';
}
// NWS text is often structured "* WHAT...", "* WHERE...", "* WHEN..."; lead with the WHAT part
function firstSentence(t, max = 220) {
const what = String(t || '').match(/\*\s*WHAT\.\.\.([\s\S]*?)(?:\n\s*\n|\*\s*[A-Z ]+\.\.\.|$)/);
const c = (what ? what[1] : String(t || '')).replace(/\s+/g, ' ').trim();
const m = c.match(/^.*?[.!?](\s|$)/);
const r = (m ? m[0] : c).trim();
return r.length > max ? r.slice(0, max - 3) + '…' : r;
}
const area = (a) => String(a || '').replace(/;\s*/g, ', ');
const when = (iso) => (iso ? new Date(iso).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : '');
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
// "ends in 3h 10m" / "ended" for active alerts
function remaining(iso) {
if (!iso) return '';
const ms = Date.parse(iso) - Date.now();
if (!Number.isFinite(ms)) return '';
if (ms <= 0) return 'expired';
const m = Math.round(ms / 60000);
return m < 60 ? `ends in ${m} min` : m < 1440 ? `ends in ${Math.floor(m / 60)}h ${m % 60}m` : `ends in ${Math.round(m / 144) / 10}d`.replace('.0d', 'd');
}
window.AlertInfo = { meaning, firstSentence, area, when, esc, remaining, GLOSSARY };
})();