@sailingrotevista/rotevista-dash 2.0.23 → 3.0.1

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 +66 -37
  2. package/index.html +90 -160
  3. package/package.json +1 -1
  4. package/style.css +335 -231
package/app.js CHANGED
@@ -308,10 +308,14 @@ function startDisplayLoop() {
308
308
  if (tick % 2 === 0) { refreshGraph('stw'); refreshGraph(displayModeSog === 'VMG' ? 'vmg' : 'sog'); refreshGraph('depth'); refreshGraph('tws'); }
309
309
 
310
310
  // SLOW TIER (3s)
311
- if (tick % 3 === 0) {
312
- let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, 30000, false), cObj = getCircularAverageFromBuffer(store.longBuf.cog, 30000, false),
313
- awObj = getCircularAverageFromBuffer(store.longBuf.awa, 30000, true), twObj = getCircularAverageFromBuffer(store.longBuf.twa, 30000, true),
314
- twdObj = getCircularAverageFromBuffer(store.longBuf.twd, 30000, false);
311
+ if (tick % 3 === 0) {
312
+ // Utilizziamo CONFIG.averages.longWindow (che ora è 30000ms)
313
+ // In questo modo, se cambi il tempo su Signal K, la dashboard si aggiorna da sola.
314
+ let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averages.longWindow, false),
315
+ cObj = getCircularAverageFromBuffer(store.longBuf.cog, CONFIG.averages.longWindow, false),
316
+ awObj = getCircularAverageFromBuffer(store.longBuf.awa, CONFIG.averages.longWindow, true),
317
+ twObj = getCircularAverageFromBuffer(store.longBuf.twa, CONFIG.averages.longWindow, true),
318
+ twdObj = getCircularAverageFromBuffer(store.longBuf.twd, CONFIG.averages.longWindow, false);
315
319
 
316
320
  const upUI = (el, obj, isCompass = false) => {
317
321
  if (!obj || obj.val === null) { el.innerHTML = "---°"; el.classList.remove('unstable-data'); }
@@ -418,47 +422,72 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
418
422
  // 8. INTERAZIONI E RETE
419
423
  // ==========================================================================
420
424
  function toggleFocusMode(type, element) {
421
- const container = document.querySelector('.main-container'); const parentPanel = element.closest('.side-panel'); const isLeft = parentPanel.classList.contains('left-panel');
425
+ const container = document.querySelector('.main-container');
426
+
427
+ // Con la nuova struttura Flat HTML, non abbiamo più '.left-panel'.
428
+ // Dobbiamo determinare il lato guardando il tipo di box.
429
+ const leftBoxes = ['stw', 'sog', 'hdg', 'cog', 'tack'];
430
+ const isLeft = leftBoxes.includes(type);
431
+
422
432
  isFocusActive = !isFocusActive;
423
- if (isFocusActive) { container.classList.add('focus-active', isLeft ? 'focus-side-left' : 'focus-side-right'); parentPanel.classList.add('has-focus'); element.classList.add('is-focused'); }
424
- else { container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right'); document.querySelectorAll('.side-panel').forEach(p => p.classList.remove('has-focus')); document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused')); }
433
+
434
+ if (isFocusActive) {
435
+ container.classList.add('focus-active', isLeft ? 'focus-side-left' : 'focus-side-right');
436
+ element.classList.add('is-focused');
437
+ } else {
438
+ container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right');
439
+ document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused'));
440
+ }
425
441
  }
426
442
 
427
443
  ['stw', 'sog', 'tws', 'depth'].forEach(type => {
444
+ // Risale dal grafico al contenitore principale (funziona con la nuova struttura)
428
445
  const el = document.getElementById(type + '-graph').closest('.data-box');
429
446
  let lastTapTime = 0, tapTimeout, isLongPressActive = false;
430
- el.addEventListener('pointerdown', (e) => { isLongPressActive = false; pressTimer = setTimeout(() => { if (!isFocusActive) { isLongPressActive = true; toggleFocusMode(type, el); lastTapTime = 0; } }, 1000); });
431
- el.addEventListener('pointerup', (e) => {
432
- clearTimeout(pressTimer); if (isLongPressActive) return;
433
- const currentTime = new Date().getTime(), tapDelay = currentTime - lastTapTime;
434
-
435
- // RILEVAMENTO DOPPIO TAP (HERCULES)
436
- if (tapDelay < 300 && tapDelay > 0) {
437
- clearTimeout(tapTimeout); // Cancella l'azione del tap singolo (uscita dal focus)
438
-
439
- // Permette il toggle Hercules sempre, sia in vista normale che in Focus
440
- graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard';
441
- localStorage.setItem('mode_' + type, graphModes[type]);
442
-
447
+
448
+ el.addEventListener('pointerdown', (e) => {
449
+ isLongPressActive = false;
450
+ pressTimer = setTimeout(() => {
451
+ if (!isFocusActive) {
452
+ isLongPressActive = true;
453
+ toggleFocusMode(type, el);
443
454
  lastTapTime = 0;
444
455
  }
445
- // RILEVAMENTO TAP SINGOLO (ESCI DA FOCUS o CAMBIA SOG/VMG)
446
- else {
447
- lastTapTime = currentTime;
448
- tapTimeout = setTimeout(() => {
449
- // Se siamo in focus e clicchiamo il box ingrandito, usciamo
450
- if (isFocusActive && el.classList.contains('is-focused')) {
451
- toggleFocusMode(type, el);
452
- }
453
- // Se siamo in vista normale e clicchiamo SOG, cambiamo in VMG
454
- else if (!isFocusActive && type === 'sog') {
455
- displayModeSog = (displayModeSog === 'SOG') ? 'VMG' : 'SOG';
456
- el.style.backgroundColor = "rgba(0, 0, 0, 0.05)";
457
- setTimeout(() => el.style.backgroundColor = "", 150);
458
- }
459
- }, 250);
460
- }
461
- });
456
+ }, 1000);
457
+ });
458
+
459
+ el.addEventListener('pointerup', (e) => {
460
+ clearTimeout(pressTimer);
461
+ if (isLongPressActive) return; // Se era focus, ignora l'up
462
+
463
+ const currentTime = new Date().getTime(), tapDelay = currentTime - lastTapTime;
464
+
465
+ // DOPPIO TAP: Toggle Hercules Mode (Funziona anche in Focus)
466
+ if (tapDelay < 300 && tapDelay > 0) {
467
+ clearTimeout(tapTimeout); // Cancella l'uscita dal Focus
468
+ graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard';
469
+ localStorage.setItem('mode_' + type, graphModes[type]);
470
+ refreshGraph(type); // Mostra istantaneamente
471
+ lastTapTime = 0;
472
+ }
473
+ // TAP SINGOLO (con ritardo di 250ms per aspettare l'eventuale doppio tap)
474
+ else {
475
+ lastTapTime = currentTime;
476
+ tapTimeout = setTimeout(() => {
477
+ // Uscita Focus
478
+ if (isFocusActive && el.classList.contains('is-focused')) {
479
+ toggleFocusMode(type, el);
480
+ }
481
+ // Toggle SOG/VMG (solo se non in focus)
482
+ else if (!isFocusActive && type === 'sog') {
483
+ displayModeSog = (displayModeSog === 'SOG') ? 'VMG' : 'SOG';
484
+ el.style.backgroundColor = "rgba(0, 0, 0, 0.05)";
485
+ setTimeout(() => el.style.backgroundColor = "", 150);
486
+ }
487
+ }, 250); // Attesa critica per il doppio tap
488
+ }
489
+ });
490
+
462
491
  el.addEventListener('pointerleave', () => clearTimeout(pressTimer));
463
492
  });
464
493
 
package/index.html CHANGED
@@ -12,80 +12,54 @@
12
12
  <body>
13
13
 
14
14
  <div class="main-container">
15
-
16
- <!-- ======================================================= -->
17
- <!-- COLONNA SINISTRA: Dati Rotta e Velocità -->
18
- <!-- ======================================================= -->
19
- <div class="side-panel left-panel">
20
-
21
- <!-- STW: Velocità attraverso l'acqua -->
22
- <div class="data-box">
23
- <div class="label-row">
24
- <span class="label">STW</span>
25
- <span class="unit">kts</span>
26
- </div>
27
- <span class="value" id="stw">0.0</span>
28
- <div class="graph-wrapper">
29
- <svg class="sparkline" id="stw-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
30
- <div class="scale-labels" id="stw-scale"></div>
31
- </div>
32
- </div>
15
+ <!-- STATO CONNESSIONE -->
16
+ <div id="status" class="offline">OFFLINE</div>
33
17
 
34
- <!-- SOG / VMG: Velocità sul fondo o verso il vento -->
35
- <div class="data-box">
36
- <div class="label-row">
37
- <span class="label" id="sog-vmg-label">SOG</span>
38
- <span class="unit">kts</span>
39
- </div>
40
- <span class="value" id="sog">0.0</span>
41
- <div class="graph-wrapper">
42
- <svg class="sparkline" id="sog-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
43
- <div class="scale-labels" id="sog-scale"></div>
44
- </div>
18
+ <!-- COLONNA SINISTRA -->
19
+ <div class="data-box box-stw">
20
+ <div class="label-row"><span class="label">STW</span><span class="unit">kts</span></div>
21
+ <span class="value" id="stw">0.0</span>
22
+ <div class="graph-wrapper">
23
+ <svg class="sparkline" id="stw-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
24
+ <div class="scale-labels" id="stw-scale"></div>
45
25
  </div>
26
+ </div>
46
27
 
47
- <!-- HEADING: Prua Bussola (Media 60s) -->
48
- <div class="data-box">
49
- <div class="label-row">
50
- <span class="label">HEADING (MEAN)</span>
51
- </div>
52
- <span class="value value-large" id="hdg">000&deg;</span>
28
+ <div class="data-box box-sog">
29
+ <div class="label-row"><span class="label" id="sog-vmg-label">SOG</span><span class="unit">kts</span></div>
30
+ <span class="value" id="sog">0.0</span>
31
+ <div class="graph-wrapper">
32
+ <svg class="sparkline" id="sog-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
33
+ <div class="scale-labels" id="sog-scale"></div>
53
34
  </div>
35
+ </div>
54
36
 
55
- <!-- COG: Rotta sul fondo (Media 60s) -->
56
- <div class="data-box">
57
- <div class="label-row">
58
- <span class="label">COG (MEAN)</span>
59
- </div>
60
- <span class="value value-large" id="cog">000&deg;</span>
61
- </div>
37
+ <div class="data-box box-hdg">
38
+ <div class="label-row"><span class="label">HEADING (MEAN)</span></div>
39
+ <span class="value-large" id="hdg">000&deg;</span>
40
+ </div>
41
+
42
+ <div class="data-box box-cog">
43
+ <div class="label-row"><span class="label">COG (MEAN)</span></div>
44
+ <span class="value-large" id="cog">000&deg;</span>
45
+ </div>
62
46
 
63
- <!-- TACK: Calcolo mure opposte (Previsione) -->
64
- <div class="data-box">
65
- <div class="label-row">
66
- <span class="label">TACK</span>
47
+ <div class="data-box box-tack">
48
+ <div class="label-row"><span class="label">TACK</span></div>
49
+ <div class="dual-value-container">
50
+ <div class="dual-value-col tack-hdg-col">
51
+ <span class="dual-label">HDG</span>
52
+ <span class="value dual-val" id="tack-hdg">---&deg;</span>
67
53
  </div>
68
- <div class="dual-value-container">
69
- <div class="dual-value-col">
70
- <span class="dual-label">HDG</span>
71
- <span class="value dual-val" id="tack-hdg">---&deg;</span>
72
- </div>
73
- <div class="dual-value-col right-col">
74
- <span class="dual-label">COG</span>
75
- <span class="value dual-val" id="tack-cog">---&deg;</span>
76
- </div>
54
+ <div class="dual-value-col tack-cog-col">
55
+ <span class="dual-label">COG</span>
56
+ <span class="value dual-val" id="tack-cog">---&deg;</span>
77
57
  </div>
78
58
  </div>
79
-
80
59
  </div>
81
60
 
82
- <!-- ======================================================= -->
83
- <!-- CENTRO: Strumento Vento SVG (Pannello di Controllo) -->
84
- <!-- ======================================================= -->
85
- <div class="center-panel">
86
- <!-- Stato Connessione integrato nell'angolo del pannello centrale -->
87
- <div id="status" class="offline">OFFLINE</div>
88
-
61
+ <!-- BUSSOLA CENTRALE -->
62
+ <div class="box-gauge">
89
63
  <svg id="wind-gauge" viewBox="35 38 330 395" preserveAspectRatio="xMidYMid meet">
90
64
  <defs>
91
65
  <clipPath id="boat-clip"><circle cx="200" cy="200" r="50" /></clipPath>
@@ -116,26 +90,21 @@
116
90
  <circle cx="200" cy="200" r="160" fill="#050505" />
117
91
  <circle cx="200" cy="200" r="125" fill="#121212" />
118
92
 
119
- <path d="M 82.0 101.0 A 154 154 0 0 1 142.3 57.2" fill="none" stroke="#ff0000" stroke-width="12" opacity="1"/>
120
- <path d="M 257.7 57.2 A 154 154 0 0 1 318.0 101.0" fill="none" stroke="#00ff00" stroke-width="12" opacity="1"/>
121
- <path d="M 265.1 339.6 A 154 154 0 0 1 134.9 339.6" fill="none" stroke="#ff8800" stroke-width="12" opacity="1"/>
93
+ <path d="M 82.0 101.0 A 154 154 0 0 1 142.3 57.2" fill="none" stroke="#ff0000" stroke-width="12" />
94
+ <path d="M 257.7 57.2 A 154 154 0 0 1 318.0 101.0" fill="none" stroke="#00ff00" stroke-width="12" />
95
+ <path d="M 265.1 339.6 A 154 154 0 0 1 134.9 339.6" fill="none" stroke="#ff8800" stroke-width="12" />
122
96
 
123
97
  <g id="ticks"></g>
124
98
 
125
- <!-- Etichette riposizionate con raggio ridotto per un contatto perfetto con la ghiera -->
126
99
  <g id="tick-labels" fill="#bbb" text-anchor="middle" dominant-baseline="middle" font-family="Arial" font-weight="bold">
127
- <!-- Etichette Principali (Distanza 126px) -->
128
100
  <text font-size="16" transform="translate(200, 74)">0</text>
129
101
  <text font-size="16" transform="translate(326, 200) rotate(90)">90</text>
130
102
  <text font-size="16" transform="translate(74, 200) rotate(-90)">90</text>
131
103
  <text font-size="16" transform="translate(200, 326) rotate(180)">180</text>
132
-
133
- <!-- Etichette Intermedie (Distanza 125px) -->
134
104
  <text font-size="11" transform="translate(262.5, 91.7) rotate(30)">30</text>
135
105
  <text font-size="11" transform="translate(308.3, 137.5) rotate(60)">60</text>
136
106
  <text font-size="11" transform="translate(308.3, 262.5) rotate(120)">120</text>
137
107
  <text font-size="11" transform="translate(262.5, 308.3) rotate(150)">150</text>
138
-
139
108
  <text font-size="11" transform="translate(137.5, 91.7) rotate(-30)">30</text>
140
109
  <text font-size="11" transform="translate(91.7, 137.5) rotate(-60)">60</text>
141
110
  <text font-size="11" transform="translate(91.7, 262.5) rotate(-120)">120</text>
@@ -149,131 +118,92 @@
149
118
  <circle id="fullscreen-hotspot" cx="200" cy="200" r="55" fill="#181818" stroke="#333" stroke-width="1" filter="url(#center-glow)" cursor="pointer" />
150
119
 
151
120
  <path id="boat-icon" d="M200,150 Q165,185 170,250 Q165,190 200,173 Q235,190 230,250 Q235,185 200,150 Z"
152
- fill="url(#axiom-grad)"
153
- transform="translate(0, 5)"
154
- clip-path="url(#boat-clip)"
155
- style="pointer-events: none;" />
121
+ fill="url(#axiom-grad)" transform="translate(0, 5)" clip-path="url(#boat-clip)" style="pointer-events: none;" />
156
122
 
157
123
  <g id="aws-display-group" transform="translate(200, 265)">
158
124
  <text x="0" y="0" fill="#777" font-size="10" font-weight="bold" text-anchor="middle" text-transform="uppercase">Apparent Wind kts</text>
159
125
  <text id="aws-val-svg" x="0" y="42" fill="#fff" font-size="52" font-weight="bold" text-anchor="middle">0.0</text>
160
126
  </g>
161
127
 
162
- <!-- Lancetta Apparente (AWA) - Versione allungata -->
163
128
  <g id="awa-pointer" transform="rotate(0, 200, 200)" opacity="0.9">
164
- <!-- Ho portato 75 -> 70 (fuori) e 145 -> 155 (dentro) -->
165
- <path d="M 200,70 L 213,95 L 200,145 L 187,95 Z"
166
- fill="#ff8c00" stroke="#000" stroke-width="1" />
129
+ <path d="M 200,70 L 213,95 L 200,145 L 187,95 Z" fill="#ff8c00" stroke="#000" stroke-width="1" />
167
130
  <text x="200" y="90" fill="#000" font-size="10" font-weight="900" text-anchor="middle" font-family="Arial Black">A</text>
168
131
  </g>
169
132
 
170
- <!-- Lancetta Reale (TWA) - FORMA A GOCCIA (Secondaria/Piccola) -->
171
133
  <g id="twa-pointer" transform="rotate(0, 200, 200)" opacity="0.9">
172
- <!-- Goccia piccola: inizia a y=92, punta a y=128 -->
173
134
  <path d="M 200,92 A 8,8 0 0 1 208,100 C 208,108 200,128 200,128 C 200,128 192,108 192,100 A 8,8 0 0 1 200,92 Z"
174
135
  fill="#ffff00" stroke="#000" stroke-width="0.8" />
175
136
  <text x="200" y="106" fill="#000" font-size="9" font-weight="900" text-anchor="middle" font-family="Arial Black">T</text>
176
-
177
- <!-- Pallini Trend (Sempre agganciati alla rotazione del TWA) -->
178
- <circle id="trend-gauge-cw" cx="215" cy="110" r="4" fill="#ffffff" />
179
- <circle id="trend-gauge-ccw" cx="185" cy="110" r="4" fill="#ffffff" />
137
+ <circle id="trend-gauge-cw" cx="215" cy="110" r="4" fill="#bbb" />
138
+ <circle id="trend-gauge-ccw" cx="185" cy="110" r="4" fill="#bbb" />
180
139
  </g>
181
140
 
182
141
  <g transform="translate(75, 395)">
183
142
  <text x="125" y="-12" id="leeway-val" fill="#aaa" font-size="11" text-anchor="middle" font-weight="bold">LEEWAY: 0.0&deg;</text>
184
143
  <rect x="0" y="0" width="250" height="12" fill="#222" rx="3" />
185
144
  <rect x="0" y="0" width="250" height="12" fill="url(#leeway-grad)" clip-path="url(#leeway-clip)" rx="3" />
186
-
187
- <!-- RIPRISTINO SCALA: Tacche ogni 5 gradi (31.25px per intervallo) -->
188
145
  <g stroke="#555" stroke-width="1">
189
- <line x1="0" y1="-2" x2="0" y2="14" /> <!-- -20° -->
190
- <line x1="31.25" y1="2" x2="31.25" y2="10" /> <!-- -15° -->
191
- <line x1="62.5" y1="2" x2="62.5" y2="10" /> <!-- -10° -->
192
- <line x1="93.75" y1="3" x2="93.75" y2="9" /> <!-- -5° -->
193
- <line x1="125" y1="-2" x2="125" y2="14" /> <!-- 0° centrale -->
194
- <line x1="156.25" y1="3" x2="156.25" y2="9" /> <!-- +5° -->
195
- <line x1="187.5" y1="2" x2="187.5" y2="10" /> <!-- +10° -->
196
- <line x1="218.75" y1="2" x2="218.75" y2="10" /> <!-- +15° -->
197
- <line x1="250" y1="-2" x2="250" y2="14" /> <!-- +20° -->
146
+ <line x1="0" y1="-2" x2="0" y2="14" /><line x1="62.5" y1="2" x2="62.5" y2="10" />
147
+ <line x1="125" y1="-2" x2="125" y2="14" /><line x1="187.5" y1="2" x2="187.5" y2="10" />
148
+ <line x1="250" y1="-2" x2="250" y2="14" />
198
149
  </g>
199
-
200
- <!-- Etichette numeriche riallineate -->
201
150
  <g fill="#555" font-size="8" text-anchor="middle" font-weight="bold">
202
- <text x="0" y="24">-20&deg;</text>
203
- <text x="62.5" y="24">-10</text>
204
- <text x="125" y="24">0&deg;</text>
205
- <text x="187.5" y="24">10</text>
151
+ <text x="0" y="24">-20&deg;</text><text x="62.5" y="24">-10</text>
152
+ <text x="125" y="24">0&deg;</text><text x="187.5" y="24">10</text>
206
153
  <text x="250" y="24">20&deg;</text>
207
154
  </g>
208
155
  </g>
209
156
  </svg>
210
157
  </div>
211
158
 
212
- <!-- ======================================================= -->
213
- <!-- COLONNA DESTRA: Profondità e Vento Reale -->
214
- <!-- ======================================================= -->
215
- <div class="side-panel right-panel">
216
-
217
- <div class="data-box">
218
- <div class="label-row">
219
- <span class="unit">m</span>
220
- <span class="label">DEPTH</span>
221
- </div>
222
- <span class="value" id="depth">--.-</span>
223
- <div class="graph-wrapper">
224
- <div class="scale-labels" id="depth-scale"></div>
225
- <svg class="sparkline" id="depth-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
226
- </div>
159
+ <!-- COLONNA DESTRA -->
160
+ <!-- NOTA: Etichetta a SX e Unità a DX nel codice, il CSS usa row-reverse per specchiarli sul bordo -->
161
+ <div class="data-box box-depth">
162
+ <div class="label-row"><span class="label">DEPTH</span><span class="unit">m</span></div>
163
+ <span class="value" id="depth">--.-</span>
164
+ <div class="graph-wrapper">
165
+ <div class="scale-labels" id="depth-scale"></div>
166
+ <svg class="sparkline" id="depth-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
227
167
  </div>
168
+ </div>
228
169
 
229
- <div class="data-box">
230
- <div class="label-row">
231
- <span class="unit">kts</span>
232
- <span class="label">TWS</span>
233
- </div>
234
- <span class="value" id="tws">0.0</span>
235
- <div class="graph-wrapper">
236
- <div class="scale-labels" id="tws-scale"></div>
237
- <svg class="sparkline" id="tws-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
238
- </div>
170
+ <div class="data-box box-tws">
171
+ <div class="label-row"><span class="label">TWS</span><span class="unit">kts</span></div>
172
+ <span class="value" id="tws">0.0</span>
173
+ <div class="graph-wrapper">
174
+ <div class="scale-labels" id="tws-scale"></div>
175
+ <svg class="sparkline" id="tws-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
239
176
  </div>
177
+ </div>
240
178
 
241
- <div class="data-box">
242
- <div class="label-row">
243
- <span class="label">TWA (MEAN)</span>
244
- </div>
245
- <span class="value value-large" id="twa-avg">---&deg;</span>
246
- </div>
179
+ <div class="data-box box-twa">
180
+ <div class="label-row"><span class="label">TWA (MEAN)</span></div>
181
+ <span class="value-large" id="twa-avg">---&deg;</span>
182
+ </div>
247
183
 
248
- <div class="data-box">
249
- <div class="label-row">
250
- <span class="label">AWA (MEAN)</span>
251
- </div>
252
- <span class="value value-large" id="awa-avg">---&deg;</span>
253
- </div>
184
+ <div class="data-box box-awa">
185
+ <div class="label-row"><span class="label">AWA (MEAN)</span></div>
186
+ <span class="value-large" id="awa-avg">---&deg;</span>
187
+ </div>
254
188
 
255
- <div class="data-box">
256
- <div class="label-row">
257
- <span class="label">TWD (MEAN)</span>
258
- </div>
259
- <div class="value-with-compass">
260
- <svg class="mini-compass" viewBox="0 0 40 40">
261
- <circle cx="20" cy="20" r="18.5" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="1.2"/>
262
- <text x="20" y="8" fill="#e74c3c" font-size="5.5" text-anchor="middle" font-weight="900">N</text>
263
- <text x="20" y="36" fill="rgba(255,255,255,0.3)" font-size="5.5" text-anchor="middle" font-weight="900">S</text>
264
- <g id="twd-boat-wrap" transform="rotate(0, 20, 20)">
265
- <path d="M 20,17 L 17,26 L 20,24.5 L 23,26 Z" fill="white" opacity="0.2" />
266
- </g>
267
- <g id="twd-arrow" transform="rotate(0, 20, 20)">
268
- <path id="twd-wind-chevron" d="M 17,5 L 20,7.5 L 23,5" fill="none" stroke="#ffff00" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" />
269
- <circle id="trend-dot-cw" cx="24" cy="7.5" r="1.5" fill="#ffffff" opacity="0.3" />
270
- <circle id="trend-dot-ccw" cx="16" cy="7.5" r="1.5" fill="#ffffff" opacity="0.3" />
271
- </g>
272
- </svg>
273
- <span class="value value-large" id="twd-avg">---&deg;</span>
274
- </div>
189
+ <div class="data-box box-twd">
190
+ <div class="label-row"><span class="label">TWD (MEAN)</span></div>
191
+ <div class="value-with-compass">
192
+ <svg class="mini-compass" viewBox="0 0 40 40">
193
+ <circle cx="20" cy="20" r="18.5" fill="none" stroke="rgba(0,0,0,0.1)" stroke-width="1.2"/>
194
+ <text x="20" y="8" fill="#e74c3c" font-size="5.5" text-anchor="middle" font-weight="900">N</text>
195
+ <text x="20" y="36" fill="#000" font-size="5.5" text-anchor="middle" font-weight="900">S</text>
196
+ <g id="twd-boat-wrap" transform="rotate(0, 20, 20)">
197
+ <path d="M 20,17 L 17,26 L 20,24.5 L 23,26 Z" fill="#000" opacity="0.3" />
198
+ </g>
199
+ <g id="twd-arrow" transform="rotate(0, 20, 20)">
200
+ <path id="twd-wind-chevron" d="M 17,5 L 20,7.5 L 23,5" fill="none" stroke="#000" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" />
201
+ <circle id="trend-dot-cw" cx="24" cy="7.5" r="1.5" fill="#bbb" />
202
+ <circle id="trend-dot-ccw" cx="16" cy="7.5" r="1.5" fill="#bbb" />
203
+ </g>
204
+ </svg>
205
+ <span class="value-large" id="twd-avg">---&deg;</span>
275
206
  </div>
276
-
277
207
  </div>
278
208
  </div>
279
209
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "2.0.23",
3
+ "version": "3.0.1",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/style.css CHANGED
@@ -2,235 +2,351 @@
2
2
  1. BASE E RESET DI SISTEMA
3
3
  ========================================================================== */
4
4
  body {
5
- background-color: #fff; /* Day Mode Default */
6
- color: #000; /* Day Mode Default */
5
+ background-color: #fff;
6
+ color: #000;
7
7
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
8
- margin: 0;
9
- padding: 0;
10
- height: 100vh;
11
- width: 100vw;
8
+ margin: 0; padding: 0;
9
+ height: 100vh; width: 100vw;
12
10
  overflow: hidden;
13
- /* Inibisce i gesti di sistema per favorire Long Press e Swipe della dashboard */
14
- -webkit-touch-callout: none;
15
- -webkit-user-select: none;
16
- user-select: none;
17
- touch-action: none;
11
+ -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; touch-action: none;
18
12
  }
19
13
 
20
14
  /* ==========================================================================
21
- 2. LAYOUT PRINCIPALE (LIQUID GRID)
15
+ 2. LAYOUT PRINCIPALE (GRID AREAS)
22
16
  ========================================================================== */
23
17
  .main-container {
24
18
  display: grid;
25
- width: 100%;
26
- height: 100%;
27
- padding: 5px;
28
- box-sizing: border-box;
29
- gap: 8px;
30
- /* Rapporto Standard: Lati flessibili, Centro bilanciato a 1.5fr */
31
- grid-template-columns: minmax(180px, 1fr) minmax(auto, 1.5fr) minmax(180px, 1fr);
32
- grid-template-rows: 100%;
19
+ width: 100%; height: 100%;
20
+ padding: 5px; box-sizing: border-box; gap: 8px;
21
+ grid-template-areas:
22
+ "stw gauge depth"
23
+ "sog gauge tws"
24
+ "hdg gauge twa"
25
+ "cog gauge awa"
26
+ "tack gauge twd";
27
+ grid-template-rows: 1.75fr 1.75fr 0.75fr 0.75fr 1.25fr;
28
+ grid-template-columns: 1fr 1.5fr 1fr;
33
29
  transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
34
- justify-content: stretch;
35
- }
36
-
37
- .side-panel {
38
- display: flex;
39
- flex-direction: column;
40
- background: rgba(0, 0, 0, 0.03); /* Sfondo Day Mode */
41
- border-radius: 12px;
42
- height: 100%;
43
- overflow: hidden;
44
- gap: 2px; /* Massimizza lo spazio per i 5 box verticali */
45
30
  }
46
31
 
47
- .left-panel { grid-column: 1; align-items: flex-start; }
48
- .right-panel { grid-column: 3; align-items: flex-end; } /* Forza i box a destra */
49
-
50
- .center-panel {
51
- grid-column: 2;
52
- position: relative; /* Indispensabile per posizionare l'etichetta STATUS in alto a dx */
53
- display: flex;
54
- flex-direction: column;
55
- align-items: center;
56
- justify-content: center;
57
- height: 100%;
58
- overflow: hidden;
59
- }
32
+ .box-stw { grid-area: stw; }
33
+ .box-sog { grid-area: sog; }
34
+ .box-hdg { grid-area: hdg; }
35
+ .box-cog { grid-area: cog; }
36
+ .box-tack { grid-area: tack; }
37
+ .box-gauge { grid-area: gauge; display: flex; align-items: center; justify-content: center; position: relative; }
38
+ .box-depth { grid-area: depth; }
39
+ .box-tws { grid-area: tws; }
40
+ .box-twa { grid-area: twa; }
41
+ .box-awa { grid-area: awa; }
42
+ .box-twd { grid-area: twd; }
60
43
 
61
44
  /* ==========================================================================
62
- 3. DATA-BOX E TIPOGRAFIA (UNITA' ELASTICHE E NUOVE PROPORZIONI)
45
+ 3. DATA-BOX: STRUTTURA E ALLINEAMENTO (Bordi Schermo & Display Curvi)
63
46
  ========================================================================== */
64
47
  .data-box {
65
- position: relative;
66
- border-bottom: 1px solid #eee; /* Day Mode Border */
67
- padding: 2px 8px;
68
- display: flex;
69
- flex-direction: column;
70
- width: 100%;
71
- box-sizing: border-box;
72
- container-type: size; /* Permette l'uso di cqh per i font */
73
- min-height: 0;
74
- overflow: hidden;
48
+ position: relative; border-bottom: 1px solid #eee;
49
+ /* Padding alto (1.2rem) per non far toccare il numero al titolo assoluto */
50
+ padding: 1.2rem 8px 4px 8px;
51
+ display: flex; flex-direction: column; width: 100%;
52
+ box-sizing: border-box; container-type: size;
53
+ min-height: 0; overflow: hidden;
54
+ background: rgba(0, 0, 0, 0.03); border-radius: 12px;
75
55
  transition: background-color 0.2s ease;
56
+ /* Forza la centratura verticale del valore per display curvi */
57
+ justify-content: center !important;
76
58
  }
77
59
 
78
- /* RIPRISTINO ALLINEAMENTO SPECULARE RIGIDO */
79
- .left-panel .data-box { align-items: flex-start !important; text-align: left !important; }
80
- .right-panel .data-box { align-items: flex-end !important; text-align: right !important; }
81
-
82
- /* Inversione etichette colonna destra per aderenza al bordo schermo */
83
- .right-panel .label-row { flex-direction: row-reverse !important; }
84
-
85
- /* NUOVE PROPORZIONI VERTICALI 1.75 / 0.75 / 1.25 */
86
- .data-box:nth-child(1), .data-box:nth-child(2) { flex: 1.75 1 0px; }
87
- .data-box:nth-child(3), .data-box:nth-child(4) { flex: 0.75 0.75 0px; }
88
- .data-box:nth-child(5) { flex: 1.25 1 0px; border-bottom: none; }
60
+ /* Titoli ancorati sempre in alto */
61
+ .label-row {
62
+ position: absolute; top: 4px; left: 8px; right: 8px;
63
+ display: flex; align-items: baseline; z-index: 10;
64
+ }
89
65
 
90
- .label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; width: 100%; }
91
66
  .label { color: #888; font-size: 0.65rem; font-weight: bold; text-transform: uppercase; }
92
67
  .unit { color: #aaa; font-size: 0.6rem; font-weight: bold; }
93
68
 
94
- /* Font dinamico per i valori: massimo 22% dell'altezza del box */
95
- .value {
96
- color: #000;
97
- font-size: clamp(1.2rem, 22cqh, 3rem);
98
- font-weight: 600;
99
- line-height: 0.9;
100
- letter-spacing: -1px;
101
- padding-bottom: 5px;
69
+ /* --- ALLINEAMENTO SPECULARE RIGIDO --- */
70
+
71
+ /* COLONNA SINISTRA: Titolo a SX (bordo), Unità a DX */
72
+ .box-stw, .box-sog, .box-hdg, .box-cog, .box-tack { align-items: flex-start !important; text-align: left !important; }
73
+ .box-stw .label-row, .box-sog .label-row, .box-hdg .label-row, .box-cog .label-row, .box-tack .label-row {
74
+ flex-direction: row !important; justify-content: space-between !important;
102
75
  }
103
76
 
104
- /* Centratura verticale automatica per i box senza grafico */
105
- .value-large, .dual-value-container, .value-with-compass {
106
- margin-top: auto;
107
- margin-bottom: auto;
77
+ /* COLONNA DESTRA: Titolo a DX (bordo), Unità a SX */
78
+ .box-depth, .box-tws, .box-twa, .box-awa, .box-twd { align-items: flex-end !important; text-align: right !important; }
79
+ .box-depth .label-row, .box-tws .label-row, .box-twa .label-row, .box-awa .label-row, .box-twd .label-row {
80
+ flex-direction: row-reverse !important; justify-content: space-between !important;
108
81
  }
109
82
 
110
- .value-large { font-size: clamp(1.5rem, 35cqh, 4.5rem); line-height: 0.85; }
83
+ /* ==========================================================================
84
+ 4. TIPOGRAFIA GERARCHICA (REGOLE BASE)
85
+ ========================================================================== */
86
+ .value { color: #000; font-size: clamp(1.2rem, 32cqw, 3.5rem); font-weight: 600; line-height: 0.9; letter-spacing: -1.5px; }
111
87
 
112
- /* --- BUSSOLA TWD (FIX ALLINEAMENTO) --- */
113
- .value-with-compass {
114
- display: flex;
115
- justify-content: space-between; /* Bussola a SX, Valore a DX */
116
- align-items: center;
117
- width: 100%;
118
- align-self: stretch; /* Impedisce al pannello DX di schiacciare il widget a destra */
119
- gap: 5px;
88
+ /* Valore MASSIVO (Dati reali): 55% altezza box, larghezza protetta a 22% schermo */
89
+ .value-large {
90
+ font-size: clamp(1.5rem, 55cqh, 22cqw) !important;
91
+ font-weight: 600; line-height: 0.8; margin: 0 !important;
120
92
  }
121
93
 
122
- /* TACK (Mure opposte) Layout */
123
- .dual-value-container { display: flex; justify-content: space-between; width: 100%; }
124
- .dual-value-col { display: flex; flex-direction: column; width: 48%; }
125
- .dual-label { color: #888; font-size: 0.55rem; font-weight: bold; text-transform: uppercase; margin-bottom: 2px; }
126
- .value.dual-val { font-size: clamp(1rem, 22cqh, 2rem); padding-bottom: 0; line-height: 0.9; }
94
+ /* BOX TWD: Bussola a SX, Valore a DX, centrati verticalmente */
95
+ .value-with-compass { display: flex; flex-direction: row; justify-content: space-between; align-items: center; width: 100%; height: 85%; }
96
+ .box-twd .mini-compass { height: 85cqh !important; width: auto !important; aspect-ratio: 1/1; flex-shrink: 0; }
97
+ .box-twd #twd-avg { font-size: clamp(1.5rem, 55cqh, 20cqw) !important; text-align: right; line-height: 1; }
98
+
99
+ /* BOX TACK: HDG a SX, COG a DX, Titoli sopra (Default Landscape/Portrait) */
100
+ .box-tack .dual-value-container { display: flex; flex-direction: row; justify-content: space-between; align-items: center; width: 100%; height: 100%; }
101
+ .box-tack .dual-value-col { display: flex; flex-direction: column; justify-content: center; }
102
+ .box-tack .dual-value-col:first-child { align-items: flex-start; }
103
+ .box-tack .dual-value-col:last-child { align-items: flex-end; }
104
+ .box-tack .dual-label { color: #888; font-size: 0.5rem; font-weight: 800; margin-bottom: -2px; }
105
+ .box-tack .value.dual-val { font-size: clamp(1rem, 35cqh, 12cqw) !important; line-height: 0.9; }
106
+
107
+ /* Tag HERCULES: Allineato all'unità (lato interno schermo) */
108
+ .box-hercules { background: rgba(255, 0, 0, 0.05) !important; }
109
+ .box-hercules .unit::before { font-size: 7px; color: #ff4444; font-weight: 900; letter-spacing: 1px; text-transform: uppercase; content: "HERCULES "; }
127
110
 
128
111
  /* ==========================================================================
129
- 4. TACTICAL FOCUS MODE (DUAL SCREEN 60/40)
112
+ 5. TACTICAL FOCUS MODE
130
113
  ========================================================================== */
131
- .focus-active .side-panel:not(.has-focus) { display: none !important; }
132
-
133
- /* Split: 60% per il grafico scelto, 40% per il vento centrale */
134
- .focus-active.focus-side-left { grid-template-columns: 3fr 2fr !important; }
135
- .focus-active.focus-side-left .side-panel.has-focus { grid-column: 1 !important; }
136
- .focus-active.focus-side-left .center-panel { grid-column: 2 !important; justify-content: center !important; align-items: center !important; }
114
+ .focus-active .data-box:not(.is-focused),
115
+ .focus-active .box-gauge { display: none !important; }
137
116
 
138
- .focus-active.focus-side-right { grid-template-columns: 2fr 3fr !important; }
139
- .focus-active.focus-side-right .center-panel { grid-column: 1 !important; justify-content: center !important; align-items: center !important; }
140
- .focus-active.focus-side-right .side-panel.has-focus { grid-column: 2 !important; }
117
+ .focus-active.focus-side-left {
118
+ grid-template-columns: 3fr 2fr !important;
119
+ grid-template-areas: "focused gauge" !important;
120
+ grid-template-rows: 100% !important;
121
+ }
122
+ .focus-active.focus-side-right {
123
+ grid-template-columns: 2fr 3fr !important;
124
+ grid-template-areas: "gauge focused" !important;
125
+ grid-template-rows: 100% !important;
126
+ }
141
127
 
142
- /* Espansione box e tipografia massiccia in Focus Mode */
143
- .focus-active .has-focus .data-box:not(.is-focused) { display: none !important; }
144
- .focus-active .has-focus .data-box.is-focused {
128
+ .focus-active .is-focused {
129
+ grid-area: focused !important;
145
130
  height: 100vh !important;
146
- border: none;
147
- background: rgba(0, 0, 0, 0.02);
131
+ background: rgba(0, 0, 0, 0.02) !important;
148
132
  padding: 2vw 3vw !important;
149
- display: flex;
150
- flex-direction: column;
133
+ justify-content: center !important;
134
+ }
135
+
136
+ /* PRIORITÀ COLORE HERCULES IN FOCUS:
137
+ Forza lo sfondo rosso anche quando il box è ingrandito */
138
+ .focus-active .is-focused.box-hercules {
139
+ background: rgba(255, 0, 0, 0.12) !important;
140
+ box-shadow: inset 0 0 30px rgba(255, 0, 0, 0.1);
151
141
  }
152
142
 
153
- .focus-active .is-focused .value { font-size: clamp(4rem, 25cqh, 10rem) !important; margin-top: 15px; }
154
- .focus-active .is-focused .scale-labels { font-size: clamp(14px, 1.8vw, 22px) !important; min-width: 50px !important; }
155
- .focus-active .is-focused .label-row .label { font-size: 2rem !important; }
156
- .focus-active .is-focused .label-row .unit { font-size: 2rem !important; }
157
- .focus-active .is-focused .sparkline path { stroke-width: 2.5px !important; }
143
+ .focus-active .is-focused .value { font-size: clamp(4rem, 25cqh, 15rem) !important; margin-top: 20px; }
144
+ .focus-active .box-gauge { display: flex !important; grid-area: gauge !important; }
158
145
 
159
- .focus-active .center-panel svg#wind-gauge { max-width: 95% !important; max-height: 85vh !important; }
146
+ /* ==========================================================================
147
+ 6. VIEWPORT QUADRATA (U-SHAPE) - LOGICA ANTI-COLLISIONE
148
+ ========================================================================== */
149
+ @media screen and (min-aspect-ratio: 0.86/1) and (max-aspect-ratio: 1.25/1) {
150
+ .main-container {
151
+ grid-template-columns: 1fr 1fr 1fr 1fr !important;
152
+ grid-template-areas:
153
+ "stw gauge gauge depth"
154
+ "sog gauge gauge tws"
155
+ "hdg gauge gauge twa"
156
+ "cog tack twd awa" !important;
157
+ grid-template-rows: 1.75fr 1.75fr 0.75fr 1.25fr !important;
158
+ }
159
+
160
+ /* Sfondo per distinguere la riga tattica inferiore */
161
+ .box-cog, .box-tack, .box-twd, .box-awa {
162
+ border-top: 1px solid #eee;
163
+ background: rgba(0, 0, 0, 0.04) !important;
164
+ }
165
+
166
+ /* --- TACK: Impilamento Verticale Rigido in U-Shape --- */
167
+ .box-tack .dual-value-container {
168
+ flex-direction: column !important; /* Forza HDG sopra COG */
169
+ justify-content: center !important;
170
+ align-items: flex-start !important; /* Tutto a sinistra */
171
+ gap: 8px !important; /* Spazio tra i due blocchi */
172
+ }
173
+
174
+ .box-tack .dual-value-col {
175
+ display: flex !important;
176
+ flex-direction: column !important; /* Forza Titolo sopra Numero */
177
+ align-items: flex-start !important;
178
+ text-align: left !important;
179
+ width: 100% !important;
180
+ }
181
+
182
+ /* Font calibrato per non uscire dal box stretto */
183
+ .box-tack .value.dual-val {
184
+ font-size: clamp(1rem, 30cqh, 15cqw) !important;
185
+ line-height: 1 !important;
186
+ }
187
+
188
+ .box-tack .dual-label {
189
+ margin-bottom: 0px !important;
190
+ }
191
+
192
+ /* --- TWD: Calibrazione per box stretto --- */
193
+ .box-twd .value-with-compass { justify-content: center; gap: 5px; }
194
+ .box-twd .mini-compass { height: 60cqh !important; }
195
+ .box-twd #twd-avg { font-size: clamp(1.2rem, 50cqh, 20cqw) !important; }
196
+
197
+ /* NOTA: COG e AWA non vengono toccati qui, così restano massivi
198
+ ereditando la regola generale della Sezione 4 */
199
+ }
160
200
 
161
201
  /* ==========================================================================
162
- 5. GRAFICI, SCALE E HERCULES MODE
202
+ 7. GRAFICI, SCALE E TAG HERCULES
163
203
  ========================================================================== */
164
204
  .graph-wrapper {
165
- position: relative; width: 100%; flex-grow: 1; min-height: 0; margin-top: 4px;
166
- display: flex; align-items: stretch; background: rgba(0, 0, 0, 0.04);
167
- border-radius: 4px; gap: 0px !important;
205
+ position: relative;
206
+ width: 100%;
207
+ flex-grow: 1;
208
+ min-height: 0;
209
+ margin-top: 5px;
210
+ display: flex;
211
+ flex-direction: row; /* Forza l'allineamento orizzontale dei componenti */
212
+ align-items: stretch;
213
+ background: rgba(0, 0, 0, 0.04);
214
+ border-radius: 4px;
168
215
  }
169
216
 
170
- .left-panel .graph-wrapper { margin-right: -6px; }
171
- .right-panel .graph-wrapper { margin-left: -6px; }
217
+ .sparkline {
218
+ flex-grow: 1;
219
+ height: 100%;
220
+ width: 100% !important;
221
+ background: transparent !important;
222
+ }
172
223
 
173
- .sparkline { flex-grow: 1; height: 100%; width: 100% !important; background: transparent !important; display: block; }
174
- .sparkline path, .sparkline line { stroke-width: 1px !important; transition: all 0.3s ease; }
224
+ /* Forza tutte le linee dei grafici (linee dati e griglie) a 1px per un look chirurgico */
225
+ .sparkline path,
226
+ .sparkline line {
227
+ stroke-width: 1px !important;
228
+ transition: all 0.3s ease;
229
+ }
175
230
 
176
231
  .scale-labels {
177
- display: flex; flex-direction: column; justify-content: space-between;
178
- font-size: 8px; color: #444; font-weight: bold; min-width: 12px;
179
- height: 100%; line-height: 1; padding: 0; border: none !important;
232
+ display: flex;
233
+ flex-direction: column;
234
+ justify-content: space-between;
235
+ font-size: 9px;
236
+ color: #666;
237
+ font-weight: 800;
238
+ min-width: 22px;
239
+ height: 100%;
240
+ line-height: 1;
241
+ z-index: 5;
180
242
  }
181
243
 
182
- .left-panel .scale-labels { order: 2; text-align: left; padding-left: 4px; }
183
- .left-panel .sparkline { order: 1; }
184
- .right-panel .scale-labels { order: 1; text-align: right; padding-right: 4px; }
185
- .right-panel .sparkline { order: 2; }
244
+ /* --- COLONNA SINISTRA (STW, SOG) --- */
245
+ /* Ordine desiderato: [Grafico] [Scala] -> Scala verso il centro schermo */
246
+ .box-stw .sparkline, .box-sog .sparkline {
247
+ order: 1;
248
+ }
249
+ .box-stw .scale-labels, .box-sog .scale-labels {
250
+ order: 2;
251
+ text-align: left;
252
+ padding-left: 4px;
253
+ }
186
254
 
187
- /* Hercules Mode Visuals */
188
- .line-hercules { filter: drop-shadow(0 0 5px #ff0000); stroke-width: 1.8px !important; }
255
+ /* --- COLONNA DESTRA (DEPTH, TWS) --- */
256
+ /* Ordine desiderato: [Scala] [Grafico] -> Scala verso il centro schermo */
257
+ .box-depth .scale-labels, .box-tws .scale-labels {
258
+ order: 1;
259
+ text-align: right;
260
+ padding-right: 4px;
261
+ }
262
+ .box-depth .sparkline, .box-tws .sparkline {
263
+ order: 2;
264
+ }
265
+
266
+ /* --- REGOLE HERCULES (Zoom) --- */
189
267
  .box-hercules { background: rgba(255, 0, 0, 0.05) !important; }
190
268
  .box-hercules .scale-labels { color: #ff5555; }
191
- .box-hercules .unit::before, .box-hercules .unit::after, .box-hercules .label::before {
192
- font-size: 7px; color: #ff4444; font-weight: 900; letter-spacing: 1px; text-transform: uppercase;
269
+ .box-hercules .unit::before {
270
+ font-size: 7px; color: #ff4444; font-weight: 900;
271
+ letter-spacing: 1px; text-transform: uppercase; content: "HERCULES ";
193
272
  }
194
- .left-panel .box-hercules .unit::before { content: "HERCULES "; }
195
- .right-panel .box-hercules .unit::after { content: " HERCULES"; }
196
- .right-panel .box-hercules .label:only-child::after { content: " HERCULES"; }
197
273
 
198
- /* Colori standard grafici */
199
- #stw-graph { stroke: #2ecc71; fill: rgba(46, 204, 113, 0.12); }
200
- #sog-graph { stroke: #f39c12; fill: rgba(243, 156, 18, 0.12); }
201
- #depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.12); }
202
- #tws-graph { stroke: #000000; fill: rgba(0, 0, 0, 0.08); }
274
+ /* Anche in modalità Hercules manteniamo 1px per coerenza, affidandoci al filtro per lo stacco visivo */
275
+ .line-hercules {
276
+ filter: drop-shadow(0 0 5px #ff0000);
277
+ stroke-width: 1px !important;
278
+ }
203
279
 
204
280
  /* ==========================================================================
205
- 6. WIDGETS E STATUS
281
+ 8. RESPONSIVE (PORTRAIT MODE)
206
282
  ========================================================================== */
207
- #status {
208
- position: absolute; top: 10px; right: 10px;
209
- font-size: 0.6rem; font-weight: 900;
210
- text-transform: uppercase; z-index: 1000;
211
- letter-spacing: 1px; background: rgba(0,0,0,0.05);
212
- padding: 2px 6px; border-radius: 4px;
283
+ @media screen and (max-aspect-ratio: 0.85/1) {
284
+ .main-container {
285
+ grid-template-areas:
286
+ "gauge gauge"
287
+ "stw depth"
288
+ "sog tws"
289
+ "hdg twa"
290
+ "cog awa"
291
+ "tack twd" !important;
292
+ grid-template-columns: 1fr 1fr !important;
293
+ grid-template-rows: 40vh 1.75fr 1.75fr 0.75fr 0.75fr 1.25fr !important;
294
+ }
295
+
296
+ /* Impedisce ai box di collassare in altezza in verticale */
297
+ .data-box { height: auto !important; }
298
+
299
+ /* --- FOCUS MODE IN VERTICALE (STACKED) --- */
300
+ /* Quando siamo in verticale e attiviamo il focus, impiliamo Sopra/Sotto */
301
+ .main-container.focus-active {
302
+ grid-template-columns: 1fr !important; /* Colonna singola */
303
+ grid-template-rows: 65vh 35vh !important; /* 65% Altezza al dato, 35% alla bussola */
304
+ grid-template-areas:
305
+ "focused"
306
+ "gauge" !important;
307
+ }
308
+
309
+ /* Correzione dimensioni box in Focus Verticale */
310
+ .focus-active .is-focused {
311
+ height: 65vh !important;
312
+ width: 100vw !important;
313
+ padding: 5vh 5vw !important; /* Più respiro per display curvi */
314
+ }
315
+
316
+ /* Correzione Bussola in Focus Verticale */
317
+ .focus-active .box-gauge {
318
+ height: 35vh !important;
319
+ width: 100vw !important;
320
+ display: flex !important;
321
+ align-items: center;
322
+ justify-content: center;
323
+ }
324
+
325
+ /* Ridimensionamento font per Focus Verticale (per non sbordare) */
326
+ .focus-active .is-focused .value {
327
+ font-size: clamp(4rem, 20cqh, 12rem) !important;
328
+ }
329
+
330
+ /* In verticale il box TACK è largo: forziamo i due valori ai bordi */
331
+ .box-tack .dual-value-container {
332
+ flex-direction: row !important;
333
+ justify-content: space-between !important;
334
+ align-items: center !important;
335
+ }
213
336
  }
337
+
338
+ /* ==========================================================================
339
+ 9. WIDGETS E OVERRIDE SVG (GIORNO)
340
+ ========================================================================== */
341
+ #status { position: absolute; top: 10px; right: 10px; font-size: 0.6rem; font-weight: 900; text-transform: uppercase; z-index: 1000; background: rgba(0,0,0,0.05); padding: 2px 6px; border-radius: 4px; }
214
342
  .online { color: #27ae60; opacity: 0.8; }
215
343
  .offline { color: #c0392b; font-weight: bold; }
216
344
 
217
- .mini-compass {
218
- width: min(80cqh, 42cqw); height: min(80cqh, 42cqw); aspect-ratio: 1 / 1;
219
- flex-shrink: 0; background: #f0f0f0; border-radius: 50%; border: 1.5px solid #ddd;
220
- box-shadow: inset 0 0 10px rgba(0,0,0,0.05); transition: all 0.4s ease;
221
- }
222
-
345
+ .mini-compass { background: #f0f0f0; border-radius: 50%; border: 1.5px solid #ddd; }
223
346
  .mini-compass text:last-of-type { fill: #000 !important; opacity: 0.4; }
224
347
  #twd-boat-wrap path { fill: #000 !important; opacity: 0.3; }
225
348
  #twd-wind-chevron { stroke: #000 !important; }
226
349
 
227
- #awa-pointer, #twa-pointer, #track-pointer, #twd-arrow, #twd-boat-wrap {
228
- transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
229
- }
230
-
231
- #wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
232
-
233
- /* Quadrante Vento Centrale - Giorno */
234
350
  #wind-gauge circle[fill="#050505"] { fill: #fcfcfc; }
235
351
  #wind-gauge circle[fill="#121212"] { fill: #f0f0f0; }
236
352
  #boat-icon { fill: #000 !important; opacity: 1; }
@@ -240,85 +356,88 @@ body {
240
356
  #fullscreen-hotspot { fill: #eee !important; stroke: #ccc !important; }
241
357
  #ticks line[stroke="#fff"] { stroke: #000 !important; }
242
358
  #ticks line[stroke="#666"] { stroke: #bbb !important; }
243
-
244
- /* Leeway Container - Giorno */
245
359
  rect[fill="#222"] { fill: #eee !important; }
246
360
  #leeway-val { color: #000 !important; }
247
361
 
248
- /* ==========================================================================
249
- 7. STATI DI ALLARME E INSTABILITÀ
250
- ========================================================================== */
251
- .unstable-data { animation: blink-unstable 1.5s infinite ease-in-out; color: #e67e22 !important; }
252
- .alarm-warning { color: #f39c12 !important; }
253
- .alarm-danger { color: #e74c3c !important; font-weight: 900; animation: blink-unstable 1s infinite; }
254
- @keyframes blink-unstable { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
255
-
256
- /* ==========================================================================
257
- 8. RESPONSIVE (PORTRAIT MODE)
258
- ========================================================================== */
259
- @media (max-aspect-ratio: 0.9 / 1) {
260
- .main-container { grid-template-columns: 1fr 1fr !important; grid-template-rows: 45vh calc(55vh - 8px) !important; }
261
- .center-panel { grid-row: 1 !important; grid-column: 1 / span 2 !important; }
262
- .left-panel { grid-row: 2 !important; grid-column: 1 !important; }
263
- .right-panel { grid-row: 2 !important; grid-column: 2 !important; }
264
- .main-container.focus-active { display: flex !important; flex-direction: column !important; }
265
- .focus-active .center-panel { flex: 0 0 45vh !important; }
266
- .focus-active .side-panel.has-focus { flex: 0 0 calc(55vh - 8px) !important; display: flex !important; }
267
- .data-box { height: auto !important; }
268
- }
362
+ #awa-pointer, #twa-pointer, #track-pointer, #twd-arrow, #twd-boat-wrap { transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); }
363
+ #wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
269
364
 
270
365
  /* ==========================================================================
271
- 9. NIGHT MODE (TACTICAL RED)
366
+ 10. NIGHT MODE (TACTICAL RED - INTEGRALE E RIFINITO)
272
367
  ========================================================================== */
273
368
  body.night-mode { background-color: #000 !important; color: #ff0000 !important; }
274
- .night-mode .side-panel { background: rgba(20, 0, 0, 0.4) !important; border: 1px solid #330000; }
275
- .night-mode .data-box { border-bottom-color: #260000; }
276
- .night-mode .label, .night-mode .unit, .night-mode .dual-label { color: #800000 !important; }
277
- .night-mode .value, .night-mode .value-large { color: #ff3333 !important; text-shadow: 0 0 8px rgba(255, 0, 0, 0.4); }
278
369
 
279
- /* Grafici Night Mode: Solo linea, no riempimento */
280
- .night-mode .graph-wrapper { background: rgba(30, 0, 0, 0.3) !important; }
281
- .night-mode .sparkline path:first-of-type { display: none !important; }
282
- .night-mode .sparkline path { fill: none !important; stroke: #ff3333 !important; stroke-width: 1.8px !important; opacity: 1 !important; filter: drop-shadow(0 0 2px rgba(255, 0, 0, 0.4)); }
370
+ /* Pannelli e Box */
371
+ .night-mode .data-box { background: rgba(20, 0, 0, 0.4) !important; border-color: #330000; }
372
+ .night-mode .label,
373
+ .night-mode .unit,
374
+ .night-mode .dual-label { color: #800000 !important; }
283
375
 
284
- /* Sovrascrittura griglia per Night Mode: Rosso scuro trasparente */
285
- .night-mode .sparkline line { stroke: rgba(255, 0, 0, 0.15) !important; stroke-width: 0.7px !important; }
286
- .night-mode #tws-graph line:not([stroke*="rgba"]) { stroke: #ff3333 !important; stroke-width: 1.8px !important; }
376
+ .night-mode .value,
377
+ .night-mode .value-large {
378
+ color: #ff3333 !important;
379
+ text-shadow: 0 0 8px rgba(255, 0, 0, 0.4);
380
+ }
381
+
382
+ /* --- GRAFICI NIGHT MODE: Solo linee, zero abbagliamento --- */
383
+ .night-mode .graph-wrapper { background: rgba(30, 0, 0, 0.3) !important; border: 1px solid #330000; }
384
+ .night-mode .sparkline path:first-of-type { display: none !important; }
385
+ .night-mode .sparkline path {
386
+ fill: none !important;
387
+ stroke: #ff3333 !important;
388
+ stroke-width: 1px !important;
389
+ filter: drop-shadow(0 0 2px rgba(255, 0, 0, 0.4));
390
+ }
391
+ .night-mode .sparkline line { stroke: rgba(150, 0, 0, 0.1) !important; }
392
+ .night-mode #tws-graph line:not([stroke*="rgba"]) {
393
+ stroke: #ff3333 !important;
394
+ stroke-width: 1px !important;
395
+ }
287
396
  .night-mode .scale-labels { color: #660000 !important; }
288
397
 
398
+ /* Hercules in Night Mode */
289
399
  .night-mode .box-hercules { background: rgba(60, 0, 0, 0.2) !important; }
290
400
  .night-mode .line-hercules { filter: drop-shadow(0 0 6px #ff0000) !important; }
291
401
 
292
- /* Wind Gauge Night */
293
- .night-mode #wind-gauge circle { stroke: #330000; }
294
- .night-mode #wind-gauge circle[fill="#050505"] { fill: #050505 !important; }
295
- .night-mode #wind-gauge circle[fill="#121212"] { fill: #121212 !important; }
402
+ /* --- WIND GAUGE NIGHT: Scurimento settori e icone --- */
403
+ .night-mode #wind-gauge circle[fill="#050505"],
404
+ .night-mode #wind-gauge circle[fill="#fcfcfc"] { fill: #050505 !important; }
405
+
406
+ .night-mode #wind-gauge circle[fill="#121212"],
407
+ .night-mode #wind-gauge circle[fill="#f0f0f0"] { fill: #0a0000 !important; }
408
+
409
+ /* Glow e Hotspot centrale */
296
410
  .night-mode #fullscreen-hotspot { fill: #000 !important; stroke: #330000 !important; }
411
+ .night-mode #center-glow feDropShadow { flood-color: #ff0000 !important; flood-opacity: 0.6 !important; }
297
412
 
298
- .night-mode #ticks line { stroke: #4d0000 !important; }
299
- .night-mode #tick-labels { fill: #800000 !important; }
300
413
  .night-mode #boat-icon { fill: #330000 !important; opacity: 0.6 !important; }
301
414
  .night-mode #aws-val-svg { fill: #ff3333 !important; }
302
- .night-mode #aws-display-group text { fill: #ff3333 !important; }
303
- .night-mode #center-glow feDropShadow { flood-color: #ff0000 !important; flood-opacity: 0.6 !important; }
415
+ .night-mode #aws-display-group text { fill: #660000 !important; }
416
+
417
+ /* SCALA GRADUATA BUSSOLA: Tacche e numeri rosso cupo */
418
+ .night-mode #ticks line { stroke: #4d0000 !important; }
419
+ .night-mode #tick-labels { fill: #800000 !important; }
304
420
 
305
- /* Settori Vento Night */
421
+ /* SETTORI VENTO: Il verde diventa un rosso scuro trattteggiato */
306
422
  .night-mode #wind-gauge path[stroke="#ff0000"] { stroke: #660000 !important; opacity: 0.8; }
307
- .night-mode #wind-gauge path[stroke="#00ff00"] { stroke: #660000 !important; stroke-dasharray: 4, 3; opacity: 0.8; }
423
+ .night-mode #wind-gauge path[stroke="#00ff00"] { stroke: #440000 !important; stroke-dasharray: 4, 3; opacity: 0.6; }
308
424
  .night-mode #wind-gauge path[stroke="#ff8800"] { stroke: #330000 !important; stroke-width: 8; }
309
425
 
310
- /* Leeway & Pointer Night */
426
+ /* --- LEEWAY & POINTERS NIGHT --- */
311
427
  .night-mode rect[fill="url(#leeway-grad)"] { fill: url(#leeway-night-grad) !important; }
312
428
  .night-mode #leeway-val { fill: #ff3333 !important; }
429
+ .night-mode rect[fill="#222"], .night-mode rect[fill="#eee"] { fill: #0a0000 !important; stroke: #200000; }
430
+
431
+ /* Scala graduata Leeway: tacche e numeri */
313
432
  .night-mode g[stroke="#555"] line { stroke: #4d0000 !important; }
314
433
  .night-mode g[fill="#555"] text { fill: #660000 !important; }
315
- .night-mode rect[fill="#222"], .night-mode rect[fill="#eee"] { fill: #0a0000 !important; stroke: #200000; }
316
434
 
435
+ /* Lancette */
317
436
  .night-mode #awa-pointer path { fill: #ff0000; stroke: #000; }
318
437
  .night-mode #twa-pointer path { fill: #800000; stroke: #000; }
319
438
  .night-mode #track-pointer path { fill: #ff0000; stroke: #fff; stroke-width: 0.5; }
320
439
 
321
- /* Bussola TWD Night */
440
+ /* --- MINI BUSSOLA TWD NIGHT --- */
322
441
  .night-mode .mini-compass { border-color: #330000; background: #000; }
323
442
  .night-mode .mini-compass text { fill: #800000 !important; }
324
443
  .night-mode .mini-compass text:last-of-type { fill: #800000 !important; opacity: 1; }
@@ -326,25 +445,10 @@ body.night-mode { background-color: #000 !important; color: #ff0000 !important;
326
445
  .night-mode #twd-arrow #twd-wind-chevron { stroke: #ff3333 !important; filter: drop-shadow(0 0 4px #ff0000); }
327
446
 
328
447
  /* ==========================================================================
329
- 11. TREND VENTO E ALLARME STRAMBATA (GYBE)
448
+ 11. TREND E ANIMAZIONI
330
449
  ========================================================================== */
331
450
  @keyframes blink-trend { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
332
-
333
- /* Stato base dei pallini di trend: discreti al 30% */
334
- #trend-dot-cw, #trend-dot-ccw, #trend-gauge-cw, #trend-gauge-ccw {
335
- opacity: 0.3;
336
- transition: opacity 0.3s ease;
337
- }
338
-
339
- /* Quando attivi: brillano al 100% e lampeggiano */
340
- .is-trending {
341
- opacity: 1 !important;
342
- animation: blink-trend 1s infinite !important;
343
- }
344
-
345
- /* Allarme Strambata: Rosso fisso con bagliore neon massimo */
346
- .is-gybing {
347
- opacity: 1 !important;
348
- animation: none !important;
349
- filter: drop-shadow(0 0 8px #ff0000) !important;
350
- }
451
+ #trend-dot-cw, #trend-dot-ccw, #trend-gauge-cw, #trend-gauge-ccw { opacity: 0.3; transition: opacity 0.3s ease; }
452
+ .is-trending { opacity: 1 !important; animation: blink-trend 1s infinite !important; }
453
+ .is-gybing { opacity: 1 !important; animation: none !important; filter: drop-shadow(0 0 8px #ff0000) !important; }
454
+ .unstable-data { animation: blink-trend 1.5s infinite ease-in-out; color: #e67e22 !important; }