@sailingrotevista/rotevista-dash 2.0.11 → 2.0.13

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 +103 -3
  2. package/index.html +13 -1
  3. package/package.json +1 -1
  4. package/style.css +274 -92
package/app.js CHANGED
@@ -333,7 +333,48 @@ function toggleFocusMode(type, element) {
333
333
  el.addEventListener('pointerleave', () => clearTimeout(pressTimer));
334
334
  });
335
335
 
336
- if (ui.hotspot) { ui.hotspot.addEventListener('click', () => { const doc = document.documentElement, isF = document.fullscreenElement || document.webkitFullscreenElement; if (!isF) { if (doc.requestFullscreen) doc.requestFullscreen(); else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen(); } else { if (document.exitFullscreen) document.exitFullscreen(); else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); } }); }
336
+ // ==========================================================================
337
+ // GESTIONE HOTSPOT: Click (Fullscreen) e Long Press (Night Mode)
338
+ // ==========================================================================
339
+ if (ui.hotspot) {
340
+ let pressTimer;
341
+ const HOLD_DURATION = 1000; // 1 secondo di pressione per attivare Night Mode
342
+
343
+ ui.hotspot.addEventListener('pointerdown', (e) => {
344
+ // Avvia il timer al tocco
345
+ pressTimer = setTimeout(() => {
346
+ document.body.classList.toggle('night-mode');
347
+ // Feedback visivo immediato al cambio modalità
348
+ ui.hotspot.style.opacity = "0.5";
349
+ setTimeout(() => ui.hotspot.style.opacity = "1", 200);
350
+ pressTimer = null; // Reset per evitare che il click scatti dopo
351
+ }, HOLD_DURATION);
352
+ });
353
+
354
+ ui.hotspot.addEventListener('pointerup', (e) => {
355
+ if (pressTimer) {
356
+ clearTimeout(pressTimer);
357
+ pressTimer = null;
358
+ // Se arriviamo qui, è stato un click rapido -> Fullscreen
359
+ const doc = document.documentElement;
360
+ const isF = document.fullscreenElement || document.webkitFullscreenElement;
361
+ if (!isF) {
362
+ if (doc.requestFullscreen) doc.requestFullscreen();
363
+ else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen();
364
+ } else {
365
+ if (document.exitFullscreen) document.exitFullscreen();
366
+ else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
367
+ }
368
+ }
369
+ });
370
+
371
+ ui.hotspot.addEventListener('pointerleave', () => {
372
+ if (pressTimer) {
373
+ clearTimeout(pressTimer);
374
+ pressTimer = null;
375
+ }
376
+ });
377
+ }
337
378
  (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
379
 
339
380
  async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
@@ -341,7 +382,66 @@ window.addEventListener('load', init);
341
382
  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
383
  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
384
 
344
- // Simulatore
385
+ // Simulatore su Depth (3 click rapidi)
345
386
  ui.depth.closest('.data-box').addEventListener('click', (function() {
346
- let dC = 0, lC = 0; return function() { const n = Date.now(); if (n - lC < 500) dC++; else dC = 1; lC = n; if (dC === 3) { simulationMode = !simulationMode; if (simulationMode) { if (socket) socket.close(); ui.status.innerText = "SIM ATTIVO"; simInterval = setInterval(() => { processIncomingData("navigation.headingTrue", degToRad(Math.random()*360)); processIncomingData("navigation.speedOverGround", ktsToMs(8)); }, 200); } else location.reload(); dC = 0; } };
387
+ let dC = 0, lC = 0;
388
+ return function() {
389
+ const n = Date.now();
390
+ if (n - lC < 500) dC++; else dC = 1;
391
+ lC = n;
392
+ if (dC === 3) {
393
+ simulationMode = !simulationMode;
394
+ if (simulationMode) {
395
+ if (socket) socket.close();
396
+ startDynamicSimulation(); // Chiama la nuova funzione
397
+ } else {
398
+ location.reload();
399
+ }
400
+ dC = 0;
401
+ }
402
+ };
347
403
  })());
404
+
405
+ // ==========================================================================
406
+ // 9. MOTORE SIMULAZIONE DINAMICA
407
+ // ==========================================================================
408
+ function startDynamicSimulation() {
409
+ ui.status.innerText = "SIM ATTIVO";
410
+
411
+ // Parametri base iniziali
412
+ let baseTws = 60, baseDepth = 12, baseHdg = 45;
413
+
414
+ simInterval = setInterval(() => {
415
+ // T rappresenta il progresso nel ciclo di 10 minuti (da 0 a 1)
416
+ const t = (Date.now() % 600000) / 600000;
417
+ const radT = t * 2 * Math.PI;
418
+
419
+ // Oscillazioni fluide (Sinusoidali)
420
+ const tws = baseTws + Math.sin(radT) * 5; // Oscilla +- 5 kts
421
+ const depth = baseDepth + Math.sin(radT) * 3; // Oscilla +- 3 m
422
+ const twa = 40 + Math.sin(radT * 2) * 10; // Oscilla angolo +- 10°
423
+
424
+ // Calcoli Vettoriali (Nautica)
425
+ const stw = Math.min(tws * 0.7, 8); // Velocità barca legata al vento
426
+ const twaRad = degToRad(twa);
427
+
428
+ // AWS = radq(stw² + tws² + 2*stw*tws*cos(twa))
429
+ const aws = Math.sqrt(Math.pow(stw, 2) + Math.pow(tws, 2) + 2 * stw * tws * Math.cos(twaRad));
430
+ // AWA = atan2(tws*sin(twa), stw + tws*cos(twa))
431
+ const awa = radToDeg(Math.atan2(tws * Math.sin(twaRad), stw + tws * Math.cos(twaRad)));
432
+
433
+ // Calcolo Leeway (Scarroccio): più forte il vento, più scarroccia
434
+ const leeway = (tws > 5) ? Math.sin(twaRad) * (tws * 0.2) : 0;
435
+
436
+ // Invio dati al sistema
437
+ processIncomingData("environment.wind.speedTrue", ktsToMs(tws));
438
+ processIncomingData("environment.wind.angleTrueWater", degToRad(twa));
439
+ processIncomingData("environment.wind.speedApparent", ktsToMs(aws));
440
+ processIncomingData("environment.wind.angleApparent", degToRad(awa));
441
+ processIncomingData("environment.depth.belowTransducer", depth);
442
+ processIncomingData("navigation.headingTrue", degToRad(baseHdg));
443
+ processIncomingData("navigation.speedThroughWater", ktsToMs(stw));
444
+ processIncomingData("navigation.courseOverGroundTrue", degToRad(baseHdg + leeway));
445
+ }, 1000);
446
+ }
447
+
package/index.html CHANGED
@@ -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" fill="url(#axiom-grad)" transform="translate(0, 5)" style="pointer-events: none;" />
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)">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "2.0.11",
3
+ "version": "2.0.13",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/style.css CHANGED
@@ -5,9 +5,12 @@ body {
5
5
  background-color: #000;
6
6
  color: #fff;
7
7
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
8
- margin: 0; padding: 0;
9
- height: 100vh; width: 100vw;
8
+ margin: 0;
9
+ padding: 0;
10
+ height: 100vh;
11
+ width: 100vw;
10
12
  overflow: hidden;
13
+ /* BLOCCO TOTALE GESTI DI SISTEMA: Fondamentale per il Long Press su mobile */
11
14
  -webkit-touch-callout: none;
12
15
  -webkit-user-select: none;
13
16
  user-select: none;
@@ -16,30 +19,60 @@ body {
16
19
 
17
20
  .main-container {
18
21
  display: grid;
19
- width: 100%; height: 100%;
20
- padding: 5px; box-sizing: border-box;
21
- gap: 8px;
22
- grid-template-columns: minmax(200px, 1fr) minmax(auto, 3fr) minmax(230px, 1fr);
22
+ width: 100%;
23
+ height: 100%;
24
+ padding: 5px;
25
+ box-sizing: border-box;
26
+ gap: 8px; /* Spazio costante tra tutti i blocchi della dashboard */
27
+
28
+ /*
29
+ LAYOUT LIQUIDO:
30
+ I lati hanno un minimo vitale (180px) per proteggere i testi.
31
+ Il centro (auto) si adatta millimetricamente al diametro dell'SVG.
32
+ */
33
+ grid-template-columns: minmax(180px, 1fr) minmax(auto, 3fr) minmax(180px, 1fr);
23
34
  grid-template-rows: 100%;
24
35
  transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
25
36
  justify-content: stretch;
26
37
  }
27
38
 
28
- /* Espansione colonne per schermi larghi (16:10) */
39
+ /* Espansione colonne per schermi molto larghi (es. 16:10 / 16:9) */
29
40
  @media (min-aspect-ratio: 1.5) {
30
41
  .main-container {
31
- /* Aumentiamo il gap e il minimo vitale per schermi enormi */
32
42
  grid-template-columns: minmax(200px, 1fr) auto minmax(200px, 1fr);
33
43
  gap: 15px;
34
44
  }
35
45
  }
46
+
36
47
  /* ==========================================================================
37
48
  2. PANNELLI E DATA-BOX
38
49
  ========================================================================== */
39
- .side-panel { display: flex; flex-direction: column; background: rgba(255, 255, 255, 0.03); border-radius: 12px; height: 100%; overflow: hidden; }
50
+ .side-panel {
51
+ display: flex;
52
+ flex-direction: column;
53
+ background: rgba(255, 255, 255, 0.03);
54
+ border-radius: 12px;
55
+ height: 100%;
56
+ overflow: hidden;
57
+ }
58
+
40
59
  .left-panel { grid-column: 1; }
41
- .center-panel { grid-column: 2; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; overflow: hidden; }
42
- .right-panel { grid-column: 3; align-items: flex-end; text-align: right; }
60
+
61
+ .center-panel {
62
+ grid-column: 2;
63
+ display: flex;
64
+ flex-direction: column;
65
+ align-items: center;
66
+ justify-content: center;
67
+ height: 100%;
68
+ overflow: hidden;
69
+ }
70
+
71
+ .right-panel {
72
+ grid-column: 3;
73
+ align-items: flex-end;
74
+ text-align: right;
75
+ }
43
76
 
44
77
  .data-box {
45
78
  position: relative;
@@ -47,54 +80,41 @@ body {
47
80
  padding: 8px 12px;
48
81
  display: flex;
49
82
  flex-direction: column;
50
- width: 100%; box-sizing: border-box;
51
- container-type: size;
83
+ width: 100%;
84
+ box-sizing: border-box;
85
+ container-type: size; /* Rende il box misurabile per l'unità cqh */
52
86
  flex: 1;
53
87
  overflow: hidden;
54
- -webkit-touch-callout: none;
55
- -webkit-user-select: none;
56
- user-select: none;
57
- touch-action: none;
58
88
  }
59
89
 
60
- /* Altezze Desktop (Validate) */
61
- .data-box:nth-child(1), .data-box:nth-child(2) { height: 25vh; }
62
- .data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 16.666vh; }
63
-
64
- /* Allineamento Speculare Box */
90
+ /* Allineamento Speculare: SX a sinistra, DX a destra del box */
65
91
  .left-panel .data-box { align-items: flex-start; text-align: left; }
66
92
  .right-panel .data-box { align-items: flex-end; text-align: right; }
67
93
 
94
+ /* Altezze Proporzionali: 25% dell'altezza per i grafici, 16.6% per i MEAN/TACK */
95
+ .data-box:nth-child(1), .data-box:nth-child(2) { height: 25vh; }
96
+ .data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 16.666vh; }
97
+
68
98
  /* ==========================================================================
69
99
  3. TACTICAL FOCUS MODE (AUTO-EXPANDING SPLIT)
70
100
  ========================================================================== */
71
101
 
72
- /* Nascondi i pannelli laterali non interessati */
102
+ /* Nasconde le colonne non focalizzate */
73
103
  .focus-active .side-panel:not(.has-focus) { display: none !important; }
74
- .focus-active .side-panel.has-focus {
75
- min-width: 400px !important;
76
- }
77
104
 
105
+ /* Protezione larghezza minima per il grafico focalizzato */
106
+ .focus-active .side-panel.has-focus { min-width: 400px !important; }
78
107
 
79
-
80
- /*
81
- LOGICA POSIZIONE E LARGHEZZA:
82
- Il Vento prende solo la larghezza del suo cerchio (auto).
83
- Il Box focalizzato si prende TUTTO il resto dello schermo (1fr).
84
- */
85
- .focus-active.focus-side-left {
86
- grid-template-columns: 1fr auto !important; /* Dati SX giganti, Vento DX compatto */
87
- }
108
+ /* Logica di espansione: il box scelto prende tutto il resto dello spazio (1fr) */
109
+ .focus-active.focus-side-left { grid-template-columns: 1fr auto !important; }
88
110
  .focus-active.focus-side-left .side-panel.has-focus { grid-column: 1 !important; }
89
111
  .focus-active.focus-side-left .center-panel { grid-column: 2 !important; justify-content: flex-start; }
90
112
 
91
- .focus-active.focus-side-right {
92
- grid-template-columns: auto 1fr !important; /* Vento SX compatto, Dati DX giganti */
93
- }
113
+ .focus-active.focus-side-right { grid-template-columns: auto 1fr !important; }
94
114
  .focus-active.focus-side-right .center-panel { grid-column: 1 !important; justify-content: flex-start; }
95
115
  .focus-active.focus-side-right .side-panel.has-focus { grid-column: 2 !important; }
96
116
 
97
- /* Gestione Box Focalizzato a tutto schermo */
117
+ /* Styling del box in Focus Mode */
98
118
  .focus-active .has-focus .data-box:not(.is-focused) { display: none !important; }
99
119
  .focus-active .has-focus .data-box.is-focused {
100
120
  height: 100vh !important;
@@ -103,13 +123,13 @@ body {
103
123
  padding: 20px;
104
124
  }
105
125
 
106
- /* Tipografia Extreme per Focus Mode */
126
+ /* Tipografia massiccia in Focus Mode */
107
127
  .focus-active .is-focused .value { font-size: clamp(4rem, 25cqh, 4rem) !important; margin-top: 15px; }
108
128
  .focus-active .is-focused .scale-labels { font-size: 32px !important; min-width: 40px !important; line-height: 1.2; }
109
129
  .focus-active .is-focused .label-row .label { font-size: 2rem !important; }
110
130
  .focus-active .is-focused .label-row .unit { font-size: 2rem !important; }
111
131
 
112
- /* Ingrandimento scritta Hercules in Focus */
132
+ /* Ingrandimento etichette Hercules in Focus */
113
133
  .focus-active .is-focused.box-hercules .unit::before,
114
134
  .focus-active .is-focused.box-hercules .unit::after,
115
135
  .focus-active .is-focused.box-hercules .label::before,
@@ -121,46 +141,51 @@ body {
121
141
  .focus-active .is-focused .sparkline path { stroke-width: 1px !important; }
122
142
 
123
143
  /* ==========================================================================
124
- 4. TIPOGRAFIA E LABEL-ROW (SIMMETRIA)
144
+ 4. TIPOGRAFIA DINAMICA (ELASTICA CQH)
125
145
  ========================================================================== */
126
146
  .label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; width: 100%; }
127
147
 
128
- /* SX: [Titolo] ... [Unità] */
148
+ /* Simmetria Titoli/Unità: SX [Label...Unit], DX [Unit...Label] */
129
149
  .left-panel .label-row { justify-content: space-between; flex-direction: row; }
130
-
131
- /* DX: [Unità] ... [Titolo] */
132
150
  .right-panel .label-row { justify-content: space-between; flex-direction: row; }
133
151
 
134
- /* FIX: Spinge i titoli MEAN a destra nella colonna DX */
152
+ /* Forza i titoli MEAN (senza unità) a destra nella colonna DX */
135
153
  .right-panel .label-row .label:only-child { margin-left: auto; }
136
154
 
137
155
  .label { color: #666; font-size: 0.65rem; font-weight: bold; text-transform: uppercase; }
138
156
  .unit { color: #888; font-size: 0.6rem; font-weight: bold; }
139
- .value { color: #fff; font-size: clamp(1.2rem, 22cqh, 3rem); font-weight: 600; line-height: 0.9; letter-spacing: -1px; padding-bottom: 5px; }
140
157
 
141
- .value-large, .dual-value-container, .value-with-compass { margin-top: auto; margin-bottom: auto; }
158
+ /* Numero standard (22% altezza box per non coprire il grafico) */
159
+ .value {
160
+ color: #fff;
161
+ font-size: clamp(1.2rem, 22cqh, 3rem);
162
+ font-weight: 600;
163
+ line-height: 0.9;
164
+ letter-spacing: -1px;
165
+ padding-bottom: 5px;
166
+ }
167
+
168
+ /* Centratura verticale automatica per i box senza grafico */
169
+ .value-large, .dual-value-container, .value-with-compass {
170
+ margin-top: auto;
171
+ margin-bottom: auto;
172
+ }
173
+
142
174
  .value-large { font-size: clamp(1.5rem, 35cqh, 4.5rem); line-height: 0.85; }
143
175
 
144
- /* TACK Layout */
176
+ /* TACK Layout (Mure opposte) */
145
177
  .dual-value-container { display: flex; justify-content: space-between; width: 100%; }
146
178
  .dual-value-col { display: flex; flex-direction: column; width: 48%; }
147
179
  .dual-label { color: #666; font-size: 0.55rem; font-weight: bold; text-transform: uppercase; margin-bottom: 2px; }
148
180
  .value.dual-val { font-size: clamp(1rem, 22cqh, 2rem); padding-bottom: 0; line-height: 0.9; }
149
181
 
150
182
  /* ==========================================================================
151
- BUSSOLA TWD (CALIBRATA: Forma e Spazio)
183
+ 5. BUSSOLA TWD DINAMICA
152
184
  ========================================================================== */
153
- .value-with-compass {
154
- display: flex;
155
- flex: 1;
156
- justify-content: space-between;
157
- align-items: center;
158
- width: 100%;
159
- gap: 5px;
160
- }
185
+ .value-with-compass { display: flex; justify-content: space-between; align-items: center; width: 100%; gap: 5px; }
161
186
 
162
187
  .mini-compass {
163
- /* Bilanciamento: Grande ma con protezione larghezza per il testo */
188
+ /* Forma circolare protetta: 80% dell'altezza o 42% della larghezza */
164
189
  width: min(80cqh, 42cqw);
165
190
  height: min(80cqh, 42cqw);
166
191
  aspect-ratio: 1 / 1;
@@ -173,15 +198,12 @@ body {
173
198
  }
174
199
 
175
200
  .value-with-compass .value-large {
176
- margin: 0;
177
- flex: 1;
178
- text-align: right;
179
- font-size: clamp(1rem, 38cqh, 4rem);
180
- white-space: nowrap;
201
+ margin: 0; flex: 1; text-align: right;
202
+ font-size: clamp(1rem, 38cqh, 4rem); white-space: nowrap;
181
203
  }
182
204
 
183
205
  /* ==========================================================================
184
- 5. GRAFICI E SCALE
206
+ 6. GRAFICI E SCALE (SIMMETRIA INTERNA)
185
207
  ========================================================================== */
186
208
  .graph-wrapper {
187
209
  position: relative; width: 100%; flex-grow: 1; min-height: 0; margin-top: 4px;
@@ -189,84 +211,244 @@ body {
189
211
  border-radius: 4px; gap: 2px;
190
212
  }
191
213
 
192
- /* Spostamento verso il centro */
214
+ /* Spostamento verso il centro per eliminare spazi neri morti */
193
215
  .left-panel .graph-wrapper { margin-right: -6px; }
194
216
  .right-panel .graph-wrapper { margin-left: -6px; }
195
217
 
196
218
  .sparkline { flex-grow: 1; height: 100%; background: transparent !important; display: block; }
197
219
  .sparkline path, .sparkline line { stroke-width: 1px !important; transition: all 0.3s ease; }
198
220
 
199
- .scale-labels { display: flex; flex-direction: column; justify-content: space-between; font-size: 10px; color: #777; font-weight: bold; min-width: 10px; height: 100%; line-height: 1; padding: 2px 0; }
221
+ .scale-labels {
222
+ display: flex; flex-direction: column; justify-content: space-between;
223
+ font-size: 10px; color: #777; font-weight: bold; min-width: 18px;
224
+ height: 100%; line-height: 1; padding: 2px 0;
225
+ }
200
226
 
201
- /* Simmetria Scale */
227
+ /* Simmetria: le scale numeriche "guardano" sempre il quadrante centrale */
202
228
  .left-panel .scale-labels { order: 2; text-align: left; padding-left: 4px; border-left: 1px solid rgba(255,255,255,0.08); }
203
229
  .left-panel .sparkline { order: 1; }
204
230
  .right-panel .scale-labels { order: 1; text-align: right; padding-right: 4px; border-right: 1px solid rgba(255,255,255,0.08); }
205
231
  .right-panel .sparkline { order: 2; }
206
232
 
233
+ /* Colori semantici per i grafici */
207
234
  #stw-graph { stroke: #2ecc71; fill: rgba(46, 204, 113, 0.12); }
208
235
  #sog-graph { stroke: #f39c12; fill: rgba(243, 156, 18, 0.12); }
209
236
  #depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.12); }
210
237
  #tws-graph { stroke: #ffffff; fill: rgba(255, 255, 255, 0.08); }
211
238
 
212
239
  /* ==========================================================================
213
- 6. HERCULES MODE (LABEL TOP E SIMMETRIA)
240
+ 7. HERCULES MODE (ZOOM E VISUALS)
214
241
  ========================================================================== */
215
242
  .line-hercules { filter: drop-shadow(0 0 5px #ff0000); stroke-width: 1.8px !important; }
216
243
  .box-hercules { background: rgba(255, 0, 0, 0.08) !important; }
244
+ .box-hercules .scale-labels { color: #ff8888; }
217
245
 
218
- /* Ingrandimento Hercules in Focus Mode */
219
- .focus-active .is-focused.box-hercules .unit::before,
220
- .focus-active .is-focused.box-hercules .unit::after,
221
- .focus-active .is-focused.box-hercules .label::before {
222
- font-size: 16px !important; /* Raddoppiato in Focus */
223
- letter-spacing: 2px;
224
- }
225
-
226
- /* Stile base Hercules */
246
+ /* Etichetta Hercules dinamica accanto alle unità */
227
247
  .box-hercules .unit::before, .box-hercules .unit::after,
228
248
  .box-hercules .label::before {
229
249
  font-size: 7px; color: #ff4444; font-weight: 900; letter-spacing: 1px; text-transform: uppercase;
230
250
  }
231
-
232
- /* Simmetria Hercules SX: "STW [HERCULES kts]" */
233
251
  .left-panel .box-hercules .unit::before { content: "HERCULES "; }
234
-
235
- /* Simmetria Hercules DX: "[m HERCULES] DEPTH" */
236
252
  .right-panel .box-hercules .unit::after { content: " HERCULES"; }
237
-
238
- /* Eccezione: Box MEAN colonna DX (Senza unità) -> "[HERCULES TWA]" */
239
- .right-panel .box-hercules .label:only-child::before { content: "HERCULES "; }
253
+ .right-panel .box-hercules .label:only-child::after { content: " HERCULES"; }
240
254
 
241
255
  /* ==========================================================================
242
- 7. STATI E ANIMAZIONI
256
+ 8. STATI E ANIMAZIONI
243
257
  ========================================================================== */
244
258
  #status { position: absolute; top: 5px; right: 15px; font-size: 0.5rem; text-transform: uppercase; z-index: 1000; }
245
259
  .online { color: #2ecc71; opacity: 0.5; }
246
260
  .offline { color: #e74c3c; font-weight: bold; }
247
- #awa-pointer, #twa-pointer, #track-pointer, #twd-arrow, #twd-boat-wrap { transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); }
261
+
262
+ /* Animazioni fluide per gli elementi rotanti (0.8s) */
263
+ #awa-pointer, #twa-pointer, #track-pointer, #twd-arrow, #twd-boat-wrap {
264
+ transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
265
+ }
266
+
248
267
  #leeway-mask-rect { transition: none; }
249
268
  .alarm-warning { color: #f1c40f !important; }
250
269
  .alarm-danger { color: #e74c3c !important; font-weight: 900; animation: blink-unstable 1s infinite; }
270
+
251
271
  @keyframes blink-unstable { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
252
272
  .unstable-data { animation: blink-unstable 1.5s infinite ease-in-out; color: #f39c12 !important; }
273
+
253
274
  #wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
254
275
 
255
276
  /* ==========================================================================
256
- 8. RESPONSIVE (PORTRAIT)
277
+ 9. RESPONSIVE (PORTRAIT MODE - IPHONE/TABLET)
257
278
  ========================================================================== */
258
279
  @media (max-aspect-ratio: 0.9 / 1) {
259
- .main-container { grid-template-columns: 1fr 1fr !important; grid-template-rows: 45vh 55vh !important; }
280
+ .main-container {
281
+ grid-template-columns: 1fr 1fr !important;
282
+ grid-template-rows: 45vh calc(55vh - 8px) !important;
283
+ }
284
+
260
285
  .center-panel { grid-row: 1 !important; grid-column: 1 / span 2 !important; padding: 5px 0; }
261
286
  .left-panel { grid-row: 2 !important; grid-column: 1 !important; }
262
287
  .right-panel { grid-row: 2 !important; grid-column: 2 !important; }
263
288
 
289
+ /* Focus Mode Verticale: Impilamento Flex sopra/sotto */
264
290
  .main-container.focus-active { display: flex !important; flex-direction: column !important; }
265
291
  .focus-active .center-panel { flex: 0 0 45vh !important; width: 100% !important; }
266
- .focus-active .side-panel.has-focus { flex: 0 0 55vh !important; width: 100% !important; display: flex !important; }
292
+ .focus-active .side-panel.has-focus { flex: 0 0 calc(55vh - 8px) !important; width: 100% !important; display: flex !important; }
293
+
294
+ /* Altezze box Portrait dinamiche (per evitare overflow) */
295
+ .data-box { height: auto !important; flex: 1 !important; }
296
+ .data-box:nth-child(1), .data-box:nth-child(2) { flex: 1.5 !important; }
267
297
 
268
- .data-box:nth-child(1), .data-box:nth-child(2) { height: 13.5vh; }
269
- .data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 9.3vh; }
270
298
  .value-large { font-size: clamp(1.2rem, 35cqh, 2.5rem); margin: auto 0; }
271
299
  .value.dual-val { font-size: clamp(0.9rem, 30cqh, 1.4rem); }
300
+
301
+ /* Protezione bussola per box bassi (Portrait) */
302
+ .mini-compass {
303
+ width: min(70cqh, 40cqw) !important;
304
+ height: min(70cqh, 40cqw) !important;
305
+ }
306
+
307
+ .right-panel .label-row .label:only-child { margin-left: auto !important; }
308
+ .box-hercules .unit::before, .box-hercules .unit::after { font-size: 6px !important; }
309
+ }
310
+
311
+ /* ==========================================================================
312
+ 10. NIGHT MODE (RED ON BLACK - TACTICAL)
313
+ ========================================================================== */
314
+ body.night-mode {
315
+ background-color: #000 !important;
316
+ color: #ff0000 !important;
317
+ }
318
+
319
+ /* --- Pannelli e Box --- */
320
+ .night-mode .side-panel {
321
+ background: rgba(20, 0, 0, 0.4);
322
+ border: 1px solid #330000;
323
+ }
324
+ .night-mode .data-box {
325
+ border-bottom-color: #260000;
326
+ }
327
+
328
+ /* --- Tipografia Secondaria (Titoli, Unità, TACK Labels) --- */
329
+ .night-mode .label,
330
+ .night-mode .unit,
331
+ .night-mode .dual-label {
332
+ color: #800000 !important; /* Rosso cupo per non affaticare la vista */
333
+ }
334
+
335
+ /* --- Valori Numerici --- */
336
+ .night-mode .value,
337
+ .night-mode .value-large {
338
+ color: #ff3333 !important;
339
+ text-shadow: 0 0 8px rgba(255, 0, 0, 0.4); /* Bagliore per profondità */
340
+ }
341
+
342
+ /* --- Grafici Sparkline (Solo Linea, No Riempimento) --- */
343
+ .night-mode .graph-wrapper {
344
+ background: rgba(30, 0, 0, 0.3) !important;
345
+ }
346
+
347
+ /* Nasconde l'area di riempimento sotto la linea */
348
+ .night-mode .sparkline path:first-of-type {
349
+ display: none !important;
350
+ }
351
+
352
+ /* Rende la linea del tracciato molto più visibile */
353
+ .night-mode .sparkline path {
354
+ fill: none !important;
355
+ stroke: #ff3333 !important;
356
+ stroke-width: 1.8px !important;
357
+ opacity: 1 !important;
358
+ filter: drop-shadow(0 0 2px rgba(255, 0, 0, 0.4));
359
+ }
360
+
361
+ /* Griglie interne (Orizzontali e Verticali) uniformi */
362
+ .night-mode .sparkline line {
363
+ stroke: #4d0000 !important;
364
+ stroke-width: 0.7px !important;
365
+ opacity: 1 !important;
366
+ }
367
+
368
+ /* Forza i segmenti colorati del TWS alla stessa luminosità della linea */
369
+ .night-mode #tws-graph line:not([stroke*="rgba"]) {
370
+ stroke: #ff3333 !important;
371
+ stroke-width: 1.8px !important;
372
+ }
373
+
374
+ /* Numeri delle scale a lato dei grafici */
375
+ .night-mode .scale-labels {
376
+ color: #660000 !important;
377
+ }
378
+
379
+ /* --- Hercules Mode in Night Mode --- */
380
+ .night-mode .box-hercules {
381
+ background: rgba(60, 0, 0, 0.2) !important;
382
+ }
383
+ .night-mode .line-hercules {
384
+ filter: drop-shadow(0 0 6px #ff0000) !important;
385
+ }
386
+
387
+ /* Scritta Hercules in alto (Colore personalizzato #f60000) */
388
+ .night-mode .box-hercules .unit::before,
389
+ .night-mode .box-hercules .unit::after,
390
+ .night-mode .box-hercules .label::before,
391
+ .night-mode .box-hercules .label::after {
392
+ color: #f60000 !important;
393
+ font-weight: 700;
394
+ opacity: 0.9;
395
+ letter-spacing: 1px;
396
+ }
397
+
398
+ /* --- Strumento Centrale (Wind Gauge) --- */
399
+ .night-mode #wind-gauge circle { stroke: #330000; }
400
+ .night-mode #ticks line { stroke: #4d0000 !important; }
401
+ .night-mode #tick-labels { fill: #800000 !important; }
402
+ .night-mode #boat-icon { fill: #330000 !important; opacity: 0.6; }
403
+ .night-mode #aws-val-svg { fill: #ff3333 !important; }
404
+
405
+ /* Settori Vento (Distinguibili per stile, non per colore) */
406
+ .night-mode #wind-gauge path[stroke="#ff0000"] { stroke: #660000 !important; opacity: 0.8; } /* Sx Solid */
407
+ .night-mode #wind-gauge path[stroke="#00ff00"] {
408
+ stroke: #660000 !important;
409
+ stroke-dasharray: 4, 3; /* Dx Dashed */
410
+ opacity: 0.8;
411
+ }
412
+ .night-mode #wind-gauge path[stroke="#ff8800"] { stroke: #330000 !important; stroke-width: 8; } /* Poppa Dark */
413
+
414
+ /* Lancette Wind Gauge */
415
+ .night-mode #awa-pointer path { fill: #ff0000; stroke: #000; }
416
+ .night-mode #twa-pointer path { fill: #800000; stroke: #000; }
417
+ .night-mode #track-pointer path { fill: #ff0000; stroke: #fff; stroke-width: 0.5; }
418
+
419
+ /* --- Blocco Leeway (Scarroccio) --- */
420
+ .night-mode rect[fill="url(#leeway-grad)"] { fill: url(#leeway-night-grad) !important; }
421
+ .night-mode #leeway-val { fill: #ff3333 !important; }
422
+ .night-mode g[stroke="#555"] line { stroke: #4d0000 !important; }
423
+ .night-mode g[fill="#555"] text { fill: #660000 !important; }
424
+ .night-mode rect[fill="#222"] { fill: #0a0000 !important; stroke: #200000; }
425
+
426
+ /* --- Bussola TWD --- */
427
+ .night-mode .mini-compass { border-color: #330000; background: #000; }
428
+ .night-mode .mini-compass text { fill: #800000 !important; }
429
+ .night-mode #twd-arrow path { fill: #ff0000 !important; stroke: #000 !important; }
430
+ .night-mode #twd-boat-wrap path { fill: #ff0000 !important; opacity: 0.15; }
431
+
432
+ .night-mode #center-glow feDropShadow {
433
+ flood-color: #ff0000 !important;
434
+ flood-opacity: 0.6 !important;
435
+ }
436
+
437
+ .night-mode #aws-display-group text {
438
+ fill: #ff3333 !important;
439
+ }
440
+
441
+ /* --- Modifica: Miglioramento visibilità TWD in Night Mode --- */
442
+ .night-mode #twd-arrow #twd-wind-chevron {
443
+ stroke: #ff3333 !important; /* Rosso più brillante */
444
+ stroke-width: 3px !important; /* Più spesso */
445
+ opacity: 1 !important; /* Piena opacità */
446
+ filter: drop-shadow(0 0 4px #ff0000); /* Effetto neon per staccarsi dallo sfondo */
447
+ transform-origin: center; /* Forza il centro corretto */
448
+ }
449
+
450
+ /* Assicuriamoci che anche la punta della barca sia visibile */
451
+ .night-mode #twd-boat-wrap path {
452
+ fill: #ff3333 !important;
453
+ opacity: 0.4 !important;
272
454
  }