PageSourceSearch

https://earthquakes.zone/js/realtime-map.js?v=3hLQbKj25rmTZ4ackEvSRVtJ6sVND0759XrUojX0_80

js earthquakes.zone collected 2026-10-03 02:07:02 UTC 8,155 bytes, 217 lines download raw bytes

1(function () {
2  function formatUtc(value) {
3    const date = new Date(value);
4
5    if (Number.isNaN(date.getTime())) {
6      return value;
7    }
8
9    return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')} ${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}:${String(date.getUTCSeconds()).padStart(2, '0')} UTC`;
10  }
11
12  function getLabels() {
13    const language = (document.documentElement.lang || 'en').split('-')[0].toLowerCase();
14    const labels = {
15      en: {
16        earthquake: 'Earthquake',
17        noMappable: 'No mappable earthquakes are available right now.',
18        mapError: 'The map could not load earthquake markers right now.',
19        magnitude: 'Magnitude',
20        depth: 'Depth',
21        time: 'Time',
22        country: 'Country',
23        openDetail: 'Open detail page'
24      },
25      fr: {
26        earthquake: 'Séisme',
27        noMappable: 'Aucun séisme cartographiable n’est disponible pour le moment.',
28        mapError: 'La carte ne peut pas charger les marqueurs de séismes pour le moment.',
29        magnitude: 'Magnitude',
30        depth: 'Profondeur',
31        time: 'Heure',
32        country: 'Pays',
33        openDetail: 'Voir le détail'
34      },
35      es: {
36        earthquake: 'Terremoto',
37        noMappable: 'No hay terremotos cartografiables disponibles ahora.',
38        mapError: 'El mapa no puede cargar los marcadores de terremotos ahora.',
39        magnitude: 'Magnitud',
40        depth: 'Profundidad',
41        time: 'Hora',
42        country: 'País',
43        openDetail: 'Ver detalle'
44      },
45      ja: {
46        earthquake: '地震',
47        noMappable: '現在、地図に表示できる地震はありません。',
48        mapError: '現在、地震マーカーを読み込めません。',
49        magnitude: 'マグニチュード',
50        depth: '深さ',
51        time: '時刻',
52        country: '国',
53        openDetail: '詳細を見る'
54      }
55    };
56
57    return labels[language] || labels.en;
58  }
59
60  function escapeHtml(value) {
61    return String(value || '')
62      .replace(/&/g, '&')
63      .replace(/</g, '&lt;')
64      .replace(/>/g, '&gt;')
65      .replace(/"/g, '&quot;')
66      .replace(/'/g, '&#39;');
67  }
68
69  function buildDetailsUrl(earthquake, template) {
70    const occurredAt = new Date(earthquake.occurredAtUtc);
71
72    if (Number.isNaN(occurredAt.getTime())) {
73      return '#';
74    }
75
76    const timeSegment = [
77      String(occurredAt.getUTCHours()).padStart(2, '0'),
78      String(occurredAt.getUTCMinutes()).padStart(2, '0'),
79      String(occurredAt.getUTCSeconds()).padStart(2, '0'),
80      String(occurredAt.getUTCMilliseconds()).padStart(3, '0')
81    ].join('-');
82
83    return template
84      .replace('YYYY', String(occurredAt.getUTCFullYear()))
85      .replace('MM', String(occurredAt.getUTCMonth() + 1).padStart(2, '0'))
86      .replace('DD', String(occurredAt.getUTCDate()).padStart(2, '0'))
87      .replace('TIME', timeSegment);
88  }
89
90  function createMarkerIcon(earthquake) {
91    const magnitude = Number(earthquake.magnitude) || 0;
92    const size = Math.max(28, Math.min(46, 14 + magnitude * 3.5));
93
94    let accentClass = 'bg-emerald-500/85 border-emerald-700/70';
95    if (magnitude >= 7) {
96      accentClass = 'bg-rose-600/90 border-rose-900/80';
97    } else if (magnitude >= 6) {
98      accentClass = 'bg-orange-500/90 border-orange-900/80';
99    } else if (magnitude >= 5) {
100      accentClass = 'bg-amber-400/90 border-amber-800/80';
101    }
102
103    return L.divIcon({
104      className: 'earthquake-marker-wrapper',
105      html: `<div class="flex items-center justify-center rounded-full border-2 text-[11px] font-black text-white shadow-lg ${accentClass}" style="width:${size}px;height:${size}px;">${magnitude.toFixed(1)}</div>`,
106      iconSize: [size, size],
107      iconAnchor: [size / 2, size / 2],
108      popupAnchor: [0, -(size / 2)]
109    });
110  }
111
112  async function bootRealtimeMap() {
113    const mapElement = document.getElementById('earthquakes-realtime-map');
114    if (!mapElement || typeof window.L === 'undefined') {
115      return;
116    }
117
118    const apiUrl = mapElement.dataset.earthquakesApiUrl;
119    const detailsTemplate = mapElement.dataset.detailsUrlTemplate || '/YYYY/MM/DD/TIME';
120    const labels = getLabels();
121
122    const map = L.map(mapElement, {
123      scrollWheelZoom: false
124    }
124).setView([20, 0], 2);
125
126    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
127      detectRetina: true,
128      maxZoom: 18,
129      attribution: '&copy; OpenStreetMap contributors'
130    }).addTo(map);
131
132    try {
133      const response = await fetch(apiUrl, {
134        headers: {
135          Accept: 'application/json'
136        }
137      });
138
139      if (!response.ok) {
140        throw new Error(`HTTP ${response.status}`);
141      }
142
143      const earthquakes = await response.json();
144      const validEarthquakes = earthquakes.filter((earthquake) =>
145        Number.isFinite(Number(earthquake.latitude)) &&
146        Number.isFinite(Number(earthquake.longitude)))
147        .slice(0, 60);
148
149      if (!validEarthquakes.length) {
150        mapElement.insertAdjacentHTML(
151          'beforeend',
152          `<div class="pointer-events-none absolute inset-0 flex items-center justify-center bg-white/70 text-sm font-semibold text-slate-700">${escapeHtml(labels.noMappable)}</div>`);
153        return;
154      }
155
156      const bounds = [];
157
158      validEarthquakes.forEach((earthquake) => {
159        const latitude = Number(earthquake.latitude);
160        const longitude = Number(earthquake.longitude);
161        const detailsUrl = buildDetailsUrl(earthquake, detailsTemplate);
162        const liveLabel = earthquake.region || earthquake.title || labels.earthquake;
163        const title = earthquake.title && earthquake.title !== liveLabel ? earthquake.title : '';
164        const canOpenEarthquake = Number(earthquake.magnitude) >= 5;
165
166        bounds.push([latitude, longitude]);
167
168        const marker = L.marker([latitude, longitude], {
169          icon: createMarkerIcon(earthquake),
170          keyboard: false,
171          interactive: canOpenEarthquake
172        }).addTo(map);
173
174        const markerElement = marker.getElement();
175        markerElement?.removeAttribute('tabindex');
176        markerElement?.removeAttribute('role');
177
178        if (!canOpenEarthquake) {
179          return;
180        }
181
182        marker.bindPopup(`
183          <div style="min-width: 220px;">
184            <div style="font-weight: 800; font-size: 1rem; margin-bottom: 0.35rem;">${escapeHtml(liveLabel)}</div>
185            ${title ? `<div style="font-size: 0.875rem; color: #475569; margin-bottom: 0.5rem;">${escapeHtml(title)}</div>` : ''}
186            <div style="display: grid; gap: 0.2rem; font-size: 0.875rem;">
187              <div><strong>${escapeHtml(labels.magnitude)}:</strong> ${Number(earthquake.magnitude).toFixed(1)}</div>
188              <div><strong>${escapeHtml(labels.country)}:</strong> ${escapeHtml(earthquake.country || '')}</div>
189              <div><strong>${escapeHtml(labels.depth)}:</strong> ${Number(earthquake.depthKm).toFixed(1)} km</div>
190              <div><strong>${escapeHtml(labels.time)}:</strong> ${escapeHtml(formatUtc(earthquake.occurredAtUtc))}</div>
191            </div>
192            ${canOpenEarthquake ? `<div style="margin-top: 0.65rem;">
193              <a href="${escapeHtml(detailsUrl)}" style="font-weight: 700; color: #c2410c; text-decoration: none;">${escapeHtml(labels.openDetail)}</a>
194            </div>` : ''}
195          </div>
196        `);
197      });
198
199      if (bounds.length === 1) {
200        map.setView(bounds[0], 5);
201      } else {
202        map.fitBounds(bounds, { padding: [28, 28] });
203      }
204    } catch (error) {
205      console.error('Failed to initialize earthquake map.', error);
206      mapElement.insertAdjacentHTML(
207        'beforeend',
208        `<div class="pointer-events-none absolute inset-0 flex items-center justify-center bg-white/70 px-6 text-center text-sm font-semibold text-slate-700">${escapeHtml(labels.mapError)}</div>`);
209    }
210  }
211
212  if (document.readyState === 'loading') {
213    document.addEventListener('DOMContentLoaded', bootRealtimeMap);
214  } else {
215    bootRealtimeMap();
216  }
217})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.