(async () => { // === Curie "Locate Site" scraper → CSV: year,Facility,Assemblies,Tons (stops at 2082) === // - Facility from dropdown selection text // - Assemblies from:
  • Number of Assemblies: N
  • (missing -> 0) // - Tons from:
  • Metric Tons of Heavy Metal (MTHM): N
  • (missing -> 0) // - Debounced popup read (waits for popup content change to avoid offset) // - Dedupes per (year|Facility) // - Steps year-by-year to 2082 and saves Curie_Spent_Fuel_Timeline.csv const STOP_YEAR = 2082; const sleep = ms => new Promise(r => setTimeout(r, ms)); const inViewport = el => { if (!el || !(el instanceof Element)) return false; const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; }; // --- Year readout --- const getCurrentYear = () => { const el = document.querySelector('#slider-current-year.slider-display'); if (!el) return null; const t = (el.textContent || '').trim(); const m = t.match(/\b(18|19|20)\d{2}\b/); return m ? parseInt(m[0], 10) : null; }; // --- Timeline arrows --- const clickableSel = [ 'button', '[role="button"]', 'a', '.btn', '.button', '.MuiIconButton-root', '.v-btn', '.mdc-icon-button', '.ant-btn', '.el-button', '.mat-icon-button', '.mat-button', '.bp3-button', '[onclick]', '[ng-click]', '[data-action]', '[data-testid]', '[data-cy]', '[data-qa]' ].join(','); const findRightBtn = () => { const icon = Array.from(document.querySelectorAll('i.fa.fa-arrow-right, i.fa-arrow-right, i.fas.fa-arrow-right')) .find(inViewport); return icon ? (icon.closest(clickableSel) || icon) : null; }; const findLeftBtn = () => { const icon = Array.from(document.querySelectorAll('i.fa.fa-arrow-left, i.fa-arrow-left, i.fas.fa-arrow-left')) .find(inViewport); return icon ? (icon.closest(clickableSel) || icon) : null; }; const waitForYearChangeFrom = async (prevYear, timeoutMs = 4000, pollMs = 120) => { const start = performance.now(); while (performance.now() - start < timeoutMs) { const y = getCurrentYear(); if (Number.isInteger(y) && y !== prevYear) return y; await sleep(pollMs); } return getCurrentYear(); }; const stepRightOneYear = async (rightBtn, leftBtn, prevYear) => { if (!rightBtn) return null; rightBtn.click(); let y = await waitForYearChangeFrom(prevYear); if (!Number.isInteger(y) || y === prevYear) { await sleep(250); rightBtn.click(); y = await waitForYearChangeFrom(prevYear); } // Overshoot correction (> +1) if (Number.isInteger(y) && y > prevYear + 1 && leftBtn) { let guard = 0; while (y > prevYear + 1 && guard < 10) { leftBtn.click(); const prev = y; y = await waitForYearChangeFrom(prev, 2500, 120); if (!Number.isInteger(y)) y = prev - 1; guard++; } if (y > prevYear + 1) y = prevYear + 1; } if (!Number.isInteger(y) || y === prevYear) return null; return y; }; // --- Popup helpers & parsing (DOM-based li extraction) --- const popupEl = () => document.querySelector('.leaflet-popup-content'); const popupSignature = () => { const el = popupEl(); return el ? (el.innerHTML || '').trim() : ''; }; const waitForPopup = async (timeoutMs = 3500, pollMs = 90) => { const start = performance.now(); while (performance.now() - start < timeoutMs) { const el = popupEl(); if (el && el.offsetParent !== null) return el; await sleep(pollMs); } return null; }; const waitForPopupContentChange = async (prevSig, timeoutMs = 4000, pollMs = 90) => { const start = performance.now(); while (performance.now() - start < timeoutMs) { const sig = popupSignature(); if (sig && sig !== prevSig) return sig; await sleep(pollMs); } return popupSignature(); // may be unchanged }; const closePopupIfOpen = () => { const btn = document.querySelector('.leaflet-popup-close-button'); if (btn) btn.click(); }; // Extract Assemblies and Tons from
  • elements in the popup const extractFromPopup = (popup) => { let Assemblies = 0; let Tons = 0; if (!popup) return { Assemblies, Tons }; const lis = Array.from(popup.querySelectorAll('li')); for (const li of lis) { const t = (li.textContent || '').trim(); if (/^number\s+of\s+assemblies\s*:/i.test(t)) { const after = t.split(':').slice(1).join(':'); const m = (after || '').replace(/,/g, '').match(/\d+/); Assemblies = m ? parseInt(m[0], 10) : 0; } if (/^metric\s+tons\s+of\s+heavy\s+metal\s*\(mthm\)\s*:/i.test(t)) { const after = t.split(':').slice(1).join(':'); const m = (after || '').replace(/,/g, '').match(/-?\d+(?:\.\d+)?/); Tons = m ? parseFloat(m[0]) : 0; } } return { Assemblies: Number.isFinite(Assemblies) ? Assemblies : 0, Tons: Number.isFinite(Tons) ? Tons : 0 }; }; // --- Locate Site control discovery (native