/* global React */ // ──────────────────────────────────────────────────────────── // AFTERSHOCKS — таймлайн повторных толчков для основного события. // Радиус 50км, окно 72ч после основного. // Используется как inline-секция в EventDetailScreen для M ≥ 5.0. // ──────────────────────────────────────────────────────────── window.AftershockSection = ({ T, dark, mainEvent }) => { const [aftershocks, setAftershocks] = React.useState(null); // null = loading, [] = none const [error, setError] = React.useState(null); React.useEffect(() => { if (!mainEvent) return; let cancelled = false; window.DATA.fetchAftershocks(mainEvent, { radiusKm: 50, windowHours: 72 }) .then((rows) => { if (!cancelled) setAftershocks(rows); }) .catch((e) => { if (!cancelled) setError(String(e)); }); return () => { cancelled = true; }; }, [mainEvent && mainEvent.id]); if (error) return null; // silent fail — don't break detail screen if (aftershocks === null) { return (
ПОИСК АФТЕРШОКОВ…
); } // Don't bother rendering for fewer than 2 aftershocks. if (aftershocks.length < 2) return null; return ( ); }; const AftershockBlock = ({ T, dark, mainEvent, aftershocks }) => { const main = mainEvent; const total = aftershocks.length; const maxAft = total > 0 ? Math.max(...aftershocks.map((a) => a.mag)) : 0; const mainTime = new Date(main.timeUtc).getTime(); const lastEnd = total > 0 ? new Date(aftershocks[aftershocks.length - 1].timeUtc).getTime() : mainTime; const totalMin = Math.max(60, Math.ceil((lastEnd - mainTime) / 60000)); // Bar bottom strip = baseline; height = magnitude scaled const heightFor = (mag) => 14 + Math.max(0, mag - 3) * 22; const events = [{ ...main, _isMain: true, min: 0 }, ...aftershocks.map((a) => ({ ...a, _isMain: false, min: Math.floor((new Date(a.timeUtc).getTime() - mainTime) / 60000), }))]; return (
АФТЕРШОКИ · {total} ШТ. · РАДИУС 50 КМ · 72 Ч
{/* Stat row */}
= 5 ? T.warning : T.text} />
{/* Timeline visualization */}
ВРЕМЕННАЯ ШКАЛА · ЗАТУХАНИЕ ПО ОМОРИ
{/* Baseline */}
{/* Bars */} {events.map((e, i) => { const x = (e.min / totalMin) * 100; const h = heightFor(e.mag); const isMain = e._isMain; const col = isMain ? T.danger : (e.mag >= 5 ? T.warning : T.accent); return (
); })} {/* Top labels for the strongest events */} {events.filter((e) => e.mag >= Math.max(4.5, maxAft - 0.5)) .slice(0, 5).map((e, i) => { const x = (e.min / totalMin) * 100; const h = heightFor(e.mag); const isMain = e._isMain; const col = isMain ? T.danger : T.warning; return (
{e.mag.toFixed(1)}
); })}
0 {fmtTime(Math.floor(totalMin / 4))} {fmtTime(Math.floor(totalMin / 2))} {fmtTime(Math.floor((3 * totalMin) / 4))} {fmtTime(totalMin)}
{/* List, newest first */}
СПИСОК · НОВЕЙШИЕ СВЕРХУ {total} ШТ.
{[...aftershocks].reverse().slice(0, 10).map((e, i) => { const col = e.mag >= 5 ? T.warning : T.accent; const min = Math.floor((new Date(e.timeUtc).getTime() - mainTime) / 60000); const dist = window.DATA.haversineKm(main.lat, main.lon, e.lat, e.lon); return (
{String(total - i).padStart(2, "0")}
{e.mag.toFixed(1)}
{fmtTime(min)}
~{Math.round(dist)} км
); })} {total > 10 && (
… и ещё {total - 10}
)}
); }; const Stat = ({ T, k, v, color, last }) => (
{k}
{v}
); function fmtTime(min) { if (min < 60) return `+${min}м`; const hh = Math.floor(min / 60); const mm = min % 60; if (hh < 24) return mm ? `+${hh}ч ${mm}м` : `+${hh}ч`; const dd = Math.floor(hh / 24); const rem = hh % 24; return rem ? `+${dd}д ${rem}ч` : `+${dd}д`; }