@sailingrotevista/rotevista-dash 2.0.20 → 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.js +91 -48
- package/index.js +2 -2
- package/package.json +1 -1
- package/settings.json +2 -2
- package/style.css +73 -67
- package/test.html +0 -1
package/app.js
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
// ==========================================================================
|
|
4
4
|
let CONFIG = {
|
|
5
5
|
alarms: { depthDanger: 2.5, depthWarning: 5.0 },
|
|
6
|
-
|
|
6
|
+
// Ottimizzato per mare formato: 30s di media e soglia stabilità 85%
|
|
7
|
+
averages: {
|
|
8
|
+
smoothWindow: 2000,
|
|
9
|
+
longWindow: 30000,
|
|
10
|
+
stabilityTolerance: 2000,
|
|
11
|
+
stabilityThreshold: 0.85,
|
|
12
|
+
minSpeed: 0.5
|
|
13
|
+
},
|
|
7
14
|
graphs: { reef1: 15.0, reef2: 20.0, historyMinutes: 5, samples: 60 },
|
|
8
15
|
scales: {
|
|
9
16
|
stw: { stdMax: 12, hercSpan: 4, step: 2 },
|
|
@@ -28,7 +35,7 @@ let lastAvgUIUpdate = 0, audioCtx = null, lastAlarmTime = 0;
|
|
|
28
35
|
let curAwaRot = 0, curTwaRot = 0, curTrackRot = 0, curTwdRoseRot = 0;
|
|
29
36
|
let curBoatCompassRot = 0, curWindCompassRot = 0;
|
|
30
37
|
|
|
31
|
-
let smoothedLeeway = 0, rotationTrend = 0;
|
|
38
|
+
let smoothedLeeway = 0, rotationTrend = 0, meteoTrend = 0;
|
|
32
39
|
let lastShortAvgVal = null, lastInstantTwa = null;
|
|
33
40
|
let lastTrendTime = Date.now(), lastGybeAlarmTime = 0, lastTWCompute = 0;
|
|
34
41
|
let twDirty = false, isNavigating = false, reconnectDelay = 1000;
|
|
@@ -47,6 +54,8 @@ const store = {
|
|
|
47
54
|
smoothBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
|
|
48
55
|
longBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
|
|
49
56
|
histories: { stw: [], sog: [], depth: [], tws: [], vmg: [] },
|
|
57
|
+
// Buffer temporaneo per calcolare la media dell'intervallo nei grafici
|
|
58
|
+
graphTempBuf: { stw: [], sog: [], depth: [], tws: [], vmg: [] },
|
|
50
59
|
lastUpdates: { stw: 0, sog: 0, depth: 0, tws: 0, vmg: 0 }
|
|
51
60
|
};
|
|
52
61
|
|
|
@@ -168,9 +177,14 @@ function processIncomingData(path, val) {
|
|
|
168
177
|
// ==========================================================================
|
|
169
178
|
// 5. TREND VENTO E SICUREZZA
|
|
170
179
|
// ==========================================================================
|
|
180
|
+
/**
|
|
181
|
+
* Analizza i trend di rotazione del vento su due scale temporali:
|
|
182
|
+
* 1. Tattica (veloce): per la regolazione delle vele (sulla lancetta TWA)
|
|
183
|
+
* 2. Strategica (lenta): per le previsioni meteo a lungo termine (bussola TWD)
|
|
184
|
+
*/
|
|
171
185
|
function updateWindTrend() {
|
|
172
186
|
const now = Date.now();
|
|
173
|
-
const twaAvgObj = getCircularAverageFromBuffer(store.longBuf.twa,
|
|
187
|
+
const twaAvgObj = getCircularAverageFromBuffer(store.longBuf.twa, 30000, true);
|
|
174
188
|
const shortAvg = getCircularAverageFromBuffer(store.longBuf.twd, 5000, false);
|
|
175
189
|
const instantTwaRad = store.raw["environment.wind.angleTrueWater"];
|
|
176
190
|
|
|
@@ -180,6 +194,7 @@ function updateWindTrend() {
|
|
|
180
194
|
if (lastShortAvgVal === null) { lastShortAvgVal = shortAvgDeg; lastInstantTwa = instantTwaDeg; return; }
|
|
181
195
|
const dt = (now - lastTrendTime) / 1000; lastTrendTime = now;
|
|
182
196
|
|
|
197
|
+
// ALLARME STRAMBATA
|
|
183
198
|
const gybeDetected = (Math.abs(instantTwaDeg) > 155 && Math.sign(instantTwaDeg) !== Math.sign(lastInstantTwa));
|
|
184
199
|
lastInstantTwa = instantTwaDeg;
|
|
185
200
|
|
|
@@ -192,32 +207,47 @@ function updateWindTrend() {
|
|
|
192
207
|
return;
|
|
193
208
|
}
|
|
194
209
|
|
|
210
|
+
// CALCOLO TREND
|
|
195
211
|
let diff = (shortAvgDeg - lastShortAvgVal + 540) % 360 - 180; lastShortAvgVal = shortAvgDeg;
|
|
196
212
|
if (dt > 0) {
|
|
197
213
|
let rate = Math.max(-10, Math.min(10, diff / dt));
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
// Tattico (veloce ~15s)
|
|
215
|
+
const alphaT = Math.min(1, dt / 15);
|
|
216
|
+
rotationTrend = rotationTrend * (1 - alphaT) + rate * alphaT;
|
|
217
|
+
// Strategico (molto lento ~8-10min)
|
|
218
|
+
const alphaM = Math.min(1, dt / 500);
|
|
219
|
+
meteoTrend = meteoTrend * (1 - alphaM) + rate * alphaM;
|
|
200
220
|
}
|
|
201
221
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
let
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
// VISUALIZZAZIONE METEO (Bussola Centrale)
|
|
223
|
+
if (Math.abs(meteoTrend) > 0.2) {
|
|
224
|
+
const isSouth = store.raw["navigation.position"]?.latitude < 0;
|
|
225
|
+
let meteoColor = (!isSouth) ? (meteoTrend < 0 ? "#27ae60" : "#c0392b") : (meteoTrend > 0 ? "#27ae60" : "#c0392b");
|
|
226
|
+
if (meteoTrend > 0) {
|
|
227
|
+
if (compassDots.cw) { compassDots.cw.classList.add('is-trending'); compassDots.cw.setAttribute('fill', meteoColor); }
|
|
228
|
+
if (compassDots.ccw) { compassDots.ccw.classList.remove('is-trending'); compassDots.ccw.setAttribute('fill', '#bbb'); }
|
|
229
|
+
} else {
|
|
230
|
+
if (compassDots.ccw) { compassDots.ccw.classList.add('is-trending'); compassDots.ccw.setAttribute('fill', meteoColor); }
|
|
231
|
+
if (compassDots.cw) { compassDots.cw.classList.remove('is-trending'); compassDots.cw.setAttribute('fill', '#bbb'); }
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
[compassDots.cw, compassDots.ccw].forEach(el => { if (el) { el.classList.remove('is-trending'); el.setAttribute('fill', '#bbb'); }});
|
|
235
|
+
}
|
|
209
236
|
|
|
237
|
+
// VISUALIZZAZIONE TATTICA (Lancetta Vento)
|
|
238
|
+
if (Math.abs(rotationTrend) > 3.0) {
|
|
239
|
+
let isLift = (twaAvgDeg > 0) ? (rotationTrend > 0) : (rotationTrend < 0);
|
|
240
|
+
if (Math.abs(twaAvgDeg) >= 90) isLift = !isLift;
|
|
241
|
+
const tacticColor = isLift ? "#27ae60" : "#c0392b";
|
|
210
242
|
if (rotationTrend > 0) {
|
|
211
|
-
if (compassDots.cw) { compassDots.cw.classList.add('is-trending'); compassDots.cw.setAttribute('fill', meteoColor); }
|
|
212
243
|
if (gaugeDots.cw) { gaugeDots.cw.classList.add('is-trending'); gaugeDots.cw.setAttribute('fill', tacticColor); }
|
|
213
|
-
|
|
244
|
+
if (gaugeDots.ccw) { gaugeDots.ccw.classList.remove('is-trending'); gaugeDots.ccw.setAttribute('fill', '#bbb'); }
|
|
214
245
|
} else {
|
|
215
|
-
if (compassDots.ccw) { compassDots.ccw.classList.add('is-trending'); compassDots.ccw.setAttribute('fill', meteoColor); }
|
|
216
246
|
if (gaugeDots.ccw) { gaugeDots.ccw.classList.add('is-trending'); gaugeDots.ccw.setAttribute('fill', tacticColor); }
|
|
217
|
-
|
|
247
|
+
if (gaugeDots.cw) { gaugeDots.cw.classList.remove('is-trending'); gaugeDots.cw.setAttribute('fill', '#bbb'); }
|
|
218
248
|
}
|
|
219
249
|
} else {
|
|
220
|
-
[
|
|
250
|
+
[gaugeDots.cw, gaugeDots.ccw].forEach(el => { if (el) { el.classList.remove('is-trending'); el.setAttribute('fill', '#bbb'); }});
|
|
221
251
|
}
|
|
222
252
|
}
|
|
223
253
|
|
|
@@ -249,11 +279,16 @@ function startDisplayLoop() {
|
|
|
249
279
|
if (store.raw["navigation.speedOverGround"] !== undefined) {
|
|
250
280
|
const twaRad = store.raw["environment.wind.angleTrueWater"], vmgKts = (twaRad !== undefined) ? Math.abs(stwKts * Math.cos(twaRad)) : 0;
|
|
251
281
|
manageHistory('vmg', vmgKts); manageHistory('sog', sogKts);
|
|
252
|
-
if (displayModeSog === 'VMG') {
|
|
253
|
-
|
|
282
|
+
if (displayModeSog === 'VMG') {
|
|
283
|
+
ui.sog.innerText = vmgKts.toFixed(1); ui.sog.style.color = "#16a085"; document.getElementById('sog-vmg-label').textContent = 'VMG';
|
|
284
|
+
} else {
|
|
285
|
+
ui.sog.innerText = sogKts.toFixed(1);
|
|
286
|
+
ui.sog.style.color = (sogKts - stwKts > 0.3) ? "#27ae60" : (sogKts - stwKts < -0.3 ? "#c0392b" : "#000");
|
|
287
|
+
document.getElementById('sog-vmg-label').textContent = 'SOG';
|
|
288
|
+
}
|
|
254
289
|
}
|
|
255
290
|
if (store.raw["environment.depth.belowTransducer"] !== undefined) { const d = store.raw["environment.depth.belowTransducer"]; ui.depth.innerText = d.toFixed(1); checkDepthAlarm(d); manageHistory('depth', d); }
|
|
256
|
-
if (store.raw["environment.wind.speedTrue"] !== undefined) { ui.tws.innerText = msToKts(store.raw["environment.wind.speedTrue"]).toFixed(1); ui.tws.style.color = (msToKts(store.raw["environment.wind.speedTrue"]) >= CONFIG.graphs.reef2) ? "#e74c3c" : (msToKts(store.raw["environment.wind.speedTrue"]) >= CONFIG.graphs.reef1 ? "#e67e22" : "#
|
|
291
|
+
if (store.raw["environment.wind.speedTrue"] !== undefined) { ui.tws.innerText = msToKts(store.raw["environment.wind.speedTrue"]).toFixed(1); ui.tws.style.color = (msToKts(store.raw["environment.wind.speedTrue"]) >= CONFIG.graphs.reef2) ? "#e74c3c" : (msToKts(store.raw["environment.wind.speedTrue"]) >= CONFIG.graphs.reef1 ? "#e67e22" : "#000"); manageHistory('tws', msToKts(store.raw["environment.wind.speedTrue"])); }
|
|
257
292
|
if (store.raw["environment.wind.speedApparent"] !== undefined) ui.awsSvg.textContent = msToKts(store.raw["environment.wind.speedApparent"]).toFixed(1);
|
|
258
293
|
|
|
259
294
|
const smAwa = getCircularAverageFromBuffer(store.smoothBuf.awa, 2000, true);
|
|
@@ -265,20 +300,18 @@ function startDisplayLoop() {
|
|
|
265
300
|
let driftDeg = radToDeg((store.raw["navigation.courseOverGroundTrue"] - store.raw["navigation.headingTrue"] + Math.PI * 3) % (Math.PI * 2) - Math.PI);
|
|
266
301
|
if (sogKts < CONFIG.averages.minSpeed) smoothedLeeway = 0; else smoothedLeeway = (smoothedLeeway * 0.9) + (driftDeg * 0.1);
|
|
267
302
|
curTrackRot = getShortestRotation(curTrackRot, smoothedLeeway); ui.track.setAttribute('transform', `rotate(${curTrackRot}, 200, 200)`);
|
|
268
|
-
ui.leewayVal.style.color = (Math.abs(sogKts - stwKts) > 0.5 && Math.abs(smoothedLeeway) > 7) ? "#
|
|
303
|
+
ui.leewayVal.style.color = (Math.abs(sogKts - stwKts) > 0.5 && Math.abs(smoothedLeeway) > 7) ? "#e67e22" : "#000";
|
|
269
304
|
updateLeewayDisplay(Math.max(-20, Math.min(20, smoothedLeeway)));
|
|
270
305
|
}
|
|
271
306
|
|
|
272
307
|
updateWindTrend();
|
|
273
|
-
|
|
274
|
-
// HEAVY TIER (2s)
|
|
275
308
|
if (tick % 2 === 0) { refreshGraph('stw'); refreshGraph(displayModeSog === 'VMG' ? 'vmg' : 'sog'); refreshGraph('depth'); refreshGraph('tws'); }
|
|
276
309
|
|
|
277
310
|
// SLOW TIER (3s)
|
|
278
311
|
if (tick % 3 === 0) {
|
|
279
|
-
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg,
|
|
280
|
-
awObj = getCircularAverageFromBuffer(store.longBuf.awa,
|
|
281
|
-
twdObj = getCircularAverageFromBuffer(store.longBuf.twd,
|
|
312
|
+
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, 30000, false), cObj = getCircularAverageFromBuffer(store.longBuf.cog, 30000, false),
|
|
313
|
+
awObj = getCircularAverageFromBuffer(store.longBuf.awa, 30000, true), twObj = getCircularAverageFromBuffer(store.longBuf.twa, 30000, true),
|
|
314
|
+
twdObj = getCircularAverageFromBuffer(store.longBuf.twd, 30000, false);
|
|
282
315
|
|
|
283
316
|
const upUI = (el, obj, isCompass = false) => {
|
|
284
317
|
if (!obj || obj.val === null) { el.innerHTML = "---°"; el.classList.remove('unstable-data'); }
|
|
@@ -290,10 +323,10 @@ function startDisplayLoop() {
|
|
|
290
323
|
upUI(ui.hdg, hObj, true); upUI(ui.cog, cObj, true); upUI(ui.awaAvg, awObj, false); upUI(ui.twaAvg, twObj, false); upUI(ui.twdAvg, twdObj, true);
|
|
291
324
|
|
|
292
325
|
if (hObj && twObj) {
|
|
293
|
-
const tackHdgDeg = radToDeg((hObj.val
|
|
326
|
+
const tackHdgDeg = radToDeg((hObj.val + twObj.val * 2 + Math.PI * 2) % (Math.PI * 2));
|
|
294
327
|
ui.tackHdg.innerHTML = `${Math.round((tackHdgDeg + 360) % 360).toString().padStart(3, '0')}°`;
|
|
295
328
|
if (cObj) {
|
|
296
|
-
const tackCogDeg = radToDeg((cObj.val
|
|
329
|
+
const tackCogDeg = radToDeg((cObj.val + twObj.val * 2 + Math.PI * 2) % (Math.PI * 2));
|
|
297
330
|
ui.tackCog.innerHTML = `${Math.round((tackCogDeg + 360) % 360).toString().padStart(3, '0')}°`;
|
|
298
331
|
}
|
|
299
332
|
}
|
|
@@ -335,8 +368,19 @@ async function fetchServerConfig() {
|
|
|
335
368
|
}
|
|
336
369
|
|
|
337
370
|
function manageHistory(t, v) {
|
|
338
|
-
const n = Date.now();
|
|
339
|
-
|
|
371
|
+
const n = Date.now();
|
|
372
|
+
const interval = simulationMode ? SIM_SAMPLE_INTERVAL : (CONFIG.graphs.historyMinutes * 60000) / CONFIG.graphs.samples;
|
|
373
|
+
if (!store.graphTempBuf[t]) store.graphTempBuf[t] = [];
|
|
374
|
+
store.graphTempBuf[t].push(v);
|
|
375
|
+
|
|
376
|
+
if (n - store.lastUpdates[t] > interval || store.histories[t].length === 0) {
|
|
377
|
+
const sum = store.graphTempBuf[t].reduce((a, b) => a + b, 0);
|
|
378
|
+
const avg = sum / store.graphTempBuf[t].length;
|
|
379
|
+
store.histories[t].push(avg);
|
|
380
|
+
if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift();
|
|
381
|
+
store.graphTempBuf[t] = [];
|
|
382
|
+
store.lastUpdates[t] = n;
|
|
383
|
+
}
|
|
340
384
|
}
|
|
341
385
|
|
|
342
386
|
function calculateScale(type, data, mode) {
|
|
@@ -350,20 +394,24 @@ function updateScaleLabels(t, min, max) { const el = document.getElementById(t +
|
|
|
350
394
|
function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
351
395
|
const svg = document.getElementById(id); if (!svg || d.length < 2) return;
|
|
352
396
|
const w = 200, h = 40, range = max - min || 1;
|
|
353
|
-
let grids = "";
|
|
354
|
-
|
|
397
|
+
let grids = "";
|
|
398
|
+
[0.25, 0.5, 0.75].forEach(p => { grids += `<line x1="0" y1="${h-(p*h)}" x2="${w}" y2="${h-(p*h)}" stroke="rgba(0,0,0,0.12)" stroke-width="0.5" />`; });
|
|
399
|
+
for (let m = 1; m < CONFIG.graphs.historyMinutes; m++) {
|
|
400
|
+
const x = w - (m / CONFIG.graphs.historyMinutes) * w;
|
|
401
|
+
grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(0,0,0,0.08)" stroke-width="0.5" />`;
|
|
402
|
+
}
|
|
355
403
|
let pD = ""; let cS = "";
|
|
356
404
|
d.forEach((v, i) => {
|
|
357
405
|
const x = (i/(CONFIG.graphs.samples-1))*w, y = h-(Math.max(0,Math.min(1,(v-min)/range))*h); pD += `${i===0?'M':'L'} ${x} ${y} `;
|
|
358
406
|
if (isTws && i > 0) {
|
|
359
407
|
const px = ((i-1)/(CONFIG.graphs.samples-1))*w, py = h-(Math.max(0,Math.min(1,(d[i-1]-min)/range))*h);
|
|
360
|
-
let c = (v >= CONFIG.graphs.reef2) ? "#e74c3c" : (v >= CONFIG.graphs.reef1 ? "#e67e22" : "#
|
|
408
|
+
let c = (v >= CONFIG.graphs.reef2) ? "#e74c3c" : (v >= CONFIG.graphs.reef1 ? "#e67e22" : "#000");
|
|
361
409
|
cS += `<line x1="${px}" y1="${py}" x2="${x}" y2="${y}" stroke="${c}" class="${isHercules?'line-hercules':''}" />`;
|
|
362
410
|
}
|
|
363
411
|
});
|
|
364
|
-
const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#
|
|
365
|
-
const colorKey = id === 'sog-graph' && displayModeSog === 'VMG' ? '#
|
|
366
|
-
svg.innerHTML = isTws ? `${grids}<path d="${pD} L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z" fill="rgba(
|
|
412
|
+
const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#000' };
|
|
413
|
+
const colorKey = id === 'sog-graph' && displayModeSog === 'VMG' ? '#16a085' : clrs[id];
|
|
414
|
+
svg.innerHTML = isTws ? `${grids}<path d="${pD} L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z" fill="rgba(0,0,0,0.05)" stroke="none" />${cS}` : `${grids}<path d="${pD} L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z" fill="${colorKey}22" stroke="none" /><path d="${pD}" class="${isHercules?'line-hercules':''}" fill="none" stroke="${colorKey}" />`;
|
|
367
415
|
}
|
|
368
416
|
|
|
369
417
|
// ==========================================================================
|
|
@@ -384,7 +432,7 @@ function toggleFocusMode(type, element) {
|
|
|
384
432
|
clearTimeout(pressTimer); if (isLongPressActive) return;
|
|
385
433
|
const currentTime = new Date().getTime(), tapDelay = currentTime - lastTapTime;
|
|
386
434
|
if (tapDelay < 300 && tapDelay > 0) { clearTimeout(tapTimeout); if (!isFocusActive) { graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard'; localStorage.setItem('mode_' + type, graphModes[type]); } lastTapTime = 0; }
|
|
387
|
-
else { lastTapTime = currentTime; tapTimeout = setTimeout(() => { if (isFocusActive && el.classList.contains('is-focused')) toggleFocusMode(type, el); else if (!isFocusActive && type === 'sog') { displayModeSog = (displayModeSog === 'SOG') ? 'VMG' : 'SOG'; el.style.backgroundColor = "rgba(
|
|
435
|
+
else { lastTapTime = currentTime; tapTimeout = setTimeout(() => { if (isFocusActive && el.classList.contains('is-focused')) toggleFocusMode(type, el); else if (!isFocusActive && type === 'sog') { displayModeSog = (displayModeSog === 'SOG') ? 'VMG' : 'SOG'; el.style.backgroundColor = "rgba(0, 0, 0, 0.05)"; setTimeout(() => el.style.backgroundColor = "", 150); } }, 250); }
|
|
388
436
|
});
|
|
389
437
|
el.addEventListener('pointerleave', () => clearTimeout(pressTimer));
|
|
390
438
|
});
|
|
@@ -414,24 +462,19 @@ function startDynamicSimulation() {
|
|
|
414
462
|
let sim = { hdg: 45, tws: 12, twd: 90, depth: 12, stw: 5, leeway: 0, currentSpeed: 1.5, currentDir: 90, startTime: Date.now() };
|
|
415
463
|
simInterval = setInterval(() => {
|
|
416
464
|
const elapsed = (Date.now() - sim.startTime) / 1000;
|
|
417
|
-
if (elapsed > 120 && elapsed < 121) sim.twd = Math.random() * 360;
|
|
418
465
|
sim.twd = (sim.twd + (Math.sin(elapsed / 20) * 0.5) + 360) % 360;
|
|
419
|
-
const
|
|
466
|
+
const targetTws = 10 + Math.sin(elapsed / 10) * 2;
|
|
420
467
|
sim.tws += (targetTws - sim.tws) * 0.05; sim.hdg = (sim.hdg + (Math.random() - 0.5) * 1 + 360) % 360;
|
|
421
468
|
let twaRel = (sim.twd - sim.hdg + 360) % 360; if (twaRel > 180) twaRel -= 360;
|
|
422
|
-
let targetStw =
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
const bX = sim.stw * Math.sin(degToRad(sim.hdg + sim.leeway)), bY = sim.stw * Math.cos(degToRad(sim.hdg + sim.leeway));
|
|
426
|
-
const cX = sim.currentSpeed * Math.sin(degToRad(sim.currentDir)), cY = sim.currentSpeed * Math.cos(degToRad(sim.currentDir));
|
|
427
|
-
const sog = Math.sqrt(Math.pow(bX + cX, 2) + Math.pow(bY + cY, 2)), cog = (radToDeg(Math.atan2(bX + cX, bY + cY)) + 360) % 360;
|
|
428
|
-
|
|
469
|
+
let targetStw = 5; sim.stw += (targetStw - sim.stw) * 0.05;
|
|
470
|
+
const bX = sim.stw * Math.sin(degToRad(sim.hdg)), bY = sim.stw * Math.cos(degToRad(sim.hdg));
|
|
471
|
+
const sog = sim.stw, cog = sim.hdg;
|
|
429
472
|
const twaRad = degToRad(twaRel), aws = Math.sqrt(Math.pow(sim.stw, 2) + Math.pow(sim.tws, 2) + 2 * sim.stw * sim.tws * Math.cos(twaRad));
|
|
430
473
|
const awa = Math.atan2(sim.tws * Math.sin(twaRad), sim.stw + sim.tws * Math.cos(twaRad));
|
|
431
474
|
processIncomingData("environment.wind.speedApparent", ktsToMs(aws)); processIncomingData("environment.wind.angleApparent", awa);
|
|
432
475
|
processIncomingData("environment.depth.belowTransducer", sim.depth); processIncomingData("navigation.headingTrue", degToRad(sim.hdg));
|
|
433
476
|
processIncomingData("navigation.speedThroughWater", ktsToMs(sim.stw)); processIncomingData("navigation.speedOverGround", ktsToMs(sog));
|
|
434
|
-
processIncomingData("navigation.courseOverGroundTrue", degToRad(cog));
|
|
477
|
+
processIncomingData("navigation.courseOverGroundTrue", degToRad(cog));
|
|
435
478
|
}, 1000);
|
|
436
479
|
}
|
|
437
480
|
|
|
@@ -439,6 +482,6 @@ function startDynamicSimulation() {
|
|
|
439
482
|
// 10. INIT
|
|
440
483
|
// ==========================================================================
|
|
441
484
|
window.addEventListener('contextmenu', e => e.preventDefault(), true);
|
|
442
|
-
(function genTicks() { const c = document.getElementById('ticks'); if (c) { for (let i = 0; i < 360; i += 10) { const l = document.createElementNS("http://www.w3.org/2000/svg", "line"); const m = i % 30 === 0; l.setAttribute("x1", "200"); l.setAttribute("y1", "40"); l.setAttribute("x2", "200"); l.setAttribute("y2", (m ? 60 : 50)); l.setAttribute("stroke", m ? "#
|
|
485
|
+
(function genTicks() { const c = document.getElementById('ticks'); if (c) { for (let i = 0; i < 360; i += 10) { const l = document.createElementNS("http://www.w3.org/2000/svg", "line"); const m = i % 30 === 0; l.setAttribute("x1", "200"); l.setAttribute("y1", "40"); l.setAttribute("x2", "200"); l.setAttribute("y2", (m ? 60 : 50)); l.setAttribute("stroke", m ? "#000" : "#bbb"); l.setAttribute("stroke-width", m ? "2" : "1"); l.setAttribute("transform", `rotate(${i}, 200, 200)`); c.appendChild(l); } } })();
|
|
443
486
|
async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
|
|
444
487
|
window.addEventListener('load', init);
|
package/index.js
CHANGED
|
@@ -63,8 +63,8 @@ module.exports = function (app) {
|
|
|
63
63
|
longWindow: {
|
|
64
64
|
type: 'number',
|
|
65
65
|
title: 'Long Average Window (ms)',
|
|
66
|
-
description: "Time buffer
|
|
67
|
-
default:
|
|
66
|
+
description: "Time buffer for 'MEAN' values. Larger windows produce smoother numbers but increase the 'Unstable' (orange) alerts during maneuvers or in gusty conditions, as data coherence decreases over time.",
|
|
67
|
+
default: 30000
|
|
68
68
|
},
|
|
69
69
|
smoothWindow: {
|
|
70
70
|
type: 'number',
|
package/package.json
CHANGED
package/settings.json
CHANGED
package/style.css
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
1. BASE E RESET DI SISTEMA
|
|
2
|
+
1. BASE E RESET DI SISTEMA (GIORNO: SFONDO CHIARO)
|
|
3
3
|
========================================================================== */
|
|
4
4
|
body {
|
|
5
|
-
background-color: #
|
|
6
|
-
color: #
|
|
5
|
+
background-color: #fff;
|
|
6
|
+
color: #000;
|
|
7
7
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
|
8
8
|
margin: 0;
|
|
9
9
|
padding: 0;
|
|
10
10
|
height: 100vh;
|
|
11
11
|
width: 100vw;
|
|
12
12
|
overflow: hidden;
|
|
13
|
-
/* Inibisce i gesti di sistema per favorire Long Press e Swipe della dashboard */
|
|
14
13
|
-webkit-touch-callout: none;
|
|
15
14
|
-webkit-user-select: none;
|
|
16
15
|
user-select: none;
|
|
@@ -27,7 +26,6 @@ body {
|
|
|
27
26
|
padding: 5px;
|
|
28
27
|
box-sizing: border-box;
|
|
29
28
|
gap: 8px;
|
|
30
|
-
/* Rapporto Standard: Lati flessibili, Centro bilanciato a 1.5fr */
|
|
31
29
|
grid-template-columns: minmax(180px, 1fr) minmax(auto, 1.5fr) minmax(180px, 1fr);
|
|
32
30
|
grid-template-rows: 100%;
|
|
33
31
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
@@ -37,18 +35,18 @@ body {
|
|
|
37
35
|
.side-panel {
|
|
38
36
|
display: flex;
|
|
39
37
|
flex-direction: column;
|
|
40
|
-
background: rgba(
|
|
38
|
+
background: rgba(0, 0, 0, 0.03);
|
|
41
39
|
border-radius: 12px;
|
|
42
40
|
height: 100%;
|
|
43
41
|
overflow: hidden;
|
|
44
|
-
gap: 2px;
|
|
42
|
+
gap: 2px;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
.left-panel { grid-column: 1; }
|
|
48
46
|
|
|
49
47
|
.center-panel {
|
|
50
48
|
grid-column: 2;
|
|
51
|
-
position: relative;
|
|
49
|
+
position: relative;
|
|
52
50
|
display: flex;
|
|
53
51
|
flex-direction: column;
|
|
54
52
|
align-items: center;
|
|
@@ -64,34 +62,32 @@ body {
|
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
/* ==========================================================================
|
|
67
|
-
3. DATA-BOX E TIPOGRAFIA
|
|
65
|
+
3. DATA-BOX E TIPOGRAFIA
|
|
68
66
|
========================================================================== */
|
|
69
67
|
.data-box {
|
|
70
68
|
position: relative;
|
|
71
|
-
border-bottom: 1px solid #
|
|
69
|
+
border-bottom: 1px solid #eee;
|
|
72
70
|
padding: 2px 4px;
|
|
73
71
|
display: flex;
|
|
74
72
|
flex-direction: column;
|
|
75
73
|
width: 100%;
|
|
76
74
|
box-sizing: border-box;
|
|
77
|
-
container-type: size;
|
|
78
|
-
flex: 1 1 0px;
|
|
75
|
+
container-type: size;
|
|
76
|
+
flex: 1 1 0px;
|
|
79
77
|
min-height: 0;
|
|
80
78
|
overflow: hidden;
|
|
81
79
|
transition: background-color 0.2s ease;
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
/* Allineamento speculare dei testi tra colonna SX e DX */
|
|
85
82
|
.left-panel .data-box { align-items: flex-start; text-align: left; }
|
|
86
83
|
.right-panel .data-box { align-items: flex-end; text-align: right; }
|
|
87
84
|
|
|
88
85
|
.label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; width: 100%; }
|
|
89
|
-
.label { color: #
|
|
90
|
-
.unit { color: #
|
|
86
|
+
.label { color: #888; font-size: 0.65rem; font-weight: bold; text-transform: uppercase; }
|
|
87
|
+
.unit { color: #aaa; font-size: 0.6rem; font-weight: bold; }
|
|
91
88
|
|
|
92
|
-
/* Font dinamico per i valori: massimo 22% dell'altezza del box */
|
|
93
89
|
.value {
|
|
94
|
-
color: #
|
|
90
|
+
color: #000;
|
|
95
91
|
font-size: clamp(1.2rem, 22cqh, 3rem);
|
|
96
92
|
font-weight: 600;
|
|
97
93
|
line-height: 0.9;
|
|
@@ -99,7 +95,6 @@ body {
|
|
|
99
95
|
padding-bottom: 5px;
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
/* Centratura verticale automatica per i box senza grafico */
|
|
103
98
|
.value-large, .dual-value-container, .value-with-compass {
|
|
104
99
|
margin-top: auto;
|
|
105
100
|
margin-bottom: auto;
|
|
@@ -107,42 +102,36 @@ body {
|
|
|
107
102
|
|
|
108
103
|
.value-large { font-size: clamp(1.5rem, 35cqh, 4.5rem); line-height: 0.85; }
|
|
109
104
|
|
|
110
|
-
/* --- 5. BUSSOLA TWD (FIX ALLINEAMENTO) --- */
|
|
111
105
|
.value-with-compass {
|
|
112
106
|
display: flex;
|
|
113
|
-
justify-content: space-between;
|
|
107
|
+
justify-content: space-between;
|
|
114
108
|
align-items: center;
|
|
115
109
|
width: 100%;
|
|
116
|
-
align-self: stretch;
|
|
110
|
+
align-self: stretch;
|
|
117
111
|
gap: 5px;
|
|
118
112
|
}
|
|
119
113
|
|
|
120
|
-
/* TACK (Mure opposte) Layout */
|
|
121
114
|
.dual-value-container { display: flex; justify-content: space-between; width: 100%; }
|
|
122
115
|
.dual-value-col { display: flex; flex-direction: column; width: 48%; }
|
|
123
|
-
.dual-label { color: #
|
|
116
|
+
.dual-label { color: #888; font-size: 0.55rem; font-weight: bold; text-transform: uppercase; margin-bottom: 2px; }
|
|
124
117
|
.value.dual-val { font-size: clamp(1rem, 22cqh, 2rem); padding-bottom: 0; line-height: 0.9; }
|
|
125
118
|
|
|
126
119
|
/* ==========================================================================
|
|
127
|
-
4. TACTICAL FOCUS MODE
|
|
120
|
+
4. TACTICAL FOCUS MODE
|
|
128
121
|
========================================================================== */
|
|
129
122
|
.focus-active .side-panel:not(.has-focus) { display: none !important; }
|
|
130
|
-
|
|
131
|
-
/* Split: 60% per il grafico scelto, 40% per il vento centrale */
|
|
132
123
|
.focus-active.focus-side-left { grid-template-columns: 3fr 2fr !important; }
|
|
133
124
|
.focus-active.focus-side-left .side-panel.has-focus { grid-column: 1 !important; }
|
|
134
125
|
.focus-active.focus-side-left .center-panel { grid-column: 2 !important; justify-content: center !important; align-items: center !important; }
|
|
135
|
-
|
|
136
126
|
.focus-active.focus-side-right { grid-template-columns: 2fr 3fr !important; }
|
|
137
127
|
.focus-active.focus-side-right .center-panel { grid-column: 1 !important; justify-content: center !important; align-items: center !important; }
|
|
138
128
|
.focus-active.focus-side-right .side-panel.has-focus { grid-column: 2 !important; }
|
|
139
129
|
|
|
140
|
-
/* Espansione box e tipografia massiccia in Focus Mode */
|
|
141
130
|
.focus-active .has-focus .data-box:not(.is-focused) { display: none !important; }
|
|
142
131
|
.focus-active .has-focus .data-box.is-focused {
|
|
143
132
|
height: 100vh !important;
|
|
144
133
|
border: none;
|
|
145
|
-
background: rgba(
|
|
134
|
+
background: rgba(0, 0, 0, 0.02);
|
|
146
135
|
padding: 2vw 3vw !important;
|
|
147
136
|
display: flex;
|
|
148
137
|
flex-direction: column;
|
|
@@ -153,15 +142,14 @@ body {
|
|
|
153
142
|
.focus-active .is-focused .label-row .label { font-size: 2rem !important; }
|
|
154
143
|
.focus-active .is-focused .label-row .unit { font-size: 2rem !important; }
|
|
155
144
|
.focus-active .is-focused .sparkline path { stroke-width: 2.5px !important; }
|
|
156
|
-
|
|
157
145
|
.focus-active .center-panel svg#wind-gauge { max-width: 95% !important; max-height: 85vh !important; }
|
|
158
146
|
|
|
159
147
|
/* ==========================================================================
|
|
160
148
|
5. GRAFICI, SCALE E HERCULES MODE
|
|
161
|
-
|
|
149
|
+
========================================================================= */
|
|
162
150
|
.graph-wrapper {
|
|
163
151
|
position: relative; width: 100%; flex-grow: 1; min-height: 0; margin-top: 4px;
|
|
164
|
-
display: flex; align-items: stretch; background: rgba(
|
|
152
|
+
display: flex; align-items: stretch; background: rgba(0, 0, 0, 0.04);
|
|
165
153
|
border-radius: 4px; gap: 0px !important;
|
|
166
154
|
}
|
|
167
155
|
|
|
@@ -173,7 +161,7 @@ body {
|
|
|
173
161
|
|
|
174
162
|
.scale-labels {
|
|
175
163
|
display: flex; flex-direction: column; justify-content: space-between;
|
|
176
|
-
font-size: 8px; color: #
|
|
164
|
+
font-size: 8px; color: #444; font-weight: bold; min-width: 12px;
|
|
177
165
|
height: 100%; line-height: 1; padding: 0; border: none !important;
|
|
178
166
|
}
|
|
179
167
|
|
|
@@ -182,10 +170,9 @@ body {
|
|
|
182
170
|
.right-panel .scale-labels { order: 1; text-align: right; padding-right: 4px; }
|
|
183
171
|
.right-panel .sparkline { order: 2; }
|
|
184
172
|
|
|
185
|
-
/* Hercules Mode Visuals */
|
|
186
173
|
.line-hercules { filter: drop-shadow(0 0 5px #ff0000); stroke-width: 1.8px !important; }
|
|
187
|
-
.box-hercules { background: rgba(255, 0, 0, 0.
|
|
188
|
-
.box-hercules .scale-labels { color: #
|
|
174
|
+
.box-hercules { background: rgba(255, 0, 0, 0.05) !important; }
|
|
175
|
+
.box-hercules .scale-labels { color: #ff5555; }
|
|
189
176
|
.box-hercules .unit::before, .box-hercules .unit::after, .box-hercules .label::before {
|
|
190
177
|
font-size: 7px; color: #ff4444; font-weight: 900; letter-spacing: 1px; text-transform: uppercase;
|
|
191
178
|
}
|
|
@@ -193,47 +180,67 @@ body {
|
|
|
193
180
|
.right-panel .box-hercules .unit::after { content: " HERCULES"; }
|
|
194
181
|
.right-panel .box-hercules .label:only-child::after { content: " HERCULES"; }
|
|
195
182
|
|
|
196
|
-
/* Colori standard grafici */
|
|
197
183
|
#stw-graph { stroke: #2ecc71; fill: rgba(46, 204, 113, 0.12); }
|
|
198
184
|
#sog-graph { stroke: #f39c12; fill: rgba(243, 156, 18, 0.12); }
|
|
199
185
|
#depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.12); }
|
|
200
|
-
#tws-graph { stroke: #
|
|
186
|
+
#tws-graph { stroke: #000000; fill: rgba(0, 0, 0, 0.08); }
|
|
201
187
|
|
|
202
188
|
/* ==========================================================================
|
|
203
|
-
6. WIDGETS E STATUS
|
|
189
|
+
6. WIDGETS E STATUS (GIORNO)
|
|
204
190
|
========================================================================== */
|
|
205
191
|
#status {
|
|
206
192
|
position: absolute; top: 10px; right: 10px;
|
|
207
193
|
font-size: 0.6rem; font-weight: 900;
|
|
208
194
|
text-transform: uppercase; z-index: 1000;
|
|
209
|
-
letter-spacing: 1px; background: rgba(0,0,0,0.
|
|
195
|
+
letter-spacing: 1px; background: rgba(0,0,0,0.05);
|
|
210
196
|
padding: 2px 6px; border-radius: 4px;
|
|
211
197
|
}
|
|
212
|
-
.online { color: #
|
|
213
|
-
.offline { color: #
|
|
198
|
+
.online { color: #27ae60; opacity: 0.8; }
|
|
199
|
+
.offline { color: #c0392b; font-weight: bold; }
|
|
214
200
|
|
|
215
201
|
.mini-compass {
|
|
216
202
|
width: min(80cqh, 42cqw); height: min(80cqh, 42cqw); aspect-ratio: 1 / 1;
|
|
217
|
-
flex-shrink: 0; background: #
|
|
218
|
-
|
|
203
|
+
flex-shrink: 0; background: #f0f0f0;
|
|
204
|
+
border-radius: 50%; border: 1.5px solid #ddd;
|
|
205
|
+
box-shadow: inset 0 0 10px rgba(0,0,0,0.05); transition: all 0.4s ease;
|
|
219
206
|
}
|
|
220
207
|
|
|
208
|
+
/* Inversione elementi Mini Bussola TWD - Giorno */
|
|
209
|
+
.mini-compass text:last-of-type { fill: #000 !important; opacity: 0.4; } /* La "S" nera */
|
|
210
|
+
#twd-boat-wrap path { fill: #000 !important; opacity: 0.3; } /* Barca interna nera */
|
|
211
|
+
#twd-wind-chevron { stroke: #000 !important; } /* Forza il triangolino vento a nero in giorno */
|
|
212
|
+
|
|
221
213
|
#awa-pointer, #twa-pointer, #track-pointer, #twd-arrow, #twd-boat-wrap {
|
|
222
214
|
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
|
223
215
|
}
|
|
224
216
|
|
|
225
217
|
#wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
|
|
226
218
|
|
|
219
|
+
/* Quadrante Vento Centrale - Giorno */
|
|
220
|
+
#wind-gauge circle[fill="#050505"] { fill: #fcfcfc; }
|
|
221
|
+
#wind-gauge circle[fill="#121212"] { fill: #f0f0f0; }
|
|
222
|
+
#boat-icon { fill: #000 !important; opacity: 1; }
|
|
223
|
+
#tick-labels { fill: #444 !important; }
|
|
224
|
+
#aws-val-svg { fill: #000 !important; }
|
|
225
|
+
#aws-display-group text { fill: #333 !important; }
|
|
226
|
+
#fullscreen-hotspot { fill: #eee !important; stroke: #ccc !important; }
|
|
227
|
+
#ticks line[stroke="#fff"] { stroke: #000 !important; }
|
|
228
|
+
#ticks line[stroke="#666"] { stroke: #bbb !important; }
|
|
229
|
+
|
|
230
|
+
/* Leeway Container - Giorno */
|
|
231
|
+
rect[fill="#222"] { fill: #eee !important; }
|
|
232
|
+
#leeway-val { color: #000 !important; }
|
|
233
|
+
|
|
227
234
|
/* ==========================================================================
|
|
228
|
-
7. STATI DI ALLARME
|
|
235
|
+
7. STATI DI ALLARME
|
|
229
236
|
========================================================================== */
|
|
230
|
-
.unstable-data { animation: blink-unstable 1.5s infinite ease-in-out; color: #
|
|
231
|
-
.alarm-warning { color: #
|
|
237
|
+
.unstable-data { animation: blink-unstable 1.5s infinite ease-in-out; color: #e67e22 !important; }
|
|
238
|
+
.alarm-warning { color: #f39c12 !important; }
|
|
232
239
|
.alarm-danger { color: #e74c3c !important; font-weight: 900; animation: blink-unstable 1s infinite; }
|
|
233
240
|
@keyframes blink-unstable { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
234
241
|
|
|
235
242
|
/* ==========================================================================
|
|
236
|
-
8. RESPONSIVE
|
|
243
|
+
8. RESPONSIVE
|
|
237
244
|
========================================================================== */
|
|
238
245
|
@media (max-aspect-ratio: 0.9 / 1) {
|
|
239
246
|
.main-container { grid-template-columns: 1fr 1fr !important; grid-template-rows: 45vh calc(55vh - 8px) !important; }
|
|
@@ -250,38 +257,46 @@ body {
|
|
|
250
257
|
9. NIGHT MODE (TACTICAL RED)
|
|
251
258
|
========================================================================== */
|
|
252
259
|
body.night-mode { background-color: #000 !important; color: #ff0000 !important; }
|
|
253
|
-
.night-mode .side-panel { background: rgba(20, 0, 0, 0.4); border: 1px solid #330000; }
|
|
260
|
+
.night-mode .side-panel { background: rgba(20, 0, 0, 0.4) !important; border: 1px solid #330000; }
|
|
254
261
|
.night-mode .data-box { border-bottom-color: #260000; }
|
|
255
262
|
.night-mode .label, .night-mode .unit, .night-mode .dual-label { color: #800000 !important; }
|
|
256
263
|
.night-mode .value, .night-mode .value-large { color: #ff3333 !important; text-shadow: 0 0 8px rgba(255, 0, 0, 0.4); }
|
|
257
264
|
|
|
258
|
-
/* Grafici Night Mode: Solo linea, no riempimento */
|
|
259
265
|
.night-mode .graph-wrapper { background: rgba(30, 0, 0, 0.3) !important; }
|
|
260
266
|
.night-mode .sparkline path:first-of-type { display: none !important; }
|
|
261
267
|
.night-mode .sparkline path { fill: none !important; stroke: #ff3333 !important; stroke-width: 1.8px !important; opacity: 1 !important; filter: drop-shadow(0 0 2px rgba(255, 0, 0, 0.4)); }
|
|
262
|
-
|
|
268
|
+
|
|
269
|
+
.night-mode .sparkline line { stroke: rgba(255, 0, 0, 0.15) !important; stroke-width: 0.7px !important; }
|
|
263
270
|
.night-mode #tws-graph line:not([stroke*="rgba"]) { stroke: #ff3333 !important; stroke-width: 1.8px !important; }
|
|
264
271
|
.night-mode .scale-labels { color: #660000 !important; }
|
|
265
272
|
|
|
266
|
-
/* Hercules & special in Night */
|
|
267
273
|
.night-mode .box-hercules { background: rgba(60, 0, 0, 0.2) !important; }
|
|
268
274
|
.night-mode .line-hercules { filter: drop-shadow(0 0 6px #ff0000) !important; }
|
|
269
275
|
|
|
270
|
-
/*
|
|
276
|
+
/* Fix Bussola Centrale Night */
|
|
271
277
|
.night-mode #wind-gauge circle { stroke: #330000; }
|
|
278
|
+
.night-mode #wind-gauge circle[fill="#050505"] { fill: #050505 !important; }
|
|
279
|
+
.night-mode #wind-gauge circle[fill="#121212"] { fill: #121212 !important; }
|
|
280
|
+
.night-mode #fullscreen-hotspot { fill: #000 !important; stroke: #330000 !important; }
|
|
281
|
+
|
|
272
282
|
.night-mode #ticks line { stroke: #4d0000 !important; }
|
|
273
283
|
.night-mode #tick-labels { fill: #800000 !important; }
|
|
274
|
-
.night-mode #boat-icon { fill: #330000 !important; opacity: 0.6; }
|
|
284
|
+
.night-mode #boat-icon { fill: #330000 !important; opacity: 0.6 !important; }
|
|
275
285
|
.night-mode #aws-val-svg { fill: #ff3333 !important; }
|
|
276
286
|
.night-mode #aws-display-group text { fill: #ff3333 !important; }
|
|
277
287
|
.night-mode #center-glow feDropShadow { flood-color: #ff0000 !important; flood-opacity: 0.6 !important; }
|
|
278
288
|
|
|
279
|
-
/*
|
|
289
|
+
/* Bussola TWD Night */
|
|
290
|
+
.night-mode .mini-compass { border-color: #330000; background: #000; }
|
|
291
|
+
.night-mode .mini-compass text { fill: #800000 !important; }
|
|
292
|
+
.night-mode .mini-compass text:last-of-type { fill: #800000 !important; opacity: 1; } /* La "S" torna rossa */
|
|
293
|
+
.night-mode #twd-boat-wrap path { fill: #ff3333 !important; opacity: 0.4 !important; } /* Barca torna rossa */
|
|
294
|
+
.night-mode #twd-arrow #twd-wind-chevron { stroke: #ff3333 !important; stroke-width: 3px !important; opacity: 1 !important; filter: drop-shadow(0 0 4px #ff0000); transform-origin: center; }
|
|
295
|
+
|
|
296
|
+
/* Vento e Leeway Night */
|
|
280
297
|
.night-mode #wind-gauge path[stroke="#ff0000"] { stroke: #660000 !important; opacity: 0.8; }
|
|
281
298
|
.night-mode #wind-gauge path[stroke="#00ff00"] { stroke: #660000 !important; stroke-dasharray: 4, 3; opacity: 0.8; }
|
|
282
299
|
.night-mode #wind-gauge path[stroke="#ff8800"] { stroke: #330000 !important; stroke-width: 8; }
|
|
283
|
-
|
|
284
|
-
/* Leeway & Pointer Night */
|
|
285
300
|
.night-mode rect[fill="url(#leeway-grad)"] { fill: url(#leeway-night-grad) !important; }
|
|
286
301
|
.night-mode #leeway-val { fill: #ff3333 !important; }
|
|
287
302
|
.night-mode g[stroke="#555"] line { stroke: #4d0000 !important; }
|
|
@@ -292,30 +307,21 @@ body.night-mode { background-color: #000 !important; color: #ff0000 !important;
|
|
|
292
307
|
.night-mode #twa-pointer path { fill: #800000; stroke: #000; }
|
|
293
308
|
.night-mode #track-pointer path { fill: #ff0000; stroke: #fff; stroke-width: 0.5; }
|
|
294
309
|
|
|
295
|
-
/* Bussola TWD Night */
|
|
296
|
-
.night-mode .mini-compass { border-color: #330000; background: #000; }
|
|
297
|
-
.night-mode .mini-compass text { fill: #800000 !important; }
|
|
298
|
-
.night-mode #twd-arrow #twd-wind-chevron { stroke: #ff3333 !important; stroke-width: 3px !important; opacity: 1 !important; filter: drop-shadow(0 0 4px #ff0000); transform-origin: center; }
|
|
299
|
-
.night-mode #twd-boat-wrap path { fill: #ff3333 !important; opacity: 0.4 !important; }
|
|
300
|
-
|
|
301
310
|
/* ==========================================================================
|
|
302
|
-
|
|
311
|
+
10. TREND VENTO E ALLARME STRAMBATA
|
|
303
312
|
========================================================================== */
|
|
304
313
|
@keyframes blink-trend { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
|
305
314
|
|
|
306
|
-
/* Stato base dei pallini di trend: discreti al 30% */
|
|
307
315
|
#trend-dot-cw, #trend-dot-ccw, #trend-gauge-cw, #trend-gauge-ccw {
|
|
308
316
|
opacity: 0.3;
|
|
309
317
|
transition: opacity 0.3s ease;
|
|
310
318
|
}
|
|
311
319
|
|
|
312
|
-
/* Quando attivi: brillano al 100% e lampeggiano */
|
|
313
320
|
.is-trending {
|
|
314
321
|
opacity: 1 !important;
|
|
315
322
|
animation: blink-trend 1s infinite !important;
|
|
316
323
|
}
|
|
317
324
|
|
|
318
|
-
/* Allarme Strambata: Rosso fisso con bagliore neon massimo */
|
|
319
325
|
.is-gybing {
|
|
320
326
|
opacity: 1 !important;
|
|
321
327
|
animation: none !important;
|
package/test.html
CHANGED