Merge dev into master: RU/EN web interface + scrollable forecast log
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+135
-38
@@ -39,6 +39,11 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
|
|||||||
}
|
}
|
||||||
.lede{color:var(--muted); font-size:.92rem; margin:8px 0 0}
|
.lede{color:var(--muted); font-size:.92rem; margin:8px 0 0}
|
||||||
.lede b{color:var(--text); font-weight:600}
|
.lede b{color:var(--text); font-weight:600}
|
||||||
|
header{position:relative}
|
||||||
|
.lang{position:absolute; top:0; right:0; display:flex; gap:3px}
|
||||||
|
.lang button{padding:4px 9px; font-size:.72rem; font-weight:700; letter-spacing:.06em; font-family:var(--sans);
|
||||||
|
background:transparent; color:var(--muted); border:1px solid var(--line); border-radius:7px; cursor:pointer}
|
||||||
|
.lang button.on{background:var(--brass); color:#221a06; border-color:var(--brass)}
|
||||||
|
|
||||||
.panel{
|
.panel{
|
||||||
background:linear-gradient(180deg, var(--surface), #14203a);
|
background:linear-gradient(180deg, var(--surface), #14203a);
|
||||||
@@ -153,9 +158,10 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
|
|||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<p class="eyebrow">Barometric Forecaster</p>
|
<div class="lang" id="lang"><button type="button" data-lang="ru">RU</button><button type="button" data-lang="en">EN</button></div>
|
||||||
<h1>Weather Predictor</h1>
|
<p class="eyebrow" data-i18n="eyebrow">Barometric Forecaster</p>
|
||||||
<p class="lede">Reading the sky by pressure alone, the <b>Zambretti</b> way — station at
|
<h1 data-i18n="title">Weather Predictor</h1>
|
||||||
|
<p class="lede"><span data-i18n="lede">Reading the sky by pressure alone, the Zambretti way — station at</span>
|
||||||
<span id="coords">54.99°N, 82.87°E</span>.</p>
|
<span id="coords">54.99°N, 82.87°E</span>.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -164,7 +170,7 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
|
|||||||
<svg id="gauge" viewBox="0 0 400 232" role="img" aria-label="Barometer dial"></svg>
|
<svg id="gauge" viewBox="0 0 400 232" role="img" aria-label="Barometer dial"></svg>
|
||||||
<div class="readout">
|
<div class="readout">
|
||||||
<span class="pressure"><span id="msl">----</span></span>
|
<span class="pressure"><span id="msl">----</span></span>
|
||||||
<span class="unit">hPa · sea level</span>
|
<span class="unit" data-i18n="unit_msl">hPa · sea level</span>
|
||||||
<span class="chip" id="chip" style="color:var(--muted)">—</span>
|
<span class="chip" id="chip" style="color:var(--muted)">—</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="verdict" id="verdict">Reading the sky…</p>
|
<p class="verdict" id="verdict">Reading the sky…</p>
|
||||||
@@ -173,18 +179,18 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
|
|||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<section class="panel stats">
|
<section class="panel stats">
|
||||||
<div class="stat"><div class="k">Local time</div><div class="v" id="time">--:--</div><div class="s" id="date">—</div></div>
|
<div class="stat"><div class="k" data-i18n="k_time">Local time</div><div class="v" id="time">--:--</div><div class="s" id="date">—</div></div>
|
||||||
<div class="stat"><div class="k">Temperature</div><div class="v" id="temp">--</div><div class="s">degrees celsius</div></div>
|
<div class="stat"><div class="k" data-i18n="k_temp">Temperature</div><div class="v" id="temp">--</div><div class="s" data-i18n="s_celsius">degrees celsius</div></div>
|
||||||
<div class="stat"><div class="k">3-hour move</div><div class="v" id="delta">—</div><div class="s" id="deltas">—</div></div>
|
<div class="stat"><div class="k" data-i18n="k_move">3-hour move</div><div class="v" id="delta">—</div><div class="s" id="deltas">—</div></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Barograph -->
|
<!-- Barograph -->
|
||||||
<section class="panel panel-pad">
|
<section class="panel panel-pad">
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<h2>Barograph</h2>
|
<h2 data-i18n="barograph">Barograph</h2>
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
<span><i style="background:var(--change)"></i>Pressure</span>
|
<span><i style="background:var(--change)"></i><span data-i18n="pressure">Pressure</span></span>
|
||||||
<span><i style="background:var(--fair)"></i>Temperature</span>
|
<span><i style="background:var(--fair)"></i><span data-i18n="temperature">Temperature</span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="baro">
|
<div class="baro">
|
||||||
@@ -199,7 +205,7 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
|
|||||||
<!-- Forecast log -->
|
<!-- Forecast log -->
|
||||||
<section class="panel panel-pad">
|
<section class="panel panel-pad">
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<h2>Forecast log</h2>
|
<h2 data-i18n="flog">Forecast log</h2>
|
||||||
<span class="cap" id="log-cap"></span>
|
<span class="cap" id="log-cap"></span>
|
||||||
</div>
|
</div>
|
||||||
<ol class="log" id="log"></ol>
|
<ol class="log" id="log"></ol>
|
||||||
@@ -207,27 +213,27 @@ static const char INDEX_HTML[] PROGMEM = R"HTML(
|
|||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<details class="panel set">
|
<details class="panel set">
|
||||||
<summary>Station settings <span class="caret">Adjust</span></summary>
|
<summary><span data-i18n="settings">Station settings</span> <span class="caret" data-i18n="adjust">Adjust</span></summary>
|
||||||
<hr class="rule">
|
<hr class="rule">
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<div style="margin-top:18px">
|
<div style="margin-top:18px">
|
||||||
<label>City preset</label>
|
<label data-i18n="l_city">City preset</label>
|
||||||
<select id="s-city"></select>
|
<select id="s-city"></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid2" style="margin-top:14px">
|
<div class="grid2" style="margin-top:14px">
|
||||||
<div><label>Altitude · metres</label><input id="s-alt" type="number" step="1"></div>
|
<div><label data-i18n="l_alt">Altitude · metres</label><input id="s-alt" type="number" step="1"></div>
|
||||||
<div><label>Time zone</label><select id="s-tz"></select></div>
|
<div><label data-i18n="l_tz">Time zone</label><select id="s-tz"></select></div>
|
||||||
<div><label>Latitude</label><input id="s-lat" type="number" step="0.0001"></div>
|
<div><label data-i18n="l_lat">Latitude</label><input id="s-lat" type="number" step="0.0001"></div>
|
||||||
<div><label>Longitude</label><input id="s-lon" type="number" step="0.0001"></div>
|
<div><label data-i18n="l_lon">Longitude</label><input id="s-lon" type="number" step="0.0001"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button id="save">Save settings</button>
|
<button id="save" data-i18n="save">Save settings</button>
|
||||||
<span class="saved" id="saved"></span>
|
<span class="saved" id="saved"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<footer>Served from the station · works without internet</footer>
|
<footer data-i18n="footer">Served from the station · works without internet</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -242,6 +248,93 @@ var ZONES=[
|
|||||||
];
|
];
|
||||||
var CATCOLOR={Fine:"#8FBE8A",Fair:"#E4B658",Changeable:"#79C2D0",Rain:"#5C86D6",Storm:"#E4573B"};
|
var CATCOLOR={Fine:"#8FBE8A",Fair:"#E4B658",Changeable:"#79C2D0",Rain:"#5C86D6",Storm:"#E4573B"};
|
||||||
|
|
||||||
|
// ---------- i18n ----------
|
||||||
|
var LANG=(function(){ try{ return localStorage.getItem("wp_lang")||"ru"; }catch(e){ return "ru"; } })();
|
||||||
|
var STR={
|
||||||
|
ru:{
|
||||||
|
eyebrow:"Барометрический предсказатель", title:"Предсказатель погоды",
|
||||||
|
lede:"Читаем небо по одному давлению — метод Zambretti. Станция:",
|
||||||
|
unit_msl:"гПа · у моря",
|
||||||
|
k_time:"Местное время", k_temp:"Температура", k_move:"Изм. за 3 ч", s_celsius:"градусы Цельсия",
|
||||||
|
barograph:"Барограф", pressure:"Давление", temperature:"Температура", flog:"Журнал прогнозов",
|
||||||
|
settings:"Настройки станции", adjust:"Изменить",
|
||||||
|
l_city:"Пресет города", l_alt:"Высота · метры", l_tz:"Часовой пояс", l_lat:"Широта", l_lon:"Долгота",
|
||||||
|
custom:"Вручную", save:"Сохранить", footer:"Отдаётся станцией · работает без интернета",
|
||||||
|
warm:"Читаем небо…", warm_chip:"прогрев",
|
||||||
|
warm_sub:"Прогнозу нужно около трёх часов истории давления до первого вывода.",
|
||||||
|
verdict_pre:"Показание Zambretti — ", verdict_post:" за последние три часа.",
|
||||||
|
tr_rising:"давление растёт", tr_falling:"давление падает", tr_steady:"давление стабильно",
|
||||||
|
collecting:"Сбор данных…",
|
||||||
|
hint:function(n,h){return n+" измерений за "+h+" ч · давление в гПа, температура в °C";},
|
||||||
|
log_empty:"Пока прогнозов нет — первый появится примерно через три часа.",
|
||||||
|
log_cap:function(n){var d1=n%10,d2=n%100; return n+((d1===1&&d2!==11)?" смена":((d1>=2&&d1<=4&&(d2<10||d2>=20))?" смены":" смен"));},
|
||||||
|
need3:"нужно 3 ч данных", mv_unit:"гПа · ", mv_rising:"рост", mv_steady:"стабильно", mv_falling:"падение",
|
||||||
|
saved_ok:"Сохранено.", saved_err:"Не удалось сохранить.", saved_net:"Станция недоступна."
|
||||||
|
},
|
||||||
|
en:{
|
||||||
|
eyebrow:"Barometric Forecaster", title:"Weather Predictor",
|
||||||
|
lede:"Reading the sky by pressure alone, the Zambretti way — station at",
|
||||||
|
unit_msl:"hPa · sea level",
|
||||||
|
k_time:"Local time", k_temp:"Temperature", k_move:"3-hour move", s_celsius:"degrees celsius",
|
||||||
|
barograph:"Barograph", pressure:"Pressure", temperature:"Temperature", flog:"Forecast log",
|
||||||
|
settings:"Station settings", adjust:"Adjust",
|
||||||
|
l_city:"City preset", l_alt:"Altitude · metres", l_tz:"Time zone", l_lat:"Latitude", l_lon:"Longitude",
|
||||||
|
custom:"Custom / manual entry", save:"Save settings", footer:"Served from the station · works without internet",
|
||||||
|
warm:"Reading the sky…", warm_chip:"warming up",
|
||||||
|
warm_sub:"The forecaster needs about three hours of pressure history before its first call.",
|
||||||
|
verdict_pre:"Zambretti reading — ", verdict_post:" over the last three hours.",
|
||||||
|
tr_rising:"pressure rising", tr_falling:"pressure falling", tr_steady:"pressure steady",
|
||||||
|
collecting:"Collecting data…",
|
||||||
|
hint:function(n,h){return n+" readings over "+h+" h · pressure in hPa, temperature in °C";},
|
||||||
|
log_empty:"No forecasts yet — the first call appears after about three hours.",
|
||||||
|
log_cap:function(n){return n+(n>1?" changes":" change");},
|
||||||
|
need3:"need 3 h of data", mv_unit:"hPa · ", mv_rising:"rising", mv_steady:"steady", mv_falling:"falling",
|
||||||
|
saved_ok:"Saved.", saved_err:"Could not save.", saved_net:"Could not reach the station."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function T(k){ var s=STR[LANG]||STR.en; return (k in s)?s[k]:(STR.en[k]!=null?STR.en[k]:k); }
|
||||||
|
var ZONE_RU={STORMY:"БУРЯ",RAIN:"ДОЖДЬ",CHANGE:"ПЕРЕМЕННО",FAIR:"ЯСНО","VERY DRY":"СУШЬ"};
|
||||||
|
function zoneLabel(z){ return LANG==="ru"?(ZONE_RU[z]||z):z; }
|
||||||
|
var CATS={Fine:"Ясно",Fair:"Хорошо",Changeable:"Переменно",Rain:"Дождь",Storm:"Буря"};
|
||||||
|
function trCat(s){ return (LANG==="ru"&&CATS[s])?CATS[s]:s; }
|
||||||
|
var PHRASES={
|
||||||
|
"Settled fine weather":"Устойчиво ясно",
|
||||||
|
"Fine weather":"Ясная погода",
|
||||||
|
"Becoming fine":"Проясняется",
|
||||||
|
"Fine, becoming less settled":"Ясно, погода портится",
|
||||||
|
"Fine, possibly showers":"Ясно, возможны ливни",
|
||||||
|
"Fairly fine, improving":"Довольно ясно, улучшение",
|
||||||
|
"Fairly fine, possibly showers early":"Довольно ясно, ранние ливни",
|
||||||
|
"Fairly fine, showers later":"Довольно ясно, ливни позже",
|
||||||
|
"Showery early, improving":"Ранние ливни, улучшение",
|
||||||
|
"Changeable, improving":"Переменно, улучшение",
|
||||||
|
"Fairly fine, showers likely":"Довольно ясно, вероятны ливни",
|
||||||
|
"Rather unsettled, clearing later":"Неустойчиво, позже прояснение",
|
||||||
|
"Unsettled, probably improving":"Неустойчиво, вероятно улучшение",
|
||||||
|
"Showery, bright intervals":"Ливни, прояснения",
|
||||||
|
"Showery, becoming unsettled":"Ливни, погода портится",
|
||||||
|
"Changeable, some rain":"Переменно, местами дождь",
|
||||||
|
"Unsettled, short fine intervals":"Неустойчиво, краткие прояснения",
|
||||||
|
"Unsettled, rain later":"Неустойчиво, дождь позже",
|
||||||
|
"Unsettled, rain at times":"Неустойчиво, временами дождь",
|
||||||
|
"Very unsettled, finer at times":"Очень неустойчиво, временами лучше",
|
||||||
|
"Rain at times, worse later":"Временами дождь, позже хуже",
|
||||||
|
"Rain at times, becoming very unsettled":"Временами дождь, резко портится",
|
||||||
|
"Rain at frequent intervals":"Частые дожди",
|
||||||
|
"Very unsettled, rain":"Очень неустойчиво, дождь",
|
||||||
|
"Stormy, possibly improving":"Буря, возможно улучшение",
|
||||||
|
"Stormy, much rain":"Буря, сильный дождь"
|
||||||
|
};
|
||||||
|
function trPhrase(s){ return (LANG==="ru"&&PHRASES[s])?PHRASES[s]:s; }
|
||||||
|
function applyLang(){
|
||||||
|
document.documentElement.lang=LANG;
|
||||||
|
document.querySelectorAll("[data-i18n]").forEach(function(el){ el.textContent=T(el.getAttribute("data-i18n")); });
|
||||||
|
ZONES.forEach(function(z){ var lab=document.getElementById("lab-"+z.label); if(lab) lab.textContent=zoneLabel(z.label); });
|
||||||
|
var city=document.getElementById("s-city"); if(city&&city.options.length) city.options[0].textContent=T("custom");
|
||||||
|
document.querySelectorAll("#lang button").forEach(function(b){ b.classList.toggle("on", b.getAttribute("data-lang")===LANG); });
|
||||||
|
}
|
||||||
|
function setLang(l){ LANG=l; try{localStorage.setItem("wp_lang",l);}catch(e){} applyLang(); refresh(); }
|
||||||
|
|
||||||
function clamp(v,a,b){return v<a?a:(v>b?b:v);}
|
function clamp(v,a,b){return v<a?a:(v>b?b:v);}
|
||||||
// pressure -> screen angle (deg): 960=180 (left), 1060=0 (right)
|
// pressure -> screen angle (deg): 960=180 (left), 1060=0 (right)
|
||||||
function angleOf(p){return 180 - (clamp(p,PMIN,PMAX)-PMIN)/(PMAX-PMIN)*180;}
|
function angleOf(p){return 180 - (clamp(p,PMIN,PMAX)-PMIN)/(PMAX-PMIN)*180;}
|
||||||
@@ -329,15 +422,15 @@ async function refresh(){
|
|||||||
// verdict
|
// verdict
|
||||||
var v=document.getElementById("verdict"), vs=document.getElementById("verdict-sub"), chip=document.getElementById("chip");
|
var v=document.getElementById("verdict"), vs=document.getElementById("verdict-sub"), chip=document.getElementById("chip");
|
||||||
if(c.haveTrend){
|
if(c.haveTrend){
|
||||||
v.textContent=c.forecast;
|
v.textContent=trPhrase(c.forecast);
|
||||||
var col=CATCOLOR[c.category]||"#93A3BE";
|
var col=CATCOLOR[c.category]||"#93A3BE";
|
||||||
chip.textContent=c.category; chip.style.color=col;
|
chip.textContent=trCat(c.category); chip.style.color=col;
|
||||||
var word=c.trend>0?"pressure rising":(c.trend<0?"pressure falling":"pressure steady");
|
var word=c.trend>0?T("tr_rising"):(c.trend<0?T("tr_falling"):T("tr_steady"));
|
||||||
vs.textContent="Zambretti reading — "+word+" over the last three hours.";
|
vs.textContent=T("verdict_pre")+word+T("verdict_post");
|
||||||
}else{
|
}else{
|
||||||
v.textContent="Reading the sky…";
|
v.textContent=T("warm");
|
||||||
chip.textContent="warming up"; chip.style.color="#93A3BE";
|
chip.textContent=T("warm_chip"); chip.style.color="#93A3BE";
|
||||||
vs.textContent="The forecaster needs about three hours of pressure history before its first call.";
|
vs.textContent=T("warm_sub");
|
||||||
}
|
}
|
||||||
document.getElementById("chip").setAttribute("aria-label",c.category||"");
|
document.getElementById("chip").setAttribute("aria-label",c.category||"");
|
||||||
// absolute already implied; keep footer clean
|
// absolute already implied; keep footer clean
|
||||||
@@ -356,7 +449,7 @@ async function drawLog(){
|
|||||||
ol.innerHTML="";
|
ol.innerHTML="";
|
||||||
if(!data.length){
|
if(!data.length){
|
||||||
var li=document.createElement("li"); li.className="empty";
|
var li=document.createElement("li"); li.className="empty";
|
||||||
li.textContent="No forecasts yet — the first call appears after about three hours.";
|
li.textContent=T("log_empty");
|
||||||
ol.appendChild(li); cap.textContent=""; return;
|
ol.appendChild(li); cap.textContent=""; return;
|
||||||
}
|
}
|
||||||
data.forEach(function(e){
|
data.forEach(function(e){
|
||||||
@@ -364,13 +457,13 @@ async function drawLog(){
|
|||||||
var dot=document.createElement("span"); dot.className="dot";
|
var dot=document.createElement("span"); dot.className="dot";
|
||||||
dot.style.background=CATCOLOR[e.cat]||"#93A3BE"; li.appendChild(dot);
|
dot.style.background=CATCOLOR[e.cat]||"#93A3BE"; li.appendChild(dot);
|
||||||
var lt=document.createElement("div"); lt.className="lt";
|
var lt=document.createElement("div"); lt.className="lt";
|
||||||
var b=document.createElement("b"); b.textContent=e.text; lt.appendChild(b);
|
var b=document.createElement("b"); b.textContent=trPhrase(e.text); lt.appendChild(b);
|
||||||
var lc=document.createElement("div"); lc.className="lc"; lc.textContent=e.cat; lt.appendChild(lc);
|
var lc=document.createElement("div"); lc.className="lc"; lc.textContent=trCat(e.cat); lt.appendChild(lc);
|
||||||
li.appendChild(lt);
|
li.appendChild(lt);
|
||||||
var tm=document.createElement("time"); tm.textContent=fmtStamp(e.t); li.appendChild(tm);
|
var tm=document.createElement("time"); tm.textContent=fmtStamp(e.t); li.appendChild(tm);
|
||||||
ol.appendChild(li);
|
ol.appendChild(li);
|
||||||
});
|
});
|
||||||
cap.textContent=data.length+(data.length>1?" changes":" change");
|
cap.textContent=T("log_cap")(data.length);
|
||||||
}
|
}
|
||||||
function fillAxis(id,mx,mn,deg){
|
function fillAxis(id,mx,mn,deg){
|
||||||
var box=document.getElementById(id); box.innerHTML="";
|
var box=document.getElementById(id); box.innerHTML="";
|
||||||
@@ -387,7 +480,7 @@ async function drawChart(){
|
|||||||
var svg=document.getElementById("chart"), W=640,H=200,pad=6;
|
var svg=document.getElementById("chart"), W=640,H=200,pad=6;
|
||||||
while(svg.firstChild) svg.removeChild(svg.firstChild);
|
while(svg.firstChild) svg.removeChild(svg.firstChild);
|
||||||
if(!data.length){
|
if(!data.length){
|
||||||
document.getElementById("hint").textContent="Collecting data…";
|
document.getElementById("hint").textContent=T("collecting");
|
||||||
document.getElementById("axL").innerHTML=""; document.getElementById("axR").innerHTML="";
|
document.getElementById("axL").innerHTML=""; document.getElementById("axR").innerHTML="";
|
||||||
document.getElementById("axT0").textContent="—"; document.getElementById("axT1").textContent="—";
|
document.getElementById("axT0").textContent="—"; document.getElementById("axT1").textContent="—";
|
||||||
updateSetHand(); return;
|
updateSetHand(); return;
|
||||||
@@ -412,7 +505,7 @@ async function drawChart(){
|
|||||||
document.getElementById("axT0").textContent=fmtTime(data[0].t);
|
document.getElementById("axT0").textContent=fmtTime(data[0].t);
|
||||||
document.getElementById("axT1").textContent=fmtTime(data[n-1].t);
|
document.getElementById("axT1").textContent=fmtTime(data[n-1].t);
|
||||||
var hrs=((data[n-1].t-data[0].t)/3600);
|
var hrs=((data[n-1].t-data[0].t)/3600);
|
||||||
document.getElementById("hint").textContent=n+" readings over "+hrs.toFixed(1)+" h · pressure in hPa, temperature in °C";
|
document.getElementById("hint").textContent=T("hint")(n, hrs.toFixed(1));
|
||||||
updateSetHand();
|
updateSetHand();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,13 +518,13 @@ function updateSetHand(){
|
|||||||
for(var i=0;i<d.length;i++){ if(d[i].t<=target) past=d[i]; }
|
for(var i=0;i<d.length;i++){ if(d[i].t<=target) past=d[i]; }
|
||||||
if(!past || (latest.t-past.t)<9000){ sh.setAttribute("opacity",0);
|
if(!past || (latest.t-past.t)<9000){ sh.setAttribute("opacity",0);
|
||||||
document.getElementById("delta").textContent="—";
|
document.getElementById("delta").textContent="—";
|
||||||
document.getElementById("deltas").textContent="need 3 h of data"; return; }
|
document.getElementById("deltas").textContent=T("need3"); return; }
|
||||||
setNeedle("sethand",past.msl);
|
setNeedle("sethand",past.msl);
|
||||||
sh.setAttribute("opacity",.7);
|
sh.setAttribute("opacity",.7);
|
||||||
var diff=latest.msl-past.msl;
|
var diff=latest.msl-past.msl;
|
||||||
document.getElementById("delta").textContent=fmtSigned(diff,1);
|
document.getElementById("delta").textContent=fmtSigned(diff,1);
|
||||||
var word=diff>1.6?"rising":(diff<-1.6?"falling":"steady");
|
var word=diff>1.6?T("mv_rising"):(diff<-1.6?T("mv_falling"):T("mv_steady"));
|
||||||
document.getElementById("deltas").textContent="hPa · "+word;
|
document.getElementById("deltas").textContent=T("mv_unit")+word;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tzLabel(min){var s=min<0?"-":"+";var a=Math.abs(min);var h=Math.floor(a/60);var m=a%60;return "UTC"+s+h+(m?(":"+(m<10?"0":"")+m):"");}
|
function tzLabel(min){var s=min<0?"-":"+";var a=Math.abs(min);var h=Math.floor(a/60);var m=a%60;return "UTC"+s+h+(m?(":"+(m<10?"0":"")+m):"");}
|
||||||
@@ -457,7 +550,7 @@ function setTz(min){
|
|||||||
}
|
}
|
||||||
function buildCities(){
|
function buildCities(){
|
||||||
var sel=document.getElementById("s-city");
|
var sel=document.getElementById("s-city");
|
||||||
var o0=document.createElement("option"); o0.value="-1"; o0.textContent="Custom / manual entry"; sel.appendChild(o0);
|
var o0=document.createElement("option"); o0.value="-1"; o0.textContent=T("custom"); sel.appendChild(o0);
|
||||||
CITIES.forEach(function(c,i){
|
CITIES.forEach(function(c,i){
|
||||||
var o=document.createElement("option"); o.value=i;
|
var o=document.createElement("option"); o.value=i;
|
||||||
o.textContent=c.n+" · "+c.alt+" m · "+tzLabel(c.tz); sel.appendChild(o);
|
o.textContent=c.n+" · "+c.alt+" m · "+tzLabel(c.tz); sel.appendChild(o);
|
||||||
@@ -497,16 +590,20 @@ document.getElementById("save").addEventListener("click", async function(){
|
|||||||
var msg=document.getElementById("saved");
|
var msg=document.getElementById("saved");
|
||||||
try{
|
try{
|
||||||
var r=await (await fetch("/api/settings",{method:"POST",body:JSON.stringify(body)})).json();
|
var r=await (await fetch("/api/settings",{method:"POST",body:JSON.stringify(body)})).json();
|
||||||
msg.textContent=r.ok?"Saved.":"Could not save.";
|
msg.textContent=r.ok?T("saved_ok"):T("saved_err");
|
||||||
msg.style.color=r.ok?"var(--fine)":"var(--storm)";
|
msg.style.color=r.ok?"var(--fine)":"var(--storm)";
|
||||||
if(r.ok){ loadSettings(); refresh(); }
|
if(r.ok){ loadSettings(); refresh(); }
|
||||||
}catch(e){ msg.textContent="Could not reach the station."; msg.style.color="var(--storm)"; }
|
}catch(e){ msg.textContent=T("saved_net"); msg.style.color="var(--storm)"; }
|
||||||
setTimeout(function(){msg.textContent="";},4000);
|
setTimeout(function(){msg.textContent="";},4000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("#lang button").forEach(function(b){
|
||||||
|
b.addEventListener("click",function(){ setLang(b.getAttribute("data-lang")); });
|
||||||
|
});
|
||||||
buildGauge();
|
buildGauge();
|
||||||
buildTz();
|
buildTz();
|
||||||
buildCities();
|
buildCities();
|
||||||
|
applyLang();
|
||||||
loadSettings();
|
loadSettings();
|
||||||
refresh();
|
refresh();
|
||||||
setInterval(refresh,15000);
|
setInterval(refresh,15000);
|
||||||
|
|||||||
Reference in New Issue
Block a user