@sailingrotevista/rotevista-dash 2.0.12 → 2.0.14
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 +178 -16
- package/index.html +18 -3
- package/package.json +1 -1
- package/style.css +157 -34
package/app.js
CHANGED
|
@@ -41,7 +41,7 @@ let simulationMode = false;
|
|
|
41
41
|
let socket, renderInterval, simInterval;
|
|
42
42
|
let lastAvgUIUpdate = 0, audioCtx = null, lastAlarmTime = 0;
|
|
43
43
|
let curAwaRot = 0, curTwaRot = 0, curTrackRot = 0, curTwdRoseRot = 0;
|
|
44
|
-
|
|
44
|
+
let smoothedLeeway = 0;
|
|
45
45
|
let pressTimer, isFocusActive = false, blockNextClick = false;
|
|
46
46
|
|
|
47
47
|
const graphModes = {
|
|
@@ -167,12 +167,30 @@ function startDisplayLoop() {
|
|
|
167
167
|
const smTwa = getCircularAverageFromBuffer(store.smoothBuf.twa, CONFIG.averages.smoothWindow, true);
|
|
168
168
|
if (smTwa) { curTwaRot = getShortestRotation(curTwaRot, smTwa.val); ui.twa.setAttribute('transform', `rotate(${curTwaRot}, 200, 200)`); }
|
|
169
169
|
|
|
170
|
-
if (store.raw["navigation.courseOverGroundTrue"] && store.raw["navigation.headingTrue"]) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
if (store.raw["navigation.courseOverGroundTrue"] !== undefined && store.raw["navigation.headingTrue"] !== undefined) {
|
|
171
|
+
// 1. Calcolo grezzo
|
|
172
|
+
let rawDrift = (radToDeg(store.raw["navigation.courseOverGroundTrue"]) - radToDeg(store.raw["navigation.headingTrue"]) + 360) % 360;
|
|
173
|
+
if (rawDrift > 180) rawDrift -= 360;
|
|
174
|
+
|
|
175
|
+
// 2. Filtro di stabilità (Low Pass Filter)
|
|
176
|
+
// Se la barca è ferma, azzera brutalmente. Altrimenti filtra il valore.
|
|
177
|
+
if (curSog < CONFIG.averages.minSpeed) {
|
|
178
|
+
smoothedLeeway = 0;
|
|
179
|
+
} else {
|
|
180
|
+
// Smoothing: 90% valore precedente, 10% nuovo valore
|
|
181
|
+
smoothedLeeway = (smoothedLeeway * 0.9) + (rawDrift * 0.1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 3. Renderizziamo il valore filtrato
|
|
185
|
+
curTrackRot = getShortestRotation(curTrackRot, smoothedLeeway);
|
|
186
|
+
ui.track.setAttribute('transform', `rotate(${curTrackRot}, 200, 200)`);
|
|
187
|
+
|
|
188
|
+
// Limita il valore grafico e aggiorna il testo
|
|
189
|
+
let ds = Math.max(-20, Math.min(20, smoothedLeeway));
|
|
190
|
+
updateLeewayDisplay(ds);
|
|
191
|
+
} else {
|
|
192
|
+
updateLeewayDisplay(0);
|
|
193
|
+
}
|
|
176
194
|
|
|
177
195
|
// --- 2. RENDERING MEDIE E BUSSOLA TATTICA ---
|
|
178
196
|
if (now - lastAvgUIUpdate > 3000) {
|
|
@@ -182,14 +200,35 @@ function startDisplayLoop() {
|
|
|
182
200
|
twObj = getCircularAverageFromBuffer(store.longBuf.twa, CONFIG.averages.longWindow, true),
|
|
183
201
|
twdObj = getCircularAverageFromBuffer(store.longBuf.twd, CONFIG.averages.longWindow, false);
|
|
184
202
|
|
|
185
|
-
const upUI = (el, obj) => {
|
|
186
|
-
if (!obj
|
|
187
|
-
|
|
188
|
-
el.
|
|
189
|
-
|
|
203
|
+
const upUI = (el, obj, isCompass = false) => {
|
|
204
|
+
if (!obj || obj.val === null) {
|
|
205
|
+
el.innerHTML = "---°";
|
|
206
|
+
el.classList.remove('unstable-data');
|
|
207
|
+
} else {
|
|
208
|
+
let val = Math.round(obj.val);
|
|
209
|
+
let displayVal;
|
|
210
|
+
|
|
211
|
+
if (isCompass) {
|
|
212
|
+
// Formattazione per HEADING, COG, TWD (0-360°)
|
|
213
|
+
displayVal = ((val + 360) % 360).toString().padStart(3, '0');
|
|
214
|
+
} else {
|
|
215
|
+
// Formattazione per AWA, TWA (-180 a 180°)
|
|
216
|
+
// Se è negativo, il segno viene mantenuto correttamente
|
|
217
|
+
displayVal = val.toString();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
el.innerHTML = `${displayVal}°`;
|
|
221
|
+
|
|
222
|
+
if (obj.stable || curSog < CONFIG.averages.minSpeed) el.classList.remove('unstable-data');
|
|
223
|
+
else el.classList.add('unstable-data');
|
|
190
224
|
}
|
|
191
225
|
};
|
|
192
|
-
|
|
226
|
+
|
|
227
|
+
upUI(ui.hdg, hObj, true); // Bussola
|
|
228
|
+
upUI(ui.cog, cObj, true); // Bussola
|
|
229
|
+
upUI(ui.awaAvg, awObj, false); // Angolo
|
|
230
|
+
upUI(ui.twaAvg, twObj, false); // Angolo
|
|
231
|
+
upUI(ui.twdAvg, twdObj, true); // Bussola (La direzione del vento è sempre 0-360)
|
|
193
232
|
|
|
194
233
|
if (hObj && twObj && hObj.val !== null) {
|
|
195
234
|
let tA = twObj.val * 2; ui.tackHdg.innerHTML = `${Math.round((hObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
@@ -201,6 +240,7 @@ function startDisplayLoop() {
|
|
|
201
240
|
|
|
202
241
|
// Rotazione Bussola Tattica e Colore Sincronizzato
|
|
203
242
|
if (twdObj && hObj) {
|
|
243
|
+
updateWindTrend();
|
|
204
244
|
curTwdRoseRot = getShortestRotation(curTwdRoseRot, twdObj.val);
|
|
205
245
|
ui.twdArrow.setAttribute('transform', `rotate(${curTwdRoseRot}, 20, 20)`);
|
|
206
246
|
ui.twdBoat.setAttribute('transform', `rotate(${hObj.val}, 20, 20)`);
|
|
@@ -333,7 +373,48 @@ function toggleFocusMode(type, element) {
|
|
|
333
373
|
el.addEventListener('pointerleave', () => clearTimeout(pressTimer));
|
|
334
374
|
});
|
|
335
375
|
|
|
336
|
-
|
|
376
|
+
// ==========================================================================
|
|
377
|
+
// GESTIONE HOTSPOT: Click (Fullscreen) e Long Press (Night Mode)
|
|
378
|
+
// ==========================================================================
|
|
379
|
+
if (ui.hotspot) {
|
|
380
|
+
let pressTimer;
|
|
381
|
+
const HOLD_DURATION = 1000; // 1 secondo di pressione per attivare Night Mode
|
|
382
|
+
|
|
383
|
+
ui.hotspot.addEventListener('pointerdown', (e) => {
|
|
384
|
+
// Avvia il timer al tocco
|
|
385
|
+
pressTimer = setTimeout(() => {
|
|
386
|
+
document.body.classList.toggle('night-mode');
|
|
387
|
+
// Feedback visivo immediato al cambio modalità
|
|
388
|
+
ui.hotspot.style.opacity = "0.5";
|
|
389
|
+
setTimeout(() => ui.hotspot.style.opacity = "1", 200);
|
|
390
|
+
pressTimer = null; // Reset per evitare che il click scatti dopo
|
|
391
|
+
}, HOLD_DURATION);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
ui.hotspot.addEventListener('pointerup', (e) => {
|
|
395
|
+
if (pressTimer) {
|
|
396
|
+
clearTimeout(pressTimer);
|
|
397
|
+
pressTimer = null;
|
|
398
|
+
// Se arriviamo qui, è stato un click rapido -> Fullscreen
|
|
399
|
+
const doc = document.documentElement;
|
|
400
|
+
const isF = document.fullscreenElement || document.webkitFullscreenElement;
|
|
401
|
+
if (!isF) {
|
|
402
|
+
if (doc.requestFullscreen) doc.requestFullscreen();
|
|
403
|
+
else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen();
|
|
404
|
+
} else {
|
|
405
|
+
if (document.exitFullscreen) document.exitFullscreen();
|
|
406
|
+
else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
ui.hotspot.addEventListener('pointerleave', () => {
|
|
412
|
+
if (pressTimer) {
|
|
413
|
+
clearTimeout(pressTimer);
|
|
414
|
+
pressTimer = null;
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
337
418
|
(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 ? "#fff" : "#666"); l.setAttribute("stroke-width", m ? "2" : "1"); l.setAttribute("transform", `rotate(${i}, 200, 200)`); c.appendChild(l); } } })();
|
|
338
419
|
|
|
339
420
|
async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
|
|
@@ -341,7 +422,88 @@ window.addEventListener('load', init);
|
|
|
341
422
|
function checkDepthAlarm(m) { ui.depth.classList.remove('alarm-warning', 'alarm-danger'); if (m < CONFIG.alarms.depthDanger) { ui.depth.classList.add('alarm-danger'); playBingBing(); } else if (m < CONFIG.alarms.depthWarning) ui.depth.classList.add('alarm-warning'); }
|
|
342
423
|
function playBingBing() { if (!audioCtx) return; const n = Date.now(); if (n - lastAlarmTime < 3000) return; lastAlarmTime = n; function b(f, s) { const o = audioCtx.createOscillator(); const g = audioCtx.createGain(); o.connect(g); g.connect(audioCtx.destination); o.frequency.value = f; g.gain.setValueAtTime(0.1, s); g.gain.exponentialRampToValueAtTime(0.01, s + 0.4); o.start(s); o.stop(s + 0.5); } b(880, audioCtx.currentTime); b(880, audioCtx.currentTime + 0.6); }
|
|
343
424
|
|
|
344
|
-
// Simulatore
|
|
425
|
+
// Simulatore su Depth (3 click rapidi)
|
|
345
426
|
ui.depth.closest('.data-box').addEventListener('click', (function() {
|
|
346
|
-
let dC = 0, lC = 0;
|
|
427
|
+
let dC = 0, lC = 0;
|
|
428
|
+
return function() {
|
|
429
|
+
const n = Date.now();
|
|
430
|
+
if (n - lC < 500) dC++; else dC = 1;
|
|
431
|
+
lC = n;
|
|
432
|
+
if (dC === 3) {
|
|
433
|
+
simulationMode = !simulationMode;
|
|
434
|
+
if (simulationMode) {
|
|
435
|
+
if (socket) socket.close();
|
|
436
|
+
startDynamicSimulation(); // Chiama la nuova funzione
|
|
437
|
+
} else {
|
|
438
|
+
location.reload();
|
|
439
|
+
}
|
|
440
|
+
dC = 0;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
347
443
|
})());
|
|
444
|
+
|
|
445
|
+
// ==========================================================================
|
|
446
|
+
// 9. MOTORE SIMULAZIONE DINAMICA (VERSIONE STOCASTICA)
|
|
447
|
+
// ==========================================================================
|
|
448
|
+
function startDynamicSimulation() {
|
|
449
|
+
ui.status.innerText = "SIM ATTIVO";
|
|
450
|
+
|
|
451
|
+
// 1. STATO INIZIALE (Aggiunto leeway a 0)
|
|
452
|
+
let sim = {
|
|
453
|
+
hdg: Math.random() * 360,
|
|
454
|
+
tws: 12,
|
|
455
|
+
twd: Math.random() * 360,
|
|
456
|
+
depth: 12,
|
|
457
|
+
stw: 5,
|
|
458
|
+
leeway: 0, // Inerzia per il Leeway
|
|
459
|
+
startTime: Date.now()
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
simInterval = setInterval(() => {
|
|
463
|
+
const elapsed = (Date.now() - sim.startTime) / 1000;
|
|
464
|
+
|
|
465
|
+
// 2. SALTO DI VENTO (dopo 120 secondi)
|
|
466
|
+
if (elapsed > 120 && elapsed < 121) {
|
|
467
|
+
sim.twd = Math.random() * 360;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// 3. RAFFICA (ogni 40s per 5s) con smoothing 0.05
|
|
471
|
+
const inGust = (elapsed % 40) < 5;
|
|
472
|
+
const targetTws = (inGust ? 18 : 10) + Math.sin(elapsed / 10) * 2;
|
|
473
|
+
sim.tws += (targetTws - sim.tws) * 0.05; // Smoothing lento
|
|
474
|
+
|
|
475
|
+
// 4. POLARE SEMPLIFICATA (STW in base al TWA)
|
|
476
|
+
let twaRel = (sim.twd - sim.hdg + 360) % 360;
|
|
477
|
+
if (twaRel > 180) twaRel -= 360;
|
|
478
|
+
|
|
479
|
+
let targetStw = 3 + (4 * Math.sin((Math.abs(twaRel) - 45) * Math.PI / 125));
|
|
480
|
+
sim.stw += (Math.max(3, Math.min(8, targetStw)) - sim.stw) * 0.05; // Smoothing STW
|
|
481
|
+
|
|
482
|
+
// 5. CALCOLO LEEWAY CON FILTRO (Inerzia)
|
|
483
|
+
const rawLeeway = Math.sin(degToRad(twaRel)) * 4;
|
|
484
|
+
sim.leeway += (rawLeeway - sim.leeway) * 0.05; // Smoothing 0.05 per evitare i balli di +/- 3.1
|
|
485
|
+
|
|
486
|
+
// 6. CALCOLO VETTORIALE COG
|
|
487
|
+
const cog = (sim.hdg - sim.leeway + 360) % 360;
|
|
488
|
+
|
|
489
|
+
// 7. CALCOLO VENTO APPARENTE (AWS/AWA)
|
|
490
|
+
const twaRad = degToRad(twaRel);
|
|
491
|
+
const aws = Math.sqrt(Math.pow(sim.stw, 2) + Math.pow(sim.tws, 2) + 2 * sim.stw * sim.tws * Math.cos(twaRad));
|
|
492
|
+
const awa = radToDeg(Math.atan2(sim.tws * Math.sin(twaRad), sim.stw + sim.tws * Math.cos(twaRad)));
|
|
493
|
+
|
|
494
|
+
// 8. INVIO DATI PULITI
|
|
495
|
+
processIncomingData("environment.wind.speedTrue", ktsToMs(sim.tws));
|
|
496
|
+
processIncomingData("environment.wind.directionTrue", degToRad(sim.twd));
|
|
497
|
+
processIncomingData("environment.wind.angleTrueWater", degToRad(twaRel));
|
|
498
|
+
processIncomingData("environment.wind.speedApparent", ktsToMs(aws));
|
|
499
|
+
processIncomingData("environment.wind.angleApparent", degToRad(awa));
|
|
500
|
+
processIncomingData("environment.depth.belowTransducer", sim.depth);
|
|
501
|
+
processIncomingData("navigation.headingTrue", degToRad(sim.hdg));
|
|
502
|
+
processIncomingData("navigation.speedThroughWater", ktsToMs(sim.stw));
|
|
503
|
+
processIncomingData("navigation.speedOverGround", ktsToMs(sim.stw));
|
|
504
|
+
processIncomingData("navigation.courseOverGroundTrue", degToRad(cog));
|
|
505
|
+
|
|
506
|
+
// Aggiorna display grafico
|
|
507
|
+
updateLeewayDisplay(sim.leeway);
|
|
508
|
+
}, 1000);
|
|
509
|
+
}
|
package/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<title>Sailing Dashboard Pro</title>
|
|
10
10
|
<link rel="stylesheet" href="style.css">
|
|
11
11
|
</head>
|
|
12
|
-
<body
|
|
12
|
+
<body>
|
|
13
13
|
|
|
14
14
|
<!-- Etichetta stato connessione SignalK -->
|
|
15
15
|
<div id="status" class="offline">OFFLINE</div>
|
|
@@ -89,6 +89,8 @@
|
|
|
89
89
|
<!-- ViewBox ottimizzato per ingrandire il diametro (Zoom in) -->
|
|
90
90
|
<svg id="wind-gauge" viewBox="35 38 330 395" preserveAspectRatio="xMidYMid meet">
|
|
91
91
|
<defs>
|
|
92
|
+
<!-- Maschera per tagliare la barca esattamente sul bordo del cerchio r=50 -->
|
|
93
|
+
<clipPath id="boat-clip"><circle cx="200" cy="200" r="50" /></clipPath>
|
|
92
94
|
<!-- Gradienti e Maschere per i settori del vento -->
|
|
93
95
|
<linearGradient id="axiom-grad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
94
96
|
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
|
@@ -101,6 +103,12 @@
|
|
|
101
103
|
<stop offset="75%" style="stop-color:#ff8800;stop-opacity:1" />
|
|
102
104
|
<stop offset="100%" style="stop-color:#ff0000;stop-opacity:1" />
|
|
103
105
|
</linearGradient>
|
|
106
|
+
<!-- Gradiente Leeway per la Night Mode (Rosso cupo al centro, Rosso vivo ai lati) -->
|
|
107
|
+
<linearGradient id="leeway-night-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
108
|
+
<stop offset="0%" style="stop-color:#ff0000;stop-opacity:1" /> <!-- Estremo SX: Rosso vivo -->
|
|
109
|
+
<stop offset="50%" style="stop-color:#330000;stop-opacity:1" /> <!-- Centro: Rosso cupo -->
|
|
110
|
+
<stop offset="100%" style="stop-color:#ff0000;stop-opacity:1" /> <!-- Estremo DX: Rosso vivo -->
|
|
111
|
+
</linearGradient>
|
|
104
112
|
<clipPath id="leeway-clip">
|
|
105
113
|
<rect id="leeway-mask-rect" x="125" y="0" width="0" height="12" rx="2" />
|
|
106
114
|
</clipPath>
|
|
@@ -150,7 +158,11 @@
|
|
|
150
158
|
<circle id="fullscreen-hotspot" cx="200" cy="200" r="55" fill="#181818" stroke="#333" stroke-width="1" filter="url(#center-glow)" cursor="pointer" />
|
|
151
159
|
|
|
152
160
|
<!-- Icona Barca Centrale (Spinta Y+5 per centratura visiva perfetta) -->
|
|
153
|
-
<path id="boat-icon" d="M200,150 Q165,185 170,250 Q165,190 200,173 Q235,190 230,250 Q235,185 200,150 Z"
|
|
161
|
+
<path id="boat-icon" d="M200,150 Q165,185 170,250 Q165,190 200,173 Q235,190 230,250 Q235,185 200,150 Z"
|
|
162
|
+
fill="url(#axiom-grad)"
|
|
163
|
+
transform="translate(0, 5)"
|
|
164
|
+
clip-path="url(#boat-clip)"
|
|
165
|
+
style="pointer-events: none;" />
|
|
154
166
|
|
|
155
167
|
<!-- Display Centrale Numerico: Vento Apparente -->
|
|
156
168
|
<g id="aws-display-group" transform="translate(200, 265)">
|
|
@@ -167,6 +179,8 @@
|
|
|
167
179
|
<g id="twa-pointer" transform="rotate(0, 200, 200)" opacity="0.85">
|
|
168
180
|
<path d="M200,90 L206,98 L200,125 L194,98 Z" fill="#ffff00" stroke="#000" stroke-width="0.8" />
|
|
169
181
|
<text x="200" y="104" fill="#000" font-size="8" font-weight="900" text-anchor="middle" font-family="Arial Black">T</text>
|
|
182
|
+
<circle id="trend-gauge-cw" cx="215" cy="110" r="4" fill="#ff0000" opacity="0" />
|
|
183
|
+
<circle id="trend-gauge-ccw" cx="185" cy="110" r="4" fill="#00ff00" opacity="0" />
|
|
170
184
|
</g>
|
|
171
185
|
|
|
172
186
|
<!-- Barra LEEWAY / SCARROCCIO Inferiore -->
|
|
@@ -270,7 +284,6 @@
|
|
|
270
284
|
<path d="M 20,17 L 17,26 L 20,24.5 L 23,26 Z" fill="white" opacity="0.2" />
|
|
271
285
|
</g>
|
|
272
286
|
|
|
273
|
-
<!-- 2. INDICATORE VENTO: Ruota con il TWD (id: twd-arrow) -->
|
|
274
287
|
<g id="twd-arrow" transform="rotate(0, 20, 20)">
|
|
275
288
|
<path id="twd-wind-chevron" d="M 17,5 L 20,7.5 L 23,5"
|
|
276
289
|
fill="none"
|
|
@@ -278,6 +291,8 @@
|
|
|
278
291
|
stroke-width="2.2"
|
|
279
292
|
stroke-linecap="round"
|
|
280
293
|
stroke-linejoin="round" />
|
|
294
|
+
<circle id="trend-dot-cw" cx="24" cy="7.5" r="1.5" fill="#ff0000" />
|
|
295
|
+
<circle id="trend-dot-ccw" cx="16" cy="7.5" r="1.5" fill="#00ff00" />
|
|
281
296
|
</g>
|
|
282
297
|
</svg>
|
|
283
298
|
<span class="value value-large" id="twd-avg">---°</span>
|
package/package.json
CHANGED
package/style.css
CHANGED
|
@@ -77,7 +77,7 @@ body {
|
|
|
77
77
|
.data-box {
|
|
78
78
|
position: relative;
|
|
79
79
|
border-bottom: 1px solid #222;
|
|
80
|
-
padding: 8px
|
|
80
|
+
padding: 4px 8px;
|
|
81
81
|
display: flex;
|
|
82
82
|
flex-direction: column;
|
|
83
83
|
width: 100%;
|
|
@@ -91,9 +91,18 @@ body {
|
|
|
91
91
|
.left-panel .data-box { align-items: flex-start; text-align: left; }
|
|
92
92
|
.right-panel .data-box { align-items: flex-end; text-align: right; }
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
.side-panel {
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
height: 100%;
|
|
98
|
+
gap: 2px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.data-box {
|
|
102
|
+
flex: 1 1 0px; /* Ogni box si divide lo spazio equamente, ma può ridursi se serve */
|
|
103
|
+
min-height: 0; /* Fondamentale per permettere al grafico di non eccedere */
|
|
104
|
+
padding: 2px 4px; /* Ancora più compatto */
|
|
105
|
+
}
|
|
97
106
|
|
|
98
107
|
/* ==========================================================================
|
|
99
108
|
3. TACTICAL FOCUS MODE (AUTO-EXPANDING SPLIT)
|
|
@@ -208,20 +217,20 @@ body {
|
|
|
208
217
|
.graph-wrapper {
|
|
209
218
|
position: relative; width: 100%; flex-grow: 1; min-height: 0; margin-top: 4px;
|
|
210
219
|
display: flex; align-items: stretch; background: rgba(255, 255, 255, 0.03);
|
|
211
|
-
border-radius: 4px; gap:
|
|
220
|
+
border-radius: 4px; gap: 0px !important;
|
|
212
221
|
}
|
|
213
222
|
|
|
214
223
|
/* Spostamento verso il centro per eliminare spazi neri morti */
|
|
215
224
|
.left-panel .graph-wrapper { margin-right: -6px; }
|
|
216
225
|
.right-panel .graph-wrapper { margin-left: -6px; }
|
|
217
226
|
|
|
218
|
-
.sparkline { flex-grow: 1; height: 100%; background: transparent !important; display: block; }
|
|
227
|
+
.sparkline { flex-grow: 1; height: 100%; width: 100% !important; background: transparent !important; display: block; }
|
|
219
228
|
.sparkline path, .sparkline line { stroke-width: 1px !important; transition: all 0.3s ease; }
|
|
220
229
|
|
|
221
230
|
.scale-labels {
|
|
222
231
|
display: flex; flex-direction: column; justify-content: space-between;
|
|
223
|
-
font-size: 10px; color: #777; font-weight: bold; min-width:
|
|
224
|
-
height: 100%; line-height: 1; padding:
|
|
232
|
+
font-size: 10px; color: #777; font-weight: bold; min-width: 12px;
|
|
233
|
+
height: 100%; line-height: 1; padding: 0 px 0; border: none !important;
|
|
225
234
|
}
|
|
226
235
|
|
|
227
236
|
/* Simmetria: le scale numeriche "guardano" sempre il quadrante centrale */
|
|
@@ -309,49 +318,163 @@ body {
|
|
|
309
318
|
}
|
|
310
319
|
|
|
311
320
|
/* ==========================================================================
|
|
312
|
-
10. NIGHT MODE (RED ON BLACK)
|
|
321
|
+
10. NIGHT MODE (RED ON BLACK - TACTICAL)
|
|
313
322
|
========================================================================== */
|
|
314
323
|
body.night-mode {
|
|
315
324
|
background-color: #000 !important;
|
|
316
|
-
color: #ff0000 !important;
|
|
325
|
+
color: #ff0000 !important;
|
|
317
326
|
}
|
|
318
327
|
|
|
319
|
-
/* Pannelli e Box */
|
|
320
|
-
.night-mode .side-panel {
|
|
321
|
-
|
|
328
|
+
/* --- Pannelli e Box --- */
|
|
329
|
+
.night-mode .side-panel {
|
|
330
|
+
background: rgba(20, 0, 0, 0.4);
|
|
331
|
+
border: 1px solid #330000;
|
|
332
|
+
}
|
|
333
|
+
.night-mode .data-box {
|
|
334
|
+
border-bottom-color: #260000;
|
|
335
|
+
}
|
|
322
336
|
|
|
323
|
-
/*
|
|
324
|
-
.night-mode .label,
|
|
325
|
-
|
|
337
|
+
/* --- Tipografia Secondaria (Titoli, Unità, TACK Labels) --- */
|
|
338
|
+
.night-mode .label,
|
|
339
|
+
.night-mode .unit,
|
|
340
|
+
.night-mode .dual-label {
|
|
341
|
+
color: #800000 !important; /* Rosso cupo per non affaticare la vista */
|
|
326
342
|
}
|
|
327
343
|
|
|
328
|
-
/* Valori Numerici */
|
|
329
|
-
.night-mode .value,
|
|
344
|
+
/* --- Valori Numerici --- */
|
|
345
|
+
.night-mode .value,
|
|
346
|
+
.night-mode .value-large {
|
|
330
347
|
color: #ff3333 !important;
|
|
331
|
-
text-shadow: 0 0
|
|
348
|
+
text-shadow: 0 0 8px rgba(255, 0, 0, 0.4); /* Bagliore per profondità */
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* --- Grafici Sparkline (Solo Linea, No Riempimento) --- */
|
|
352
|
+
.night-mode .graph-wrapper {
|
|
353
|
+
background: rgba(30, 0, 0, 0.3) !important;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Nasconde l'area di riempimento sotto la linea */
|
|
357
|
+
.night-mode .sparkline path:first-of-type {
|
|
358
|
+
display: none !important;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Rende la linea del tracciato molto più visibile */
|
|
362
|
+
.night-mode .sparkline path {
|
|
363
|
+
fill: none !important;
|
|
364
|
+
stroke: #ff3333 !important;
|
|
365
|
+
stroke-width: 1.8px !important;
|
|
366
|
+
opacity: 1 !important;
|
|
367
|
+
filter: drop-shadow(0 0 2px rgba(255, 0, 0, 0.4));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* Griglie interne (Orizzontali e Verticali) uniformi */
|
|
371
|
+
.night-mode .sparkline line {
|
|
372
|
+
stroke: #4d0000 !important;
|
|
373
|
+
stroke-width: 0.7px !important;
|
|
374
|
+
opacity: 1 !important;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* Forza i segmenti colorati del TWS alla stessa luminosità della linea */
|
|
378
|
+
.night-mode #tws-graph line:not([stroke*="rgba"]) {
|
|
379
|
+
stroke: #ff3333 !important;
|
|
380
|
+
stroke-width: 1.8px !important;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* Numeri delle scale a lato dei grafici */
|
|
384
|
+
.night-mode .scale-labels {
|
|
385
|
+
color: #660000 !important;
|
|
332
386
|
}
|
|
333
387
|
|
|
334
|
-
/*
|
|
335
|
-
.night-mode .
|
|
336
|
-
|
|
337
|
-
|
|
388
|
+
/* --- Hercules Mode in Night Mode --- */
|
|
389
|
+
.night-mode .box-hercules {
|
|
390
|
+
background: rgba(60, 0, 0, 0.2) !important;
|
|
391
|
+
}
|
|
392
|
+
.night-mode .line-hercules {
|
|
393
|
+
filter: drop-shadow(0 0 6px #ff0000) !important;
|
|
394
|
+
}
|
|
338
395
|
|
|
339
|
-
/*
|
|
340
|
-
.night-mode
|
|
396
|
+
/* Scritta Hercules in alto (Colore personalizzato #f60000) */
|
|
397
|
+
.night-mode .box-hercules .unit::before,
|
|
398
|
+
.night-mode .box-hercules .unit::after,
|
|
399
|
+
.night-mode .box-hercules .label::before,
|
|
400
|
+
.night-mode .box-hercules .label::after {
|
|
401
|
+
color: #f60000 !important;
|
|
402
|
+
font-weight: 700;
|
|
403
|
+
opacity: 0.9;
|
|
404
|
+
letter-spacing: 1px;
|
|
405
|
+
}
|
|
341
406
|
|
|
342
|
-
/* Strumento Centrale (
|
|
407
|
+
/* --- Strumento Centrale (Wind Gauge) --- */
|
|
343
408
|
.night-mode #wind-gauge circle { stroke: #330000; }
|
|
344
|
-
.night-mode #ticks line { stroke: #
|
|
345
|
-
.night-mode #tick-labels { fill: #
|
|
346
|
-
.night-mode #boat-icon { fill: #
|
|
409
|
+
.night-mode #ticks line { stroke: #4d0000 !important; }
|
|
410
|
+
.night-mode #tick-labels { fill: #800000 !important; }
|
|
411
|
+
.night-mode #boat-icon { fill: #330000 !important; opacity: 0.6; }
|
|
347
412
|
.night-mode #aws-val-svg { fill: #ff3333 !important; }
|
|
348
413
|
|
|
349
|
-
/*
|
|
414
|
+
/* Settori Vento (Distinguibili per stile, non per colore) */
|
|
415
|
+
.night-mode #wind-gauge path[stroke="#ff0000"] { stroke: #660000 !important; opacity: 0.8; } /* Sx Solid */
|
|
416
|
+
.night-mode #wind-gauge path[stroke="#00ff00"] {
|
|
417
|
+
stroke: #660000 !important;
|
|
418
|
+
stroke-dasharray: 4, 3; /* Dx Dashed */
|
|
419
|
+
opacity: 0.8;
|
|
420
|
+
}
|
|
421
|
+
.night-mode #wind-gauge path[stroke="#ff8800"] { stroke: #330000 !important; stroke-width: 8; } /* Poppa Dark */
|
|
422
|
+
|
|
423
|
+
/* Lancette Wind Gauge */
|
|
350
424
|
.night-mode #awa-pointer path { fill: #ff0000; stroke: #000; }
|
|
351
|
-
.night-mode #twa-pointer path { fill: #
|
|
425
|
+
.night-mode #twa-pointer path { fill: #800000; stroke: #000; }
|
|
352
426
|
.night-mode #track-pointer path { fill: #ff0000; stroke: #fff; stroke-width: 0.5; }
|
|
353
427
|
|
|
354
|
-
/*
|
|
355
|
-
.night-mode
|
|
356
|
-
.night-mode
|
|
428
|
+
/* --- Blocco Leeway (Scarroccio) --- */
|
|
429
|
+
.night-mode rect[fill="url(#leeway-grad)"] { fill: url(#leeway-night-grad) !important; }
|
|
430
|
+
.night-mode #leeway-val { fill: #ff3333 !important; }
|
|
431
|
+
.night-mode g[stroke="#555"] line { stroke: #4d0000 !important; }
|
|
432
|
+
.night-mode g[fill="#555"] text { fill: #660000 !important; }
|
|
433
|
+
.night-mode rect[fill="#222"] { fill: #0a0000 !important; stroke: #200000; }
|
|
434
|
+
|
|
435
|
+
/* --- Bussola TWD --- */
|
|
436
|
+
.night-mode .mini-compass { border-color: #330000; background: #000; }
|
|
437
|
+
.night-mode .mini-compass text { fill: #800000 !important; }
|
|
357
438
|
.night-mode #twd-arrow path { fill: #ff0000 !important; stroke: #000 !important; }
|
|
439
|
+
.night-mode #twd-boat-wrap path { fill: #ff0000 !important; opacity: 0.15; }
|
|
440
|
+
|
|
441
|
+
.night-mode #center-glow feDropShadow {
|
|
442
|
+
flood-color: #ff0000 !important;
|
|
443
|
+
flood-opacity: 0.6 !important;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.night-mode #aws-display-group text {
|
|
447
|
+
fill: #ff3333 !important;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* --- Modifica: Miglioramento visibilità TWD in Night Mode --- */
|
|
451
|
+
.night-mode #twd-arrow #twd-wind-chevron {
|
|
452
|
+
stroke: #ff3333 !important; /* Rosso più brillante */
|
|
453
|
+
stroke-width: 3px !important; /* Più spesso */
|
|
454
|
+
opacity: 1 !important; /* Piena opacità */
|
|
455
|
+
filter: drop-shadow(0 0 4px #ff0000); /* Effetto neon per staccarsi dallo sfondo */
|
|
456
|
+
transform-origin: center; /* Forza il centro corretto */
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/* Assicuriamoci che anche la punta della barca sia visibile */
|
|
460
|
+
.night-mode #twd-boat-wrap path {
|
|
461
|
+
fill: #ff3333 !important;
|
|
462
|
+
opacity: 0.4 !important;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/* Definiamo il lampeggio */
|
|
466
|
+
@keyframes blink-trend {
|
|
467
|
+
0%, 100% { opacity: 1; }
|
|
468
|
+
50% { opacity: 0; }
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/* Stile base dei pallini */
|
|
472
|
+
#trend-dot-cw, #trend-dot-ccw, #trend-gauge-cw, #trend-gauge-ccw {
|
|
473
|
+
opacity: 0.3;
|
|
474
|
+
transition: opacity 0.3s ease;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* Quando attivi la classe, forziamo l'animazione */
|
|
478
|
+
.is-trending {
|
|
479
|
+
animation: blink-trend 1s infinite !important;
|
|
480
|
+
}
|