@sailingrotevista/rotevista-dash 2.0.2 → 2.0.4

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 +75 -18
  2. package/index.html +5 -5
  3. package/package.json +1 -1
  4. package/style.css +53 -29
package/app.js CHANGED
@@ -283,39 +283,96 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
283
283
  }
284
284
 
285
285
  // ==========================================================================
286
- // 8. EVENTI E INTERAZIONI (Touch, Focus e Fullscreen)
286
+ // 8. EVENTI E INTERAZIONI (Smart Touch Manager)
287
287
  // ==========================================================================
288
288
 
289
- // Blocco globale del menu contestuale per Android/iOS
290
- window.addEventListener('contextmenu', e => e.preventDefault());
289
+ // Uccide il menu contestuale di Android/iOS
290
+ window.addEventListener('contextmenu', e => e.preventDefault(), true);
291
291
 
292
292
  function toggleFocusMode(type, element) {
293
293
  const container = document.querySelector('.main-container');
294
294
  const parentPanel = element.closest('.side-panel');
295
295
  const isLeft = parentPanel.classList.contains('left-panel');
296
+
296
297
  isFocusActive = !isFocusActive;
297
- if (isFocusActive) { container.classList.add('focus-active'); container.classList.add(isLeft ? 'focus-side-left' : 'focus-side-right'); parentPanel.classList.add('has-focus'); element.classList.add('is-focused'); blockNextClick = true; }
298
- 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')); }
298
+
299
+ if (isFocusActive) {
300
+ container.classList.add('focus-active');
301
+ container.classList.add(isLeft ? 'focus-side-left' : 'focus-side-right');
302
+ parentPanel.classList.add('has-focus');
303
+ element.classList.add('is-focused');
304
+ } else {
305
+ container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right');
306
+ document.querySelectorAll('.side-panel').forEach(p => p.classList.remove('has-focus'));
307
+ document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused'));
308
+ }
299
309
  }
300
310
 
311
+ // Configurazione Interazioni per i 4 grafici principali
301
312
  ['stw', 'sog', 'tws', 'depth'].forEach(type => {
302
313
  const el = document.getElementById(type + '-graph').closest('.data-box');
303
314
 
304
- // Doppio Click -> Hercules Zoom
305
- el.addEventListener('dblclick', (e) => { if (isFocusActive) return; e.preventDefault(); graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard'; localStorage.setItem('mode_' + type, graphModes[type]); el.style.backgroundColor = "rgba(255,255,255,0.15)"; setTimeout(() => el.style.backgroundColor = "", 200); });
306
-
307
- // Long Press -> Tactical Focus
308
- const startPress = () => { if (!isFocusActive) pressTimer = setTimeout(() => toggleFocusMode(type, el), 1000); };
309
- const cancelPress = () => { clearTimeout(pressTimer); };
310
- el.addEventListener('mousedown', startPress); el.addEventListener('touchstart', startPress, {passive: true});
311
- ['mouseup', 'mouseleave', 'touchend', 'touchcancel'].forEach(evt => el.addEventListener(evt, cancelPress));
312
-
313
- // Click -> Exit Focus o Ghost click filtering
314
- el.addEventListener('click', (e) => { if (blockNextClick) { blockNextClick = false; return; } if (isFocusActive && el.classList.contains('is-focused')) toggleFocusMode(type, el); });
315
+ let lastTapTime = 0;
316
+ let tapTimeout;
317
+
318
+ const handleInteraction = (e) => {
319
+ // Impedisce al browser di fare qualsiasi cosa (zoom, menu, click fantasma)
320
+ if (e.cancelable) e.preventDefault();
321
+
322
+ const currentTime = new Date().getTime();
323
+ const tapDelay = currentTime - lastTapTime;
324
+
325
+ // --- 1. RILEVAMENTO LONG PRESS ---
326
+ // Avviamo un timer per la pressione lunga
327
+ pressTimer = setTimeout(() => {
328
+ if (!isFocusActive) {
329
+ toggleFocusMode(type, el);
330
+ lastTapTime = 0; // Reset per non innescare click singoli al rilascio
331
+ }
332
+ }, 800); // 800ms per attivare il Focus
333
+
334
+ // --- 2. GESTIONE DOPPIO E SINGOLO TOCCO ---
335
+ // Se tocchiamo di nuovo entro 300ms è un DOUBLE TAP
336
+ if (tapDelay < 300 && tapDelay > 0) {
337
+ clearTimeout(tapTimeout); // Cancella l'azione del singolo tap
338
+ if (!isFocusActive) {
339
+ // Toggle Hercules Mode
340
+ graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard';
341
+ localStorage.setItem('mode_' + type, graphModes[type]);
342
+ el.style.backgroundColor = "rgba(255,255,255,0.15)"; setTimeout(() => el.style.backgroundColor = "", 200);
343
+ }
344
+ lastTapTime = 0;
345
+ } else {
346
+ // Se è passato più tempo, potrebbe essere un SINGOLO TAP
347
+ lastTapTime = currentTime;
348
+ tapTimeout = setTimeout(() => {
349
+ // Se siamo in focus mode, il singolo tap esce
350
+ if (isFocusActive && el.classList.contains('is-focused')) {
351
+ toggleFocusMode(type, el);
352
+ }
353
+ }, 350); // Attesa per vedere se arriva il secondo tap
354
+ }
355
+ };
356
+
357
+ const stopInteraction = () => {
358
+ clearTimeout(pressTimer);
359
+ };
360
+
361
+ // Usiamo PointerEvents: funzionano identici per Mouse e Touch
362
+ el.addEventListener('pointerdown', handleInteraction);
363
+ el.addEventListener('pointerup', stopInteraction);
364
+ el.addEventListener('pointerleave', stopInteraction);
315
365
  });
316
366
 
317
- // Fullscreen via Hotspot
318
- 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(); } }); }
367
+ // Fullscreen via Hotspot (Click Singolo)
368
+ if (ui.hotspot) {
369
+ ui.hotspot.addEventListener('click', (e) => {
370
+ e.preventDefault();
371
+ const doc = document.documentElement, isF = document.fullscreenElement || document.webkitFullscreenElement;
372
+ if (!isF) { if (doc.requestFullscreen) doc.requestFullscreen(); else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen(); }
373
+ else { if (document.exitFullscreen) document.exitFullscreen(); else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); }
374
+ });
375
+ }
319
376
 
320
377
  // ==========================================================================
321
378
  // 9. INIZIALIZZAZIONE
package/index.html CHANGED
@@ -40,13 +40,13 @@
40
40
 
41
41
  <!-- HEADING MEAN -->
42
42
  <div class="data-box">
43
- <div class="label-row"><span class="label">HEADING MEAN</span></div>
43
+ <div class="label-row"><span class="label">HEADING (MEAN)</span></div>
44
44
  <span class="value value-large" id="hdg">000&deg;</span>
45
45
  </div>
46
46
 
47
47
  <!-- COG MEAN -->
48
48
  <div class="data-box">
49
- <div class="label-row"><span class="label">COG MEAN</span></div>
49
+ <div class="label-row"><span class="label">COG (MEAN)</span></div>
50
50
  <span class="value value-large" id="cog">000&deg;</span>
51
51
  </div>
52
52
 
@@ -136,19 +136,19 @@
136
136
 
137
137
  <!-- TWA MEAN -->
138
138
  <div class="data-box">
139
- <div class="label-row"><span class="label">TWA MEAN</span></div>
139
+ <div class="label-row"><span class="label">TWA (MEAN)</span></div>
140
140
  <span class="value value-large" id="twa-avg">---&deg;</span>
141
141
  </div>
142
142
 
143
143
  <!-- AWA MEAN -->
144
144
  <div class="data-box">
145
- <div class="label-row"><span class="label">AWA MEAN</span></div>
145
+ <div class="label-row"><span class="label">AWA (MEAN)</span></div>
146
146
  <span class="value value-large" id="awa-avg">---&deg;</span>
147
147
  </div>
148
148
 
149
149
  <!-- TWD MEAN con Bussola -->
150
150
  <div class="data-box">
151
- <div class="label-row"><span class="label">TWD MEAN</span></div>
151
+ <div class="label-row"><span class="label">TWD (MEAN)</span></div>
152
152
  <div class="value-with-compass">
153
153
  <svg class="mini-compass" viewBox="0 0 40 40">
154
154
  <circle cx="20" cy="20" r="19" fill="#151515" stroke="#444" stroke-width="1.5"/>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailingrotevista/rotevista-dash",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Public Wind Dashboard with navigation and course aids",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/style.css CHANGED
@@ -22,7 +22,7 @@ body {
22
22
  gap: 8px;
23
23
  grid-template-columns: 1.7fr 3fr 1.7fr;
24
24
  grid-template-rows: 100%;
25
- transition: all 0.4s ease;
25
+ transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
26
26
  }
27
27
 
28
28
  /* ==========================================================================
@@ -43,14 +43,22 @@ body {
43
43
  container-type: size;
44
44
  flex: 1;
45
45
  overflow: hidden;
46
+ -webkit-touch-callout: none;
47
+ -webkit-user-select: none;
48
+ user-select: none;
49
+ touch-action: none;
46
50
  }
47
51
 
48
52
  /* Altezze Desktop */
49
53
  .data-box:nth-child(1), .data-box:nth-child(2) { height: 25vh; }
50
54
  .data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 16.666vh; }
51
55
 
56
+ /* Allineamento Speculare Data Box */
57
+ .left-panel .data-box { align-items: flex-start; text-align: left; }
58
+ .right-panel .data-box { align-items: flex-end; text-align: right; }
59
+
52
60
  /* ==========================================================================
53
- 3. TACTICAL FOCUS MODE (ORIZZONTALE)
61
+ 3. TACTICAL FOCUS MODE (ORIZZONTALE) E INGRANDIMENTI
54
62
  ========================================================================== */
55
63
  .focus-active { grid-template-columns: 1fr 1fr !important; }
56
64
  .focus-active .side-panel:not(.has-focus) { display: none !important; }
@@ -62,17 +70,35 @@ body {
62
70
  .focus-active.focus-side-right .side-panel.has-focus { grid-column: 2 !important; }
63
71
 
64
72
  .focus-active .has-focus .data-box:not(.is-focused) { display: none !important; }
65
- .focus-active .has-focus .data-box.is-focused { height: 100vh !important; border: none; background: rgba(255, 255, 255, 0.05); }
73
+ .focus-active .has-focus .data-box.is-focused { height: 100vh !important; border: none; background: rgba(255, 255, 255, 0.05); padding: 20px; }
74
+
75
+ /* Ingrandimenti in Focus Mode */
76
+ .focus-active .is-focused .value { font-size: clamp(4rem, 25cqh, 10rem) !important; margin-top: 15px; }
77
+ .focus-active .is-focused .scale-labels { font-size: 24px !important; min-width: 60px !important; line-height: 1.2; }
78
+ .focus-active .is-focused .label-row .label { font-size: 1.5rem !important; }
79
+ .focus-active .is-focused .label-row .unit { font-size: 1.5rem !important; }
80
+
81
+ /* Ingrandimento scritta Hercules */
82
+ .focus-active .is-focused.box-hercules .unit::before,
83
+ .focus-active .is-focused.box-hercules .unit::after,
84
+ .focus-active .is-focused.box-hercules .label::before,
85
+ .focus-active .is-focused.box-hercules .label::after {
86
+ font-size: 14px !important;
87
+ letter-spacing: 2px;
88
+ }
66
89
 
67
- /* Tipografia e Grafica in Focus */
68
- .focus-active .is-focused .value { font-size: clamp(3rem, 18cqh, 8rem) !important; margin-top: 10px; }
69
- .focus-active .is-focused .scale-labels { font-size: 22px !important; min-width: 40px !important; }
70
90
  .focus-active .is-focused .sparkline path { stroke-width: 0.8px !important; }
71
91
 
72
92
  /* ==========================================================================
73
- 4. TIPOGRAFIA DINAMICA (CQH)
93
+ 4. TIPOGRAFIA DINAMICA E SIMMETRIA
74
94
  ========================================================================== */
75
95
  .label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; width: 100%; }
96
+
97
+ /* Simmetria Titoli/Unità */
98
+ .left-panel .label-row { justify-content: space-between; }
99
+ .right-panel .label-row { justify-content: space-between; flex-direction: row; }
100
+ .right-panel .label-row .label:only-child { margin-left: auto; }
101
+
76
102
  .label { color: #666; font-size: 0.65rem; font-weight: bold; text-transform: uppercase; }
77
103
  .unit { color: #888; font-size: 0.6rem; font-weight: bold; }
78
104
  .value { color: #fff; font-size: clamp(1.2rem, 22cqh, 3rem); font-weight: 600; line-height: 0.9; letter-spacing: -1px; padding-bottom: 5px; }
@@ -107,12 +133,23 @@ body {
107
133
  #depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.12); }
108
134
  #tws-graph { stroke: #f1c40f; fill: rgba(241, 196, 15, 0.12); }
109
135
 
136
+ /* ==========================================================================
137
+ 6. HERCULES MODE (LABEL TOP E SIMMETRIA)
138
+ ========================================================================== */
110
139
  .line-hercules { filter: drop-shadow(0 0 5px #ff0000); stroke-width: 1.8px !important; }
111
140
  .box-hercules { background: rgba(255, 0, 0, 0.08) !important; }
112
- .box-hercules::after { content: "HERCULES"; position: absolute; bottom: 4px; right: 35px; font-size: 7px; color: #ff4444; font-weight: 900; opacity: 0.8; }
141
+
142
+ /* Simmetria Scritta Hercules */
143
+ .box-hercules .unit::before, .box-hercules .unit::after,
144
+ .box-hercules .label::before, .box-hercules .label::after {
145
+ font-size: 7px; color: #ff4444; font-weight: 900; letter-spacing: 1px; text-transform: uppercase;
146
+ }
147
+ .left-panel .box-hercules .unit::before { content: "HERCULES "; }
148
+ .right-panel .box-hercules .unit::after { content: " HERCULES"; }
149
+ .right-panel .box-hercules .label:only-child::before { content: "HERCULES "; }
113
150
 
114
151
  /* ==========================================================================
115
- 6. STATI E ANIMAZIONI
152
+ 7. STATI E ANIMAZIONI
116
153
  ========================================================================== */
117
154
  #status { position: absolute; top: 5px; right: 15px; font-size: 0.5rem; text-transform: uppercase; z-index: 1000; }
118
155
  .online { color: #2ecc71; opacity: 0.5; }
@@ -126,7 +163,7 @@ body {
126
163
  #wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
127
164
 
128
165
  /* ==========================================================================
129
- 7. RESPONSIVE (PORTRAIT) - FIX DEFINITIVO 2x2 E FOCUS
166
+ 8. RESPONSIVE (PORTRAIT)
130
167
  ========================================================================== */
131
168
  @media (max-aspect-ratio: 0.9 / 1) {
132
169
  .main-container {
@@ -134,29 +171,16 @@ body {
134
171
  grid-template-rows: 45vh 55vh !important;
135
172
  }
136
173
 
137
- /* Ordine Standard Portrait: Vento (1), SX (2), DX (3) */
138
- .center-panel { grid-row: 1 !important; grid-column: 1 / span 2 !important; }
174
+ .center-panel { grid-row: 1 !important; grid-column: 1 / span 2 !important; padding: 5px 0; }
139
175
  .left-panel { grid-row: 2 !important; grid-column: 1 !important; }
140
176
  .right-panel { grid-row: 2 !important; grid-column: 2 !important; }
141
177
 
142
- /* FOCUS MODE IN VERTICALE: Abbandoniamo la Grid, usiamo Flex per sicurezza */
143
- .main-container.focus-active {
144
- display: flex !important;
145
- flex-direction: column !important;
146
- }
147
- .focus-active .center-panel {
148
- flex: 0 0 45vh !important;
149
- width: 100% !important;
150
- }
151
- .focus-active .side-panel.has-focus {
152
- flex: 0 0 55vh !important;
153
- width: 100% !important;
154
- display: flex !important;
155
- }
178
+ .main-container.focus-active { display: flex !important; flex-direction: column !important; }
179
+ .focus-active .center-panel { flex: 0 0 45vh !important; width: 100% !important; }
180
+ .focus-active .side-panel.has-focus { flex: 0 0 55vh !important; width: 100% !important; display: flex !important; }
156
181
 
157
- /* Calibrazione altezze box per Portrait */
158
182
  .data-box:nth-child(1), .data-box:nth-child(2) { height: 13.5vh; }
159
183
  .data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 9.3vh; }
160
-
161
- .value-large { font-size: clamp(1.2rem, 35cqh, 2.5rem); }
184
+ .value-large { font-size: clamp(1.2rem, 35cqh, 2.5rem); margin: auto 0; }
185
+ .value.dual-val { font-size: clamp(0.9rem, 30cqh, 1.4rem); }
162
186
  }