const RADIUS_KM = 80.4672; const KM_PER_MI = 1.609344; let COLORS = { strand: "#0A8F99", jacket: "#F0B400", ink: "#17212B", contract: "#A23B72" }; const DARK_QUERY = window.matchMedia("(prefers-color-scheme: dark)"); const STATUS = { open: "Open for bids", proposed: "Proposed", provisional: "Provisional award", awarded: "Awarded", construction: "Under construction", complete: "Built", defaulted: "Defaulted" }; const S = { manifest: null, map: null, layers: {}, fine: new Map(), coarse: new Map(), contracts: new Map(), beadFine: new Map(), beadCoarse: new Map(), beadProjects: new Map() }; const $ = id => document.getElementById(id); function el(tag, cls, text) { const n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; } function esc(s) { return String(s).replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" })[c]); } async function getJSON(url) { try { const r = await fetch(url, { cache: "no-cache" }); return r.ok ? await r.json() : null; } catch { return null; } } function fmtSpeed(mbps) { if (!mbps) return "speed not listed"; if (mbps >= 1000) { const g = mbps / 1000; return `${Number.isInteger(g) ? g : g.toFixed(1)} Gig`; } return `${mbps} Mbps`; } const fmtInt = n => Math.round(n).toLocaleString("en-US"); const fmtMoney = n => "$" + Math.round(n).toLocaleString("en-US"); function haversineKm(a, b) { const R = 6371.0088; const rad = Math.PI / 180; const dLat = (b[0] - a[0]) * rad; const dLng = (b[1] - a[1]) * rad; const s = Math.sin(dLat / 2) ** 2 + Math.cos(a[0] * rad) * Math.cos(b[0] * rad) * Math.sin(dLng / 2) ** 2; return 2 * R * Math.asin(Math.sqrt(s)); } function inRing(pt, ring) { let inside = false; for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { const [xi, yi] = ring[i]; const [xj, yj] = ring[j]; if ((yi > pt[1]) !== (yj > pt[1]) && pt[0] < ((xj - xi) * (pt[1] - yi)) / (yj - yi) + xi) inside = !inside; } return inside; } function geomDistanceKm(geom, latlng) { const pt = [latlng[1], latlng[0]]; const polys = geom.type === "Polygon" ? [geom.coordinates] : geom.type === "MultiPolygon" ? geom.coordinates : []; if (polys.some(p => inRing(pt, p[0]) && !p.slice(1).some(h => inRing(pt, h)))) return 0; let best = Infinity; const visit = c => { if (typeof c[0] === "number") best = Math.min(best, haversineKm(latlng, [c[1], c[0]])); else c.forEach(visit); }; visit(geom.coordinates); return best; } function cssVar(name) { return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); } function savedTheme() { try { const t = localStorage.getItem("theme"); return t === "dark" || t === "light" ? t : null; } catch { return null; } } function currentTheme() { return savedTheme() || S.themeOverride || (DARK_QUERY.matches ? "dark" : "light"); } function refreshColors() { COLORS = { strand: cssVar("--strand") || COLORS.strand, jacket: cssVar("--jacket") || COLORS.jacket, ink: cssVar("--ink") || COLORS.ink, contract: cssVar("--contract") || COLORS.contract }; } function setTiles() { const dark = currentTheme() === "dark"; const shade = dark ? "Dark" : "Light"; const esri = "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/"; if (S.tiles) S.tiles.forEach(t => S.map.removeLayer(t)); S.tiles = [ L.tileLayer(esri + `World_${shade}_Gray_Base/MapServer/tile/{z}/{y}/{x}`, { maxZoom: 16, attribution: "Tiles © Esri, HERE, Garmin, © OpenStreetMap contributors" }), L.tileLayer(esri + `World_${shade}_Gray_Reference/MapServer/tile/{z}/{y}/{x}`, { maxZoom: 16, pane: "labels" }) ]; S.tiles.forEach(t => t.addTo(S.map)); } function renderLegend() { if (!S.legendEl) return; S.legendEl.innerHTML = `
Fiber reported
` + `
Contract or bid area
` + `
50 miles
`; } function updateToggle() { const btn = $("theme"); if (!btn) return; const dark = currentTheme() === "dark"; btn.textContent = dark ? "Light mode" : "Dark mode"; btn.setAttribute("aria-pressed", String(dark)); } function applyTheme() { const saved = savedTheme() || S.themeOverride; if (saved) document.documentElement.dataset.theme = saved; else delete document.documentElement.dataset.theme; refreshColors(); setTiles(); renderLegend(); updateToggle(); if (S.last) { const { place, at, region, bead, deals } = S.last; drawRegion(region, bead); drawContracts(deals.items); drawFocus(place, at); } } function toggleTheme() { const next = currentTheme() === "dark" ? "light" : "dark"; S.themeOverride = next; try { localStorage.setItem("theme", next); } catch {} applyTheme(); } function initMap() { const map = L.map("map", { preferCanvas: true }).setView([34.3, -111.7], 6); S.map = map; const labels = map.createPane("labels"); labels.style.zIndex = 450; labels.style.pointerEvents = "none"; S.layers.bead = L.layerGroup().addTo(map); S.layers.fiber = L.layerGroup().addTo(map); S.layers.contracts = L.layerGroup().addTo(map); S.layers.focus = L.layerGroup().addTo(map); const legend = L.control({ position: "bottomright" }); legend.onAdd = () => { S.legendEl = L.DomUtil.create("div", "legend"); renderLegend(); return S.legendEl; }; legend.addTo(map); applyTheme(); } async function geocodeEsri(q) { const url = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?" + new URLSearchParams({ SingleLine: q, f: "json", outFields: "Match_addr,RegionAbbr,Subregion", sourceCountry: "USA", maxLocations: "1", outSR: "4326" }); try { const r = await fetch(url); if (!r.ok) return null; const d = await r.json(); const c = (d.candidates || [])[0]; if (!c || c.score < 80) return null; const a = c.attributes || {}; return { lat: c.location.y, lng: c.location.x, label: a.Match_addr || c.address, state: a.RegionAbbr || null, county: (a.Subregion || "").replace(/\s+County$/i, "").trim() }; } catch { return null; } } async function geocodeOsm(q) { const url = "https://nominatim.openstreetmap.org/search?" + new URLSearchParams({ q, format: "jsonv2", addressdetails: "1", limit: "1", countrycodes: "us" }); try { const r = await fetch(url, { headers: { "Accept-Language": "en" } }); if (!r.ok) return null; const list = await r.json(); if (!list.length) return null; const hit = list[0]; const a = hit.address || {}; const iso = a["ISO3166-2-lvl4"] || ""; return { lat: Number(hit.lat), lng: Number(hit.lon), label: hit.display_name.split(",").slice(0, 4).join(",").trim(), state: iso.startsWith("US-") ? iso.slice(3) : null, county: (a.county || "").replace(/\s+County$/i, "").trim() }; } catch { return null; } } async function geocode(q) { return (await geocodeEsri(q)) || (await geocodeOsm(q)); } function shard(cache, dir, id, root = "fiber") { const key = `${root}/${dir}/${id}`; if (!cache.has(key)) cache.set(key, getJSON(`data/${key}.json`)); return cache.get(key); } async function beadNear(lat, lng) { const m = S.manifest; const cell = h3.latLngToCell(lat, lng, m.fineRes); const ring = h3.gridDisk(cell, 1); const ids = [...new Set(ring.map(c => h3.cellToParent(c, m.fineShardRes)))]; const docs = new Map(await Promise.all(ids.map(async id => [id, await shard(S.beadFine, "r8", id, "bead")]))); const own = new Set(); const next = new Set(); for (const c of ring) { const d = docs.get(h3.cellToParent(c, m.fineShardRes)); const v = d && d.c[c]; if (!v) continue; for (const [gpid] of v) (c === cell ? own : next).add(gpid); } const ids2 = h3.gridDisk(h3.latLngToCell(lat, lng, m.coarseShardRes), 2); const coarse = await Promise.all(ids2.map(id => shard(S.beadCoarse, "r6", id, "bead"))); const cells = []; const projects = new Map(); for (const d of coarse) { if (!d) continue; for (const [c, v] of Object.entries(d.c)) { const km = haversineKm([lat, lng], h3.cellToLatLng(c)); if (km > RADIUS_KM) continue; cells.push({ cell: c, projects: v }); for (const [gpid, n] of v) { const e = projects.get(gpid) || { gpid, near: 0, km: Infinity }; e.near += n; e.km = Math.min(e.km, km); projects.set(gpid, e); } } } for (const gpid of [...own, ...next]) if (!projects.has(gpid)) projects.set(gpid, { gpid, near: 0, km: 0 }); const states = [...new Set([...projects.keys()].map(g => g.split(":")[0]))]; const meta = new Map(await Promise.all(states.map(async st => { if (!S.beadProjects.has(st)) S.beadProjects.set(st, getJSON(`data/bead/projects/${st}.json`)); return [st, await S.beadProjects.get(st)]; }))); const list = []; for (const e of projects.values()) { const i = e.gpid.indexOf(":"); const st = e.gpid.slice(0, i); const pid = e.gpid.slice(i + 1); const doc = meta.get(st); const v = doc && doc.p[pid]; if (!v) continue; list.push({ ...e, state: st, status: doc.status, name: v[0], awardee: v[1] || v[0] || "BEAD project", amount: v[2], technology: v[3], locations: v[4], covers: own.has(e.gpid) ? "own" : next.has(e.gpid) ? "next" : null }); } list.sort((a, b) => (a.covers === "own" ? 0 : a.covers === "next" ? 1 : 2) - (b.covers === "own" ? 0 : b.covers === "next" ? 1 : 2) || a.km - b.km); return { cells, list, byId: new Map(list.map(p => [p.gpid, p])) }; } async function fiberAt(lat, lng) { const m = S.manifest; const cell = h3.latLngToCell(lat, lng, m.fineRes); const ring = h3.gridDisk(cell, 1); const ids = [...new Set(ring.map(c => h3.cellToParent(c, m.fineShardRes)))]; const docs = new Map(await Promise.all(ids.map(async id => [id, await shard(S.fine, "r8", id)]))); const read = c => { const d = docs.get(h3.cellToParent(c, m.fineShardRes)); const v = d && d.c[c]; if (!v) return null; return { cell: c, count: v[0], providers: v[1].map(([i, down, up]) => ({ name: d.p[i], down, up })) }; }; return { cell, own: read(cell), near: ring.filter(c => c !== cell).map(read).filter(Boolean) }; } async function fiberRegion(lat, lng) { const m = S.manifest; const ids = h3.gridDisk(h3.latLngToCell(lat, lng, m.coarseShardRes), 2); const docs = await Promise.all(ids.map(id => shard(S.coarse, "r6", id))); const cells = []; const totals = new Map(); for (const d of docs) { if (!d) continue; for (const [c, v] of Object.entries(d.c)) { if (haversineKm([lat, lng], h3.cellToLatLng(c)) > RADIUS_KM) continue; const names = v[1].map(i => d.p[i]); cells.push({ cell: c, count: v[0], names }); for (const n of names) totals.set(n, (totals.get(n) || 0) + 1); } } return { cells, providers: [...totals.entries()].sort((a, b) => b[1] - a[1]) }; } async function contractsNear(place) { const have = new Set(S.manifest.contractStates || []); const states = [...new Set([place.state, ...(S.manifest.contractGeoStates || [])])].filter(st => have.has(st)); const docs = await Promise.all(states.map(async st => { if (!S.contracts.has(st)) S.contracts.set(st, getJSON(`data/contracts/${st}.json`)); return [st, await S.contracts.get(st)]; })); const items = []; const programs = []; const county = place.county.toLowerCase(); for (const [st, d] of docs) { if (!d) continue; const home = st === place.state; if (home) programs.push(...(d.programs || [])); for (const it of d.items || []) { let where = null; let distance = null; if (it.geometry) { const km = geomDistanceKm(it.geometry, [place.lat, place.lng]); if (km <= RADIUS_KM) { distance = km; where = km === 0 ? "Covers this address" : `${Math.max(1, Math.round(km / KM_PER_MI))} mi away`; } } if (!where && home && county && (it.counties || []).some(c => c.toLowerCase() === county)) where = `${place.county} County`; if (!where && home && it.scope === "statewide") where = "Statewide"; if (where) items.push({ ...it, state: st, where, distance }); } } const rank = it => (it.distance != null ? it.distance : it.where === "Statewide" ? 1e6 : 1e5); items.sort((a, b) => (a.status === "open" ? 0 : 1) - (b.status === "open" ? 0 : 1) || rank(a) - rank(b)); return { items, programs }; } function drawFocus(place, at) { const g = S.layers.focus; g.clearLayers(); L.circle([place.lat, place.lng], { radius: RADIUS_KM * 1000, color: COLORS.ink, weight: 1.5, dashArray: "6 6", fill: false, interactive: false }).addTo(g); if (at) { L.polygon(h3.cellToBoundary(at.cell), { color: COLORS.jacket, weight: 2, fill: false, interactive: false }).addTo(g); } L.marker([place.lat, place.lng], { icon: L.divIcon({ className: "", html: "
", iconSize: [18, 18], iconAnchor: [9, 9] }), title: place.label, keyboard: false }).addTo(g); } function drawRegion(region, bead) { const g = S.layers.fiber; g.clearLayers(); const b = S.layers.bead; b.clearLayers(); const fiberCells = new Map((region ? region.cells : []).map(c => [c.cell, c])); const beadCells = new Map((bead ? bead.cells : []).map(c => [c.cell, c])); const max = region ? region.cells.reduce((m, c) => Math.max(m, c.count), 1) : 1; const beadText = c => { const names = [...new Set(c.projects.map(([gpid]) => bead.byId.get(gpid)).filter(Boolean).map(p => p.awardee))]; const n = c.projects.reduce((s, [, k]) => s + k, 0); const shown = names.slice(0, 3).map(esc).join(", ") + (names.length > 3 ? `, +${names.length - 3} more` : ""); return `${fmtInt(n)} BEAD locations${shown ? `
${shown}` : ""}`; }; for (const c of beadCells.values()) { const poly = L.polygon(h3.cellToBoundary(c.cell), { color: COLORS.contract, weight: 1.5, dashArray: "3 3", fillColor: COLORS.contract, fillOpacity: 0.16, interactive: !fiberCells.has(c.cell) }); if (!fiberCells.has(c.cell)) poly.bindTooltip(beadText(c), { sticky: true }); poly.addTo(b); } for (const c of fiberCells.values()) { const t = Math.log1p(c.count) / Math.log1p(max); const names = c.names.slice(0, 4).map(esc).join(", ") + (c.names.length > 4 ? `, +${c.names.length - 4} more` : ""); const extra = beadCells.has(c.cell) ? `

${beadText(beadCells.get(c.cell))}` : ""; L.polygon(h3.cellToBoundary(c.cell), { color: COLORS.strand, weight: 0.5, opacity: 0.4, fillColor: COLORS.strand, fillOpacity: 0.12 + 0.5 * t }).bindTooltip(`${fmtInt(c.count)} fiber locations
${names}${extra}`, { sticky: true }).addTo(g); } } function drawContracts(items) { const g = S.layers.contracts; g.clearLayers(); for (const it of items) { if (!it.geometry) continue; const color = it.status === "open" ? COLORS.jacket : COLORS.contract; L.geoJSON(it.geometry, { style: { color, weight: 2, fillColor: color, fillOpacity: 0.12 }, pointToLayer: (f, ll) => L.circleMarker(ll, { radius: 7, color, weight: 2, fillColor: color, fillOpacity: 0.5 }) }).bindTooltip(`${esc(it.awardee || "Open bid")}
${esc(it.program || "")}`, { sticky: true }).addTo(g); } } function verdictFor(place, covered, at, region) { if (!covered) { const list = S.manifest.states.length ? S.manifest.states.join(", ") : "no states yet"; return { kind: "unknown", title: "No fiber data for this state yet", text: `Fiber data currently covers ${list}. Add ${place.state || "this state"} to the data build to check this address.` }; } if (at.own) { return { kind: "yes", title: "Fiber is available here", text: `Reported at ${fmtInt(at.own.count)} ${at.own.count === 1 ? "location" : "locations"} in the quarter square mile around this address.` }; } if (at.near.length) { const n = at.near.reduce((s, c) => s + c.count, 0); return { kind: "near", title: "Fiber is close by", text: `Not reported on this block, but reported at ${fmtInt(n)} ${n === 1 ? "location" : "locations"} within about half a mile.` }; } return { kind: "no", title: "No fiber reported here", text: region && region.cells.length ? "Fiber is reported elsewhere within 50 miles. See the map." : "No fiber is reported within 50 miles of this address." }; } function mergeProviders(cells) { const m = new Map(); for (const c of cells) { for (const p of c.providers) { const e = m.get(p.name); if (!e) m.set(p.name, { ...p }); else { e.down = Math.max(e.down, p.down); e.up = Math.max(e.up, p.up); } } } return [...m.values()].sort((a, b) => b.down - a.down || a.name.localeCompare(b.name)); } function providerList(providers) { const ul = el("ul", "list"); for (const p of providers) { const li = el("li"); li.append(el("span", null, p.name), el("span", "sub", `${fmtSpeed(p.down)} down, ${fmtSpeed(p.up)} up`)); ul.append(li); } return ul; } function beadSection(bead) { const wrap = el("div", "bead"); const covering = bead.list.filter(p => p.covers); const others = bead.list.filter(p => !p.covers); for (const p of covering) { wrap.append(contractCard({ awardee: p.awardee, status: p.status, where: p.covers === "own" ? "Covers this address" : "Next to this address", program: "BEAD", technology: p.technology, amount: p.amount, locations: p.locations, notes: p.name && p.name !== p.awardee ? `Project: ${p.name}` : null })); } if (others.length) { const shown = 6; wrap.append(el("h4", null, `BEAD projects within 50 miles (${others.length})`)); const ul = el("ul", "list"); others.forEach((p, i) => { const li = el("li"); if (i >= shown) li.hidden = true; const parts = [`${Math.max(1, Math.round(p.km / KM_PER_MI))} mi`]; if (p.amount) parts.push(fmtMoney(p.amount)); if (p.technology) parts.push(p.technology); li.append(el("span", null, p.awardee), el("span", "sub", parts.join(", "))); ul.append(li); }); wrap.append(ul); if (others.length > shown) { const b = el("button", "more", `Show all ${others.length}`); b.type = "button"; b.addEventListener("click", () => { ul.querySelectorAll("li[hidden]").forEach(li => { li.hidden = false; }); b.remove(); }); wrap.append(b); } } const proposed = bead.list.some(p => p.status === "proposed"); wrap.append(el("p", "note", `BEAD project areas from state Final Proposals${proposed ? ", some still awaiting NTIA approval" : ""}. Shaded purple on the map.`)); return wrap; } function contractCard(it) { const card = el("article", "contract" + (it.status === "open" ? " open" : "")); card.append(el("h4", null, it.awardee || it.title || "Open bid")); const facts = el("div", "facts"); const badge = el("span", "badge" + (it.status === "open" ? " open" : ""), STATUS[it.status] || it.status || "Listed"); facts.append(badge, el("span", null, it.where)); if (it.program) facts.append(el("span", null, it.program)); if (it.technology) facts.append(el("span", null, it.technology)); if (it.amount != null) facts.append(el("span", null, fmtMoney(it.amount))); if (it.locations != null) facts.append(el("span", null, `${fmtInt(it.locations)} locations`)); if (it.deadline) facts.append(el("span", null, `Bids due ${it.deadline}`)); card.append(facts); if (it.notes) card.append(el("p", null, it.notes)); if (it.source) { const p = el("p"); const a = el("a", null, "Source"); a.href = it.source; a.target = "_blank"; a.rel = "noopener"; p.append(a); if (it.updated) p.append(` (as of ${it.updated})`); card.append(p); } return card; } function statewideList(items) { const shown = 6; const wrap = el("div", "statewide"); wrap.append(el("h4", null, `Statewide awards (${items.length})`)); const provisional = items.every(it => it.status === "provisional"); wrap.append(el("p", "note", `${provisional ? "Provisional awards" : "Awards"} across the state. Project areas are not mapped here yet, so these may not cover this address.`)); const ul = el("ul", "list"); items.forEach((it, i) => { const li = el("li"); if (i >= shown) li.hidden = true; const parts = []; if (it.amount) parts.push(fmtMoney(it.amount)); if (it.technology) parts.push(it.technology); if (it.locations) parts.push(`${fmtInt(it.locations)} locations`); li.append(el("span", null, it.awardee || it.title), el("span", "sub", parts.join(", "))); ul.append(li); }); wrap.append(ul); if (items.length > shown) { const b = el("button", "more", `Show all ${items.length}`); b.type = "button"; b.addEventListener("click", () => { ul.querySelectorAll("li[hidden]").forEach(li => { li.hidden = false; }); b.remove(); }); wrap.append(b); } const src = items.find(it => it.source); if (src) { const p = el("p", "note"); const a = el("a", null, "Source"); a.href = src.source; a.target = "_blank"; a.rel = "noopener"; p.append(a); if (src.updated) p.append(` (as of ${src.updated})`); wrap.append(p); } return wrap; } function render(place, covered, at, region, deals, bead) { const root = $("result"); root.replaceChildren(); const v = verdictFor(place, covered, at, region); const vb = el("section", `verdict ${v.kind}`); vb.append(el("p", "address", place.label), el("h2", null, v.title), el("p", null, v.text), el("div", "strand")); root.append(vb); if (covered && (at.own || at.near.length)) { const b = el("section", "block"); b.append(el("h3", null, at.own ? "Providers at this address" : "Providers close by")); b.append(providerList(mergeProviders(at.own ? [at.own] : at.near))); root.append(b); } if (covered) { const b = el("section", "block"); b.append(el("h3", null, "Fiber within 50 miles")); if (region.cells.length) { b.append(el("p", "note", `Fiber is reported in ${fmtInt(region.cells.length)} of the map areas within 50 miles.`)); const ul = el("ul", "list"); for (const [name, n] of region.providers.slice(0, 8)) { const li = el("li"); li.append(el("span", null, name), el("span", "sub", `${fmtInt(n)} ${n === 1 ? "area" : "areas"}`)); ul.append(li); } b.append(ul); if (region.providers.length > 8) b.append(el("p", "note", `Plus ${region.providers.length - 8} more providers. Hover the map for details.`)); } else { b.append(el("p", "note", "No fiber reported within 50 miles.")); } root.append(b); } const cb = el("section", "block"); cb.append(el("h3", null, "Contracts and bids")); const local = deals.items.filter(it => it.where !== "Statewide"); const wide = deals.items.filter(it => it.where === "Statewide").sort((a, b) => (b.amount || 0) - (a.amount || 0)); local.forEach(it => cb.append(contractCard(it))); if (bead && bead.list.length) cb.append(beadSection(bead)); if (wide.length) cb.append(statewideList(wide)); if (!local.length && !wide.length && !(bead && bead.list.length)) cb.append(el("p", "note", "No broadband contracts or open bids are listed near this address.")); for (const p of deals.programs) { const n = el("p", "note"); n.append(`${p.name}: ${p.summary} `); if (p.url) { const a = el("a", null, p.office || "Program page"); a.href = p.url; a.target = "_blank"; a.rel = "noopener"; n.append(a); } cb.append(n); } root.append(cb); } function renderError(msg) { const root = $("result"); root.replaceChildren(el("p", "error", msg)); } function renderMeta() { const m = S.manifest; const f = $("meta"); f.replaceChildren(); const p1 = el("p"); if (m.asOf) { p1.append(`Fiber data: FCC National Broadband Map, as of ${m.asOf}. Providers report their own coverage, so confirm with the provider before you sign up. `); } else { p1.append("Fiber data has not been built yet. Run the Update fiber data workflow. "); } const a = el("a", null, "Check the FCC map"); a.href = "https://broadbandmap.fcc.gov/"; a.target = "_blank"; a.rel = "noopener"; p1.append(a); f.append(p1); if (m.beadAsOf) { const p2 = el("p"); p2.append(`BEAD project areas: state Final Proposals compiled by `); const b = el("a", null, "BroadbandExpanded"); b.href = m.beadSource || "https://broadbandexpanded.com/funding/beadfinalproposaldata"; b.target = "_blank"; b.rel = "noopener"; p2.append(b, `, as of ${m.beadAsOf}.`); f.append(p2); } f.append(el("p", null, "Address search by Esri, with OpenStreetMap Nominatim as backup.")); } async function check(q) { const btn = $("go"); btn.disabled = true; btn.textContent = "Checking..."; try { const place = await geocode(q); if (!place) { renderError("No match for that address. Add the city and state, then try again."); return; } const covered = S.manifest.states.includes(place.state); const hasBead = (S.manifest.beadStates || []).length > 0; const [at, region, deals, bead] = await Promise.all([ covered ? fiberAt(place.lat, place.lng) : null, covered ? fiberRegion(place.lat, place.lng) : null, contractsNear(place), hasBead ? beadNear(place.lat, place.lng).catch(() => null) : null ]); if (bead && (S.manifest.beadStates || []).includes(place.state)) { deals.items = deals.items.filter(it => it.program !== "BEAD"); } S.last = { place, at, region, bead, deals }; drawRegion(region, bead); drawContracts(deals.items); drawFocus(place, at); S.map.fitBounds(L.latLng(place.lat, place.lng).toBounds(RADIUS_KM * 2000), { padding: [10, 10] }); render(place, covered, at, region, deals, bead); const url = new URL(location.href); url.searchParams.set("q", q); history.replaceState(null, "", url); } catch (e) { renderError(e.message || "Something went wrong loading the data. Reload the page and try again."); } finally { btn.disabled = false; btn.textContent = "Check address"; } } async function main() { refreshColors(); initMap(); $("theme").addEventListener("click", toggleTheme); DARK_QUERY.addEventListener("change", () => { if (!savedTheme() && !S.themeOverride) applyTheme(); }); S.manifest = (await getJSON("data/manifest.json")) || { states: [], contractStates: [], fineRes: 8, fineShardRes: 5, coarseRes: 6, coarseShardRes: 3 }; renderMeta(); $("search").addEventListener("submit", e => { e.preventDefault(); const q = $("address").value.trim(); if (q) check(q); }); const q = new URLSearchParams(location.search).get("q"); if (q) { $("address").value = q; check(q); } } main();