const HULL_TYPE_MAP = { CVN: 'carrier', CV: 'carrier', DDG: 'destroyer', CG: 'destroyer', LHD: 'amphibious', LHA: 'amphibious', LPD: 'amphibious', LSD: 'amphibious', LCC: 'amphibious', SSN: 'submarine', SSBN: 'submarine', SSGN: 'submarine', FFG: 'frigate', LCS: 'frigate', MCM: 'patrol', PC: 'patrol', AS: 'auxiliary', ESB: 'auxiliary', ESD: 'auxiliary', 'T-AO': 'auxiliary', 'T-AKE': 'auxiliary', 'T-AOE': 'auxiliary', 'T-ARS': 'auxiliary', 'T-ESB': 'auxiliary', 'T-EPF': 'auxiliary', 'T-AGOS': 'research', 'T-AGS': 'research', 'T-AGM': 'research', AGOS: 'research', }; const USNI_REGION_COORDS = { 'Philippine Sea': { lat: 18.0, lon: 130.0 }, 'South China Sea': { lat: 14.0, lon: 115.0 }, 'East China Sea': { lat: 28.0, lon: 125.0 }, 'Sea of Japan': { lat: 40.0, lon: 135.0 }, 'Arabian Sea': { lat: 18.0, lon: 63.0 }, 'Red Sea': { lat: 20.0, lon: 38.0 }, 'Mediterranean Sea': { lat: 35.0, lon: 18.0 }, 'Eastern Mediterranean': { lat: 34.5, lon: 33.0 }, 'Western Mediterranean': { lat: 37.0, lon: 3.0 }, 'Persian Gulf': { lat: 26.5, lon: 52.0 }, 'Gulf of Oman': { lat: 24.5, lon: 58.5 }, 'Gulf of Aden': { lat: 12.0, lon: 47.0 }, 'Caribbean Sea': { lat: 15.0, lon: -73.0 }, 'North Atlantic': { lat: 45.0, lon: -30.0 }, 'Atlantic Ocean': { lat: 30.0, lon: -40.0 }, 'Western Atlantic': { lat: 30.0, lon: -60.0 }, 'Pacific Ocean': { lat: 20.0, lon: -150.0 }, 'Eastern Pacific': { lat: 18.0, lon: -125.0 }, 'Western Pacific': { lat: 20.0, lon: 140.0 }, 'Indian Ocean': { lat: -5.0, lon: 75.0 }, Antarctic: { lat: -70.0, lon: 20.0 }, 'Baltic Sea': { lat: 58.0, lon: 20.0 }, 'Black Sea': { lat: 43.5, lon: 34.0 }, 'Bay of Bengal': { lat: 14.0, lon: 87.0 }, Yokosuka: { lat: 35.29, lon: 139.67 }, Japan: { lat: 35.29, lon: 139.67 }, Sasebo: { lat: 33.16, lon: 129.72 }, Guam: { lat: 13.45, lon: 144.79 }, 'Pearl Harbor': { lat: 21.35, lon: -157.95 }, 'San Diego': { lat: 32.68, lon: -117.15 }, Norfolk: { lat: 36.95, lon: -76.30 }, Mayport: { lat: 30.39, lon: -81.40 }, Bahrain: { lat: 26.23, lon: 50.55 }, Rota: { lat: 36.63, lon: -6.35 }, 'Diego Garcia': { lat: -7.32, lon: 72.42 }, Djibouti: { lat: 11.55, lon: 43.15 }, Singapore: { lat: 1.35, lon: 103.82 }, 'Souda Bay': { lat: 35.49, lon: 24.08 }, Naples: { lat: 40.84, lon: 14.25 }, 'Tasman Sea': { lat: -40.0, lon: 160.0 }, 'Eastern Atlantic': { lat: 40.0, lon: -15.0 }, 'Laem Chabang, Thailand': { lat: 13.08, lon: 100.88 }, 'Laem Chabang': { lat: 13.08, lon: 100.88 }, 'Split, Croatia': { lat: 43.51, lon: 16.44 }, Split: { lat: 43.51, lon: 16.44 }, Pacific: { lat: 20.0, lon: -150.0 }, }; function usniStripHtml(html) { // & is decoded LAST: every other replace is a literal string that cannot // regenerate an entity, so amp-last decodes exactly one level // (`<` -> `<`, never `<`). return html.replace(/<[^>]+>/g, ' ').replace(/ /g, ' ').replace(/</g, '<') .replace(/>/g, '>').replace(/’/g, "'") .replace(/“/g, '"').replace(/”/g, '"').replace(/–/g, '\u2013') .replace(/&/g, '&') .replace(/\s+/g, ' ').trim(); } function usniHullToType(hull) { if (!hull) return 'unknown'; for (const [prefix, type] of Object.entries(HULL_TYPE_MAP)) { if (hull.startsWith(prefix)) return type; } return 'unknown'; } function usniDetectStatus(text) { if (!text) return 'unknown'; const l = text.toLowerCase(); if (l.includes('deployed') || l.includes('deployment')) return 'deployed'; if (l.includes('underway') || l.includes('transiting')) return 'underway'; if (l.includes('homeport') || l.includes('in port') || l.includes('pierside')) return 'in-port'; return 'unknown'; } function usniGetRegionCoords(regionText) { const norm = regionText.replace(/^(In the|In|The)\s+/i, '').trim(); if (USNI_REGION_COORDS[norm]) return USNI_REGION_COORDS[norm]; const lower = norm.toLowerCase(); let bestMatch = null; for (const [key, coords] of Object.entries(USNI_REGION_COORDS)) { const normalizedKey = key.toLowerCase(); if (normalizedKey === lower) return coords; if (lower.includes(normalizedKey) && (!bestMatch || key.length > bestMatch.key.length)) { bestMatch = { key, coords }; } } return bestMatch?.coords ?? null; } function usniParseLeadingInt(text) { const m = text.match(/\d{1,3}(?:,\d{3})*/); return m ? parseInt(m[0].replace(/,/g, ''), 10) : undefined; } function usniExtractBattleForceSummary(tableHtml) { const rows = Array.from(tableHtml.matchAll(/