@sailingrotevista/rotevista-dash 1.0.23 → 1.0.24
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 +107 -63
- package/index.html +21 -4
- package/package.json +1 -1
- package/style.css +109 -205
package/app.js
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
// ==========================================================================
|
|
2
|
-
// 1. CONFIGURAZIONE E DEFAULT
|
|
2
|
+
// 1. CONFIGURAZIONE E DEFAULT (Sincronizzato con il Plugin SignalK)
|
|
3
3
|
// ==========================================================================
|
|
4
4
|
let CONFIG = {
|
|
5
|
-
alarms: {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
alarms: {
|
|
6
|
+
depthDanger: 2.5,
|
|
7
|
+
depthWarning: 5.0
|
|
8
|
+
},
|
|
9
|
+
averages: {
|
|
10
|
+
smoothWindow: 2000, // Media veloce per le lancette (ms)
|
|
11
|
+
longWindow: 60000, // Media lunga per i dati MEAN (ms)
|
|
12
|
+
stabilityTolerance: 2000, // Tolleranza riempimento buffer (ms)
|
|
13
|
+
stabilityThreshold: 0.90, // Indice R minimo per dati stabili
|
|
14
|
+
minSpeed: 0.5 // Velocità sotto la quale non lampeggia nulla (kts)
|
|
15
|
+
},
|
|
16
|
+
graphs: {
|
|
17
|
+
reef1: 15.0, // Soglia arancio TWS
|
|
18
|
+
reef2: 20.0, // Soglia rossa TWS
|
|
19
|
+
historyMinutes: 5, // Durata temporale dei grafici (minuti)
|
|
20
|
+
samples: 60 // Numero di punti disegnati nel grafico
|
|
21
|
+
},
|
|
8
22
|
scales: {
|
|
9
|
-
stw:
|
|
10
|
-
sog:
|
|
11
|
-
tws:
|
|
23
|
+
stw: { stdMax: 12, hercSpan: 4, step: 2 },
|
|
24
|
+
sog: { stdMax: 12, hercSpan: 4, step: 2 },
|
|
25
|
+
tws: { stdMax: 25, hercSpan: 10, step: 5 },
|
|
12
26
|
depth: { stdMax: 20, hercSpan: 10, step: 10 }
|
|
13
27
|
},
|
|
14
|
-
server: {
|
|
28
|
+
server: {
|
|
29
|
+
fallbackIp: "192.168.111.240:3000"
|
|
30
|
+
}
|
|
15
31
|
};
|
|
16
32
|
|
|
17
33
|
const RENDER_INTERVAL_MS = 1000;
|
|
@@ -19,13 +35,17 @@ const TIMEOUT_MS = 5000;
|
|
|
19
35
|
const SIM_SAMPLE_INTERVAL = 1000;
|
|
20
36
|
|
|
21
37
|
// ==========================================================================
|
|
22
|
-
// 2. STATO GLOBALE E UI
|
|
38
|
+
// 2. STATO GLOBALE E RIFERIMENTI UI
|
|
23
39
|
// ==========================================================================
|
|
24
40
|
let simulationMode = false;
|
|
25
41
|
let socket, renderInterval, simInterval;
|
|
26
42
|
let lastAvgUIUpdate = 0, audioCtx = null, lastAlarmTime = 0;
|
|
27
43
|
let curAwaRot = 0, curTwaRot = 0, curTrackRot = 0, curTwdRoseRot = 0;
|
|
28
44
|
|
|
45
|
+
// Gestione Focus e Interazioni
|
|
46
|
+
let pressTimer, isFocusActive = false, blockNextClick = false;
|
|
47
|
+
|
|
48
|
+
// Modalità Scale (Standard/Hercules) salvate nel browser
|
|
29
49
|
const graphModes = {
|
|
30
50
|
stw: localStorage.getItem('mode_stw') || 'standard',
|
|
31
51
|
sog: localStorage.getItem('mode_sog') || 'standard',
|
|
@@ -33,7 +53,14 @@ const graphModes = {
|
|
|
33
53
|
depth: localStorage.getItem('mode_depth') || 'standard'
|
|
34
54
|
};
|
|
35
55
|
|
|
36
|
-
const store = {
|
|
56
|
+
const store = {
|
|
57
|
+
raw: {},
|
|
58
|
+
timestamps: {},
|
|
59
|
+
smoothBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
|
|
60
|
+
longBuf: { hdg: [], cog: [], awa: [], twa: [], twd: [] },
|
|
61
|
+
histories: { stw: [], sog: [], depth: [], tws: [] },
|
|
62
|
+
lastUpdates: { stw: 0, sog: 0, depth: 0, tws: 0 }
|
|
63
|
+
};
|
|
37
64
|
|
|
38
65
|
const ui = {
|
|
39
66
|
stw: document.getElementById('stw'), sog: document.getElementById('sog'),
|
|
@@ -49,7 +76,7 @@ const ui = {
|
|
|
49
76
|
};
|
|
50
77
|
|
|
51
78
|
// ==========================================================================
|
|
52
|
-
// 3. CARICAMENTO CONFIGURAZIONE
|
|
79
|
+
// 3. CARICAMENTO CONFIGURAZIONE DAL SERVER
|
|
53
80
|
// ==========================================================================
|
|
54
81
|
async function fetchServerConfig() {
|
|
55
82
|
if (!window.location.protocol.includes("http")) return;
|
|
@@ -72,9 +99,11 @@ async function fetchServerConfig() {
|
|
|
72
99
|
parseNumbers(actual);
|
|
73
100
|
if (actual.alarms) CONFIG.alarms = { ...CONFIG.alarms, ...actual.alarms };
|
|
74
101
|
if (actual.graphs) CONFIG.graphs = { ...CONFIG.graphs, ...actual.graphs };
|
|
75
|
-
if (actual.averaging) CONFIG.averages = { ...CONFIG.averages, ...actual.averaging };
|
|
76
|
-
if (actual.scales) {
|
|
77
|
-
|
|
102
|
+
if (actual.averaging) CONFIG.averages = { ...CONFIG.averages, ...actual.averaging };
|
|
103
|
+
if (actual.scales) {
|
|
104
|
+
for (let key in actual.scales) { CONFIG.scales[key] = { ...CONFIG.scales[key], ...actual.scales[key] }; }
|
|
105
|
+
}
|
|
106
|
+
console.log("Dashboard: Configurazione caricata dal server.");
|
|
78
107
|
return;
|
|
79
108
|
}
|
|
80
109
|
}
|
|
@@ -83,7 +112,7 @@ async function fetchServerConfig() {
|
|
|
83
112
|
}
|
|
84
113
|
|
|
85
114
|
// ==========================================================================
|
|
86
|
-
// 4. MATEMATICA
|
|
115
|
+
// 4. MATEMATICA E GESTIONE DATI
|
|
87
116
|
// ==========================================================================
|
|
88
117
|
function radToDeg(rad) { return rad * (180 / Math.PI); }
|
|
89
118
|
function degToRad(deg) { return deg * (Math.PI / 180); }
|
|
@@ -91,6 +120,7 @@ function msToKts(ms) { return ms * 1.94384; }
|
|
|
91
120
|
function ktsToMs(kts) { return kts / 1.94384; }
|
|
92
121
|
function getShortestRotation(curr, target) { let diff = (target - curr) % 360; if (diff > 180) diff -= 360; else if (diff < -180) diff += 360; return curr + diff; }
|
|
93
122
|
|
|
123
|
+
// Calcolo media circolare vettoriale con rilevamento stabilità
|
|
94
124
|
function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
|
|
95
125
|
const now = Date.now();
|
|
96
126
|
const validData = bufferArray.filter(item => (now - item.time) <= windowMs);
|
|
@@ -104,9 +134,6 @@ function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
|
|
|
104
134
|
return { val: signed ? avgDeg : (avgDeg + 360) % 360, stable: isStable };
|
|
105
135
|
}
|
|
106
136
|
|
|
107
|
-
// ==========================================================================
|
|
108
|
-
// 5. GESTIONE DATI IN INGRESSO
|
|
109
|
-
// ==========================================================================
|
|
110
137
|
function processIncomingData(path, val) {
|
|
111
138
|
const now = Date.now(); store.timestamps[path] = now; store.raw[path] = val;
|
|
112
139
|
if (path === "navigation.headingTrue") { store.smoothBuf.hdg.push({ val: val, time: now }); store.longBuf.hdg.push({ val: val, time: now }); }
|
|
@@ -114,6 +141,7 @@ function processIncomingData(path, val) {
|
|
|
114
141
|
if (path === "environment.wind.angleApparent") { store.smoothBuf.awa.push({ val: val, time: now }); store.longBuf.awa.push({ val: val, time: now }); }
|
|
115
142
|
if (path === "environment.wind.angleTrueWater") { store.smoothBuf.twa.push({ val: val, time: now }); store.longBuf.twa.push({ val: val, time: now }); }
|
|
116
143
|
|
|
144
|
+
// Calcolo TWD (True Wind Direction)
|
|
117
145
|
if (path === "navigation.headingTrue" || path === "environment.wind.angleTrueWater" || path === "environment.wind.directionTrue") {
|
|
118
146
|
let twdRad = 0;
|
|
119
147
|
if (path === "environment.wind.directionTrue") twdRad = val;
|
|
@@ -127,7 +155,7 @@ function processIncomingData(path, val) {
|
|
|
127
155
|
}
|
|
128
156
|
|
|
129
157
|
// ==========================================================================
|
|
130
|
-
//
|
|
158
|
+
// 5. MOTORE DI RENDERING PRINCIPALE
|
|
131
159
|
// ==========================================================================
|
|
132
160
|
function startDisplayLoop() {
|
|
133
161
|
renderInterval = setInterval(() => {
|
|
@@ -146,6 +174,7 @@ function startDisplayLoop() {
|
|
|
146
174
|
const smTwa = getCircularAverageFromBuffer(store.smoothBuf.twa, CONFIG.averages.smoothWindow, true);
|
|
147
175
|
if (smTwa) { curTwaRot = getShortestRotation(curTwaRot, smTwa.val); ui.twa.setAttribute('transform', `rotate(${curTwaRot}, 200, 200)`); }
|
|
148
176
|
|
|
177
|
+
// Calcolo Drift Reale (COG-HDG)
|
|
149
178
|
if (store.raw["navigation.courseOverGroundTrue"] && store.raw["navigation.headingTrue"]) {
|
|
150
179
|
let drift = (radToDeg(store.raw["navigation.courseOverGroundTrue"]) - radToDeg(store.raw["navigation.headingTrue"]) + 360) % 360;
|
|
151
180
|
if (curSog < CONFIG.averages.minSpeed) drift = 0;
|
|
@@ -186,7 +215,7 @@ function startDisplayLoop() {
|
|
|
186
215
|
}
|
|
187
216
|
|
|
188
217
|
// ==========================================================================
|
|
189
|
-
//
|
|
218
|
+
// 6. CONNESSIONE SIGNALK
|
|
190
219
|
// ==========================================================================
|
|
191
220
|
function connect() {
|
|
192
221
|
if (simulationMode) return;
|
|
@@ -200,22 +229,15 @@ function connect() {
|
|
|
200
229
|
}
|
|
201
230
|
|
|
202
231
|
// ==========================================================================
|
|
203
|
-
//
|
|
232
|
+
// 7. FUNZIONI GRAFICHE E SCALATURA
|
|
204
233
|
// ==========================================================================
|
|
205
234
|
function updateLeewayDisplay(deg) { const c = 125, px = 125/20; let w = Math.min(Math.abs(deg)*px, 125); ui.leewayMask.setAttribute('x', deg >= 0 ? c : c - w); ui.leewayMask.setAttribute('width', w); ui.leewayVal.textContent = `LEEWAY: ${deg.toFixed(1)}°`; }
|
|
206
235
|
|
|
207
236
|
function manageHistory(t, v) {
|
|
208
237
|
const n = Date.now();
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (n - store.lastUpdates[t] > interval || store.histories[t].length === 0) {
|
|
213
|
-
store.histories[t].push(v);
|
|
214
|
-
if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift();
|
|
215
|
-
store.lastUpdates[t] = n;
|
|
216
|
-
}
|
|
217
|
-
const mode = graphModes[t];
|
|
218
|
-
const cfg = calculateScale(t, store.histories[t], mode);
|
|
238
|
+
const interval = simulationMode ? SIM_SAMPLE_INTERVAL : (CONFIG.graphs.historyMinutes * 60000) / CONFIG.graphs.samples;
|
|
239
|
+
if (n - store.lastUpdates[t] > interval || store.histories[t].length === 0) { store.histories[t].push(v); if (store.histories[t].length > CONFIG.graphs.samples) store.histories[t].shift(); store.lastUpdates[t] = n; }
|
|
240
|
+
const mode = graphModes[t], cfg = calculateScale(t, store.histories[t], mode);
|
|
219
241
|
const box = document.getElementById(t + '-graph').closest('.data-box');
|
|
220
242
|
if (mode === 'hercules') box.classList.add('box-hercules'); else box.classList.remove('box-hercules');
|
|
221
243
|
updateScaleLabels(t, cfg.min, cfg.max);
|
|
@@ -231,9 +253,7 @@ function calculateScale(type, data, mode) {
|
|
|
231
253
|
if (span % 2 !== 0) span += 1;
|
|
232
254
|
let min = Math.max(0, Math.floor(avg - (span / 2)));
|
|
233
255
|
return { min, max: min + span };
|
|
234
|
-
} else {
|
|
235
|
-
return { min: 0, max: Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step) };
|
|
236
|
-
}
|
|
256
|
+
} else return { min: 0, max: Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step) };
|
|
237
257
|
}
|
|
238
258
|
|
|
239
259
|
function updateScaleLabels(t, min, max) {
|
|
@@ -244,15 +264,8 @@ function updateScaleLabels(t, min, max) {
|
|
|
244
264
|
function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
245
265
|
const svg = document.getElementById(id); if (!svg || d.length < 2) return;
|
|
246
266
|
const w = 200, h = 40, range = max - min || 1;
|
|
247
|
-
let grids = "";
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const minutes = CONFIG.graphs.historyMinutes;
|
|
251
|
-
for (let m = 1; m < minutes; m++) {
|
|
252
|
-
const x = w - (m / minutes) * w;
|
|
253
|
-
grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(255,255,255,0.05)" stroke-width="0.5" />`;
|
|
254
|
-
}
|
|
255
|
-
|
|
267
|
+
let grids = ""; [0.25, 0.5, 0.75].forEach(p => { grids += `<line x1="0" y1="${h-(p*h)}" x2="${w}" y2="${h-(p*h)}" stroke="rgba(255,255,255,0.08)" stroke-width="0.5" />`; });
|
|
268
|
+
for (let m = 1; m < CONFIG.graphs.historyMinutes; m++) { const x = w - (m / CONFIG.graphs.historyMinutes) * w; grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(255,255,255,0.05)" stroke-width="0.5" />`; }
|
|
256
269
|
let pD = "", cS = "";
|
|
257
270
|
d.forEach((v, i) => {
|
|
258
271
|
const x = (i/(CONFIG.graphs.samples-1))*w, y = h-(Math.max(0,Math.min(1,(v-min)/range))*h); pD += `${i===0?'M':'L'} ${x} ${y} `;
|
|
@@ -262,44 +275,75 @@ function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
|
262
275
|
cS += `<line x1="${px}" y1="${py}" x2="${x}" y2="${y}" stroke="${c}" stroke-width="${isHercules?3:2}" class="${isHercules?'line-hercules':''}" />`;
|
|
263
276
|
}
|
|
264
277
|
});
|
|
265
|
-
|
|
266
|
-
const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#f1c40f' };
|
|
267
|
-
const aP = pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`;
|
|
278
|
+
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' };
|
|
268
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]}" stroke-width="1.5" />`;
|
|
269
280
|
}
|
|
270
281
|
|
|
271
282
|
// ==========================================================================
|
|
272
|
-
//
|
|
283
|
+
// 8. EVENTI, INTERAZIONI E FOCUS MODE
|
|
273
284
|
// ==========================================================================
|
|
285
|
+
|
|
286
|
+
function toggleFocusMode(type, element) {
|
|
287
|
+
const container = document.querySelector('.main-container');
|
|
288
|
+
const parentPanel = element.closest('.side-panel');
|
|
289
|
+
const isLeft = parentPanel.classList.contains('left-panel');
|
|
290
|
+
|
|
291
|
+
isFocusActive = !isFocusActive;
|
|
292
|
+
|
|
293
|
+
if (isFocusActive) {
|
|
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
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
274
306
|
['stw', 'sog', 'tws', 'depth'].forEach(type => {
|
|
275
307
|
const el = document.getElementById(type + '-graph').closest('.data-box');
|
|
308
|
+
|
|
309
|
+
// Doppio Click -> Toggle Hercules Zoom
|
|
276
310
|
el.addEventListener('dblclick', (e) => {
|
|
277
|
-
|
|
311
|
+
if (isFocusActive) return;
|
|
312
|
+
e.preventDefault();
|
|
313
|
+
graphModes[type] = graphModes[type] === 'standard' ? 'hercules' : 'standard';
|
|
278
314
|
localStorage.setItem('mode_' + type, graphModes[type]);
|
|
279
315
|
el.style.backgroundColor = "rgba(255,255,255,0.15)"; setTimeout(() => el.style.backgroundColor = "", 200);
|
|
280
316
|
});
|
|
317
|
+
|
|
318
|
+
// Gestione Pressione Prolungata (1s) -> Tactical Focus
|
|
319
|
+
const startPress = () => { if (!isFocusActive) pressTimer = setTimeout(() => toggleFocusMode(type, el), 1000); };
|
|
320
|
+
const cancelPress = () => { clearTimeout(pressTimer); };
|
|
321
|
+
el.addEventListener('mousedown', startPress); el.addEventListener('touchstart', startPress, {passive: true});
|
|
322
|
+
['mouseup', 'mouseleave', 'touchend'].forEach(evt => el.addEventListener(evt, cancelPress));
|
|
323
|
+
|
|
324
|
+
// Click per uscire o per catturare il rilascio del tocco lungo
|
|
325
|
+
el.addEventListener('click', (e) => {
|
|
326
|
+
if (blockNextClick) { blockNextClick = false; return; }
|
|
327
|
+
if (isFocusActive && el.classList.contains('is-focused')) toggleFocusMode(type, el);
|
|
328
|
+
});
|
|
281
329
|
});
|
|
282
330
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
e.preventDefault(); const doc = document.documentElement, isF = document.fullscreenElement || document.webkitFullscreenElement;
|
|
286
|
-
if (!isF) { if (doc.requestFullscreen) doc.requestFullscreen(); else if (doc.webkitRequestFullscreen) doc.webkitRequestFullscreen(); }
|
|
287
|
-
else { if (document.exitFullscreen) document.exitFullscreen(); else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); }
|
|
288
|
-
});
|
|
289
|
-
}
|
|
331
|
+
// Fullscreen (Hotspot)
|
|
332
|
+
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(); } }); }
|
|
290
333
|
|
|
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
|
+
// ==========================================================================
|
|
339
|
+
// 9. INIZIALIZZAZIONE
|
|
340
|
+
// ==========================================================================
|
|
291
341
|
(function generateTicks() { 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); } } })();
|
|
292
342
|
|
|
293
343
|
async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
|
|
294
344
|
window.addEventListener('load', init);
|
|
295
345
|
|
|
296
|
-
|
|
297
|
-
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); }
|
|
298
|
-
|
|
346
|
+
// Simulatore (Triple click su Depth)
|
|
299
347
|
ui.depth.closest('.data-box').addEventListener('click', (function() {
|
|
300
|
-
let dC = 0, lC = 0;
|
|
301
|
-
return function() {
|
|
302
|
-
const n = Date.now(); if (n - lC < 500) dC++; else dC = 1; lC = n;
|
|
303
|
-
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; }
|
|
304
|
-
};
|
|
348
|
+
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; } };
|
|
305
349
|
})());
|
package/index.html
CHANGED
|
@@ -187,9 +187,27 @@
|
|
|
187
187
|
<div class="label-row"><span class="label">TWD MEAN</span></div>
|
|
188
188
|
<div class="value-with-compass">
|
|
189
189
|
<svg class="mini-compass" viewBox="0 0 40 40">
|
|
190
|
-
|
|
191
|
-
<
|
|
192
|
-
|
|
190
|
+
<!-- Sfondo e bordo bussola -->
|
|
191
|
+
<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) -->
|
|
197
|
+
<g stroke="#555" stroke-width="0.8">
|
|
198
|
+
<line x1="20" y1="11" x2="20" y2="13"/> <!-- Nord marker -->
|
|
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 -->
|
|
202
|
+
</g>
|
|
203
|
+
|
|
204
|
+
<!-- Freccia TWD (Gialla, forma a punta di freccia aeronautica) -->
|
|
205
|
+
<g id="twd-arrow" transform="rotate(0, 20, 20)">
|
|
206
|
+
<path d="M20,10 L16,26 L20,23 L24,26 Z" fill="#ffff00" stroke="#000" stroke-width="0.5"/>
|
|
207
|
+
</g>
|
|
208
|
+
|
|
209
|
+
<!-- Perno centrale -->
|
|
210
|
+
<circle cx="20" cy="20" r="1.5" fill="#555" />
|
|
193
211
|
</svg>
|
|
194
212
|
<span class="value value-large" id="twd-avg">---°</span>
|
|
195
213
|
</div>
|
|
@@ -200,4 +218,3 @@
|
|
|
200
218
|
<script src="app.js"></script>
|
|
201
219
|
</body>
|
|
202
220
|
</html>
|
|
203
|
-
---Fine Codice ---
|
package/package.json
CHANGED
package/style.css
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
1. BASE E STRUTTURA (GRID LAYOUT)
|
|
2
|
+
1. BASE E STRUTTURA GENERALE (GRID LAYOUT)
|
|
3
3
|
========================================================================== */
|
|
4
|
-
body {
|
|
4
|
+
body {
|
|
5
|
+
background-color: #000;
|
|
6
|
+
color: #fff;
|
|
7
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
height: 100vh;
|
|
11
|
+
width: 100vw;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
.main-container {
|
|
7
16
|
display: grid;
|
|
@@ -9,24 +18,23 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
9
18
|
height: 100%;
|
|
10
19
|
padding: 5px;
|
|
11
20
|
box-sizing: border-box;
|
|
12
|
-
|
|
21
|
+
gap: 8px;
|
|
22
|
+
/* Proporzioni Desktop Standard */
|
|
13
23
|
grid-template-columns: 1fr 3.5fr 1fr;
|
|
14
24
|
grid-template-rows: 100%;
|
|
15
|
-
|
|
16
|
-
transition: grid-template-columns 0.4s ease;
|
|
25
|
+
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
17
26
|
}
|
|
18
27
|
|
|
19
|
-
/*
|
|
28
|
+
/* Ottimizzazione per schermi molto larghi (es. 16:10, 21:9) */
|
|
20
29
|
@media (min-aspect-ratio: 1.6) {
|
|
21
30
|
.main-container {
|
|
22
|
-
/* Allarghiamo le colonne laterali per sfruttare lo spazio orizzontale */
|
|
23
31
|
grid-template-columns: 1.4fr 3fr 1.4fr;
|
|
24
|
-
gap: 15px;
|
|
32
|
+
gap: 15px;
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
/* ==========================================================================
|
|
29
|
-
2. PANNELLI
|
|
37
|
+
2. PANNELLI E DATA-BOX
|
|
30
38
|
========================================================================== */
|
|
31
39
|
.side-panel {
|
|
32
40
|
display: flex;
|
|
@@ -37,12 +45,19 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
37
45
|
height: 100%;
|
|
38
46
|
}
|
|
39
47
|
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
.left-panel { grid-column: 1; }
|
|
49
|
+
.right-panel { grid-column: 3; align-items: flex-end; text-align: right; }
|
|
50
|
+
|
|
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;
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
/* Riquadro singolo: agisce da contenitore per i font dinamici (cqh) */
|
|
46
61
|
.data-box {
|
|
47
62
|
position: relative;
|
|
48
63
|
border-bottom: 1px solid #222;
|
|
@@ -51,69 +66,49 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
51
66
|
flex-direction: column;
|
|
52
67
|
width: 100%;
|
|
53
68
|
box-sizing: border-box;
|
|
69
|
+
container-type: size; /* Cruciale per il calcolo cqh */
|
|
54
70
|
flex: 1;
|
|
55
|
-
container-type: size;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* Definizione altezze proporzionali (25% grafici, 16.6% dati semplici) */
|
|
59
|
-
.data-box:nth-child(1),
|
|
60
|
-
.data-box:nth-child(2) {
|
|
61
|
-
height: 25vh;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
.data-box:nth-child(
|
|
66
|
-
.data-box:nth-child(5) {
|
|
67
|
-
height: 16.666vh;
|
|
68
|
-
}
|
|
73
|
+
/* Definizione altezze percentuali basate sul Viewport */
|
|
74
|
+
.data-box:nth-child(1), .data-box:nth-child(2) { height: 25vh; }
|
|
75
|
+
.data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 16.666vh; }
|
|
69
76
|
|
|
70
77
|
/* ==========================================================================
|
|
71
|
-
3.
|
|
78
|
+
3. TACTICAL FOCUS MODE (50/50 SPLIT)
|
|
72
79
|
========================================================================== */
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
flex-direction: column;
|
|
76
|
-
align-items: center;
|
|
77
|
-
justify-content: center;
|
|
78
|
-
height: 100%;
|
|
79
|
-
overflow: hidden;
|
|
80
|
-
}
|
|
80
|
+
.focus-active { grid-template-columns: 1fr 1fr !important; }
|
|
81
|
+
.focus-active .side-panel:not(.has-focus) { display: none !important; }
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
object-fit: contain;
|
|
87
|
-
}
|
|
83
|
+
.focus-active.focus-side-left .side-panel.has-focus { grid-column: 1; }
|
|
84
|
+
.focus-active.focus-side-left .center-panel { grid-column: 2; }
|
|
85
|
+
.focus-active.focus-side-right .center-panel { grid-column: 1; }
|
|
86
|
+
.focus-active.focus-side-right .side-panel.has-focus { grid-column: 2; }
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
========================================================================== */
|
|
92
|
-
.label-row {
|
|
93
|
-
display: flex;
|
|
94
|
-
justify-content: space-between;
|
|
95
|
-
align-items: baseline;
|
|
96
|
-
margin-bottom: 2px;
|
|
97
|
-
width: 100%;
|
|
98
|
-
}
|
|
88
|
+
.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.04); }
|
|
99
90
|
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
font-size: 0.6rem;
|
|
103
|
-
font-weight: bold;
|
|
104
|
-
text-transform: uppercase;
|
|
105
|
-
}
|
|
91
|
+
.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: 35px !important; }
|
|
106
93
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
/* Bussola Esplosa in Focus Mode */
|
|
95
|
+
.focus-active .is-focused .mini-compass {
|
|
96
|
+
width: 45vh !important;
|
|
97
|
+
height: 45vh !important;
|
|
111
98
|
}
|
|
112
99
|
|
|
113
|
-
|
|
100
|
+
.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
|
+
|
|
103
|
+
/* ==========================================================================
|
|
104
|
+
4. TIPOGRAFIA DINAMICA (ELASTICA CQH)
|
|
105
|
+
========================================================================== */
|
|
106
|
+
.label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; width: 100%; }
|
|
107
|
+
.label { color: #666; font-size: 0.6rem; font-weight: bold; text-transform: uppercase; }
|
|
108
|
+
.unit { color: #888; font-size: 0.55rem; font-weight: bold; }
|
|
109
|
+
|
|
114
110
|
.value {
|
|
115
111
|
color: #fff;
|
|
116
|
-
/* 22% dell'altezza del box, minimo 1.2rem */
|
|
117
112
|
font-size: clamp(1.2rem, 22cqh, 3rem);
|
|
118
113
|
font-weight: 600;
|
|
119
114
|
line-height: 0.9;
|
|
@@ -122,40 +117,30 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
122
117
|
padding-bottom: 5px;
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
/* Valore grande senza grafico (Heading, COG, Mean) centrato verticalmente */
|
|
126
120
|
.value-large {
|
|
127
121
|
margin-top: auto;
|
|
128
122
|
margin-bottom: auto;
|
|
129
|
-
/* 35% dell'altezza del box, minimo 1.5rem */
|
|
130
123
|
font-size: clamp(1.5rem, 35cqh, 4rem);
|
|
131
124
|
line-height: 0.85;
|
|
132
125
|
}
|
|
133
126
|
|
|
134
|
-
/*
|
|
135
|
-
5. LAYOUT SPECIALI (TACK E BUSSOLINA TWD)
|
|
136
|
-
========================================================================== */
|
|
137
|
-
/* Box TACK (Doppio valore HDG/COG) */
|
|
127
|
+
/* TACK Layout - Centratura verticale e compattezza */
|
|
138
128
|
.dual-value-container {
|
|
139
129
|
display: flex;
|
|
140
130
|
justify-content: space-between;
|
|
141
|
-
align-items:
|
|
131
|
+
align-items: center;
|
|
142
132
|
width: 100%;
|
|
133
|
+
/* La "molla" auto sopra e sotto lo tiene al centro esatto del box */
|
|
143
134
|
margin-top: auto;
|
|
144
|
-
margin-bottom: auto;
|
|
135
|
+
margin-bottom: auto;
|
|
145
136
|
padding-bottom: 5px;
|
|
146
137
|
}
|
|
147
138
|
|
|
148
139
|
.dual-value-col {
|
|
149
140
|
display: flex;
|
|
150
141
|
flex-direction: column;
|
|
151
|
-
justify-content: space-between;
|
|
152
142
|
width: 48%;
|
|
153
|
-
height
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.dual-value-col.right-col {
|
|
157
|
-
align-items: flex-end;
|
|
158
|
-
text-align: right;
|
|
143
|
+
/* Rimosso height 100% per non farlo allungare fino al titolo */
|
|
159
144
|
}
|
|
160
145
|
|
|
161
146
|
.dual-label {
|
|
@@ -163,39 +148,47 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
163
148
|
font-size: 0.55rem;
|
|
164
149
|
font-weight: bold;
|
|
165
150
|
text-transform: uppercase;
|
|
166
|
-
margin-bottom:
|
|
151
|
+
margin-bottom: 2px; /* Distanza fissa e piccola dal numero sottostante */
|
|
167
152
|
}
|
|
168
153
|
|
|
169
154
|
.value.dual-val {
|
|
170
|
-
font-size: clamp(1rem,
|
|
155
|
+
font-size: clamp(1rem, 22cqh, 1.8rem);
|
|
171
156
|
padding-bottom: 0;
|
|
157
|
+
line-height: 0.9;
|
|
172
158
|
}
|
|
173
159
|
|
|
174
|
-
/*
|
|
160
|
+
/* BUSSOLA TWD: Centrata e proporzionata */
|
|
175
161
|
.value-with-compass {
|
|
176
162
|
display: flex;
|
|
177
163
|
justify-content: space-between;
|
|
178
164
|
align-items: center;
|
|
179
165
|
width: 100%;
|
|
180
166
|
margin-top: auto;
|
|
181
|
-
margin-bottom: auto;
|
|
167
|
+
margin-bottom: auto;
|
|
168
|
+
gap: 8px;
|
|
182
169
|
}
|
|
183
170
|
|
|
184
171
|
.mini-compass {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
172
|
+
/* Impostata a 60% dell'altezza come da tua prova */
|
|
173
|
+
width: clamp(45px, 60cqh, 120px);
|
|
174
|
+
height: clamp(45px, 60cqh, 120px);
|
|
175
|
+
background: #000;
|
|
188
176
|
border-radius: 50%;
|
|
189
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;
|
|
190
181
|
}
|
|
191
182
|
|
|
192
183
|
.value-with-compass .value-large {
|
|
193
|
-
margin
|
|
194
|
-
|
|
184
|
+
margin: 0;
|
|
185
|
+
flex-grow: 1;
|
|
186
|
+
text-align: right;
|
|
187
|
+
font-size: clamp(1.2rem, 35cqh, 4rem);
|
|
195
188
|
}
|
|
196
189
|
|
|
197
190
|
/* ==========================================================================
|
|
198
|
-
|
|
191
|
+
5. GRAFICI E SCALE
|
|
199
192
|
========================================================================== */
|
|
200
193
|
.graph-wrapper {
|
|
201
194
|
position: relative;
|
|
@@ -205,152 +198,63 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
205
198
|
margin-top: 4px;
|
|
206
199
|
display: flex;
|
|
207
200
|
align-items: stretch;
|
|
208
|
-
gap: 4px;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.sparkline {
|
|
212
|
-
flex-grow: 1;
|
|
213
|
-
height: 100%;
|
|
214
|
-
background: rgba(255, 255, 255, 0.03);
|
|
215
|
-
border-radius: 4px;
|
|
201
|
+
gap: 4px;
|
|
216
202
|
}
|
|
203
|
+
.sparkline { flex-grow: 1; height: 100%; background: rgba(255, 255, 255, 0.03); border-radius: 4px; }
|
|
204
|
+
.sparkline path { stroke-width: 1px !important; }
|
|
217
205
|
|
|
218
206
|
.scale-labels {
|
|
219
207
|
display: flex;
|
|
220
208
|
flex-direction: column;
|
|
221
209
|
justify-content: space-between;
|
|
222
|
-
font-size: 10px;
|
|
223
|
-
color: #
|
|
210
|
+
font-size: 10px;
|
|
211
|
+
color: #666;
|
|
224
212
|
font-weight: bold;
|
|
225
|
-
min-width: 16px;
|
|
213
|
+
min-width: 16px;
|
|
226
214
|
height: 100%;
|
|
227
215
|
line-height: 1;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/* SIMMETRIA INTERNA:
|
|
231
|
-
Pannello SX -> Scala a DESTRA (vicino al vento)
|
|
232
|
-
Pannello DX -> Scala a SINISTRA (vicino al vento)
|
|
233
|
-
*/
|
|
234
|
-
.left-panel .scale-labels {
|
|
235
|
-
order: 2;
|
|
236
|
-
text-align: left;
|
|
237
|
-
}
|
|
238
|
-
.left-panel .sparkline {
|
|
239
|
-
order: 1;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.right-panel .scale-labels {
|
|
243
|
-
order: 1;
|
|
244
|
-
text-align: right;
|
|
245
|
-
}
|
|
246
|
-
.right-panel .sparkline {
|
|
247
|
-
order: 2;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/* Colori linee grafici */
|
|
251
|
-
#stw-graph { stroke: #2ecc71; fill: rgba(46, 204, 113, 0.15); }
|
|
252
|
-
#sog-graph { stroke: #f39c12; fill: rgba(243, 156, 18, 0.15); }
|
|
253
|
-
#depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.15); }
|
|
254
|
-
#tws-graph { stroke: #f1c40f; fill: rgba(241, 196, 15, 0.15); }
|
|
255
|
-
|
|
256
|
-
/* Effetto bagliore ROSSO per la linea Hercules */
|
|
257
|
-
.line-hercules {
|
|
258
|
-
filter: drop-shadow(0 0 5px #ff0000); /* Glow Rosso */
|
|
259
|
-
stroke-width: 2.2px !important;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/* Sfondo speciale ROSSO SCURO per il box in modalità Hercules */
|
|
263
|
-
.box-hercules {
|
|
264
|
-
background: rgba(255, 0, 0, 0.08) !important; /* Una punta di rosso nel fondo */
|
|
265
|
-
border-radius: 10px;
|
|
266
216
|
transition: all 0.3s ease;
|
|
267
217
|
}
|
|
268
218
|
|
|
269
|
-
.
|
|
270
|
-
|
|
271
|
-
}
|
|
219
|
+
.left-panel .scale-labels { order: 2; text-align: left; }
|
|
220
|
+
.left-panel .sparkline { order: 1; }
|
|
221
|
+
.right-panel .scale-labels { order: 1; text-align: right; }
|
|
222
|
+
.right-panel .sparkline { order: 2; }
|
|
272
223
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
bottom: 4px;
|
|
278
|
-
right: 35px;
|
|
279
|
-
font-size: 7px;
|
|
280
|
-
color: #ff4444;
|
|
281
|
-
font-weight: 900;
|
|
282
|
-
letter-spacing: 1px;
|
|
283
|
-
opacity: 0.8;
|
|
284
|
-
}
|
|
224
|
+
#stw-graph { stroke: #2ecc71; fill: rgba(46, 204, 113, 0.12); }
|
|
225
|
+
#sog-graph { stroke: #f39c12; fill: rgba(243, 156, 18, 0.12); }
|
|
226
|
+
#depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.12); }
|
|
227
|
+
#tws-graph { stroke: #f1c40f; fill: rgba(241, 196, 15, 0.12); }
|
|
285
228
|
|
|
286
229
|
/* ==========================================================================
|
|
287
|
-
|
|
230
|
+
6. HERCULES E ANIMAZIONI
|
|
288
231
|
========================================================================== */
|
|
289
|
-
#
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
right: 15px;
|
|
293
|
-
font-size: 0.5rem;
|
|
294
|
-
text-transform: uppercase;
|
|
295
|
-
z-index: 1000;
|
|
296
|
-
}
|
|
232
|
+
.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; transition: all 0.3s ease; }
|
|
234
|
+
.box-hercules::after { content: "HERCULES"; position: absolute; bottom: 4px; right: 35px; font-size: 7px; color: #ff4444; font-weight: 900; letter-spacing: 1px; opacity: 0.8; }
|
|
297
235
|
|
|
236
|
+
#status { position: absolute; top: 5px; right: 15px; font-size: 0.5rem; text-transform: uppercase; z-index: 1000; }
|
|
298
237
|
.online { color: #2ecc71; opacity: 0.5; }
|
|
299
238
|
.offline { color: #e74c3c; font-weight: bold; }
|
|
300
239
|
|
|
301
|
-
|
|
302
|
-
#awa-pointer, #twa-pointer, #track-pointer, #twd-arrow {
|
|
303
|
-
transition: all 0.6s cubic-bezier(0.1, 0.7, 0.1, 1);
|
|
304
|
-
}
|
|
305
|
-
|
|
240
|
+
#awa-pointer, #twa-pointer, #track-pointer, #twd-arrow { transition: all 0.6s cubic-bezier(0.1, 0.7, 0.1, 1); }
|
|
306
241
|
#leeway-mask-rect { transition: none; }
|
|
307
|
-
|
|
308
|
-
/* Allarmi Profondità */
|
|
309
242
|
.alarm-warning { color: #f1c40f !important; }
|
|
310
|
-
.alarm-danger {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
animation: blink-unstable 1s infinite;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/* Lampeggio dati instabili (MEAN) */
|
|
317
|
-
@keyframes blink-unstable {
|
|
318
|
-
0% { opacity: 1; }
|
|
319
|
-
50% { opacity: 0.3; }
|
|
320
|
-
100% { opacity: 1; }
|
|
321
|
-
}
|
|
243
|
+
.alarm-danger { color: #e74c3c !important; font-weight: 900; animation: blink-unstable 1s infinite; }
|
|
244
|
+
@keyframes blink-unstable { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
|
|
245
|
+
.unstable-data { animation: blink-unstable 1.5s infinite ease-in-out; color: #f39c12 !important; }
|
|
322
246
|
|
|
323
|
-
|
|
324
|
-
animation: blink-unstable 1.5s infinite ease-in-out;
|
|
325
|
-
color: #f39c12 !important;
|
|
326
|
-
}
|
|
247
|
+
#wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
|
|
327
248
|
|
|
328
249
|
/* ==========================================================================
|
|
329
|
-
|
|
250
|
+
7. RESPONSIVE (PORTRAIT)
|
|
330
251
|
========================================================================== */
|
|
331
252
|
@media (max-aspect-ratio: 0.9 / 1) {
|
|
332
|
-
.main-container {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/* Vento sopra a tutta larghezza */
|
|
338
|
-
.center-panel {
|
|
339
|
-
grid-column: 1 / span 2;
|
|
340
|
-
grid-row: 1;
|
|
341
|
-
height: 50vh;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/* Colonne dati sotto affiancate */
|
|
345
|
-
.side-panel.left-panel { grid-column: 1; grid-row: 2; height: 50vh; }
|
|
346
|
-
.side-panel.right-panel { grid-column: 2; grid-row: 2; height: 50vh; }
|
|
347
|
-
|
|
348
|
-
#wind-gauge { max-height: 100%; width: auto; }
|
|
349
|
-
|
|
350
|
-
/* Altezze box dimezzate in modalità responsive */
|
|
253
|
+
.main-container { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
|
|
254
|
+
.center-panel { grid-column: 1 / span 2; grid-row: 1; height: 50vh; }
|
|
255
|
+
.left-panel { grid-column: 1; grid-row: 2; height: 50vh; }
|
|
256
|
+
.right-panel { grid-column: 2; grid-row: 2; height: 50vh; }
|
|
351
257
|
.data-box:nth-child(1), .data-box:nth-child(2) { height: 12.5vh; }
|
|
352
258
|
.data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 8.333vh; }
|
|
353
|
-
|
|
354
|
-
/* Font più protettivi per schermi piccoli */
|
|
355
259
|
.value-large { font-size: clamp(1.2rem, 35cqh, 2.5rem); }
|
|
356
260
|
}
|