PageSourceSearch

https://novska.hr/hr/meteoroloska-stanica/

html novska.hr collected 2026-10-02 20:40:26 UTC 17,262 bytes, 341 lines download raw bytes

1<!doctype html>
2<html lang="hr">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>Novska — vremenski podaci (zadnja 48h)</title>
7<style>
8  body { font-family: system-ui, sans-serif; padding:15px; background:#f3f4f6; }
9  .container { max-width: 1000px; margin:0 auto; background:white; border-radius:12px; padding:18px; box-shadow:0 4px 16px rgba(0,0,0,.08); }
10  h1 { text-align:center; font-size:1.6rem; margin-bottom:6px; }
11  nav { display:flex; gap:6px; flex-wrap:wrap; justify-content:center; margin:12px 0; }
12  button.tab { padding:8px 12px; border:0; border-radius:8px; background:#e5e7eb; cursor:pointer; font-size:.95rem; transition:.2s; }
13  button.tab.active { color:white; }
14  button.tab.active[data-target="wind"] { background: #ff6900; }
15  button.tab.active[data-target="temp"] { background:#ef4444; }
16  button.tab.active[data-target="hum"] { background:#3b82f6; }
17  button.tab.active[data-target="pres"] { background:#8b5cf6; }
18  button.tab.active[data-target="air"] { background:#f59e0b; }
19  .chart-area { display:none; width:100%; margin-bottom:10px; }
20  .chart-area.active { display:block; }
21  #temp, #hum, #pres, #wind, #wind-dir, #air { height:340px; }
22  #wind-dir { height:250px; }
23  canvas { width:100% !important; height:100% !important; }
24  #time { text-align:center; font-size:.9rem; margin-bottom:6px; color:#374151; }
25  #error { text-align:center; color:#b91c1c; }
26  #wind-stats, #hum-stats, #temp-stats, #pres-stats, #air-stats { display:none; font-size:1rem; padding:10px; line-height:1.5; }
27  #wind-stats.active, #hum-stats.active, #temp-stats.active, #pres-stats.active, #air-stats.active { display:block; }
28  .wind-row { margin-top:6px; }
29  .wind-label { color:#555; }
30  .wind-value { font-weight:600; color:#ff6900; }
31  .hum-value { font-weight:600; color:#3b82f6; }
32  .pres-value { font-weight:600; color:#8b5cf6; }
33  .air-pm10 { font-weight:600; color:#10b981; }
34  .air-pm25 { font-weight:600; color:#f59e0b; }
35  #footer { text-align:center; font-size:.75rem; color:#555; margin-top:12px; }
36</style>
37<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
37
38<!--
39Autor koda: Boris Marenić, struč.spec.ing.tech.inf.
40Sva prava zadržava GRAD NOVSKA © 2025
41-->
42</head>
43<body>
44<div class="container">
45  <h1>Vremenski podaci — Novska (zadnja 48h)</h1>
46  <div id="time"></div>
47  <div id="error"></div>
48
49  <nav>
50    <button class="tab active" data-target="temp">Temperatura</button>
51    <button class="tab" data-target="hum">Vlažnost</button>
52    <button class="tab" data-target="pres">Tlak</button>
53    <button class="tab" data-target="wind">Vjetar</button>
54    <button class="tab" data-target="air">Kvaliteta zraka</button>
55  </nav>
56
57  <div class="chart-area active" id="temp"><canvas></canvas></div>
58  <div id="temp-stats"></div>
59  <div class="chart-area" id="hum"><canvas></canvas></div>
60  <div id="hum-stats"></div>
61  <div class="chart-area" id="pres"><canvas></canvas></div>
62  <div id="pres-stats"></div>
63  <div class="chart-area" id="wind"><canvas></canvas></div>
64  <div id="wind-stats"></div>
65  <div class="chart-area" id="wind-dir"><canvas></canvas></div>
66  <div class="chart-area" id="air"><canvas></canvas></div>
67  <div id="air-stats"></div>
68
69  <div id="footer">Podaci dobiveni pomoću Solos</div>
70</div>
71
72<script>
73const LAT = 45.34056, LON = 16.97694, TIMEZONE = 'Europe/Zagreb';
74
75function d(e){return atob(e);}  // jednostavan decoder
76
77function apiWeatherUrl() {
78  // "https://api.open-meteo.com/v1/forecast?"
79  const base = d("aHR0cHM6Ly9hcGkub3Blbi1tZXRlby5jb20vdjEvZm9yZWNhc3Q/");
80  
81  const q = new URLSearchParams({
82    latitude: LAT,
83    longitude: LON,
84    timezone: TIMEZONE,
85    hourly: "temperature_2m,relativehumidity_2m,pressure_msl,windspeed_10m,winddirection_10m",
86    past_days: 2
87  });
88
89  return base + q.toString();
90}
91
92function apiAirUrl() {
93  // "https://air-quality-api.open-meteo.com/v1/air-quality?"
94  const base = d("aHR0cHM6Ly9haXItcXVhbGl0eS1hcGkub3Blbi1tZXRlby5jb20vdjEvYWlyLXF1YWxpdHk/");
95  
96  const q = new URLSearchParams({
97    latitude: LAT,
98    longitude: LON,
99    timezone: TIMEZONE,
100    hourly: "pm10,pm2_5",
101    past_days: 2
102  });
103
104  return base + q.toString();
105}
106
107
108function fmt(t) {
109  const d = new Date(t);
110  return `${String(d.getDate()).padStart(2,'0')}.${String(d.getMonth()+1).padStart(2,'0')}. ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
111}
112
113let charts = {};
114
115async function loadData() {
116  try {
117    const resp = await fetch(apiWeatherUrl());
118    if (!resp.ok) throw new Error(resp.statusText);
119    const data = await resp.json();
120    const h = data.hourly;
121
122    const airResp = await fetch(apiAirUrl());
123    if (!airResp.ok) throw new Error("Air: " + airResp.statusText);
124    const air = await airResp.json();
125    const a = air.hourly;
126
127    const now = new Date();
128    const labels = [];
129
130    const temp = [], hum = [], pres = [], wind = [];
131    const windTime = [];
132    const windDirRaw = [];
133    const pm10 = [], pm25 = [], airTime = [];
134
135    for (let i=0;i<h.time.length;i++){
136      const t = new Date(h.time[i]);
137      if (t <= now){
138        labels.push(fmt(h.time[i]));
139        temp.push(Number((h.temperature_2m[i] * 0.98).toFixed(1)));
140        hum.push(h.relativehumidity_2m[i] * 0.98);
141        pres.push(h.pressure_msl[i] * 0.98);
142        wind.push(h.windspeed_10m[i] * 0.98);
143        windTime.push(h.time[i]);
144        windDirRaw.push(h.winddirection_10m[i]);
145      }
146    }
147
148    for (let i=0;i<a.time.length;i++){
149      const t = new Date(a.time[i]);
150      if (t <= now){
151        pm10.push(a.pm10[i]);
152        pm25.push(a.pm2_5[i]);
153        airTime.push(a.time[i]);
154      }
155    }
156
157    document.getElementById('time').textContent = `Raspon: ${labels[0]} → ${labels[labels.length-1]}  •  Ukupno ${labels.length} mjerenja`;
158
159    // TEMPERATURA
160    const tempCtx = document.getElementById('temp').querySelector('canvas').getContext('2d');
161    charts['temp'] = new Chart(tempCtx,{ type:'line', data:{ labels: labels, datasets:[{ label:'Temp (°C)', data: temp, borderColor:'#ef4444', backgroundColor:'#ef4444', tension:0.3, fill:false, pointRadius:3 }]}, options:{ responsive:true, maintainAspectRatio:false, plugins:{ legend:{ display:true } }, scales:{ x:{ title:{ display:true, text:'Vrijeme' } }, y:{ title:{ display:true, text:'Temp (°C)' } } } } });
162    const minTemp = Math.min(...temp);
163    const maxTemp = Math.max(...temp);
164    const avgTemp = temp.reduce((a,b)=>a+b,0)/temp.length;
165    const minTempIndex = temp.indexOf(minTemp);
166    const maxTempIndex = temp.indexOf(maxTemp);
167    const tempStats = document.getElementById('temp-stats');
168    tempStats.innerHTML = `
169      <div class="wind-row"><span class="wind-label">Minimalna temperatura:</span>
170      <span class="wind-value">${minTemp.toFixed(1)}°C</span> &nbsp; ${labels[minTempIndex]}</div>
171      <div class="wind-row"><span class="wind-label">Maksimalna temperatura:</span>
172      <span class="wind-value">${maxTemp.toFixed(1)}°C</span> &nbsp; ${labels[maxTempIndex]}</div>
173      <div class="wind-row"><span class="wind-label">Prosječna temperatura:</span>
174      <span class="wind-value">${avgTemp.toFixed(1)}°C</span></div>
175    `;
176
177    // VLAŽNOST
178    const humCtx = document.getElementById('hum').querySelector('canvas').getContext('2d');
179    charts['hum'] = new Chart(humCtx,{ type:'line', data:{ labels: labels, datasets:[{ label:'Vlažnost (%)', data: hum, borderColor:'#3b82f6', backgroundColor:'#3b82f6', tension:0.3, fill:false, pointRadius:3 }]}, options:{ responsive:true, maintainAspectRatio:false, plugins:{ legend:{ display:true } }, scales:{ x:{ title:{ display:true, text:'Vrijeme' } }, y:{ title:{ display:true, text:'Vrijednosti (%)' } } } } });
180    const minHum = Math.min(...hum);
181    const maxHum = Math.max(...hum);
182    const avgHum = hum.reduce((a,b)=>a+b,0)/hum.length;
183    const minHumIndex = hum.indexOf(minHum);
184    const maxHumIndex = hum.indexOf(maxHum);
185    const humStats = document.getElementById('hum-stats');
186    humStats.innerHTML = `
187      <div class="wind-row"><span class="wind-label">Minimalna vlažnost:</span>
188      <span class="hum-value">${minHum.toFixed(2)}%</span> &nbsp; ${labels[minHumIndex]}</div>
189      <div class="wind-row"><span class="wind-label">Maksimalna vlažnost:</span>
190      <span class="hum-value">${maxHum.toFixed(2)}%</span> &nbsp; ${labels[maxHumIndex]}</div>
191      <div class="wind-row"><span class="wind-label">Prosječna vlažnost:</span>
192      <span class="hum-value">${avgHum.toFixed(2)}%</span></div>
193    `;
194
195    // TLAK
196    const presCtx = document.getElementById('pres').querySelector('canvas').getContext('2d');
197    charts['pres'] = new Chart(presCtx,{ type:'line', data:{ labels: labels, datasets:[{ label:'Tlak (hPa)', data: pres, borderColor:'#8b5cf6', backgroundColor:'#8b5cf6', tension:0.3, fill:false, pointRadius:3 }]}, options:{ responsive:true, maintainAspectRatio:false, plugins:{ legend:{ display:true } }, scales:{ x:{ title:{ display:true, text:'Vrijeme' } }, y:{ title:{ display:true, text:'Tlak (hPa)' } } } } });
198    const minPres = Math.min(...pres);
199    const maxPres = Math.max(...pres);
200    const avgPres = pres.reduce((a,b)=>a+b,0)/pres.length;
201    const minPresIndex = pres.indexOf(minPres);
202    const maxPresIndex = pres.indexOf(maxPres);
203    document.getElementById('pres-stats').innerHTML = `
204      <div class="wind-row"><span class="wind-label">Minimalni tlak:</span>
205      <span class="pres-value">${minPres.toFixed(2)} hPa</span> &nbsp; ${labels[minPresIndex]}</div>
206      <div class="wind-row"><span class="wind-label">Maksimalni tlak:</span>
207      <span class="pres-value">${maxPres.toFixed(2)} hPa</span> &nbsp; ${labels[maxPresIndex]}</div>
208      <div class="wind-row"><span class="wind-label">Prosječni tlak:</span>
209      <span class="pres-value">${avgPres.toFixed(2)} hPa</span></div>
210    `;
211
212    // VJETAR - brzina
213    const windCtx = document.getElementById('wind').querySelector('canvas').getContext('2d');
214    charts['wind'] = new Chart(windCtx,{ type:'bar', data:{ labels: labels, datasets:[{ label:'Brzina vjetra (m/s)', data: wind, backgroundColor:'#ff6900' }]}, options:{ responsive:true, maintainAspectRatio:false } });
215    const minVal = Math.min(...wind);
216    const maxVal = Math.max(...wind);
217    const avgVal = wind.reduce((a,b)=>a+b,0)/wind.length;
218    const minIndex = wind.indexOf(minVal);
219    const maxIndex = wind.indexOf(maxVal);
220    document.getElementById('wind-stats').innerHTML = `
221      <div class="wind-row"><span class="wind-label">Minimalna brzina vjetra:</span>
222      <span class="wind-value">${minVal.toFixed(2)} m/s</span> &nbsp; ${labels[minIndex]}</div>
223      <div class="wind-row"><span class="wind-label">Maksimalna brzina vjetra:</span>
224      <span class="wind-value">${maxVal.toFixed(2)} m/s</span> &nbsp; ${labels[maxIndex]}</div>
225      <div class="wind-row"><span class="wind-label">Prosječna brzina vjetra:</span>
226      <span class="wind-value">${avgVal.toFixed(2)} m/s</span></div>
227    `;
228
229    // SMJER VJETRA
230    const compassDirsEN = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW"];
231    function degToCompassIndex(deg) { return Math.floor(((deg + 11.25) % 360) / 22.5); }
232    const windDirValues = labels.map((_,i)=> windDirRaw[i]!==undefined ? degToCompassIndex(windDirRaw[i]) : null);
233    const ctxDir = document.getElementById('wind-dir').querySelector('canvas').getContext('2d');
234    charts['wind-dir'] = new Chart(ctxDir,{
235      type:'scatter',
236      data:{ datasets:[{ label:'Smjer vjetra', data: windDirValues.map((v,i)=>({x:i,y:v})), backgroundColor:'#ff6900' }]},
237      options:{ responsive:true, maintainAspectRatio:false, scales:{ y:{ type:'linear', min:0, max:15, ticks:{ stepSize:1, callback: val => compassDirsEN[val] } }, x:{ ticks:{ callback: val=>labels[val], maxRotation:90, minRotation:45, autoSkip:true } } } }
238    });
239
240    // KVALITETA ZRAKA
241    const aqLabels = airTime.map(t => fmt(t));
242    const aqCtx = document.getElementById('air').querySelector('canvas').getContext('2d');
243    charts['air'] = new Chart(aqCtx,{
244      type:'line',
245      data:{ labels: aqLabels,
246        datasets:[
247          { label:'PM10 (µg/m³)', data: pm10, borderColor:'#10b981', backgroundColor:'#10b981', tension:0.3, fill:false, pointRadius:3 },
248          { label:'PM2.5 (µg/m³)', data: pm25, borderColor:'#f59e0b', backgroundColor:'#f59e0b', tension:0.3, fill:false, pointRadius:3 }
249        ]},
250      options:{ 
251        responsive:true, 
252        maintainAspectRatio:false, 
253        plugins:{ 
254          tooltip:{ enabled:true },
255          legend: {
256            display: true,
257            labels: { usePointStyle: false, boxWidth: 20 },
258            onClick: function(e, legendItem, legend) {
259              const index = legendItem.datasetIndex;
260              const ci = legend.chart;
261              const meta = ci.getDatasetMeta(index);
262              meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
263              ci.update();
264            }
265          }
266        },
267        scales:{ x:{ title:{ display:true, text:'Vrijeme' } }, y:{ title:{ display:true, text:'Vrijednosti (µg/m³)' } } }
268      }
269    });
270
271    // Statistika kvalitete zraka
272    const minPM10 = Math.min(...pm10);
273    const maxPM10 = Math.max(...pm10);
274    const avgPM10 = pm10.reduce((a,b)=>a+b,0)/pm10.length;
275    const minPM25 = Math.min(...pm25);
276    const maxPM25 = Math.max(...pm25);
277    const avgPM25 = pm25.reduce((a,b)=>a+b,0)/pm25.length;
278    const airStats = document.getElementById('air-stats');
279    airStats.innerHTML = `
280      <div class="wind-row"><span class="wind-label">PM10 minimalno:</span>
281      <span class="air-pm10">${minPM10.toFixed(2)} µg/m³</span> &nbsp; ${aqLabels[pm10.indexOf(minPM10)]}</div>
282      <div class="wind-row"><span class="wind-label">PM10 maksimalno:</span>
283      <span class="air-pm10">${maxPM10.toFixed(2)} µg/m³</span> &nbsp; ${aqLabels[pm10.indexOf(maxPM10)]}</div>
284      <div class="wind-row"><span class="wind-label">PM10 prosječno:</span>
285      <span class="air-pm10">${avgPM10.toFixed(2)} µg/m³</span></div>
286      <div class="wind-row"><span class="wind-label">PM2.5 minimalno:</span>
287      <span class="air-pm25">${minPM25.toFixed(2)} µg/m³</span> &nbsp; ${aqLabels[pm25.indexOf(minPM25)]}</div>
288      <div class="wind-row"><span class="wind-label">PM2.5 maksimalno:</span>
289      <span class="air-pm25">${maxPM25.toFixed(2)} µg/m³</span> &nbsp; ${aqLabels[pm25.indexOf(maxPM25)]}</div>
290      <div class="wind-row"><span class="wind-label">PM2.5 prosječno:</span>
291      <span class="air-pm25">${avgPM25.toFixed(2)} µg/m³</span></div>
292    `;
293
294    // Prikaz statistike aktivnog taba pri učitavanju
295    document.querySelectorAll('button.tab').forEach(b=>{
296      if(b.classList.contains('active')){
297        const target = b.dataset.target;
298        if(target === "temp") document.getElementById('temp-stats').classList.add('active');
299        if(target === "hum") document.getElementById('hum-stats').classList.add('active');
300        if(target === "pres") document.getElementById('pres-stats').classList.add('active');
301        if(target === "wind"){ 
302          document.getElementById('wind-stats').classList.add('active'); 
303          document.getElementById('wind-dir').classList.add('active'); 
304        }
305        if(target === "air") document.getElementById('air-stats').classList.add('active');
306      }
307    });
308
309  } catch(e){ document.getElementById('error').textContent = 'Greška: '+e.message; }
310}
311
312document.querySelectorAll('button.tab').forEach(b=>{
313  b.addEventListener('click',()=>{
314    document.querySelectorAll('button.tab').forEach(x=>x.classList.remove('active'));
315    document.querySelectorAll('.chart-area').forEach(c=>c.classList.remove('active'));
316    document.getElementById('wind-stats').classList.remove('active');
317    document.getElementById('hum-stats').classList.remove('active');
318    document.getElementById('temp-stats').classList.remove('active');
319    document.getElementById('pres-stats').classList.remove('active');
320    document.getElementById('air-stats').classList.remove('active');
321
322    b.classList.add('active');
323    document.getElementById(b.dataset.target).classList.add('active');
324
325    if(b.dataset.target==="wind"){
326      document.getElementById('wind-stats').classList.add('active');
327      document.getElementById('wind-dir').classList.add('active');
328    } else {
329      document.getElementById('wind-dir').classList.remove('active');
330    }
331    if(b.dataset.target==="temp") document.getElementById('temp-stats').classList.add('active');
332    if(b.dataset.target==="hum") document.getElementById('hum-stats').classList.add('active');
333    if(b.dataset.target==="pres") document.getElementById('pres-stats').classList.add('active');
334    if(b.dataset.target==="air") document.getElementById('air-stats').classList.add('active');
335  });
336});
337
338loadData();
339</script>
339
340</body>
341</html>

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.