@sailingrotevista/rotevista-dash 4.0.16 → 4.0.18

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.
Files changed (4) hide show
  1. package/app.js +99 -35
  2. package/index.js +27 -7
  3. package/package.json +1 -1
  4. package/style.css +90 -195
package/app.js CHANGED
@@ -12,21 +12,21 @@
12
12
  // 1. CONFIGURAZIONE E DEFAULT
13
13
  // ==========================================================================
14
14
  let CONFIG = {
15
- alarms: { depthDanger: 2.5, depthWarning: 5.0 },
15
+ alarms: { depthDanger: 2.5, depthWarning: 3.5 },
16
16
  averaging: {
17
17
  smoothWindow: 2000,
18
18
  longWindow: 30000,
19
19
  stabilityTolerance: 2000,
20
20
  stabilityThreshold: 0.85,
21
- minSpeed: 1,
21
+ minSpeed: 0,
22
22
  stabilityBreakout: 15
23
23
  },
24
- graphs: { reef1: 20.0, reef2: 25.0, historyMinutes: 10, samples: 60 },
24
+ graphs: { reef1: 8, reef2: 10.0, historyMinutes: 30, samples: 60 },
25
25
  scales: {
26
26
  stw: { stdMax: 8, hercSpan: 4, step: 2 },
27
27
  sog: { stdMax: 8, hercSpan: 4, step: 2 },
28
28
  tws: { stdMax: 15, hercSpan: 10, step: 5 },
29
- depth: { stdMax: 10, hercSpan: 10, step: 10 }
29
+ depth: { stdMax: 8, hercSpan: 5, step: 5 }
30
30
  },
31
31
  server: { fallbackIp: "192.168.111.240:3000" }
32
32
  };
@@ -437,19 +437,21 @@ function startDisplayLoop() {
437
437
  const labelEl = document.getElementById('sog-vmg-label');
438
438
  if (displayModeSog === 'VMG') {
439
439
  ui.sog.innerText = vmg.toFixed(1);
440
- ui.sog.style.setProperty('color', '#16a085', 'important'); // Verde Petrolio
440
+ // AGGIORNATO AL NUOVO COLORE CYAN VIBRANTE (#00b8d4)
441
+ ui.sog.style.setProperty('color', '#00b8d4', 'important');
441
442
  if (labelEl) labelEl.textContent = 'VMG';
442
443
  } else {
443
444
  ui.sog.innerText = sogKts.toFixed(1);
444
445
  if (labelEl) labelEl.textContent = 'SOG';
445
446
 
446
- // Colore Corrente: se neutro usiamo "", così il CSS Night Mode può agire
447
- if (sogKts - stwKts > 0.3) ui.sog.style.setProperty('color', '#27ae60', 'important');
448
- else if (sogKts - stwKts < -0.3) ui.sog.style.setProperty('color', '#c0392b', 'important');
449
- else ui.sog.style.color = "";
447
+ // Colore Corrente: Giallo Caldo per corrente a favore, Rosso per corrente contraria
448
+ // (Allineiamo il colore della corrente a favore con il #ffbb33 usato nei grafici)
449
+ if (sogKts - stwKts > 0.3) ui.sog.style.setProperty('color', '#ffbb33', 'important');
450
+ else if (sogKts - stwKts < -0.3) ui.sog.style.setProperty('color', '#ff3b30', 'important'); // Rosso vivido
451
+ else ui.sog.style.color = ""; // Torna normale
450
452
  }
451
453
  }
452
-
454
+
453
455
  if (store.raw["environment.depth.belowTransducer"] !== undefined) {
454
456
  ui.depth.innerText = store.raw["environment.depth.belowTransducer"].toFixed(1);
455
457
  checkDepthAlarm(store.raw["environment.depth.belowTransducer"]);
@@ -612,46 +614,108 @@ function updateScaleLabels(t, min, max) {
612
614
 
613
615
  /**
614
616
  * drawGraph: Disegna i grafici con griglia temporale intelligente
617
+ * Usa un Gradiente Lineare SVG dinamico per eliminare le giunzioni dei poligoni.
615
618
  */
616
619
  function drawGraph(d, id, min, max, isTws, isHercules) {
617
- const svg = document.getElementById(id); if (!svg || d.length < 2) return;
618
- const w = 200, h = 40, range = max - min || 1;
619
-
620
- // Griglia orizzontale
620
+ const svg = document.getElementById(id);
621
+ if (!svg || d.length < 2) return;
622
+
623
+ const w = 200, h = 40;
624
+ const range = max - min || 1;
625
+ const isDepth = (id === 'depth-graph');
626
+ const samples = CONFIG.graphs.samples;
627
+
628
+ // 1. Griglia
621
629
  let grids = "";
622
630
  [0.25, 0.5, 0.75].forEach(p => {
623
631
  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" />`;
624
632
  });
625
-
626
- /**
627
- * Griglia Temporale Intelligente:
628
- * - Storia <= 15 min: linee ogni 1 minuto.
629
- * - Storia > 15 min: linee ogni 5 minuti.
630
- */
631
633
  const gridInterval = (CONFIG.graphs.historyMinutes <= 15) ? 1 : 5;
632
-
633
634
  for (let m = gridInterval; m < CONFIG.graphs.historyMinutes; m += gridInterval) {
634
635
  const x = w - (m / CONFIG.graphs.historyMinutes) * w;
635
636
  grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(0,0,0,0.08)" stroke-width="0.5" />`;
636
637
  }
637
638
 
638
- let pD = ""; let cS = "";
639
- d.forEach((v, i) => {
640
- 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} `;
641
- if (isTws && i > 0) {
642
- const px = ((i-1)/(CONFIG.graphs.samples-1))*w, py = h-(Math.max(0,Math.min(1,(d[i-1]-min)/range))*h);
643
- let c = (v >= CONFIG.graphs.reef2) ? "#e74c3c" : (v >= CONFIG.graphs.reef1 ? "#e67e22" : "#000");
644
- // Aggiunta della classe 'tws-reef-line' per la gestione Night Mode
645
- cS += `<line x1="${px}" y1="${py}" x2="${x}" y2="${y}" stroke="${c}" class="tws-reef-line ${isHercules?'line-hercules':''}" />`;
639
+ // 2. Colori (Tavolozza Vivida)
640
+ const baseColorTws = "#2c3e50";
641
+ const baseColorDepth = "#0088cc";
642
+
643
+ const getColor = (val) => {
644
+ if (isTws) return (val >= CONFIG.graphs.reef2) ? "#ff3b30" : (val >= CONFIG.graphs.reef1 ? "#ff9800" : baseColorTws);
645
+ if (isDepth) return (val < CONFIG.alarms.depthDanger) ? "#ff3b30" : (val < CONFIG.alarms.depthWarning ? "#ff9800" : baseColorDepth);
646
+ switch(id) {
647
+ case 'stw-graph': return "#00C851";
648
+ case 'sog-graph': return (displayModeSog === 'VMG') ? "#00b8d4" : "#ffbb33";
649
+ default: return baseColorTws;
646
650
  }
647
- });
651
+ };
652
+
653
+ // 3. Creazione Gradiente e Linee
654
+ let gradientStops = "";
655
+ let lines = "";
656
+ let areaPath = `M 0 ${h} `;
657
+
658
+ for (let i = 1; i < d.length; i++) {
659
+ // Calcolo Percentuali per il gradiente
660
+ const percentPrev = ((i - 1) / (samples - 1)) * 100;
661
+ const percentCurr = (i / (samples - 1)) * 100;
662
+
663
+ // Coordinate geometriche
664
+ const x1 = ((i - 1) / (samples - 1)) * w;
665
+ const y1 = h - (Math.max(0, Math.min(1, (d[i - 1] - min) / range)) * h);
666
+ const x2 = (i / (samples - 1)) * w;
667
+ const y2 = h - (Math.max(0, Math.min(1, (d[i] - min) / range)) * h);
668
+
669
+ const color = getColor(d[i]);
670
+
671
+ // Assegnazione specifica di Opacità e Spessore Linea
672
+ let fillOpacity = "0.15"; // Default per valori base
673
+ let strokeWidth = "1";
674
+
675
+ if (color === "#ff3b30") {
676
+ // ROSSO (Danger / Reef 2): Molto solido
677
+ fillOpacity = "0.85";
678
+ strokeWidth = "1.5";
679
+ } else if (color === "#ff9800") {
680
+ // ARANCIONE (Warning / Reef 1): Più trasparente (Velo)
681
+ fillOpacity = "0.45";
682
+ strokeWidth = "1"; // Manteniamo la linea spessa per leggerla bene
683
+ }
648
684
 
649
- const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#000' };
650
- const colorKey = id === 'sog-graph' && displayModeSog === 'VMG' ? '#16a085' : clrs[id];
685
+ // GRADIENTE: Due stop alla stessa percentuale per creare uno stacco di colore netto (no sfumature)
686
+ gradientStops += `<stop offset="${percentPrev}%" stop-color="${color}" stop-opacity="${fillOpacity}" />`;
687
+ gradientStops += `<stop offset="${percentCurr}%" stop-color="${color}" stop-opacity="${fillOpacity}" />`;
688
+
689
+ // LINEE: Disegnate normalmente sopra l'area
690
+ lines += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}"
691
+ style="stroke: ${color}; stroke-width: ${strokeWidth}; stroke-linecap: round;" />`;
692
+
693
+ // AGGIORNAMENTO PATH UNICO
694
+ if (i === 1) areaPath += `L ${x1} ${y1} `;
695
+ areaPath += `L ${x2} ${y2} `;
696
+ }
651
697
 
652
- svg.innerHTML = isTws ?
653
- `${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}` :
654
- `${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}" />`;
698
+ // Chiusura del path dell'area
699
+ areaPath += `L ${w} ${h} Z`;
700
+
701
+ // 4. Iniezione del <defs> (Gradiente) nell'SVG
702
+ const gradId = `grad-${id}`;
703
+ const defs = `
704
+ <defs>
705
+ <linearGradient id="${gradId}" x1="0%" y1="0%" x2="100%" y2="0%">
706
+ ${gradientStops}
707
+ </linearGradient>
708
+ </defs>
709
+ `;
710
+
711
+ // 5. Render Finale
712
+ // Se è Depth o Tws applichiamo il gradiente, altrimenti colore standard fisso (per SOG/STW)
713
+ if (isTws || isDepth) {
714
+ svg.innerHTML = `${defs}${grids}<path d="${areaPath}" fill="url(#${gradId})" stroke="none" />${lines}`;
715
+ } else {
716
+ const standardColor = getColor(d[d.length - 1]);
717
+ svg.innerHTML = `${grids}<path d="${areaPath}" fill="${standardColor}" fill-opacity="0.15" stroke="none" />${lines}`;
718
+ }
655
719
  }
656
720
 
657
721
  // ==========================================================================
package/index.js CHANGED
@@ -12,20 +12,40 @@ module.exports = function (app) {
12
12
  plugin.name = 'Rotevista Dash Configuration';
13
13
  plugin.description = 'Configure boat-specific tactical and safety parameters for the Dashboard';
14
14
 
15
+ let currentConfig = {};
16
+ let routeRegistered = false;
17
+
15
18
  /**
16
- * plugin.start: Inizializza il plugin e crea la rotta API per la Dashboard.
19
+ * plugin.start: Inizializza il plugin.
20
+ * Viene chiamato all'avvio e OGNI VOLTA che clicchi "Save" nelle impostazioni.
17
21
  */
18
22
  plugin.start = function (options) {
19
- // Esponiamo i settings su un endpoint dedicato per bypassare i blocchi 401.
20
- app.get('/rotevista-config', (req, res) => {
21
- res.json(options);
22
- });
23
+ // 1. Aggiorna la configurazione in memoria (per l'endpoint pubblico)
24
+ currentConfig = options;
25
+
26
+ // 2. Log di debug nel server Signal K
27
+ app.debug(`${plugin.name} started/updated with new options`);
23
28
 
24
- app.debug('Rotevista Dashboard Config Endpoint active at /rotevista-config');
29
+ // 3. Registra la rotta API solo la prima volta
30
+ if (!routeRegistered) {
31
+ app.get('/rotevista-config', (req, res) => {
32
+ res.json(currentConfig);
33
+ });
34
+ routeRegistered = true;
35
+ app.debug('Public API endpoint registered at /rotevista-config');
36
+ }
25
37
  };
26
38
 
39
+ /**
40
+ * plugin.stop: Chiamato quando il plugin viene disattivato o prima di un aggiornamento.
41
+ */
27
42
  plugin.stop = function () {
28
- // Pulizia risorse allo spegnimento del plugin.
43
+ app.debug(`${plugin.name} stopped`);
44
+ };
45
+
46
+ // Se desideri avere una funzione plugin.debug personalizzata (opzionale)
47
+ plugin.debug = function(msg) {
48
+ app.debug(msg);
29
49
  };
30
50
 
31
51
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "4.0.16",
3
+ "version": "4.0.18",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/style.css CHANGED
@@ -112,7 +112,7 @@ body {
112
112
  /* --- BOX TWD (Bussola Meteo) --- */
113
113
  .value-with-compass {
114
114
  display: flex; flex-direction: row; justify-content: space-between;
115
- align-items: center; width: 100%; height: 85%;
115
+ align-items: center; width: 100%; height: 85%;
116
116
  }
117
117
  .box-twd .mini-compass {
118
118
  height: 85cqh !important; width: auto !important; aspect-ratio: 1/1; flex-shrink: 0;
@@ -137,7 +137,6 @@ body {
137
137
  /* ==========================================================================
138
138
  AGGIUNTA: Gestione Allarmi Profondità
139
139
  ========================================================================== */
140
-
141
140
  .alarm-warning {
142
141
  color: #e67e22 !important; /* Arancione per Warning */
143
142
  }
@@ -170,7 +169,7 @@ body.night-mode .alarm-danger {
170
169
 
171
170
  .focus-active .is-focused .sparkline path,
172
171
  .focus-active .is-focused .sparkline line {
173
- stroke-width: 1.5px !important; /* Un briciolo più spessa per la stabilità visiva su schermi grandi */
172
+ stroke-width: 1.5px !important;
174
173
  }
175
174
 
176
175
  .focus-active.focus-side-left {
@@ -236,42 +235,38 @@ body.night-mode .alarm-danger {
236
235
  }
237
236
 
238
237
  /* --- TACK: Impilamento Verticale Rigido in U-Shape --- */
239
- .box-tack .dual-value-container {
240
- flex-direction: column !important; /* Forza HDG sopra COG */
241
- justify-content: center !important;
242
- align-items: flex-start !important; /* Tutto a sinistra */
243
- gap: 8px !important; /* Spazio tra i due blocchi */
244
- }
245
-
246
- .box-tack .dual-value-col {
247
- display: flex !important;
248
- flex-direction: column !important; /* Forza Titolo sopra Numero */
249
- align-items: flex-start !important;
250
- text-align: left !important;
251
- width: 100% !important;
252
- }
253
-
254
- /* Font calibrato per non uscire dal box stretto */
255
- .box-tack .value.dual-val {
256
- font-size: clamp(1rem, 30cqh, 15cqw) !important;
257
- line-height: 1 !important;
258
- }
259
-
260
- .box-tack .dual-label {
261
- margin-bottom: 0px !important;
262
- }
238
+ .box-tack .dual-value-container {
239
+ flex-direction: column !important;
240
+ justify-content: center !important;
241
+ align-items: flex-start !important;
242
+ gap: 8px !important;
243
+ }
244
+
245
+ .box-tack .dual-value-col {
246
+ display: flex !important;
247
+ flex-direction: column !important;
248
+ align-items: flex-start !important;
249
+ text-align: left !important;
250
+ width: 100% !important;
251
+ }
252
+
253
+ .box-tack .value.dual-val {
254
+ font-size: clamp(1rem, 30cqh, 15cqw) !important;
255
+ line-height: 1 !important;
256
+ }
257
+
258
+ .box-tack .dual-label {
259
+ margin-bottom: 0px !important;
260
+ }
263
261
 
264
262
  /* --- TWD: Calibrazione per box stretto --- */
265
263
  .box-twd .value-with-compass { justify-content: center; gap: 5px; }
266
264
  .box-twd .mini-compass { height: 60cqh !important; }
267
265
  .box-twd #twd-avg { font-size: clamp(1.2rem, 50cqh, 20cqw) !important; }
268
-
269
- /* NOTA: COG e AWA non vengono toccati qui, così restano massivi
270
- ereditando la regola generale della Sezione 4 */
271
266
  }
272
267
 
273
268
  /* ==========================================================================
274
- 7. GRAFICI, SCALE E TAG HERCULES
269
+ 7. GRAFICI, SCALE E STILE DINAMICO
275
270
  ========================================================================== */
276
271
  .graph-wrapper {
277
272
  position: relative;
@@ -280,7 +275,7 @@ body.night-mode .alarm-danger {
280
275
  min-height: 0;
281
276
  margin-top: 5px;
282
277
  display: flex;
283
- flex-direction: row; /* Forza l'allineamento orizzontale dei componenti */
278
+ flex-direction: row;
284
279
  align-items: stretch;
285
280
  background: rgba(0, 0, 0, 0.04);
286
281
  border-radius: 4px;
@@ -293,15 +288,15 @@ body.night-mode .alarm-danger {
293
288
  background: transparent !important;
294
289
  }
295
290
 
296
- /* Forza tutte le linee dei grafici (linee dati e griglie) a 1px per un look chirurgico */
297
- .sparkline path,
298
- .sparkline line {
299
- stroke-width: 1px !important;
291
+ /* I componenti interni dell'SVG gestiscono autonomamente spessori e colori via JS */
292
+ .graph-wrapper polygon,
293
+ .graph-wrapper line {
300
294
  transition: all 0.3s ease;
301
- /* --- FIX LINEE SPEZZATE --- */
302
- stroke-linecap: round; /* Arrotonda le estremità dei segmenti */
303
- stroke-linejoin: round; /* Arrotonda le giunture tra i segmenti */
304
- shape-rendering: geometricPrecision; /* Forza il browser a calcolare la linea con precisione sub-pixel */
295
+ shape-rendering: geometricPrecision;
296
+ }
297
+
298
+ .graph-wrapper polygon {
299
+ filter: drop-shadow(0 0 2px rgba(0,0,0,0.05));
305
300
  }
306
301
 
307
302
  .scale-labels {
@@ -318,26 +313,12 @@ body.night-mode .alarm-danger {
318
313
  }
319
314
 
320
315
  /* --- COLONNA SINISTRA (STW, SOG) --- */
321
- /* Ordine desiderato: [Grafico] [Scala] -> Scala verso il centro schermo */
322
- .box-stw .sparkline, .box-sog .sparkline {
323
- order: 1;
324
- }
325
- .box-stw .scale-labels, .box-sog .scale-labels {
326
- order: 2;
327
- text-align: left;
328
- padding-left: 4px;
329
- }
316
+ .box-stw .sparkline, .box-sog .sparkline { order: 1; }
317
+ .box-stw .scale-labels, .box-sog .scale-labels { order: 2; text-align: left; padding-left: 4px; }
330
318
 
331
319
  /* --- COLONNA DESTRA (DEPTH, TWS) --- */
332
- /* Ordine desiderato: [Scala] [Grafico] -> Scala verso il centro schermo */
333
- .box-depth .scale-labels, .box-tws .scale-labels {
334
- order: 1;
335
- text-align: right;
336
- padding-right: 4px;
337
- }
338
- .box-depth .sparkline, .box-tws .sparkline {
339
- order: 2;
340
- }
320
+ .box-depth .scale-labels, .box-tws .scale-labels { order: 1; text-align: right; padding-right: 4px; }
321
+ .box-depth .sparkline, .box-tws .sparkline { order: 2; }
341
322
 
342
323
  /* --- REGOLE HERCULES (Zoom) --- */
343
324
  .box-hercules { background: rgba(255, 0, 0, 0.05) !important; }
@@ -347,10 +328,8 @@ body.night-mode .alarm-danger {
347
328
  letter-spacing: 1px; text-transform: uppercase; content: "HERCULES ";
348
329
  }
349
330
 
350
- /* Anche in modalità Hercules manteniamo 1px per coerenza, affidandoci al filtro per lo stacco visivo */
351
331
  .line-hercules {
352
332
  filter: drop-shadow(0 0 5px #ff0000);
353
- stroke-width: 1px !important;
354
333
  }
355
334
 
356
335
  /* ==========================================================================
@@ -365,44 +344,24 @@ body.night-mode .alarm-danger {
365
344
 
366
345
  .data-box { height: auto !important; }
367
346
 
368
- /* --- OTTIMIZZAZIONE COMPATTA PER I BOX CON GRAFICO --- */
369
- .box-stw, .box-sog, .box-depth, .box-tws {
370
- justify-content: flex-start !important;
371
- /* Ridotto lo spazio superiore per avvicinare il valore al titolo */
372
- padding-top: 1.2rem !important;
373
- }
347
+ .box-stw, .box-sog, .box-depth, .box-tws { justify-content: flex-start !important; padding-top: 1.2rem !important; }
374
348
 
375
- /* --- OTTIMIZZAZIONE FONT VALORI CON GRAFICO IN VERTICALE --- */
376
- .box-stw .value,
377
- .box-sog .value,
378
- .box-depth .value,
379
- .box-tws .value {
380
- /* Aumentato a 30cqh per pareggiare il peso visivo di HEADING */
381
- /* Alzato anche il limite cqw a 35 per permettere al numero di allargarsi */
349
+ .box-stw .value, .box-sog .value, .box-depth .value, .box-tws .value {
382
350
  font-size: clamp(1.2rem, 30cqh, 35cqw) !important;
383
351
  line-height: 0.8 !important;
384
- margin-top: -4px !important; /* Saliamo ancora un briciolo verso il titolo */
352
+ margin-top: -4px !important;
385
353
  margin-bottom: 4px !important;
386
354
  padding-bottom: 0 !important;
387
- font-weight: 700; /* Leggermente più marcato */
355
+ font-weight: 700;
388
356
  }
389
357
 
390
- /* Espansione del grafico per occupare tutto il resto del box */
391
- .box-stw .graph-wrapper,
392
- .box-sog .graph-wrapper,
393
- .box-depth .graph-wrapper,
394
- .box-tws .graph-wrapper {
358
+ .box-stw .graph-wrapper, .box-sog .graph-wrapper, .box-depth .graph-wrapper, .box-tws .graph-wrapper {
395
359
  margin-top: 0 !important;
396
360
  flex-grow: 1 !important;
397
361
  }
398
362
 
399
- /* --- ALTRI BOX (HEADING, COG, ecc.) RESTANO CENTRATI --- */
400
- .box-hdg, .box-cog, .box-twa, .box-awa, .box-twd, .box-tack {
401
- justify-content: center !important;
402
- padding-top: 1.2rem !important;
403
- }
363
+ .box-hdg, .box-cog, .box-twa, .box-awa, .box-twd, .box-tack { justify-content: center !important; padding-top: 1.2rem !important; }
404
364
 
405
- /* --- FOCUS MODE IN VERTICALE --- */
406
365
  .main-container.focus-active {
407
366
  grid-template-columns: 1fr !important;
408
367
  grid-template-rows: 65vh 35vh !important;
@@ -412,7 +371,6 @@ body.night-mode .alarm-danger {
412
371
  .focus-active .is-focused .value { font-size: clamp(4rem, 20cqh, 15rem) !important; margin-top: 20px !important; }
413
372
  .focus-active .box-gauge { height: 35vh !important; width: 100vw !important; display: flex !important; align-items: center; justify-content: center; }
414
373
 
415
- /* --- TACK IN VERTICALE --- */
416
374
  .box-tack .dual-value-container { flex-direction: row !important; justify-content: space-between !important; align-items: center !important; }
417
375
  .box-tack .dual-value-col { display: flex !important; flex-direction: column !important; justify-content: center; }
418
376
  .box-tack .dual-value-col:first-child { align-items: flex-start !important; text-align: left !important; }
@@ -425,18 +383,11 @@ body.night-mode .alarm-danger {
425
383
  ========================================================================== */
426
384
  #status {
427
385
  position: absolute;
428
- /* Posizionato con un po' di margine dal bordo del riquadro bussola */
429
- top: 15px;
430
- right: 15px;
431
- font-size: 0.6rem;
432
- font-weight: 900;
433
- text-transform: uppercase;
434
- z-index: 1000;
435
- /* Sfondo leggermente più marcato per leggerlo sopra il quadrante */
386
+ top: 15px; right: 15px;
387
+ font-size: 0.6rem; font-weight: 900;
388
+ text-transform: uppercase; z-index: 1000;
436
389
  background: rgba(0,0,0,0.08);
437
- padding: 3px 8px;
438
- border-radius: 4px;
439
- letter-spacing: 1px;
390
+ padding: 3px 8px; border-radius: 4px; letter-spacing: 1px;
440
391
  }
441
392
  .online { color: #27ae60; opacity: 0.8; }
442
393
  .offline { color: #c0392b; font-weight: bold; }
@@ -463,11 +414,7 @@ rect[fill="#222"] { fill: #eee !important; }
463
414
 
464
415
  /* ==========================================================================
465
416
  10. NIGHT MODE (TACTICAL RED - ARCHITETTURA INTEGRALE)
466
- ==========================================================================
467
- Questa sezione sovrascrive interamente lo schema Day Mode.
468
- Obiettivo: Massima leggibilità, zero abbagliamento, preservazione visione notturna.
469
417
  ========================================================================== */
470
-
471
418
  body.night-mode {
472
419
  background-color: #000 !important;
473
420
  color: #ff3333 !important;
@@ -476,130 +423,62 @@ body.night-mode {
476
423
  /* --- 10.1 STRUTTURA DEI CONTENITORI --- */
477
424
  .night-mode .data-box {
478
425
  background: rgba(20, 0, 0, 0.4) !important;
479
- border-color: #440000 !important; /* Confini rossi cupi per orientamento spaziale */
426
+ border-color: #440000 !important;
480
427
  box-shadow: inset 0 0 15px rgba(255, 0, 0, 0.05) !important;
481
428
  }
482
429
 
483
430
  /* --- 10.2 TIPOGRAFIA E TESTI --- */
484
- /* Etichette primarie (Titoli dei box) */
485
- .night-mode .label,
486
- .night-mode .unit,
487
- .night-mode .dual-label {
488
- color: #800000 !important; /* Rosso sangue scuro per non distrarre */
489
- }
431
+ .night-mode .label, .night-mode .unit, .night-mode .dual-label { color: #800000 !important; }
432
+ .night-mode .value-large { color: #ff3333 !important; text-shadow: 0 0 8px rgba(255, 0, 0, 0.4) !important; }
433
+ .night-mode .value { color: #ff3333; text-shadow: 0 0 8px rgba(255, 0, 0, 0.3); }
434
+ .night-mode #status { background: rgba(255, 0, 0, 0.1) !important; color: #ff3333 !important; }
490
435
 
491
- /* Valori digitali primari (Heading, Cog, Awa, Twa) */
492
- .night-mode .value-large {
493
- color: #ff3333 !important;
494
- text-shadow: 0 0 8px rgba(255, 0, 0, 0.4) !important;
495
- }
496
-
497
- /* Valori dinamici (SOG, TWS, STW):
498
- Non usiamo !important per permettere al JavaScript di iniettare i colori dei Reef/Corrente */
499
- .night-mode .value {
500
- color: #ff3333;
501
- text-shadow: 0 0 8px rgba(255, 0, 0, 0.3);
502
- }
503
-
504
- /* Widget di Stato */
505
- .night-mode #status {
506
- background: rgba(255, 0, 0, 0.1) !important;
507
- color: #ff3333 !important;
508
- }
509
-
510
- /* --- 10.3 GRAFICI (STILE CHIRURGICO 1PX) --- */
436
+ /* --- 10.3 GRAFICI (STILE DINAMICO NIGHT MODE) --- */
511
437
  .night-mode .graph-wrapper {
512
- background: rgba(0, 0, 0, 0.6) !important;
438
+ background: rgba(10, 0, 0, 0.8) !important;
513
439
  border: 1px solid #330000;
514
440
  }
515
441
 
516
- /* Rimuove l'area di riempimento per evitare luce diffusa sul display */
517
- .night-mode .sparkline path:first-of-type {
518
- display: none !important;
442
+ .night-mode .sparkline line[stroke="rgba(0,0,0,0.12)"],
443
+ .night-mode .sparkline line[stroke="rgba(0,0,0,0.08)"] {
444
+ stroke: rgba(255, 0, 0, 0.1) !important; /* Griglia temporale rossa soffusa */
519
445
  }
520
446
 
521
- /* Linea dati: rossa, sottile e nitida */
522
- .night-mode .sparkline path {
523
- fill: none !important;
524
- stroke: #ff3333 !important;
525
- stroke-width: 1px !important;
526
- filter: drop-shadow(0 0 2px rgba(255, 0, 0, 0.4));
447
+ /* Regolazione della luminosità notturna per preservare i gradienti e i colori del JS */
448
+ .night-mode .graph-wrapper svg {
449
+ filter: brightness(0.6) contrast(1.2);
527
450
  }
528
451
 
529
- /* Griglia temporale (minuti e livelli): quasi impercettibile */
530
- .night-mode .sparkline line {
531
- stroke: rgba(150, 0, 0, 0.15) !important;
452
+ .night-mode .graph-wrapper line[style] {
453
+ stroke-width: 1px !important;
454
+ filter: drop-shadow(0 0 2px rgba(255, 0, 0, 0.3));
532
455
  }
533
456
 
534
- /* LOGICA REEF TWS: Mantiene i colori degli allarmi sul grafico */
535
- .night-mode .tws-reef-line { stroke-width: 1px !important; opacity: 0.9 !important; }
536
- .night-mode .tws-reef-line[stroke="#000000"],
537
- .night-mode .tws-reef-line[stroke="#000"] { stroke: #440000 !important; } /* Vento basso */
538
- .night-mode .tws-reef-line[stroke="#e67e22"] { stroke: #b35500 !important; } /* 1° Reef */
539
- .night-mode .tws-reef-line[stroke="#e74c3c"] {
540
- stroke: #ff0000 !important;
541
- filter: drop-shadow(0 0 2px #ff0000);
542
- } /* 2° Reef */
543
-
544
- /* Scale graduate dei grafici */
545
457
  .night-mode .scale-labels { color: #800000 !important; }
546
458
  .night-mode .is-focused .scale-labels { color: #ff3333 !important; }
547
459
 
548
460
  /* --- 10.4 WIND GAUGE (STRUMENTO CENTRALE) --- */
549
- /* Oscuramento cerchi concentrici per eliminare bagliori bianchi */
550
- .night-mode #wind-gauge circle {
551
- fill: #000 !important;
552
- stroke: #220000 !important;
553
- }
554
-
555
- /* Cerchio interno e hotspot centrale (nero profondo) */
556
- .night-mode #wind-gauge circle:nth-of-type(2),
557
- .night-mode #fullscreen-hotspot {
558
- fill: #050000 !important;
559
- stroke: #330000 !important;
560
- }
561
-
562
- /* Effetto Glow e Logo Barca */
461
+ .night-mode #wind-gauge circle { fill: #000 !important; stroke: #220000 !important; }
462
+ .night-mode #wind-gauge circle:nth-of-type(2), .night-mode #fullscreen-hotspot { fill: #050000 !important; stroke: #330000 !important; }
563
463
  .night-mode #center-glow feDropShadow { flood-color: #ff0000 !important; flood-opacity: 0.6 !important; }
564
- /* ICONA BARCA NIGHT: Definizione migliorata con bordo rosso */
565
- .night-mode #boat-icon {
566
- fill: #220000 !important; /* Rosso scurissimo per il corpo della barca */
567
- stroke: #ff0000 !important; /* Bordo rosso vivo per definire la sagoma */
568
- stroke-width: 0.8px !important; /* Spessore sottile ma netto */
569
- opacity: 1 !important; /* Piena opacità per far risaltare il bordo */
570
- }
571
- /* Valori interni (AWS) */
464
+ .night-mode #boat-icon { fill: #220000 !important; stroke: #ff0000 !important; stroke-width: 0.8px !important; opacity: 1 !important; }
572
465
  .night-mode #aws-display-group text { fill: #800000 !important; }
573
466
  .night-mode #aws-val-svg { fill: #ff3333 !important; }
574
-
575
- /* Tacche e Numeri Gradi della Bussola */
576
467
  .night-mode #ticks line { stroke: #550000 !important; }
577
468
  .night-mode #tick-labels { fill: #800000 !important; }
578
-
579
- /* SETTORI VENTO: Conversione sicura per la notte */
580
469
  .night-mode #wind-gauge path[stroke="#ff0000"] { stroke: #800000 !important; opacity: 0.8; }
581
- .night-mode #wind-gauge path[stroke="#00ff00"] {
582
- stroke: #440000 !important;
583
- stroke-dasharray: 5, 4 !important;
584
- opacity: 0.7;
585
- } /* Verde Starboard -> Rosso tratteggiato */
470
+ .night-mode #wind-gauge path[stroke="#00ff00"] { stroke: #440000 !important; stroke-dasharray: 5, 4 !important; opacity: 0.7; }
586
471
  .night-mode #wind-gauge path[stroke="#ff8800"] { stroke: #330000 !important; opacity: 0.6; }
587
-
588
- /* Lancette AWA/TWA e COG */
589
472
  .night-mode #awa-pointer path { fill: #ff0000 !important; stroke: #000 !important; }
590
473
  .night-mode #twa-pointer path { fill: #800000 !important; stroke: #000 !important; }
591
474
  .night-mode #track-pointer path { fill: #ff3333 !important; stroke: none !important; }
592
475
 
593
476
  /* --- 10.5 WIDGETS ACCESSORI --- */
594
- /* Barra Leeway */
595
477
  .night-mode #leeway-val { fill: #ff3333 !important; color: #ff3333 !important; }
596
478
  .night-mode rect[fill="url(#leeway-grad)"] { fill: url(#leeway-night-grad) !important; }
597
- .night-mode rect[fill="#eee"],
598
- .night-mode rect[fill="#222"] { fill: #0a0000 !important; stroke: #330000 !important; }
479
+ .night-mode rect[fill="#eee"], .night-mode rect[fill="#222"] { fill: #0a0000 !important; stroke: #330000 !important; }
599
480
  .night-mode g[stroke="#555"] line { stroke: #440000 !important; }
600
481
  .night-mode g[fill="#555"] text { fill: #800000 !important; }
601
-
602
- /* Mini Bussola TWD */
603
482
  .night-mode .mini-compass { border-color: #330000 !important; background: #000 !important; }
604
483
  .night-mode .mini-compass text { fill: #800000 !important; }
605
484
  .night-mode .mini-compass text:last-of-type { fill: #800000 !important; opacity: 1; }
@@ -607,10 +486,9 @@ body.night-mode {
607
486
  .night-mode #twd-arrow #twd-wind-chevron { stroke: #ff3333 !important; }
608
487
 
609
488
  /* --- 10.6 LOGICA RED-SHIFT TREND --- */
610
- /* Converte i colori generati dal JS per i pallini di trend */
611
- .night-mode circle[fill="#bbb"], .night-mode circle[fill="#bbbbbb"] { fill: #440000 !important; } /* Neutro */
612
- .night-mode circle[fill="#27ae60"] { fill: #ff0000 !important; } /* Lift */
613
- .night-mode circle[fill="#c0392b"] { fill: #800000 !important; } /* Header */
489
+ .night-mode circle[fill="#bbb"], .night-mode circle[fill="#bbbbbb"] { fill: #440000 !important; }
490
+ .night-mode circle[fill="#27ae60"] { fill: #ff0000 !important; }
491
+ .night-mode circle[fill="#c0392b"] { fill: #800000 !important; }
614
492
 
615
493
  /* ==========================================================================
616
494
  11. TREND E ANIMAZIONI
@@ -620,3 +498,20 @@ body.night-mode {
620
498
  .is-trending { opacity: 1 !important; animation: blink-trend 1s infinite !important; }
621
499
  .is-gybing { opacity: 1 !important; animation: none !important; filter: drop-shadow(0 0 8px #ff0000) !important; }
622
500
  .unstable-data { animation: blink-trend 1.5s infinite ease-in-out; color: #e67e22 !important; }
501
+
502
+ /* --- CORREZIONE VISIBILITÀ COLORI BASE SCURI IN NIGHT MODE --- */
503
+ /* Il Navy Slate (#2c3e50) diventa nero con il filtro notturno. Lo schiariamo a un Grigio-Azzurro. */
504
+ .night-mode .graph-wrapper line[style*="#2c3e50"] {
505
+ stroke: #6c8ea0 !important;
506
+ }
507
+ .night-mode .graph-wrapper polygon[style*="#2c3e50"] {
508
+ fill: #6c8ea0 !important;
509
+ }
510
+
511
+ /* Opzionale: Diamo una piccola "spinta" anche all'azzurro della Depth per bilanciarlo */
512
+ .night-mode .graph-wrapper line[style*="#0088cc"] {
513
+ stroke: #33aadd !important;
514
+ }
515
+ .night-mode .graph-wrapper polygon[style*="#0088cc"] {
516
+ fill: #33aadd !important;
517
+ }