PageSourceSearch

https://currentwildfires.com/radar.js

js currentwildfires.com collected 2026-10-01 14:42:21 UTC 6,682 bytes, 91 lines download raw bytes

1/* radar.js — Radar meteorológico ANIMADO (RainViewer) como toggle sobre un mapa Leaflet.
2   window.CWRadar.attach(map, {es, pos}) agrega UN botón compacto "Radar": al prenderlo superpone la lluvia
3   (últimas ~2h) animándose y muestra la hora inline. Capa semi-transparente en pane propio (debajo de focos).
4   Datos: RainViewer (API pública gratis, sin key; tiles por su CDN). Aditivo + carga al click (cero peso hasta pedirlo). */
5window.CWRadar = (function () {
6  var API = 'https://api.rainviewer.com/public/weather-maps.json';
7  var COLOR = 4, OPTS = '1_1', SIZE = 512, TILE = 256, RADAR_OPACITY = 0.62;   // color 4 = The Weather Channel; smooth
8  // ⚠️ RainViewer SOLO tiene tiles hasta z7: de z8 para arriba devuelve un PNG que dice "Zoom Level Not Supported"
9  // (verificado: mismo hash en Spokane/Miami/Madrid/Londres). En las fichas (z≈11) tapaba el mapa entero.
10  // maxNativeZoom => Leaflet pide el tile de z7 y lo escala. SIZE 512 en la URL dibujado a TILE 256 = doble
11  // resolución de origen, así el escalado en zooms altos se ve la mitad de borroso (y nítido en pantallas retina).
12  var MAX_NATIVE = 7;
13  var RAIN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"/><path d="M16 14v6"/><path d="M8 14v6"/><path d="M12 16v6"/></svg>';
14  var POSNAMES = { tr2: 1, tl2: 1, tl: 1, tr: 1, bl: 1, br: 1, tr3: 1, home: 1 };   // whitelist de posiciones (clases cwr-<pos>)
15
16  function injectCSS() {
17    if (document.getElementById('cwr-css')) return;
18    var s = document.createElement('style'); s.id = 'cwr-css';
19    s.textContent =
20      ".cwr-btn{position:absolute;z-index:1000;display:inline-flex;align-items:center;gap:6px;background:rgba(20,20,20,.9);" +
21        "color:#f0f0f0;border:1px solid rgba(255,255,255,.15);border-radius:10px;padding:8px 12px;font:600 12px 'Inter',system-ui,sans-serif;" +
22        "cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.4);white-space:nowrap;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}" +
23      ".cwr-btn:hover{border-color:rgba(255,255,255,.35)}" +
24      ".cwr-btn.active{background:#3aa0ff;color:#0c0d0e;border-color:#3aa0ff}" +
25      ".cwr-btn svg{width:15px;height:15px;flex-shrink:0}" +
26      ".cwr-btn .cwr-t{font-variant-numeric:tabular-nums;opacity:.9}" +
27      ".cwr-tr2{top:56px;right:14px}.cwr-tr{top:14px;right:14px}.cwr-tl{top:14px;left:14px}" +
28      ".cwr-tl2{top:56px;left:14px}.cwr-bl{bottom:20px;left:14px}.cwr-br{bottom:20px;right:14px}" +
29      ".cwr-tr3{top:66px;right:14px}" +   // home: debajo de la hamburguesa (fijo, no se mueve en mobile)
30      // home responsive: desktop = centro-arriba debajo del nav (única zona libre: filtros van a la izq, stats a la
31      // der, ad+zoom abajo); mobile = debajo de la hamburguesa (der). No lo tapa el anuncio.
32      ".cwr-home{top:18px;left:50%;transform:translateX(-50%)}" +   // desktop: centro-arriba, alineado con las cards laterales
33      "@media(max-width:640px){.cwr-home{top:100px;left:14px;right:auto;transform:none}}" +   // mobile: arriba-izq, debajo de la card del header
34
35      // en mobile: el toggle de basemap ("Light map") se OCULTA → el radar sube a la esquina; y queda SOLO EL ÍCONO
36      // (sin texto ni hora), igual que el botón del loop, por consistencia. Activo = ícono azul.
37      "@media(max-width:640px){.cwr-tr2{top:14px}.cwr-tl2{top:14px}.cwr-btn span{display:none}.cwr-btn{padding:8px 10px}}";
38    document.head.appendChild(s);
39  }
40
41  function attach(map, opts) {
42    opts = opts || {};
43    if (!map || !window.L) return;
44    var es = !!opts.es;
45    var T = { radar: 'Radar', loading: es ? 'Cargando…' : 'Loading…', none: es ? 'Sin radar' : 'No radar', err: 'Error' };
46    injectCSS();
47    map.createPane('cwrRadar'); var pane = map.getPane('cwrRadar'); pane.style.zIndex = 250; pane.style.pointerEvents = 'none';
48
49    var pos = POSNAMES[opts.pos] ? opts.pos : 'tr2';
50    var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'cwr-btn cwr-' + pos;
51    var lbl = document.createElement('span'); lbl.textContent = T.radar;
52    var tspan = document.createElement('span'); tspan.className = 'cwr-t';
53    btn.innerHTML = RAIN; btn.appendChild(lbl); btn.appendChild(tspan);
54    map.getContainer().appendChild(btn);
55    L.DomEvent.disableClickPropagation(btn); L.DomEvent.disableScrollPropagation(btn);
56
57    var layers = [], idx = 0, timer = null, loaded = false, loading = false, on = false;
58    function fmtTime(ts) { var d = new Date(ts * 1000); return ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2); }
59    function show(i) { if (layers[idx]) layers[idx].setOpacity(0); idx = i; layers[idx].setOpacity(RADAR_OPACITY); tspan.textContent = ' · ' + fmtTime(layers[idx]._ts); }
60    function startTimer() { stopTimer(); timer = setInterval(function () { if (on && layers.length) show((idx + 1) % layers.length); }, 480); }
61    function stopTimer() { if (timer) { clearInterval(timer); timer = null; } }
62
63    function activate() {
64      on = true; btn.classList.add('active');
65      if (loaded) { show(idx); startTimer(); return; }
66      if (loading) return;
67      loading = true; tspan.textContent = ' · ' + T.loading;
68      fetch(API).then(function (r) { return r.json(); }).then(function (data) {
69        if (!on) { loading = false; return; }
70        var host = data.host, past = (data.radar && data.radar.past) || [], now = (data.radar && data.radar.nowcast) || [];
71        var frames = past.concat(now);
72        if (!host || !frames.length) { tspan.textContent = ' · ' + T.none; loading = false; return; }
73        frames.forEach(function (f) {
74          var url = host + f.path + '/' + SIZE + '/{z}/{x}/{y}/' + COLOR + '/' + OPTS + '.png';
75          var ly = L.tileLayer(url, { opacity: 0, pane: 'cwrRadar', tileSize: TILE, crossOrigin: true,
76            maxNativeZoom: MAX_NATIVE, maxZoom: 22,   // 22 = por encima del maxZoom de cualquiera de nuestros mapas
77            attribution: 'Radar © <a href="https://www.rainviewer.com/" target="_blank" rel="noopener">RainViewer</a>' }).addTo(map);
78          ly._ts = f.time; layers.push(ly);
79        });
80        loaded = true; loading = false; show(0); startTimer();
81      }).catch(function () { tspan.textContent = ' · ' + T.err; loading = false; });
82    }
83    function deactivate() {
84      on = false; stopTimer(); btn.classList.remove('active'); tspan.textContent = '';
85      layers.forEach(function (ly) { ly.setOpacity(0); });
86    }
87    btn.onclick = function () { on ? deactivate() : activate(); };
88  }
89
90  return { attach: attach };
91})();

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.