@sailingrotevista/rotevista-dash 4.0.15 → 4.0.17

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 (3) hide show
  1. package/app.js +124 -69
  2. package/package.json +1 -1
  3. package/style.css +117 -194
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: 0.5,
21
+ minSpeed: 0,
22
22
  stabilityBreakout: 15
23
23
  },
24
- graphs: { reef1: 15.0, reef2: 20.0, historyMinutes: 5, samples: 60 },
24
+ graphs: { reef1: 8, reef2: 10.0, historyMinutes: 30, samples: 60 },
25
25
  scales: {
26
- stw: { stdMax: 12, hercSpan: 4, step: 2 },
27
- sog: { stdMax: 12, hercSpan: 4, step: 2 },
28
- tws: { stdMax: 25, hercSpan: 10, step: 5 },
29
- depth: { stdMax: 20, hercSpan: 10, step: 10 }
26
+ stw: { stdMax: 8, hercSpan: 4, step: 2 },
27
+ sog: { stdMax: 8, hercSpan: 4, step: 2 },
28
+ tws: { stdMax: 15, hercSpan: 10, step: 5 },
29
+ depth: { stdMax: 8, hercSpan: 5, step: 5 }
30
30
  },
31
31
  server: { fallbackIp: "192.168.111.240:3000" }
32
32
  };
@@ -241,9 +241,16 @@ function playGybeAlarm() {
241
241
  }
242
242
 
243
243
  function checkDepthAlarm(m) {
244
- ui.depth.classList.remove('alarm-warning', 'alarm-danger');
245
- if (m < CONFIG.alarms.depthDanger) { ui.depth.classList.add('alarm-danger'); playBingBing(); }
246
- else if (m < CONFIG.alarms.depthWarning) ui.depth.classList.add('alarm-warning');
244
+ // Rimuoviamo sempre le classi prima di riapplicarle
245
+ ui.depth.classList.remove('alarm-warning', 'alarm-danger', 'blink-alarm');
246
+
247
+ // Logica di confronto dinamica
248
+ if (m < CONFIG.alarms.depthDanger) {
249
+ ui.depth.classList.add('alarm-danger', 'blink-alarm');
250
+ playBingBing(); // Il tuo suono di allarme
251
+ } else if (m < CONFIG.alarms.depthWarning) {
252
+ ui.depth.classList.add('alarm-warning');
253
+ }
247
254
  }
248
255
 
249
256
  function updateLeewayDisplay(deg) {
@@ -430,19 +437,21 @@ function startDisplayLoop() {
430
437
  const labelEl = document.getElementById('sog-vmg-label');
431
438
  if (displayModeSog === 'VMG') {
432
439
  ui.sog.innerText = vmg.toFixed(1);
433
- 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');
434
442
  if (labelEl) labelEl.textContent = 'VMG';
435
443
  } else {
436
444
  ui.sog.innerText = sogKts.toFixed(1);
437
445
  if (labelEl) labelEl.textContent = 'SOG';
438
446
 
439
- // Colore Corrente: se neutro usiamo "", così il CSS Night Mode può agire
440
- if (sogKts - stwKts > 0.3) ui.sog.style.setProperty('color', '#27ae60', 'important');
441
- else if (sogKts - stwKts < -0.3) ui.sog.style.setProperty('color', '#c0392b', 'important');
442
- 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
443
452
  }
444
453
  }
445
-
454
+
446
455
  if (store.raw["environment.depth.belowTransducer"] !== undefined) {
447
456
  ui.depth.innerText = store.raw["environment.depth.belowTransducer"].toFixed(1);
448
457
  checkDepthAlarm(store.raw["environment.depth.belowTransducer"]);
@@ -552,41 +561,25 @@ function startDisplayLoop() {
552
561
  async function fetchServerConfig() {
553
562
  try {
554
563
  const response = await fetch('/rotevista-config');
555
- if (!response.ok) throw new Error(`Server offline o rotta non trovata`);
564
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
556
565
  const data = await response.json();
557
566
 
558
- // 1. Funzione di pulizia: trasforma stringhe in numeri
559
- const parse = (obj) => {
560
- for (let k in obj) {
561
- if (typeof obj[k] === 'object' && obj[k] !== null) parse(obj[k]);
562
- else if (!isNaN(obj[k]) && typeof obj[k] === 'string' && obj[k] !== "")
563
- obj[k] = parseFloat(obj[k]);
564
- }
565
- return obj;
566
- };
567
- const actual = parse(data);
567
+ // Stampa di debug per verificare cosa riceve il client
568
+ console.log("🔍 Configurazione ricevuta dal Server:", data);
568
569
 
569
- // 2. Mappatura Forzata (Deep Merge)
570
- if (actual.alarms) Object.assign(CONFIG.alarms, actual.alarms);
570
+ // Merge intelligente dei dati ricevuti nel CONFIG esistente
571
+ Object.assign(CONFIG.alarms, data.alarms || {});
572
+ Object.assign(CONFIG.graphs, data.graphs || {});
573
+ Object.assign(CONFIG.averaging, data.averaging || {});
571
574
 
572
- if (actual.graphs) {
573
- Object.assign(CONFIG.graphs, actual.graphs);
574
- console.log(`📈 GRAFICI: Durata ${CONFIG.graphs.historyMinutes}m | Reef1: ${CONFIG.graphs.reef1}kts | Reef2: ${CONFIG.graphs.reef2}kts`);
575
- }
576
-
577
- if (actual.averaging) {
578
- Object.assign(CONFIG.averaging, actual.averaging);
579
- console.log(`⏱️ STABILITÀ: MinSpeed ${CONFIG.averaging.minSpeed}kts | Breakout: ${CONFIG.averaging.stabilityBreakout}°`);
580
- }
581
-
582
- if (actual.scales) {
583
- // Per le scale facciamo un merge profondo per ogni box
584
- for (let key in actual.scales) {
585
- if (CONFIG.scales[key]) Object.assign(CONFIG.scales[key], actual.scales[key]);
575
+ // Per le scale, siccome sono nidificate, facciamo un loop
576
+ if (data.scales) {
577
+ for (let key in data.scales) {
578
+ if (CONFIG.scales[key]) Object.assign(CONFIG.scales[key], data.scales[key]);
586
579
  }
587
580
  }
588
581
 
589
- console.log("✅ Configurazione sincronizzata con successo.");
582
+ console.log("✅ Configurazione applicata. Alarmi attivi:", CONFIG.alarms);
590
583
  } catch (err) {
591
584
  console.warn("⚠️ Utilizzo default locali. Motivo:", err.message);
592
585
  }
@@ -621,46 +614,108 @@ function updateScaleLabels(t, min, max) {
621
614
 
622
615
  /**
623
616
  * drawGraph: Disegna i grafici con griglia temporale intelligente
617
+ * Usa un Gradiente Lineare SVG dinamico per eliminare le giunzioni dei poligoni.
624
618
  */
625
619
  function drawGraph(d, id, min, max, isTws, isHercules) {
626
- const svg = document.getElementById(id); if (!svg || d.length < 2) return;
627
- const w = 200, h = 40, range = max - min || 1;
628
-
629
- // 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
630
629
  let grids = "";
631
630
  [0.25, 0.5, 0.75].forEach(p => {
632
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" />`;
633
632
  });
634
-
635
- /**
636
- * Griglia Temporale Intelligente:
637
- * - Storia <= 15 min: linee ogni 1 minuto.
638
- * - Storia > 15 min: linee ogni 5 minuti.
639
- */
640
633
  const gridInterval = (CONFIG.graphs.historyMinutes <= 15) ? 1 : 5;
641
-
642
634
  for (let m = gridInterval; m < CONFIG.graphs.historyMinutes; m += gridInterval) {
643
635
  const x = w - (m / CONFIG.graphs.historyMinutes) * w;
644
636
  grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(0,0,0,0.08)" stroke-width="0.5" />`;
645
637
  }
646
638
 
647
- let pD = ""; let cS = "";
648
- d.forEach((v, i) => {
649
- 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} `;
650
- if (isTws && i > 0) {
651
- const px = ((i-1)/(CONFIG.graphs.samples-1))*w, py = h-(Math.max(0,Math.min(1,(d[i-1]-min)/range))*h);
652
- let c = (v >= CONFIG.graphs.reef2) ? "#e74c3c" : (v >= CONFIG.graphs.reef1 ? "#e67e22" : "#000");
653
- // Aggiunta della classe 'tws-reef-line' per la gestione Night Mode
654
- 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;
655
650
  }
656
- });
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.5";
674
+
675
+ if (color === "#ff3b30") {
676
+ // ROSSO (Danger / Reef 2): Molto solido
677
+ fillOpacity = "0.85";
678
+ strokeWidth = "2.5";
679
+ } else if (color === "#ff9800") {
680
+ // ARANCIONE (Warning / Reef 1): Più trasparente (Velo)
681
+ fillOpacity = "0.45";
682
+ strokeWidth = "2.5"; // Manteniamo la linea spessa per leggerla bene
683
+ }
657
684
 
658
- const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#000' };
659
- 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
+ }
660
697
 
661
- svg.innerHTML = isTws ?
662
- `${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}` :
663
- `${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
+ }
664
719
  }
665
720
 
666
721
  // ==========================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "4.0.15",
3
+ "version": "4.0.17",
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;
@@ -134,6 +134,33 @@ body {
134
134
  .box-tack .value.dual-val { font-size: clamp(0.9rem, 28cqh, 10cqw) !important; }
135
135
  }
136
136
 
137
+ /* ==========================================================================
138
+ AGGIUNTA: Gestione Allarmi Profondità
139
+ ========================================================================== */
140
+ .alarm-warning {
141
+ color: #e67e22 !important; /* Arancione per Warning */
142
+ }
143
+
144
+ .alarm-danger {
145
+ color: #c0392b !important; /* Rosso per Danger */
146
+ }
147
+
148
+ .blink-alarm {
149
+ animation: blink-depth 0.8s infinite alternate;
150
+ }
151
+
152
+ @keyframes blink-depth {
153
+ from { opacity: 1; }
154
+ to { opacity: 0.3; }
155
+ }
156
+
157
+ /* Override per la Night Mode: se siamo in modalità notte,
158
+ il rosso deve restare ben visibile o diventare più acceso */
159
+ body.night-mode .alarm-danger {
160
+ color: #ff0000 !important;
161
+ text-shadow: 0 0 10px rgba(255, 0, 0, 0.8) !important;
162
+ }
163
+
137
164
  /* ==========================================================================
138
165
  5. TACTICAL FOCUS MODE
139
166
  ========================================================================== */
@@ -142,7 +169,7 @@ body {
142
169
 
143
170
  .focus-active .is-focused .sparkline path,
144
171
  .focus-active .is-focused .sparkline line {
145
- stroke-width: 1.5px !important; /* Un briciolo più spessa per la stabilità visiva su schermi grandi */
172
+ stroke-width: 1.5px !important;
146
173
  }
147
174
 
148
175
  .focus-active.focus-side-left {
@@ -208,42 +235,38 @@ body {
208
235
  }
209
236
 
210
237
  /* --- TACK: Impilamento Verticale Rigido in U-Shape --- */
211
- .box-tack .dual-value-container {
212
- flex-direction: column !important; /* Forza HDG sopra COG */
213
- justify-content: center !important;
214
- align-items: flex-start !important; /* Tutto a sinistra */
215
- gap: 8px !important; /* Spazio tra i due blocchi */
216
- }
217
-
218
- .box-tack .dual-value-col {
219
- display: flex !important;
220
- flex-direction: column !important; /* Forza Titolo sopra Numero */
221
- align-items: flex-start !important;
222
- text-align: left !important;
223
- width: 100% !important;
224
- }
225
-
226
- /* Font calibrato per non uscire dal box stretto */
227
- .box-tack .value.dual-val {
228
- font-size: clamp(1rem, 30cqh, 15cqw) !important;
229
- line-height: 1 !important;
230
- }
231
-
232
- .box-tack .dual-label {
233
- margin-bottom: 0px !important;
234
- }
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
+ }
235
261
 
236
262
  /* --- TWD: Calibrazione per box stretto --- */
237
263
  .box-twd .value-with-compass { justify-content: center; gap: 5px; }
238
264
  .box-twd .mini-compass { height: 60cqh !important; }
239
265
  .box-twd #twd-avg { font-size: clamp(1.2rem, 50cqh, 20cqw) !important; }
240
-
241
- /* NOTA: COG e AWA non vengono toccati qui, così restano massivi
242
- ereditando la regola generale della Sezione 4 */
243
266
  }
244
267
 
245
268
  /* ==========================================================================
246
- 7. GRAFICI, SCALE E TAG HERCULES
269
+ 7. GRAFICI, SCALE E STILE DINAMICO
247
270
  ========================================================================== */
248
271
  .graph-wrapper {
249
272
  position: relative;
@@ -252,7 +275,7 @@ body {
252
275
  min-height: 0;
253
276
  margin-top: 5px;
254
277
  display: flex;
255
- flex-direction: row; /* Forza l'allineamento orizzontale dei componenti */
278
+ flex-direction: row;
256
279
  align-items: stretch;
257
280
  background: rgba(0, 0, 0, 0.04);
258
281
  border-radius: 4px;
@@ -265,15 +288,15 @@ body {
265
288
  background: transparent !important;
266
289
  }
267
290
 
268
- /* Forza tutte le linee dei grafici (linee dati e griglie) a 1px per un look chirurgico */
269
- .sparkline path,
270
- .sparkline line {
271
- 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 {
272
294
  transition: all 0.3s ease;
273
- /* --- FIX LINEE SPEZZATE --- */
274
- stroke-linecap: round; /* Arrotonda le estremità dei segmenti */
275
- stroke-linejoin: round; /* Arrotonda le giunture tra i segmenti */
276
- 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));
277
300
  }
278
301
 
279
302
  .scale-labels {
@@ -290,26 +313,12 @@ body {
290
313
  }
291
314
 
292
315
  /* --- COLONNA SINISTRA (STW, SOG) --- */
293
- /* Ordine desiderato: [Grafico] [Scala] -> Scala verso il centro schermo */
294
- .box-stw .sparkline, .box-sog .sparkline {
295
- order: 1;
296
- }
297
- .box-stw .scale-labels, .box-sog .scale-labels {
298
- order: 2;
299
- text-align: left;
300
- padding-left: 4px;
301
- }
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; }
302
318
 
303
319
  /* --- COLONNA DESTRA (DEPTH, TWS) --- */
304
- /* Ordine desiderato: [Scala] [Grafico] -> Scala verso il centro schermo */
305
- .box-depth .scale-labels, .box-tws .scale-labels {
306
- order: 1;
307
- text-align: right;
308
- padding-right: 4px;
309
- }
310
- .box-depth .sparkline, .box-tws .sparkline {
311
- order: 2;
312
- }
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; }
313
322
 
314
323
  /* --- REGOLE HERCULES (Zoom) --- */
315
324
  .box-hercules { background: rgba(255, 0, 0, 0.05) !important; }
@@ -319,10 +328,8 @@ body {
319
328
  letter-spacing: 1px; text-transform: uppercase; content: "HERCULES ";
320
329
  }
321
330
 
322
- /* Anche in modalità Hercules manteniamo 1px per coerenza, affidandoci al filtro per lo stacco visivo */
323
331
  .line-hercules {
324
332
  filter: drop-shadow(0 0 5px #ff0000);
325
- stroke-width: 1px !important;
326
333
  }
327
334
 
328
335
  /* ==========================================================================
@@ -337,44 +344,24 @@ body {
337
344
 
338
345
  .data-box { height: auto !important; }
339
346
 
340
- /* --- OTTIMIZZAZIONE COMPATTA PER I BOX CON GRAFICO --- */
341
- .box-stw, .box-sog, .box-depth, .box-tws {
342
- justify-content: flex-start !important;
343
- /* Ridotto lo spazio superiore per avvicinare il valore al titolo */
344
- padding-top: 1.2rem !important;
345
- }
347
+ .box-stw, .box-sog, .box-depth, .box-tws { justify-content: flex-start !important; padding-top: 1.2rem !important; }
346
348
 
347
- /* --- OTTIMIZZAZIONE FONT VALORI CON GRAFICO IN VERTICALE --- */
348
- .box-stw .value,
349
- .box-sog .value,
350
- .box-depth .value,
351
- .box-tws .value {
352
- /* Aumentato a 30cqh per pareggiare il peso visivo di HEADING */
353
- /* 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 {
354
350
  font-size: clamp(1.2rem, 30cqh, 35cqw) !important;
355
351
  line-height: 0.8 !important;
356
- margin-top: -4px !important; /* Saliamo ancora un briciolo verso il titolo */
352
+ margin-top: -4px !important;
357
353
  margin-bottom: 4px !important;
358
354
  padding-bottom: 0 !important;
359
- font-weight: 700; /* Leggermente più marcato */
355
+ font-weight: 700;
360
356
  }
361
357
 
362
- /* Espansione del grafico per occupare tutto il resto del box */
363
- .box-stw .graph-wrapper,
364
- .box-sog .graph-wrapper,
365
- .box-depth .graph-wrapper,
366
- .box-tws .graph-wrapper {
358
+ .box-stw .graph-wrapper, .box-sog .graph-wrapper, .box-depth .graph-wrapper, .box-tws .graph-wrapper {
367
359
  margin-top: 0 !important;
368
360
  flex-grow: 1 !important;
369
361
  }
370
362
 
371
- /* --- ALTRI BOX (HEADING, COG, ecc.) RESTANO CENTRATI --- */
372
- .box-hdg, .box-cog, .box-twa, .box-awa, .box-twd, .box-tack {
373
- justify-content: center !important;
374
- padding-top: 1.2rem !important;
375
- }
363
+ .box-hdg, .box-cog, .box-twa, .box-awa, .box-twd, .box-tack { justify-content: center !important; padding-top: 1.2rem !important; }
376
364
 
377
- /* --- FOCUS MODE IN VERTICALE --- */
378
365
  .main-container.focus-active {
379
366
  grid-template-columns: 1fr !important;
380
367
  grid-template-rows: 65vh 35vh !important;
@@ -384,7 +371,6 @@ body {
384
371
  .focus-active .is-focused .value { font-size: clamp(4rem, 20cqh, 15rem) !important; margin-top: 20px !important; }
385
372
  .focus-active .box-gauge { height: 35vh !important; width: 100vw !important; display: flex !important; align-items: center; justify-content: center; }
386
373
 
387
- /* --- TACK IN VERTICALE --- */
388
374
  .box-tack .dual-value-container { flex-direction: row !important; justify-content: space-between !important; align-items: center !important; }
389
375
  .box-tack .dual-value-col { display: flex !important; flex-direction: column !important; justify-content: center; }
390
376
  .box-tack .dual-value-col:first-child { align-items: flex-start !important; text-align: left !important; }
@@ -397,18 +383,11 @@ body {
397
383
  ========================================================================== */
398
384
  #status {
399
385
  position: absolute;
400
- /* Posizionato con un po' di margine dal bordo del riquadro bussola */
401
- top: 15px;
402
- right: 15px;
403
- font-size: 0.6rem;
404
- font-weight: 900;
405
- text-transform: uppercase;
406
- z-index: 1000;
407
- /* 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;
408
389
  background: rgba(0,0,0,0.08);
409
- padding: 3px 8px;
410
- border-radius: 4px;
411
- letter-spacing: 1px;
390
+ padding: 3px 8px; border-radius: 4px; letter-spacing: 1px;
412
391
  }
413
392
  .online { color: #27ae60; opacity: 0.8; }
414
393
  .offline { color: #c0392b; font-weight: bold; }
@@ -435,11 +414,7 @@ rect[fill="#222"] { fill: #eee !important; }
435
414
 
436
415
  /* ==========================================================================
437
416
  10. NIGHT MODE (TACTICAL RED - ARCHITETTURA INTEGRALE)
438
- ==========================================================================
439
- Questa sezione sovrascrive interamente lo schema Day Mode.
440
- Obiettivo: Massima leggibilità, zero abbagliamento, preservazione visione notturna.
441
417
  ========================================================================== */
442
-
443
418
  body.night-mode {
444
419
  background-color: #000 !important;
445
420
  color: #ff3333 !important;
@@ -448,130 +423,62 @@ body.night-mode {
448
423
  /* --- 10.1 STRUTTURA DEI CONTENITORI --- */
449
424
  .night-mode .data-box {
450
425
  background: rgba(20, 0, 0, 0.4) !important;
451
- border-color: #440000 !important; /* Confini rossi cupi per orientamento spaziale */
426
+ border-color: #440000 !important;
452
427
  box-shadow: inset 0 0 15px rgba(255, 0, 0, 0.05) !important;
453
428
  }
454
429
 
455
430
  /* --- 10.2 TIPOGRAFIA E TESTI --- */
456
- /* Etichette primarie (Titoli dei box) */
457
- .night-mode .label,
458
- .night-mode .unit,
459
- .night-mode .dual-label {
460
- color: #800000 !important; /* Rosso sangue scuro per non distrarre */
461
- }
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; }
462
435
 
463
- /* Valori digitali primari (Heading, Cog, Awa, Twa) */
464
- .night-mode .value-large {
465
- color: #ff3333 !important;
466
- text-shadow: 0 0 8px rgba(255, 0, 0, 0.4) !important;
467
- }
468
-
469
- /* Valori dinamici (SOG, TWS, STW):
470
- Non usiamo !important per permettere al JavaScript di iniettare i colori dei Reef/Corrente */
471
- .night-mode .value {
472
- color: #ff3333;
473
- text-shadow: 0 0 8px rgba(255, 0, 0, 0.3);
474
- }
475
-
476
- /* Widget di Stato */
477
- .night-mode #status {
478
- background: rgba(255, 0, 0, 0.1) !important;
479
- color: #ff3333 !important;
480
- }
481
-
482
- /* --- 10.3 GRAFICI (STILE CHIRURGICO 1PX) --- */
436
+ /* --- 10.3 GRAFICI (STILE DINAMICO NIGHT MODE) --- */
483
437
  .night-mode .graph-wrapper {
484
- background: rgba(0, 0, 0, 0.6) !important;
438
+ background: rgba(10, 0, 0, 0.8) !important;
485
439
  border: 1px solid #330000;
486
440
  }
487
441
 
488
- /* Rimuove l'area di riempimento per evitare luce diffusa sul display */
489
- .night-mode .sparkline path:first-of-type {
490
- 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 */
491
445
  }
492
446
 
493
- /* Linea dati: rossa, sottile e nitida */
494
- .night-mode .sparkline path {
495
- fill: none !important;
496
- stroke: #ff3333 !important;
497
- stroke-width: 1px !important;
498
- 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);
499
450
  }
500
451
 
501
- /* Griglia temporale (minuti e livelli): quasi impercettibile */
502
- .night-mode .sparkline line {
503
- 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));
504
455
  }
505
456
 
506
- /* LOGICA REEF TWS: Mantiene i colori degli allarmi sul grafico */
507
- .night-mode .tws-reef-line { stroke-width: 1px !important; opacity: 0.9 !important; }
508
- .night-mode .tws-reef-line[stroke="#000000"],
509
- .night-mode .tws-reef-line[stroke="#000"] { stroke: #440000 !important; } /* Vento basso */
510
- .night-mode .tws-reef-line[stroke="#e67e22"] { stroke: #b35500 !important; } /* 1° Reef */
511
- .night-mode .tws-reef-line[stroke="#e74c3c"] {
512
- stroke: #ff0000 !important;
513
- filter: drop-shadow(0 0 2px #ff0000);
514
- } /* 2° Reef */
515
-
516
- /* Scale graduate dei grafici */
517
457
  .night-mode .scale-labels { color: #800000 !important; }
518
458
  .night-mode .is-focused .scale-labels { color: #ff3333 !important; }
519
459
 
520
460
  /* --- 10.4 WIND GAUGE (STRUMENTO CENTRALE) --- */
521
- /* Oscuramento cerchi concentrici per eliminare bagliori bianchi */
522
- .night-mode #wind-gauge circle {
523
- fill: #000 !important;
524
- stroke: #220000 !important;
525
- }
526
-
527
- /* Cerchio interno e hotspot centrale (nero profondo) */
528
- .night-mode #wind-gauge circle:nth-of-type(2),
529
- .night-mode #fullscreen-hotspot {
530
- fill: #050000 !important;
531
- stroke: #330000 !important;
532
- }
533
-
534
- /* 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; }
535
463
  .night-mode #center-glow feDropShadow { flood-color: #ff0000 !important; flood-opacity: 0.6 !important; }
536
- /* ICONA BARCA NIGHT: Definizione migliorata con bordo rosso */
537
- .night-mode #boat-icon {
538
- fill: #220000 !important; /* Rosso scurissimo per il corpo della barca */
539
- stroke: #ff0000 !important; /* Bordo rosso vivo per definire la sagoma */
540
- stroke-width: 0.8px !important; /* Spessore sottile ma netto */
541
- opacity: 1 !important; /* Piena opacità per far risaltare il bordo */
542
- }
543
- /* Valori interni (AWS) */
464
+ .night-mode #boat-icon { fill: #220000 !important; stroke: #ff0000 !important; stroke-width: 0.8px !important; opacity: 1 !important; }
544
465
  .night-mode #aws-display-group text { fill: #800000 !important; }
545
466
  .night-mode #aws-val-svg { fill: #ff3333 !important; }
546
-
547
- /* Tacche e Numeri Gradi della Bussola */
548
467
  .night-mode #ticks line { stroke: #550000 !important; }
549
468
  .night-mode #tick-labels { fill: #800000 !important; }
550
-
551
- /* SETTORI VENTO: Conversione sicura per la notte */
552
469
  .night-mode #wind-gauge path[stroke="#ff0000"] { stroke: #800000 !important; opacity: 0.8; }
553
- .night-mode #wind-gauge path[stroke="#00ff00"] {
554
- stroke: #440000 !important;
555
- stroke-dasharray: 5, 4 !important;
556
- opacity: 0.7;
557
- } /* Verde Starboard -> Rosso tratteggiato */
470
+ .night-mode #wind-gauge path[stroke="#00ff00"] { stroke: #440000 !important; stroke-dasharray: 5, 4 !important; opacity: 0.7; }
558
471
  .night-mode #wind-gauge path[stroke="#ff8800"] { stroke: #330000 !important; opacity: 0.6; }
559
-
560
- /* Lancette AWA/TWA e COG */
561
472
  .night-mode #awa-pointer path { fill: #ff0000 !important; stroke: #000 !important; }
562
473
  .night-mode #twa-pointer path { fill: #800000 !important; stroke: #000 !important; }
563
474
  .night-mode #track-pointer path { fill: #ff3333 !important; stroke: none !important; }
564
475
 
565
476
  /* --- 10.5 WIDGETS ACCESSORI --- */
566
- /* Barra Leeway */
567
477
  .night-mode #leeway-val { fill: #ff3333 !important; color: #ff3333 !important; }
568
478
  .night-mode rect[fill="url(#leeway-grad)"] { fill: url(#leeway-night-grad) !important; }
569
- .night-mode rect[fill="#eee"],
570
- .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; }
571
480
  .night-mode g[stroke="#555"] line { stroke: #440000 !important; }
572
481
  .night-mode g[fill="#555"] text { fill: #800000 !important; }
573
-
574
- /* Mini Bussola TWD */
575
482
  .night-mode .mini-compass { border-color: #330000 !important; background: #000 !important; }
576
483
  .night-mode .mini-compass text { fill: #800000 !important; }
577
484
  .night-mode .mini-compass text:last-of-type { fill: #800000 !important; opacity: 1; }
@@ -579,10 +486,9 @@ body.night-mode {
579
486
  .night-mode #twd-arrow #twd-wind-chevron { stroke: #ff3333 !important; }
580
487
 
581
488
  /* --- 10.6 LOGICA RED-SHIFT TREND --- */
582
- /* Converte i colori generati dal JS per i pallini di trend */
583
- .night-mode circle[fill="#bbb"], .night-mode circle[fill="#bbbbbb"] { fill: #440000 !important; } /* Neutro */
584
- .night-mode circle[fill="#27ae60"] { fill: #ff0000 !important; } /* Lift */
585
- .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; }
586
492
 
587
493
  /* ==========================================================================
588
494
  11. TREND E ANIMAZIONI
@@ -592,3 +498,20 @@ body.night-mode {
592
498
  .is-trending { opacity: 1 !important; animation: blink-trend 1s infinite !important; }
593
499
  .is-gybing { opacity: 1 !important; animation: none !important; filter: drop-shadow(0 0 8px #ff0000) !important; }
594
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
+ }