@sailingrotevista/rotevista-dash 1.0.24 → 1.0.26
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.
- package/app.js +38 -52
- package/index.html +26 -74
- package/package.json +1 -1
- package/style.css +79 -177
package/app.js
CHANGED
|
@@ -42,7 +42,7 @@ let socket, renderInterval, simInterval;
|
|
|
42
42
|
let lastAvgUIUpdate = 0, audioCtx = null, lastAlarmTime = 0;
|
|
43
43
|
let curAwaRot = 0, curTwaRot = 0, curTrackRot = 0, curTwdRoseRot = 0;
|
|
44
44
|
|
|
45
|
-
// Gestione Focus
|
|
45
|
+
// Gestione Interazioni (Long Press, Focus, Ghost Clicks)
|
|
46
46
|
let pressTimer, isFocusActive = false, blockNextClick = false;
|
|
47
47
|
|
|
48
48
|
// Modalità Scale (Standard/Hercules) salvate nel browser
|
|
@@ -54,8 +54,7 @@ const graphModes = {
|
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
const store = {
|
|
57
|
-
raw: {},
|
|
58
|
-
timestamps: {},
|
|
57
|
+
raw: {}, timestamps: {},
|
|
59
58
|
smoothBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
|
|
60
59
|
longBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
|
|
61
60
|
histories: { stw: [], sog: [], depth: [], tws: [] },
|
|
@@ -76,7 +75,7 @@ const ui = {
|
|
|
76
75
|
};
|
|
77
76
|
|
|
78
77
|
// ==========================================================================
|
|
79
|
-
// 3.
|
|
78
|
+
// 3. COMUNICAZIONE CON IL SERVER (FETCH CONFIG)
|
|
80
79
|
// ==========================================================================
|
|
81
80
|
async function fetchServerConfig() {
|
|
82
81
|
if (!window.location.protocol.includes("http")) return;
|
|
@@ -90,6 +89,7 @@ async function fetchServerConfig() {
|
|
|
90
89
|
const data = await response.json();
|
|
91
90
|
const actual = data.configuration || data;
|
|
92
91
|
if (actual && typeof actual === 'object') {
|
|
92
|
+
// Normalizzazione dati (conversione stringhe -> numeri)
|
|
93
93
|
const parseNumbers = (obj) => {
|
|
94
94
|
for (let k in obj) {
|
|
95
95
|
if (typeof obj[k] === 'object') parseNumbers(obj[k]);
|
|
@@ -97,6 +97,7 @@ async function fetchServerConfig() {
|
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
99
|
parseNumbers(actual);
|
|
100
|
+
// Merge nel sistema locale
|
|
100
101
|
if (actual.alarms) CONFIG.alarms = { ...CONFIG.alarms, ...actual.alarms };
|
|
101
102
|
if (actual.graphs) CONFIG.graphs = { ...CONFIG.graphs, ...actual.graphs };
|
|
102
103
|
if (actual.averaging) CONFIG.averages = { ...CONFIG.averages, ...actual.averaging };
|
|
@@ -120,7 +121,7 @@ function msToKts(ms) { return ms * 1.94384; }
|
|
|
120
121
|
function ktsToMs(kts) { return kts / 1.94384; }
|
|
121
122
|
function getShortestRotation(curr, target) { let diff = (target - curr) % 360; if (diff > 180) diff -= 360; else if (diff < -180) diff += 360; return curr + diff; }
|
|
122
123
|
|
|
123
|
-
// Calcolo media circolare vettoriale
|
|
124
|
+
// Calcolo media circolare vettoriale
|
|
124
125
|
function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
|
|
125
126
|
const now = Date.now();
|
|
126
127
|
const validData = bufferArray.filter(item => (now - item.time) <= windowMs);
|
|
@@ -141,7 +142,7 @@ function processIncomingData(path, val) {
|
|
|
141
142
|
if (path === "environment.wind.angleApparent") { store.smoothBuf.awa.push({ val: val, time: now }); store.longBuf.awa.push({ val: val, time: now }); }
|
|
142
143
|
if (path === "environment.wind.angleTrueWater") { store.smoothBuf.twa.push({ val: val, time: now }); store.longBuf.twa.push({ val: val, time: now }); }
|
|
143
144
|
|
|
144
|
-
// Calcolo TWD (
|
|
145
|
+
// Calcolo TWD (Vento Reale Direzione Geografica)
|
|
145
146
|
if (path === "navigation.headingTrue" || path === "environment.wind.angleTrueWater" || path === "environment.wind.directionTrue") {
|
|
146
147
|
let twdRad = 0;
|
|
147
148
|
if (path === "environment.wind.directionTrue") twdRad = val;
|
|
@@ -160,21 +161,25 @@ function processIncomingData(path, val) {
|
|
|
160
161
|
function startDisplayLoop() {
|
|
161
162
|
renderInterval = setInterval(() => {
|
|
162
163
|
const now = Date.now();
|
|
164
|
+
|
|
165
|
+
// 5.1 Watchdog
|
|
163
166
|
const pathsToWatch = { "navigation.speedThroughWater": ui.stw, "navigation.speedOverGround": ui.sog, "navigation.headingTrue": ui.hdg, "navigation.courseOverGroundTrue": ui.cog, "environment.wind.speedApparent": ui.awsSvg, "environment.depth.belowTransducer": ui.depth, "environment.wind.speedTrue": ui.tws };
|
|
164
167
|
for (let p in pathsToWatch) { if (!store.timestamps[p] || (now - store.timestamps[p] > TIMEOUT_MS)) { pathsToWatch[p][pathsToWatch[p] === ui.awsSvg ? 'textContent' : 'innerText'] = "---"; delete store.raw[p]; } }
|
|
165
168
|
|
|
169
|
+
// 5.2 Renders Istantanei
|
|
166
170
|
if (store.raw["navigation.speedThroughWater"] !== undefined) { const v = msToKts(store.raw["navigation.speedThroughWater"]); ui.stw.innerText = v.toFixed(1); manageHistory('stw', v); }
|
|
167
171
|
let curSog = 0; if (store.raw["navigation.speedOverGround"] !== undefined) { curSog = msToKts(store.raw["navigation.speedOverGround"]); ui.sog.innerText = curSog.toFixed(1); manageHistory('sog', curSog); }
|
|
168
172
|
if (store.raw["environment.depth.belowTransducer"] !== undefined) { const d = store.raw["environment.depth.belowTransducer"]; ui.depth.innerText = d.toFixed(1); checkDepthAlarm(d); manageHistory('depth', d); }
|
|
169
173
|
if (store.raw["environment.wind.speedTrue"] !== undefined) { const w = msToKts(store.raw["environment.wind.speedTrue"]); ui.tws.innerText = w.toFixed(1); manageHistory('tws', w); }
|
|
170
174
|
if (store.raw["environment.wind.speedApparent"] !== undefined) ui.awsSvg.textContent = msToKts(store.raw["environment.wind.speedApparent"]).toFixed(1);
|
|
171
175
|
|
|
176
|
+
// 5.3 Render Quadrante (2s smoothing)
|
|
172
177
|
const smAwa = getCircularAverageFromBuffer(store.smoothBuf.awa, CONFIG.averages.smoothWindow, true);
|
|
173
178
|
if (smAwa) { curAwaRot = getShortestRotation(curAwaRot, smAwa.val); ui.awa.setAttribute('transform', `rotate(${curAwaRot}, 200, 200)`); }
|
|
174
179
|
const smTwa = getCircularAverageFromBuffer(store.smoothBuf.twa, CONFIG.averages.smoothWindow, true);
|
|
175
180
|
if (smTwa) { curTwaRot = getShortestRotation(curTwaRot, smTwa.val); ui.twa.setAttribute('transform', `rotate(${curTwaRot}, 200, 200)`); }
|
|
176
181
|
|
|
177
|
-
//
|
|
182
|
+
// Drift Reale (COG-HDG)
|
|
178
183
|
if (store.raw["navigation.courseOverGroundTrue"] && store.raw["navigation.headingTrue"]) {
|
|
179
184
|
let drift = (radToDeg(store.raw["navigation.courseOverGroundTrue"]) - radToDeg(store.raw["navigation.headingTrue"]) + 360) % 360;
|
|
180
185
|
if (curSog < CONFIG.averages.minSpeed) drift = 0;
|
|
@@ -182,6 +187,7 @@ function startDisplayLoop() {
|
|
|
182
187
|
let ds = drift > 180 ? drift - 360 : drift; updateLeewayDisplay(Math.max(-20, Math.min(20, ds)));
|
|
183
188
|
} else updateLeewayDisplay(0);
|
|
184
189
|
|
|
190
|
+
// 5.4 Render Medie Lunghe (Ogni 3s)
|
|
185
191
|
if (now - lastAvgUIUpdate > 3000) {
|
|
186
192
|
let hObj = getCircularAverageFromBuffer(store.longBuf.hdg, CONFIG.averages.longWindow, false),
|
|
187
193
|
cObj = getCircularAverageFromBuffer(store.longBuf.cog, CONFIG.averages.longWindow, false),
|
|
@@ -198,9 +204,9 @@ function startDisplayLoop() {
|
|
|
198
204
|
};
|
|
199
205
|
upUI(ui.hdg, hObj); upUI(ui.cog, cObj); upUI(ui.awaAvg, awObj); upUI(ui.twaAvg, twObj); upUI(ui.twdAvg, twdObj);
|
|
200
206
|
|
|
207
|
+
// TACK
|
|
201
208
|
if (hObj && twObj && hObj.val !== null) {
|
|
202
|
-
let tA = twObj.val * 2;
|
|
203
|
-
ui.tackHdg.innerHTML = `${Math.round((hObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
209
|
+
let tA = twObj.val * 2; ui.tackHdg.innerHTML = `${Math.round((hObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
204
210
|
if (cObj) ui.tackCog.innerHTML = `${Math.round((cObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
205
211
|
let tStable = (hObj.stable && twObj.stable) || (curSog < CONFIG.averages.minSpeed);
|
|
206
212
|
if (tStable) { ui.tackHdg.classList.remove('unstable-data'); ui.tackCog.classList.remove('unstable-data'); }
|
|
@@ -215,7 +221,7 @@ function startDisplayLoop() {
|
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
// ==========================================================================
|
|
218
|
-
// 6. CONNESSIONE SIGNALK
|
|
224
|
+
// 6. CONNESSIONE SIGNALK (subscribe=self)
|
|
219
225
|
// ==========================================================================
|
|
220
226
|
function connect() {
|
|
221
227
|
if (simulationMode) return;
|
|
@@ -248,11 +254,8 @@ function calculateScale(type, data, mode) {
|
|
|
248
254
|
const s = CONFIG.scales[type] || { stdMax: 12, hercSpan: 4, step: 2 };
|
|
249
255
|
let aMin = Math.min(...data), aMax = Math.max(...data);
|
|
250
256
|
if (mode === 'hercules') {
|
|
251
|
-
let avg = (aMin + aMax) / 2;
|
|
252
|
-
let
|
|
253
|
-
if (span % 2 !== 0) span += 1;
|
|
254
|
-
let min = Math.max(0, Math.floor(avg - (span / 2)));
|
|
255
|
-
return { min, max: min + span };
|
|
257
|
+
let avg = (aMin + aMax) / 2; let span = Math.max(s.hercSpan, Math.ceil(aMax - aMin)); if (span % 2 !== 0) span += 1;
|
|
258
|
+
let min = Math.max(0, Math.floor(avg - (span / 2))); return { min, max: min + span };
|
|
256
259
|
} else return { min: 0, max: Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step) };
|
|
257
260
|
}
|
|
258
261
|
|
|
@@ -272,77 +275,60 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
|
272
275
|
if (isTws && i > 0) {
|
|
273
276
|
const px = ((i-1)/(CONFIG.graphs.samples-1))*w, py = h-(Math.max(0,Math.min(1,(d[i-1]-min)/range))*h);
|
|
274
277
|
let c = "#f1c40f"; if (v >= CONFIG.graphs.reef2) c = "#e74c3c"; else if (v >= CONFIG.graphs.reef1) c = "#e67e22";
|
|
275
|
-
cS += `<line x1="${px}" y1="${py}" x2="${x}" y2="${y}" stroke="${c}"
|
|
278
|
+
cS += `<line x1="${px}" y1="${py}" x2="${x}" y2="${y}" stroke="${c}" class="${isHercules?'line-hercules':''}" />`;
|
|
276
279
|
}
|
|
277
280
|
});
|
|
278
281
|
const aP = pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`, clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#f1c40f' };
|
|
279
|
-
svg.innerHTML = isTws ? `${grids}<path d="${aP}" fill="rgba(241,196,15,0.12)" stroke="none" />${cS}` : `${grids}<path d="${aP}" fill="${clrs[id]}22" stroke="none" /><path d="${pD}" class="${isHercules?'line-hercules':''}" fill="none" stroke="${clrs[id]}"
|
|
282
|
+
svg.innerHTML = isTws ? `${grids}<path d="${aP}" fill="rgba(241,196,15,0.12)" stroke="none" />${cS}` : `${grids}<path d="${aP}" fill="${clrs[id]}22" stroke="none" /><path d="${pD}" class="${isHercules?'line-hercules':''}" fill="none" stroke="${clrs[id]}" />`;
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
// ==========================================================================
|
|
283
|
-
// 8. EVENTI
|
|
286
|
+
// 8. EVENTI E INTERAZIONI (Touch, Focus e Fullscreen)
|
|
284
287
|
// ==========================================================================
|
|
285
288
|
|
|
289
|
+
// Blocco globale del menu contestuale per Android/iOS
|
|
290
|
+
window.addEventListener('contextmenu', e => e.preventDefault());
|
|
291
|
+
|
|
286
292
|
function toggleFocusMode(type, element) {
|
|
287
293
|
const container = document.querySelector('.main-container');
|
|
288
294
|
const parentPanel = element.closest('.side-panel');
|
|
289
295
|
const isLeft = parentPanel.classList.contains('left-panel');
|
|
290
|
-
|
|
291
296
|
isFocusActive = !isFocusActive;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
container.classList.add('focus-active');
|
|
295
|
-
container.classList.add(isLeft ? 'focus-side-left' : 'focus-side-right');
|
|
296
|
-
parentPanel.classList.add('has-focus');
|
|
297
|
-
element.classList.add('is-focused');
|
|
298
|
-
blockNextClick = true; // Impedisce al click di rilascio del tocco lungo di chiudere subito
|
|
299
|
-
} else {
|
|
300
|
-
container.classList.remove('focus-active', 'focus-side-left', 'focus-side-right');
|
|
301
|
-
document.querySelectorAll('.side-panel').forEach(p => p.classList.remove('has-focus'));
|
|
302
|
-
document.querySelectorAll('.data-box').forEach(b => b.classList.remove('is-focused'));
|
|
303
|
-
}
|
|
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')); }
|
|
304
299
|
}
|
|
305
300
|
|
|
306
301
|
['stw', 'sog', 'tws', 'depth'].forEach(type => {
|
|
307
302
|
const el = document.getElementById(type + '-graph').closest('.data-box');
|
|
308
303
|
|
|
309
|
-
// Doppio Click ->
|
|
310
|
-
el.addEventListener('dblclick', (e) => {
|
|
311
|
-
if (isFocusActive) return;
|
|
312
|
-
e.preventDefault();
|
|
313
|
-
graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard';
|
|
314
|
-
localStorage.setItem('mode_' + type, graphModes[type]);
|
|
315
|
-
el.style.backgroundColor = "rgba(255,255,255,0.15)"; setTimeout(() => el.style.backgroundColor = "", 200);
|
|
316
|
-
});
|
|
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); });
|
|
317
306
|
|
|
318
|
-
//
|
|
307
|
+
// Long Press -> Tactical Focus
|
|
319
308
|
const startPress = () => { if (!isFocusActive) pressTimer = setTimeout(() => toggleFocusMode(type, el), 1000); };
|
|
320
309
|
const cancelPress = () => { clearTimeout(pressTimer); };
|
|
321
310
|
el.addEventListener('mousedown', startPress); el.addEventListener('touchstart', startPress, {passive: true});
|
|
322
|
-
['mouseup', 'mouseleave', 'touchend'].forEach(evt => el.addEventListener(evt, cancelPress));
|
|
311
|
+
['mouseup', 'mouseleave', 'touchend', 'touchcancel'].forEach(evt => el.addEventListener(evt, cancelPress));
|
|
323
312
|
|
|
324
|
-
// Click
|
|
325
|
-
el.addEventListener('click', (e) => {
|
|
326
|
-
if (blockNextClick) { blockNextClick = false; return; }
|
|
327
|
-
if (isFocusActive && el.classList.contains('is-focused')) toggleFocusMode(type, el);
|
|
328
|
-
});
|
|
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); });
|
|
329
315
|
});
|
|
330
316
|
|
|
331
|
-
// Fullscreen
|
|
317
|
+
// Fullscreen via Hotspot
|
|
332
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(); } }); }
|
|
333
319
|
|
|
334
|
-
// Allarmi Audio e Depth
|
|
335
|
-
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'); }
|
|
336
|
-
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); }
|
|
337
|
-
|
|
338
320
|
// ==========================================================================
|
|
339
321
|
// 9. INIZIALIZZAZIONE
|
|
340
322
|
// ==========================================================================
|
|
341
|
-
(function
|
|
323
|
+
(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); } } })();
|
|
342
324
|
|
|
343
325
|
async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
|
|
344
326
|
window.addEventListener('load', init);
|
|
345
327
|
|
|
328
|
+
// Allarmi e Audio
|
|
329
|
+
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'); }
|
|
330
|
+
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); }
|
|
331
|
+
|
|
346
332
|
// Simulatore (Triple click su Depth)
|
|
347
333
|
ui.depth.closest('.data-box').addEventListener('click', (function() {
|
|
348
334
|
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; } };
|
package/index.html
CHANGED
|
@@ -16,44 +16,41 @@
|
|
|
16
16
|
|
|
17
17
|
<div class="main-container">
|
|
18
18
|
|
|
19
|
-
<!--
|
|
20
|
-
<!-- COLONNA SINISTRA: Dati Rotta e Velocità -->
|
|
21
|
-
<!-- ======================================================= -->
|
|
19
|
+
<!-- COLONNA SINISTRA: Dati Rotta e Velocità -->
|
|
22
20
|
<div class="side-panel left-panel">
|
|
23
|
-
|
|
24
|
-
<!-- STW: Velocità attraverso l'acqua -->
|
|
21
|
+
<!-- STW con Sparkline -->
|
|
25
22
|
<div class="data-box">
|
|
26
23
|
<div class="label-row"><span class="label">STW</span><span class="unit">kts</span></div>
|
|
27
24
|
<span class="value" id="stw">0.0</span>
|
|
28
25
|
<div class="graph-wrapper">
|
|
29
26
|
<svg class="sparkline" id="stw-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
|
|
30
|
-
<div class="scale-labels
|
|
27
|
+
<div class="scale-labels" id="stw-scale"></div>
|
|
31
28
|
</div>
|
|
32
29
|
</div>
|
|
33
30
|
|
|
34
|
-
<!-- SOG
|
|
31
|
+
<!-- SOG con Sparkline -->
|
|
35
32
|
<div class="data-box">
|
|
36
33
|
<div class="label-row"><span class="label">SOG</span><span class="unit">kts</span></div>
|
|
37
34
|
<span class="value" id="sog">0.0</span>
|
|
38
35
|
<div class="graph-wrapper">
|
|
39
36
|
<svg class="sparkline" id="sog-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
|
|
40
|
-
<div class="scale-labels
|
|
37
|
+
<div class="scale-labels" id="sog-scale"></div>
|
|
41
38
|
</div>
|
|
42
39
|
</div>
|
|
43
40
|
|
|
44
|
-
<!-- HEADING
|
|
41
|
+
<!-- HEADING MEAN -->
|
|
45
42
|
<div class="data-box">
|
|
46
43
|
<div class="label-row"><span class="label">HEADING MEAN</span></div>
|
|
47
44
|
<span class="value value-large" id="hdg">000°</span>
|
|
48
45
|
</div>
|
|
49
46
|
|
|
50
|
-
<!-- COG
|
|
47
|
+
<!-- COG MEAN -->
|
|
51
48
|
<div class="data-box">
|
|
52
49
|
<div class="label-row"><span class="label">COG MEAN</span></div>
|
|
53
50
|
<span class="value value-large" id="cog">000°</span>
|
|
54
51
|
</div>
|
|
55
52
|
|
|
56
|
-
<!-- TACK:
|
|
53
|
+
<!-- TACK: Previsione mure opposte -->
|
|
57
54
|
<div class="data-box">
|
|
58
55
|
<div class="label-row"><span class="label">TACK</span></div>
|
|
59
56
|
<div class="dual-value-container">
|
|
@@ -69,152 +66,107 @@
|
|
|
69
66
|
</div>
|
|
70
67
|
</div>
|
|
71
68
|
|
|
72
|
-
<!--
|
|
73
|
-
<!-- CENTRO: Strumento Vento SVG (Ingrandito e Ottimizzato) -->
|
|
74
|
-
<!-- ======================================================= -->
|
|
69
|
+
<!-- CENTRO: Strumento Vento SVG -->
|
|
75
70
|
<div class="center-panel">
|
|
76
|
-
<!-- ViewBox ottimizzato (35 38 330 375) per ingrandire il diametro del quadrante -->
|
|
77
71
|
<svg id="wind-gauge" viewBox="35 38 330 395" preserveAspectRatio="xMidYMid meet">
|
|
78
72
|
<defs>
|
|
79
|
-
<!-- Gradienti e Maschere -->
|
|
80
73
|
<linearGradient id="axiom-grad" x1="0%" y1="0%" x2="0%" y2="100%"><stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" /><stop offset="100%" style="stop-color:#888888;stop-opacity:1" /></linearGradient>
|
|
81
74
|
<linearGradient id="leeway-grad" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" style="stop-color:#ff0000;stop-opacity:1" /><stop offset="25%" style="stop-color:#ff8800;stop-opacity:1" /><stop offset="50%" style="stop-color:#00ff00;stop-opacity:1" /><stop offset="75%" style="stop-color:#ff8800;stop-opacity:1" /><stop offset="100%" style="stop-color:#ff0000;stop-opacity:1" /></linearGradient>
|
|
82
75
|
<clipPath id="leeway-clip"><rect id="leeway-mask-rect" x="125" y="0" width="0" height="12" rx="2" /></clipPath>
|
|
83
|
-
|
|
84
|
-
<!-- Filtro Glow per l'area di interazione centrale -->
|
|
85
|
-
<filter id="center-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
86
|
-
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#aaaaaa" flood-opacity="0.5" />
|
|
87
|
-
</filter>
|
|
76
|
+
<filter id="center-glow" x="-20%" y="-20%" width="140%" height="140%"><feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#aaaaaa" flood-opacity="0.5" /></filter>
|
|
88
77
|
</defs>
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
<circle cx="200" cy="200" r="160" fill="#050505" />
|
|
92
|
-
<circle cx="200" cy="200" r="125" fill="#121212" />
|
|
93
|
-
|
|
94
|
-
<!-- Settori Vento (Rosso/Verde/Arancio) -->
|
|
79
|
+
<circle cx="200" cy="200" r="160" fill="#050505" /><circle cx="200" cy="200" r="125" fill="#121212" />
|
|
95
80
|
<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"/>
|
|
96
81
|
<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"/>
|
|
97
82
|
<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"/>
|
|
98
83
|
|
|
99
|
-
<!-- Tacche (Generate da Javascript) e Etichette Gradi -->
|
|
100
84
|
<g id="ticks"></g>
|
|
101
85
|
<g id="tick-labels" fill="#bbb" text-anchor="middle" dominant-baseline="hanging" font-family="Arial" font-weight="bold">
|
|
102
|
-
<text font-size="16" transform="translate(200, 65)">0</text>
|
|
103
|
-
<text font-size="16" transform="translate(335, 200) rotate(90)">90</text>
|
|
104
|
-
<text font-size="16" transform="translate(65, 200) rotate(-90)">90</text>
|
|
105
|
-
<text font-size="16" transform="translate(200, 335) rotate(180)">180</text>
|
|
86
|
+
<text font-size="16" transform="translate(200, 65)">0</text><text font-size="16" transform="translate(335, 200) rotate(90)">90</text><text font-size="16" transform="translate(65, 200) rotate(-90)">90</text><text font-size="16" transform="translate(200, 335) rotate(180)">180</text>
|
|
106
87
|
<text font-size="11" transform="translate(267.5, 83) rotate(30)">30</text><text font-size="11" transform="translate(317, 132.5) rotate(60)">60</text>
|
|
107
88
|
<text font-size="11" transform="translate(317, 267.5) rotate(120)">120</text><text font-size="11" transform="translate(267.5, 317) rotate(150)">150</text>
|
|
108
89
|
<text font-size="11" transform="translate(132.5, 83) rotate(-30)">30</text><text font-size="11" transform="translate(83, 132.5) rotate(-60)">60</text>
|
|
109
90
|
<text font-size="11" transform="translate(83, 267.5) rotate(-120)">120</text><text font-size="11" transform="translate(132.5, 317) rotate(-150)">150</text>
|
|
110
91
|
</g>
|
|
111
92
|
|
|
112
|
-
<!-- Puntatore Track (Blu) -->
|
|
113
93
|
<g id="track-pointer" transform="rotate(0, 200, 200)"><path d="M200,42 L194,58 L206,58 Z" fill="#007aff" stroke="#fff" stroke-width="0.5" /></g>
|
|
114
|
-
|
|
115
|
-
<!-- Pulsante Fullscreen Hotspot (Disco cliccabile al centro) -->
|
|
116
94
|
<circle id="fullscreen-hotspot" cx="200" cy="200" r="55" fill="#181818" stroke="#333" stroke-width="1" filter="url(#center-glow)" cursor="pointer" />
|
|
117
|
-
|
|
118
|
-
<!-- Icona Barca (Centrata visivamente con traslazione Y+5) -->
|
|
119
95
|
<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;" />
|
|
120
96
|
|
|
121
|
-
<!-- Display Velocità Vento Apparente (Al centro sotto la barca) -->
|
|
122
97
|
<g id="aws-display-group" transform="translate(200, 265)">
|
|
123
98
|
<text x="0" y="0" fill="#777" font-size="10" font-weight="bold" text-anchor="middle" text-transform="uppercase">Apparent Wind kts</text>
|
|
124
99
|
<text id="aws-val-svg" x="0" y="42" fill="#fff" font-size="52" font-weight="bold" text-anchor="middle">0.0</text>
|
|
125
100
|
</g>
|
|
126
|
-
|
|
127
|
-
<!-- Lancette AWA (A) e TWA (T) -->
|
|
128
101
|
<g id="awa-pointer" transform="rotate(0, 200, 200)" opacity="0.85"><path d="M200,80 L211,95 L200,145 L189,95 Z" fill="#ff8c00" stroke="#000" stroke-width="1" /><text x="200" y="102" fill="#000" font-size="11" font-weight="900" text-anchor="middle" font-family="Arial Black">A</text></g>
|
|
129
102
|
<g id="twa-pointer" transform="rotate(0, 200, 200)" opacity="0.85"><path d="M200,90 L206,98 L200,125 L194,98 Z" fill="#ffff00" stroke="#000" stroke-width="0.8" /><text x="200" y="104" fill="#000" font-size="8" font-weight="900" text-anchor="middle" font-family="Arial Black">T</text></g>
|
|
130
103
|
|
|
131
|
-
<!-- Barra LEEWAY / DRIFT (Posizionata più in alto a Y=372) -->
|
|
132
104
|
<g transform="translate(75, 395)">
|
|
133
105
|
<text x="125" y="-12" id="leeway-val" fill="#aaa" font-size="11" text-anchor="middle" font-weight="bold">LEEWAY: 0.0°</text>
|
|
134
|
-
<rect x="0" y="0" width="250" height="12" fill="#222" rx="3" />
|
|
135
|
-
<rect x="0" y="0" width="250" height="12" fill="url(#leeway-grad)" clip-path="url(#leeway-clip)" rx="3" />
|
|
106
|
+
<rect x="0" y="0" width="250" height="12" fill="#222" rx="3" /><rect x="0" y="0" width="250" height="12" fill="url(#leeway-grad)" clip-path="url(#leeway-clip)" rx="3" />
|
|
136
107
|
<g stroke="#555" stroke-width="1">
|
|
137
|
-
<line x1="0" y1="-2" x2="0" y2="14" /><line x1="31.25" y1="2" x2="31.25" y2="10" /><line x1="62.5" y1="2" x2="62.5" y2="10" />
|
|
138
|
-
<line x1="93.75" y1="3" x2="93.75" y2="9" /><line x1="125" y1="-2" x2="125" y2="14" /><line x1="156.25" y1="3" x2="156.25" y2="9" />
|
|
139
|
-
<line x1="187.5" y1="2" x2="187.5" y2="10" /><line x1="218.75" y1="2" x2="218.75" y2="10" /><line x1="250" y1="-2" x2="250" y2="14" />
|
|
140
|
-
</g>
|
|
141
|
-
<g fill="#555" font-size="8" text-anchor="middle" font-weight="bold">
|
|
142
|
-
<text x="0" y="24">-20°</text><text x="62.5" y="24">-10</text><text x="125" y="24">0°</text><text x="187.5" y="24">10</text><text x="250" y="24">20°</text>
|
|
108
|
+
<line x1="0" y1="-2" x2="0" y2="14" /><line x1="31.25" y1="2" x2="31.25" y2="10" /><line x1="62.5" y1="2" x2="62.5" y2="10" /><line x1="93.75" y1="3" x2="93.75" y2="9" /><line x1="125" y1="-2" x2="125" y2="14" /><line x1="156.25" y1="3" x2="156.25" y2="9" /><line x1="187.5" y1="2" x2="187.5" y2="10" /><line x1="218.75" y1="2" x2="218.75" y2="10" /><line x1="250" y1="-2" x2="250" y2="14" />
|
|
143
109
|
</g>
|
|
110
|
+
<g fill="#555" font-size="8" text-anchor="middle" font-weight="bold"><text x="0" y="24">-20°</text><text x="62.5" y="24">-10</text><text x="125" y="24">0°</text><text x="187.5" y="24">10</text><text x="250" y="24">20°</text></g>
|
|
144
111
|
</g>
|
|
145
112
|
</svg>
|
|
146
113
|
</div>
|
|
147
114
|
|
|
148
|
-
<!--
|
|
149
|
-
<!-- COLONNA DESTRA: Profondità e Vento Reale -->
|
|
150
|
-
<!-- ======================================================= -->
|
|
115
|
+
<!-- COLONNA DESTRA: Dati Vento e Profondità -->
|
|
151
116
|
<div class="side-panel right-panel">
|
|
152
|
-
|
|
153
|
-
<!-- DEPTH: Profondità sotto il trasduttore -->
|
|
117
|
+
<!-- DEPTH -->
|
|
154
118
|
<div class="data-box">
|
|
155
119
|
<div class="label-row"><span class="unit">m</span><span class="label">DEPTH</span></div>
|
|
156
120
|
<span class="value" id="depth">--.-</span>
|
|
157
121
|
<div class="graph-wrapper">
|
|
158
|
-
<div class="scale-labels
|
|
122
|
+
<div class="scale-labels" id="depth-scale"></div>
|
|
159
123
|
<svg class="sparkline" id="depth-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
|
|
160
124
|
</div>
|
|
161
125
|
</div>
|
|
162
126
|
|
|
163
|
-
<!-- TWS
|
|
127
|
+
<!-- TWS -->
|
|
164
128
|
<div class="data-box">
|
|
165
129
|
<div class="label-row"><span class="unit">kts</span><span class="label">TWS</span></div>
|
|
166
130
|
<span class="value" id="tws">0.0</span>
|
|
167
131
|
<div class="graph-wrapper">
|
|
168
|
-
<div class="scale-labels
|
|
132
|
+
<div class="scale-labels" id="tws-scale"></div>
|
|
169
133
|
<svg class="sparkline" id="tws-graph" viewBox="0 0 200 40" preserveAspectRatio="none"></svg>
|
|
170
134
|
</div>
|
|
171
135
|
</div>
|
|
172
136
|
|
|
173
|
-
<!-- TWA
|
|
137
|
+
<!-- TWA MEAN -->
|
|
174
138
|
<div class="data-box">
|
|
175
139
|
<div class="label-row"><span class="label">TWA MEAN</span></div>
|
|
176
140
|
<span class="value value-large" id="twa-avg">---°</span>
|
|
177
141
|
</div>
|
|
178
142
|
|
|
179
|
-
<!-- AWA
|
|
143
|
+
<!-- AWA MEAN -->
|
|
180
144
|
<div class="data-box">
|
|
181
145
|
<div class="label-row"><span class="label">AWA MEAN</span></div>
|
|
182
146
|
<span class="value value-large" id="awa-avg">---°</span>
|
|
183
147
|
</div>
|
|
184
148
|
|
|
185
|
-
<!-- TWD
|
|
149
|
+
<!-- TWD MEAN con Bussola -->
|
|
186
150
|
<div class="data-box">
|
|
187
151
|
<div class="label-row"><span class="label">TWD MEAN</span></div>
|
|
188
152
|
<div class="value-with-compass">
|
|
189
153
|
<svg class="mini-compass" viewBox="0 0 40 40">
|
|
190
|
-
<!-- Sfondo e bordo bussola -->
|
|
191
154
|
<circle cx="20" cy="20" r="19" fill="#151515" stroke="#444" stroke-width="1.5"/>
|
|
192
|
-
|
|
193
|
-
<!-- Etichetta Nord (N) -->
|
|
194
|
-
<text x="20" y="8" fill="#e74c3c" font-size="6" text-anchor="middle" font-weight="900" font-family="Arial">N</text>
|
|
195
|
-
|
|
196
|
-
<!-- Tacche cardinali (E, S, W) -->
|
|
155
|
+
<text x="20" y="8" fill="#e74c3c" font-size="6" text-anchor="middle" font-weight="900">N</text>
|
|
197
156
|
<g stroke="#555" stroke-width="0.8">
|
|
198
|
-
<line x1="20" y1="11" x2="20" y2="13"
|
|
199
|
-
<line x1="30" y1="20" x2="27" y2="20"/> <!-- Est -->
|
|
200
|
-
<line x1="20" y1="30" x2="20" y2="27"/> <!-- Sud -->
|
|
201
|
-
<line x1="10" y1="20" x2="13" y2="20"/> <!-- Ovest -->
|
|
157
|
+
<line x1="20" y1="11" x2="20" y2="13"/><line x1="30" y1="20" x2="27" y2="20"/><line x1="20" y1="30" x2="20" y2="27"/><line x1="10" y1="20" x2="13" y2="20"/>
|
|
202
158
|
</g>
|
|
203
|
-
|
|
204
|
-
<!-- Freccia TWD (Gialla, forma a punta di freccia aeronautica) -->
|
|
205
159
|
<g id="twd-arrow" transform="rotate(0, 20, 20)">
|
|
206
160
|
<path d="M20,10 L16,26 L20,23 L24,26 Z" fill="#ffff00" stroke="#000" stroke-width="0.5"/>
|
|
207
161
|
</g>
|
|
208
|
-
|
|
209
|
-
<!-- Perno centrale -->
|
|
210
162
|
<circle cx="20" cy="20" r="1.5" fill="#555" />
|
|
211
163
|
</svg>
|
|
212
164
|
<span class="value value-large" id="twd-avg">---°</span>
|
|
213
165
|
</div>
|
|
214
166
|
</div>
|
|
215
167
|
</div>
|
|
168
|
+
|
|
216
169
|
</div>
|
|
217
|
-
|
|
218
170
|
<script src="app.js"></script>
|
|
219
171
|
</body>
|
|
220
172
|
</html>
|
package/package.json
CHANGED
package/style.css
CHANGED
|
@@ -1,221 +1,102 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
1. BASE E STRUTTURA GENERALE
|
|
2
|
+
1. BASE E STRUTTURA GENERALE
|
|
3
3
|
========================================================================== */
|
|
4
4
|
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;
|
|
9
|
-
|
|
10
|
-
height: 100vh;
|
|
11
|
-
width: 100vw;
|
|
8
|
+
margin: 0; padding: 0;
|
|
9
|
+
height: 100vh; width: 100vw;
|
|
12
10
|
overflow: hidden;
|
|
11
|
+
/* BLOCCO TOTALE GESTI DI SISTEMA (Android/iOS) */
|
|
12
|
+
-webkit-touch-callout: none;
|
|
13
|
+
-webkit-user-select: none;
|
|
14
|
+
user-select: none;
|
|
15
|
+
touch-action: none;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
.main-container {
|
|
16
19
|
display: grid;
|
|
17
|
-
width: 100%;
|
|
18
|
-
|
|
19
|
-
padding: 5px;
|
|
20
|
-
box-sizing: border-box;
|
|
20
|
+
width: 100%; height: 100%;
|
|
21
|
+
padding: 5px; box-sizing: border-box;
|
|
21
22
|
gap: 8px;
|
|
22
|
-
|
|
23
|
-
grid-template-columns: 1fr 3.5fr 1fr;
|
|
23
|
+
grid-template-columns: 1.5fr 3.5fr 1.5fr;
|
|
24
24
|
grid-template-rows: 100%;
|
|
25
|
-
transition: all 0.
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/* Ottimizzazione per schermi molto larghi (es. 16:10, 21:9) */
|
|
29
|
-
@media (min-aspect-ratio: 1.6) {
|
|
30
|
-
.main-container {
|
|
31
|
-
grid-template-columns: 1.4fr 3fr 1.4fr;
|
|
32
|
-
gap: 15px;
|
|
33
|
-
}
|
|
25
|
+
transition: all 0.4s ease;
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
/* ==========================================================================
|
|
37
29
|
2. PANNELLI E DATA-BOX
|
|
38
30
|
========================================================================== */
|
|
39
|
-
.side-panel {
|
|
40
|
-
display: flex;
|
|
41
|
-
flex-direction: column;
|
|
42
|
-
background: rgba(255, 255, 255, 0.02);
|
|
43
|
-
border-radius: 10px;
|
|
44
|
-
z-index: 10;
|
|
45
|
-
height: 100%;
|
|
46
|
-
}
|
|
47
|
-
|
|
31
|
+
.side-panel { display: flex; flex-direction: column; background: rgba(255, 255, 255, 0.03); border-radius: 12px; height: 100%; overflow: hidden; }
|
|
48
32
|
.left-panel { grid-column: 1; }
|
|
33
|
+
.center-panel { grid-column: 2; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; overflow: hidden; }
|
|
49
34
|
.right-panel { grid-column: 3; align-items: flex-end; text-align: right; }
|
|
50
35
|
|
|
51
|
-
.center-panel {
|
|
52
|
-
grid-column: 2;
|
|
53
|
-
display: flex;
|
|
54
|
-
flex-direction: column;
|
|
55
|
-
align-items: center;
|
|
56
|
-
justify-content: center;
|
|
57
|
-
height: 100%;
|
|
58
|
-
overflow: hidden;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
36
|
.data-box {
|
|
62
37
|
position: relative;
|
|
63
38
|
border-bottom: 1px solid #222;
|
|
64
|
-
padding: 8px
|
|
39
|
+
padding: 8px 12px;
|
|
65
40
|
display: flex;
|
|
66
41
|
flex-direction: column;
|
|
67
|
-
width: 100%;
|
|
68
|
-
|
|
69
|
-
container-type: size; /* Cruciale per il calcolo cqh */
|
|
42
|
+
width: 100%; box-sizing: border-box;
|
|
43
|
+
container-type: size;
|
|
70
44
|
flex: 1;
|
|
45
|
+
overflow: hidden;
|
|
71
46
|
}
|
|
72
47
|
|
|
73
|
-
/*
|
|
48
|
+
/* Altezze Desktop */
|
|
74
49
|
.data-box:nth-child(1), .data-box:nth-child(2) { height: 25vh; }
|
|
75
50
|
.data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 16.666vh; }
|
|
76
51
|
|
|
77
52
|
/* ==========================================================================
|
|
78
|
-
3. TACTICAL FOCUS MODE (
|
|
53
|
+
3. TACTICAL FOCUS MODE (ORIZZONTALE)
|
|
79
54
|
========================================================================== */
|
|
80
55
|
.focus-active { grid-template-columns: 1fr 1fr !important; }
|
|
81
56
|
.focus-active .side-panel:not(.has-focus) { display: none !important; }
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
.focus-active.focus-side-left .
|
|
85
|
-
.focus-active.focus-side-
|
|
86
|
-
.focus-active.focus-side-right .
|
|
58
|
+
/* Simmetria Orizzontale */
|
|
59
|
+
.focus-active.focus-side-left .side-panel.has-focus { grid-column: 1 !important; }
|
|
60
|
+
.focus-active.focus-side-left .center-panel { grid-column: 2 !important; }
|
|
61
|
+
.focus-active.focus-side-right .center-panel { grid-column: 1 !important; }
|
|
62
|
+
.focus-active.focus-side-right .side-panel.has-focus { grid-column: 2 !important; }
|
|
87
63
|
|
|
88
64
|
.focus-active .has-focus .data-box:not(.is-focused) { display: none !important; }
|
|
89
|
-
.focus-active .has-focus .data-box.is-focused { height: 100vh !important; border: none; background: rgba(255, 255, 255, 0.
|
|
65
|
+
.focus-active .has-focus .data-box.is-focused { height: 100vh !important; border: none; background: rgba(255, 255, 255, 0.05); }
|
|
90
66
|
|
|
67
|
+
/* Tipografia e Grafica in Focus */
|
|
91
68
|
.focus-active .is-focused .value { font-size: clamp(3rem, 18cqh, 8rem) !important; margin-top: 10px; }
|
|
92
|
-
.focus-active .is-focused .scale-labels { font-size: 22px !important; min-width:
|
|
93
|
-
|
|
94
|
-
/* Bussola Esplosa in Focus Mode */
|
|
95
|
-
.focus-active .is-focused .mini-compass {
|
|
96
|
-
width: 45vh !important;
|
|
97
|
-
height: 45vh !important;
|
|
98
|
-
}
|
|
99
|
-
|
|
69
|
+
.focus-active .is-focused .scale-labels { font-size: 22px !important; min-width: 40px !important; }
|
|
100
70
|
.focus-active .is-focused .sparkline path { stroke-width: 0.8px !important; }
|
|
101
|
-
.focus-active .is-focused .line-hercules { stroke-width: 1.2px !important; filter: drop-shadow(0 0 3px #ff0000); }
|
|
102
71
|
|
|
103
72
|
/* ==========================================================================
|
|
104
|
-
4. TIPOGRAFIA DINAMICA (
|
|
73
|
+
4. TIPOGRAFIA DINAMICA (CQH)
|
|
105
74
|
========================================================================== */
|
|
106
75
|
.label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; width: 100%; }
|
|
107
|
-
.label { color: #666; font-size: 0.
|
|
108
|
-
.unit { color: #888; font-size: 0.
|
|
109
|
-
|
|
110
|
-
.value {
|
|
111
|
-
color: #fff;
|
|
112
|
-
font-size: clamp(1.2rem, 22cqh, 3rem);
|
|
113
|
-
font-weight: 600;
|
|
114
|
-
line-height: 0.9;
|
|
115
|
-
letter-spacing: -1px;
|
|
116
|
-
transition: color 0.3s ease;
|
|
117
|
-
padding-bottom: 5px;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.value-large {
|
|
121
|
-
margin-top: auto;
|
|
122
|
-
margin-bottom: auto;
|
|
123
|
-
font-size: clamp(1.5rem, 35cqh, 4rem);
|
|
124
|
-
line-height: 0.85;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/* TACK Layout - Centratura verticale e compattezza */
|
|
128
|
-
.dual-value-container {
|
|
129
|
-
display: flex;
|
|
130
|
-
justify-content: space-between;
|
|
131
|
-
align-items: center;
|
|
132
|
-
width: 100%;
|
|
133
|
-
/* La "molla" auto sopra e sotto lo tiene al centro esatto del box */
|
|
134
|
-
margin-top: auto;
|
|
135
|
-
margin-bottom: auto;
|
|
136
|
-
padding-bottom: 5px;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.dual-value-col {
|
|
140
|
-
display: flex;
|
|
141
|
-
flex-direction: column;
|
|
142
|
-
width: 48%;
|
|
143
|
-
/* Rimosso height 100% per non farlo allungare fino al titolo */
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.dual-label {
|
|
147
|
-
color: #666;
|
|
148
|
-
font-size: 0.55rem;
|
|
149
|
-
font-weight: bold;
|
|
150
|
-
text-transform: uppercase;
|
|
151
|
-
margin-bottom: 2px; /* Distanza fissa e piccola dal numero sottostante */
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.value.dual-val {
|
|
155
|
-
font-size: clamp(1rem, 22cqh, 1.8rem);
|
|
156
|
-
padding-bottom: 0;
|
|
157
|
-
line-height: 0.9;
|
|
158
|
-
}
|
|
76
|
+
.label { color: #666; font-size: 0.65rem; font-weight: bold; text-transform: uppercase; }
|
|
77
|
+
.unit { color: #888; font-size: 0.6rem; font-weight: bold; }
|
|
78
|
+
.value { color: #fff; font-size: clamp(1.2rem, 22cqh, 3rem); font-weight: 600; line-height: 0.9; letter-spacing: -1px; padding-bottom: 5px; }
|
|
159
79
|
|
|
160
|
-
|
|
161
|
-
.value-
|
|
162
|
-
display: flex;
|
|
163
|
-
justify-content: space-between;
|
|
164
|
-
align-items: center;
|
|
165
|
-
width: 100%;
|
|
166
|
-
margin-top: auto;
|
|
167
|
-
margin-bottom: auto;
|
|
168
|
-
gap: 8px;
|
|
169
|
-
}
|
|
80
|
+
.value-large, .dual-value-container, .value-with-compass { margin-top: auto; margin-bottom: auto; }
|
|
81
|
+
.value-large { font-size: clamp(1.5rem, 35cqh, 4.5rem); line-height: 0.85; }
|
|
170
82
|
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
background: #000;
|
|
176
|
-
border-radius: 50%;
|
|
177
|
-
flex-shrink: 0;
|
|
178
|
-
border: 1.5px solid #333;
|
|
179
|
-
box-shadow: inset 0 0 10px rgba(0,0,0,0.8);
|
|
180
|
-
transition: all 0.4s ease;
|
|
181
|
-
}
|
|
83
|
+
.dual-value-container { display: flex; justify-content: space-between; width: 100%; }
|
|
84
|
+
.dual-value-col { display: flex; flex-direction: column; width: 48%; }
|
|
85
|
+
.dual-label { color: #666; font-size: 0.55rem; font-weight: bold; text-transform: uppercase; margin-bottom: 2px; }
|
|
86
|
+
.value.dual-val { font-size: clamp(1rem, 22cqh, 2rem); padding-bottom: 0; line-height: 0.9; }
|
|
182
87
|
|
|
183
|
-
.value-with-compass
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
text-align: right;
|
|
187
|
-
font-size: clamp(1.2rem, 35cqh, 4rem);
|
|
188
|
-
}
|
|
88
|
+
.value-with-compass { display: flex; justify-content: space-between; align-items: center; width: 100%; gap: 10px; }
|
|
89
|
+
.mini-compass { width: clamp(40px, 60cqh, 120px); height: clamp(40px, 60cqh, 120px); background: #000; border-radius: 50%; flex-shrink: 0; border: 1.5px solid #333; box-shadow: inset 0 0 10px rgba(0,0,0,0.8); }
|
|
90
|
+
.value-with-compass .value-large { margin: 0; flex-grow: 1; text-align: right; font-size: clamp(1.2rem, 35cqh, 4rem); }
|
|
189
91
|
|
|
190
92
|
/* ==========================================================================
|
|
191
93
|
5. GRAFICI E SCALE
|
|
192
94
|
========================================================================== */
|
|
193
|
-
.graph-wrapper {
|
|
194
|
-
position: relative;
|
|
195
|
-
width: 100%;
|
|
196
|
-
flex-grow: 1;
|
|
197
|
-
min-height: 20px;
|
|
198
|
-
margin-top: 4px;
|
|
199
|
-
display: flex;
|
|
200
|
-
align-items: stretch;
|
|
201
|
-
gap: 4px;
|
|
202
|
-
}
|
|
95
|
+
.graph-wrapper { position: relative; width: 100%; flex-grow: 1; min-height: 0; margin-top: 4px; display: flex; align-items: stretch; gap: 6px; }
|
|
203
96
|
.sparkline { flex-grow: 1; height: 100%; background: rgba(255, 255, 255, 0.03); border-radius: 4px; }
|
|
204
|
-
.sparkline path { stroke-width: 1px !important; }
|
|
205
|
-
|
|
206
|
-
.scale-labels {
|
|
207
|
-
display: flex;
|
|
208
|
-
flex-direction: column;
|
|
209
|
-
justify-content: space-between;
|
|
210
|
-
font-size: 10px;
|
|
211
|
-
color: #666;
|
|
212
|
-
font-weight: bold;
|
|
213
|
-
min-width: 16px;
|
|
214
|
-
height: 100%;
|
|
215
|
-
line-height: 1;
|
|
216
|
-
transition: all 0.3s ease;
|
|
217
|
-
}
|
|
97
|
+
.sparkline path, .sparkline line { stroke-width: 1px !important; transition: all 0.3s ease; }
|
|
218
98
|
|
|
99
|
+
.scale-labels { display: flex; flex-direction: column; justify-content: space-between; font-size: 10px; color: #777; font-weight: bold; min-width: 20px; height: 100%; line-height: 1; }
|
|
219
100
|
.left-panel .scale-labels { order: 2; text-align: left; }
|
|
220
101
|
.left-panel .sparkline { order: 1; }
|
|
221
102
|
.right-panel .scale-labels { order: 1; text-align: right; }
|
|
@@ -226,35 +107,56 @@ body {
|
|
|
226
107
|
#depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.12); }
|
|
227
108
|
#tws-graph { stroke: #f1c40f; fill: rgba(241, 196, 15, 0.12); }
|
|
228
109
|
|
|
229
|
-
/* ==========================================================================
|
|
230
|
-
6. HERCULES E ANIMAZIONI
|
|
231
|
-
========================================================================== */
|
|
232
110
|
.line-hercules { filter: drop-shadow(0 0 5px #ff0000); stroke-width: 1.8px !important; }
|
|
233
|
-
.box-hercules { background: rgba(255, 0, 0, 0.08) !important;
|
|
234
|
-
.box-hercules::after { content: "HERCULES"; position: absolute; bottom: 4px; right: 35px; font-size: 7px; color: #ff4444; font-weight: 900;
|
|
111
|
+
.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; }
|
|
235
113
|
|
|
114
|
+
/* ==========================================================================
|
|
115
|
+
6. STATI E ANIMAZIONI
|
|
116
|
+
========================================================================== */
|
|
236
117
|
#status { position: absolute; top: 5px; right: 15px; font-size: 0.5rem; text-transform: uppercase; z-index: 1000; }
|
|
237
118
|
.online { color: #2ecc71; opacity: 0.5; }
|
|
238
119
|
.offline { color: #e74c3c; font-weight: bold; }
|
|
239
|
-
|
|
240
120
|
#awa-pointer, #twa-pointer, #track-pointer, #twd-arrow { transition: all 0.6s cubic-bezier(0.1, 0.7, 0.1, 1); }
|
|
241
121
|
#leeway-mask-rect { transition: none; }
|
|
242
122
|
.alarm-warning { color: #f1c40f !important; }
|
|
243
123
|
.alarm-danger { color: #e74c3c !important; font-weight: 900; animation: blink-unstable 1s infinite; }
|
|
244
124
|
@keyframes blink-unstable { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
|
|
245
125
|
.unstable-data { animation: blink-unstable 1.5s infinite ease-in-out; color: #f39c12 !important; }
|
|
246
|
-
|
|
247
126
|
#wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
|
|
248
127
|
|
|
249
128
|
/* ==========================================================================
|
|
250
|
-
7. RESPONSIVE (PORTRAIT)
|
|
129
|
+
7. RESPONSIVE (PORTRAIT) - FIX DEFINITIVO 2x2 E FOCUS
|
|
251
130
|
========================================================================== */
|
|
252
131
|
@media (max-aspect-ratio: 0.9 / 1) {
|
|
253
|
-
.main-container {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
132
|
+
.main-container {
|
|
133
|
+
grid-template-columns: 1fr 1fr !important;
|
|
134
|
+
grid-template-rows: 45vh 55vh !important;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Ordine Standard Portrait: Vento (1), SX (2), DX (3) */
|
|
138
|
+
.center-panel { grid-row: 1 !important; grid-column: 1 / span 2 !important; }
|
|
139
|
+
.left-panel { grid-row: 2 !important; grid-column: 1 !important; }
|
|
140
|
+
.right-panel { grid-row: 2 !important; grid-column: 2 !important; }
|
|
141
|
+
|
|
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
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Calibrazione altezze box per Portrait */
|
|
158
|
+
.data-box:nth-child(1), .data-box:nth-child(2) { height: 13.5vh; }
|
|
159
|
+
.data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 9.3vh; }
|
|
160
|
+
|
|
259
161
|
.value-large { font-size: clamp(1.2rem, 35cqh, 2.5rem); }
|
|
260
162
|
}
|