@sailingrotevista/rotevista-dash 1.0.23 → 1.0.25
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 +78 -118
- package/index.html +33 -64
- package/package.json +1 -1
- package/style.css +125 -270
package/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ==========================================================================
|
|
2
|
-
// 1. CONFIGURAZIONE E
|
|
2
|
+
// 1. CONFIGURAZIONE E STATO
|
|
3
3
|
// ==========================================================================
|
|
4
4
|
let CONFIG = {
|
|
5
5
|
alarms: { depthDanger: 2.5, depthWarning: 5.0 },
|
|
@@ -18,13 +18,11 @@ const RENDER_INTERVAL_MS = 1000;
|
|
|
18
18
|
const TIMEOUT_MS = 5000;
|
|
19
19
|
const SIM_SAMPLE_INTERVAL = 1000;
|
|
20
20
|
|
|
21
|
-
// ==========================================================================
|
|
22
|
-
// 2. STATO GLOBALE E UI
|
|
23
|
-
// ==========================================================================
|
|
24
21
|
let simulationMode = false;
|
|
25
22
|
let socket, renderInterval, simInterval;
|
|
26
23
|
let lastAvgUIUpdate = 0, audioCtx = null, lastAlarmTime = 0;
|
|
27
24
|
let curAwaRot = 0, curTwaRot = 0, curTrackRot = 0, curTwdRoseRot = 0;
|
|
25
|
+
let pressTimer, isFocusActive = false, blockNextClick = false;
|
|
28
26
|
|
|
29
27
|
const graphModes = {
|
|
30
28
|
stw: localStorage.getItem('mode_stw') || 'standard',
|
|
@@ -49,13 +47,13 @@ const ui = {
|
|
|
49
47
|
};
|
|
50
48
|
|
|
51
49
|
// ==========================================================================
|
|
52
|
-
// 3.
|
|
50
|
+
// 3. LOGICA DI COMUNICAZIONE E CALCOLO
|
|
53
51
|
// ==========================================================================
|
|
52
|
+
|
|
54
53
|
async function fetchServerConfig() {
|
|
55
54
|
if (!window.location.protocol.includes("http")) return;
|
|
56
55
|
const pluginID = 'rotevista-dash';
|
|
57
56
|
const possibleUrls = [`/skServer/plugins/${pluginID}/config`, `/plugins/${pluginID}/config` ];
|
|
58
|
-
|
|
59
57
|
for (let url of possibleUrls) {
|
|
60
58
|
try {
|
|
61
59
|
const response = await fetch(url);
|
|
@@ -63,28 +61,19 @@ async function fetchServerConfig() {
|
|
|
63
61
|
const data = await response.json();
|
|
64
62
|
const actual = data.configuration || data;
|
|
65
63
|
if (actual && typeof actual === 'object') {
|
|
66
|
-
const parseNumbers = (obj) => {
|
|
67
|
-
for (let k in obj) {
|
|
68
|
-
if (typeof obj[k] === 'object') parseNumbers(obj[k]);
|
|
69
|
-
else if (!isNaN(obj[k]) && obj[k] !== "") obj[k] = parseFloat(obj[k]);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
64
|
+
const parseNumbers = (obj) => { for (let k in obj) { if (typeof obj[k] === 'object') parseNumbers(obj[k]); else if (!isNaN(obj[k]) && obj[k] !== "") obj[k] = parseFloat(obj[k]); } };
|
|
72
65
|
parseNumbers(actual);
|
|
73
66
|
if (actual.alarms) CONFIG.alarms = { ...CONFIG.alarms, ...actual.alarms };
|
|
74
67
|
if (actual.graphs) CONFIG.graphs = { ...CONFIG.graphs, ...actual.graphs };
|
|
75
|
-
if (actual.averaging) CONFIG.averages = { ...CONFIG.averages, ...actual.averaging };
|
|
68
|
+
if (actual.averaging) CONFIG.averages = { ...CONFIG.averages, ...actual.averaging };
|
|
76
69
|
if (actual.scales) { for (let key in actual.scales) { CONFIG.scales[key] = { ...CONFIG.scales[key], ...actual.scales[key] }; } }
|
|
77
|
-
console.log("
|
|
78
|
-
return;
|
|
70
|
+
console.log("Dashboard: Configurazione caricata."); return;
|
|
79
71
|
}
|
|
80
72
|
}
|
|
81
73
|
} catch (e) { }
|
|
82
74
|
}
|
|
83
75
|
}
|
|
84
76
|
|
|
85
|
-
// ==========================================================================
|
|
86
|
-
// 4. MATEMATICA VETTORIALE
|
|
87
|
-
// ==========================================================================
|
|
88
77
|
function radToDeg(rad) { return rad * (180 / Math.PI); }
|
|
89
78
|
function degToRad(deg) { return deg * (Math.PI / 180); }
|
|
90
79
|
function msToKts(ms) { return ms * 1.94384; }
|
|
@@ -92,43 +81,33 @@ function ktsToMs(kts) { return kts / 1.94384; }
|
|
|
92
81
|
function getShortestRotation(curr, target) { let diff = (target - curr) % 360; if (diff > 180) diff -= 360; else if (diff < -180) diff += 360; return curr + diff; }
|
|
93
82
|
|
|
94
83
|
function getCircularAverageFromBuffer(bufferArray, windowMs, signed = false) {
|
|
95
|
-
const now = Date.now();
|
|
96
|
-
const validData = bufferArray.filter(item => (now - item.time) <= windowMs);
|
|
84
|
+
const now = Date.now(); const validData = bufferArray.filter(item => (now - item.time) <= windowMs);
|
|
97
85
|
if (validData.length === 0) return null;
|
|
98
|
-
let sSin = 0, sCos = 0;
|
|
99
|
-
validData.forEach(item => { sSin += Math.sin(item.val); sCos += Math.cos(item.val); });
|
|
86
|
+
let sSin = 0, sCos = 0; validData.forEach(item => { sSin += Math.sin(item.val); sCos += Math.cos(item.val); });
|
|
100
87
|
let R = Math.sqrt(sSin * sSin + sCos * sCos) / validData.length;
|
|
101
|
-
let
|
|
102
|
-
let isStable = (timeSpan >= windowMs - CONFIG.averages.stabilityTolerance) && (R > CONFIG.averages.stabilityThreshold);
|
|
88
|
+
let isStable = (validData[validData.length - 1].time - validData[0].time >= windowMs - CONFIG.averages.stabilityTolerance) && (R > CONFIG.averages.stabilityThreshold);
|
|
103
89
|
let avgDeg = Math.round(radToDeg(Math.atan2(sSin, sCos)));
|
|
104
90
|
return { val: signed ? avgDeg : (avgDeg + 360) % 360, stable: isStable };
|
|
105
91
|
}
|
|
106
92
|
|
|
107
|
-
// ==========================================================================
|
|
108
|
-
// 5. GESTIONE DATI IN INGRESSO
|
|
109
|
-
// ==========================================================================
|
|
110
93
|
function processIncomingData(path, val) {
|
|
111
94
|
const now = Date.now(); store.timestamps[path] = now; store.raw[path] = val;
|
|
112
95
|
if (path === "navigation.headingTrue") { store.smoothBuf.hdg.push({ val: val, time: now }); store.longBuf.hdg.push({ val: val, time: now }); }
|
|
113
96
|
if (path === "navigation.courseOverGroundTrue") { store.smoothBuf.cog.push({ val: val, time: now }); store.longBuf.cog.push({ val: val, time: now }); }
|
|
114
97
|
if (path === "environment.wind.angleApparent") { store.smoothBuf.awa.push({ val: val, time: now }); store.longBuf.awa.push({ val: val, time: now }); }
|
|
115
98
|
if (path === "environment.wind.angleTrueWater") { store.smoothBuf.twa.push({ val: val, time: now }); store.longBuf.twa.push({ val: val, time: now }); }
|
|
116
|
-
|
|
117
99
|
if (path === "navigation.headingTrue" || path === "environment.wind.angleTrueWater" || path === "environment.wind.directionTrue") {
|
|
118
|
-
let twdRad = 0;
|
|
119
|
-
if (
|
|
120
|
-
else
|
|
121
|
-
|
|
122
|
-
if (twdRad < 0) twdRad += (2 * Math.PI);
|
|
123
|
-
} else return;
|
|
124
|
-
store.smoothBuf.twd.push({ val: twdRad, time: now });
|
|
125
|
-
store.longBuf.twd.push({ val: twdRad, time: now });
|
|
100
|
+
let twdRad = 0; if (path === "environment.wind.directionTrue") twdRad = val;
|
|
101
|
+
else if (store.raw["navigation.headingTrue"] !== undefined && store.raw["environment.wind.angleTrueWater"] !== undefined) { twdRad = (store.raw["navigation.headingTrue"] + store.raw["environment.wind.angleTrueWater"]) % (2 * Math.PI); if (twdRad < 0) twdRad += (2 * Math.PI); }
|
|
102
|
+
else return;
|
|
103
|
+
store.smoothBuf.twd.push({ val: twdRad, time: now }); store.longBuf.twd.push({ val: twdRad, time: now });
|
|
126
104
|
}
|
|
127
105
|
}
|
|
128
106
|
|
|
129
107
|
// ==========================================================================
|
|
130
|
-
//
|
|
108
|
+
// 4. RENDERING LOOP E GRAFICA
|
|
131
109
|
// ==========================================================================
|
|
110
|
+
|
|
132
111
|
function startDisplayLoop() {
|
|
133
112
|
renderInterval = setInterval(() => {
|
|
134
113
|
const now = Date.now();
|
|
@@ -159,23 +138,12 @@ function startDisplayLoop() {
|
|
|
159
138
|
awObj = getCircularAverageFromBuffer(store.longBuf.awa, CONFIG.averages.longWindow, true),
|
|
160
139
|
twObj = getCircularAverageFromBuffer(store.longBuf.twa, CONFIG.averages.longWindow, true),
|
|
161
140
|
twdObj = getCircularAverageFromBuffer(store.longBuf.twd, CONFIG.averages.longWindow, false);
|
|
162
|
-
|
|
163
|
-
const upUI = (el, obj) => {
|
|
164
|
-
if (!obj) { el.innerHTML = "---°"; el.classList.remove('unstable-data'); }
|
|
165
|
-
else {
|
|
166
|
-
el.innerHTML = `${obj.val.toString().padStart(3, '0')}°`;
|
|
167
|
-
if (obj.stable || curSog < CONFIG.averages.minSpeed) el.classList.remove('unstable-data'); else el.classList.add('unstable-data');
|
|
168
|
-
}
|
|
169
|
-
};
|
|
141
|
+
const upUI = (el, obj) => { if (!obj) { el.innerHTML = "---°"; el.classList.remove('unstable-data'); } else { el.innerHTML = `${obj.val.toString().padStart(3, '0')}°`; if (obj.stable || curSog < CONFIG.averages.minSpeed) el.classList.remove('unstable-data'); else el.classList.add('unstable-data'); } };
|
|
170
142
|
upUI(ui.hdg, hObj); upUI(ui.cog, cObj); upUI(ui.awaAvg, awObj); upUI(ui.twaAvg, twObj); upUI(ui.twdAvg, twdObj);
|
|
171
|
-
|
|
172
143
|
if (hObj && twObj && hObj.val !== null) {
|
|
173
|
-
let tA = twObj.val * 2;
|
|
174
|
-
ui.tackHdg.innerHTML = `${Math.round((hObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
144
|
+
let tA = twObj.val * 2; ui.tackHdg.innerHTML = `${Math.round((hObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
175
145
|
if (cObj) ui.tackCog.innerHTML = `${Math.round((cObj.val - tA + 360) % 360).toString().padStart(3, '0')}°`;
|
|
176
|
-
|
|
177
|
-
if (tStable) { ui.tackHdg.classList.remove('unstable-data'); ui.tackCog.classList.remove('unstable-data'); }
|
|
178
|
-
else { ui.tackHdg.classList.add('unstable-data'); ui.tackCog.classList.add('unstable-data'); }
|
|
146
|
+
if (hObj.stable && twObj.stable || curSog < CONFIG.averages.minSpeed) { ui.tackHdg.classList.remove('unstable-data'); ui.tackCog.classList.remove('unstable-data'); } else { ui.tackHdg.classList.add('unstable-data'); ui.tackCog.classList.add('unstable-data'); }
|
|
179
147
|
}
|
|
180
148
|
if (twdObj) { curTwdRoseRot = getShortestRotation(curTwdRoseRot, twdObj.val); ui.twdArrow.setAttribute('transform', `rotate(${curTwdRoseRot}, 20, 20)`); }
|
|
181
149
|
lastAvgUIUpdate = now;
|
|
@@ -185,37 +153,13 @@ function startDisplayLoop() {
|
|
|
185
153
|
}, RENDER_INTERVAL_MS);
|
|
186
154
|
}
|
|
187
155
|
|
|
188
|
-
// ==========================================================================
|
|
189
|
-
// 7. CONNESSIONE SIGNALK (subscribe=self)
|
|
190
|
-
// ==========================================================================
|
|
191
|
-
function connect() {
|
|
192
|
-
if (simulationMode) return;
|
|
193
|
-
let addr = (window.location.protocol.includes("http")) ? window.location.host : CONFIG.server.fallbackIp;
|
|
194
|
-
try {
|
|
195
|
-
socket = new WebSocket(`ws://${addr}/signalk/v1/stream?subscribe=self`);
|
|
196
|
-
socket.onopen = () => { ui.status.className = "online"; ui.status.innerText = "ONLINE"; };
|
|
197
|
-
socket.onmessage = (e) => { const d = JSON.parse(e.data); if (d.updates) d.updates.forEach(u => u.values && u.values.forEach(v => processIncomingData(v.path, v.value))); };
|
|
198
|
-
socket.onclose = () => !simulationMode && setTimeout(connect, 5000);
|
|
199
|
-
} catch (e) { setTimeout(connect, 5000); }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ==========================================================================
|
|
203
|
-
// 8. FUNZIONI GRAFICHE (Hercules Mode)
|
|
204
|
-
// ==========================================================================
|
|
205
156
|
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
157
|
|
|
207
158
|
function manageHistory(t, v) {
|
|
208
159
|
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);
|
|
160
|
+
const interval = simulationMode ? SIM_SAMPLE_INTERVAL : (CONFIG.graphs.historyMinutes * 60000) / CONFIG.graphs.samples;
|
|
161
|
+
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; }
|
|
162
|
+
const mode = graphModes[t], cfg = calculateScale(t, store.histories[t], mode);
|
|
219
163
|
const box = document.getElementById(t + '-graph').closest('.data-box');
|
|
220
164
|
if (mode === 'hercules') box.classList.add('box-hercules'); else box.classList.remove('box-hercules');
|
|
221
165
|
updateScaleLabels(t, cfg.min, cfg.max);
|
|
@@ -226,14 +170,9 @@ function calculateScale(type, data, mode) {
|
|
|
226
170
|
const s = CONFIG.scales[type] || { stdMax: 12, hercSpan: 4, step: 2 };
|
|
227
171
|
let aMin = Math.min(...data), aMax = Math.max(...data);
|
|
228
172
|
if (mode === 'hercules') {
|
|
229
|
-
let avg = (aMin + aMax) / 2;
|
|
230
|
-
let
|
|
231
|
-
|
|
232
|
-
let min = Math.max(0, Math.floor(avg - (span / 2)));
|
|
233
|
-
return { min, max: min + span };
|
|
234
|
-
} else {
|
|
235
|
-
return { min: 0, max: Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step) };
|
|
236
|
-
}
|
|
173
|
+
let avg = (aMin + aMax) / 2; let span = Math.max(s.hercSpan, Math.ceil(aMax - aMin)); if (span % 2 !== 0) span += 1;
|
|
174
|
+
let min = Math.max(0, Math.floor(avg - (span / 2))); return { min, max: min + span };
|
|
175
|
+
} else return { min: 0, max: Math.max(s.stdMax, Math.ceil(aMax / s.step) * s.step) };
|
|
237
176
|
}
|
|
238
177
|
|
|
239
178
|
function updateScaleLabels(t, min, max) {
|
|
@@ -242,64 +181,85 @@ function updateScaleLabels(t, min, max) {
|
|
|
242
181
|
}
|
|
243
182
|
|
|
244
183
|
function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
245
|
-
const svg = document.getElementById(id);
|
|
184
|
+
const svg = document.getElementById(id);
|
|
185
|
+
if (!svg || d.length < 2) return;
|
|
186
|
+
|
|
246
187
|
const w = 200, h = 40, range = max - min || 1;
|
|
188
|
+
const lClass = isHercules ? 'line-hercules' : '';
|
|
189
|
+
|
|
190
|
+
// 1. Griglie
|
|
247
191
|
let grids = "";
|
|
248
192
|
[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" />`; });
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
for (let m = 1; m < minutes; m++) {
|
|
252
|
-
const x = w - (m / minutes) * w;
|
|
193
|
+
for (let m = 1; m < CONFIG.graphs.historyMinutes; m++) {
|
|
194
|
+
const x = w - (m / CONFIG.graphs.historyMinutes) * w;
|
|
253
195
|
grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(255,255,255,0.05)" stroke-width="0.5" />`;
|
|
254
196
|
}
|
|
255
|
-
|
|
197
|
+
|
|
198
|
+
// 2. Calcolo Percorsi
|
|
256
199
|
let pD = "", cS = "";
|
|
257
200
|
d.forEach((v, i) => {
|
|
258
|
-
const x = (i/(CONFIG.graphs.samples-1))*w, y = h-(Math.max(0,Math.min(1,(v-min)/range))*h);
|
|
201
|
+
const x = (i/(CONFIG.graphs.samples-1))*w, y = h-(Math.max(0,Math.min(1,(v-min)/range))*h);
|
|
202
|
+
pD += `${i===0?'M':'L'} ${x} ${y} `;
|
|
203
|
+
|
|
204
|
+
// Logica segmenti colorati per TWS
|
|
259
205
|
if (isTws && i > 0) {
|
|
260
206
|
const px = ((i-1)/(CONFIG.graphs.samples-1))*w, py = h-(Math.max(0,Math.min(1,(d[i-1]-min)/range))*h);
|
|
261
|
-
let c = "#f1c40f";
|
|
262
|
-
|
|
207
|
+
let c = "#f1c40f";
|
|
208
|
+
if (v >= CONFIG.graphs.reef2) c = "#e74c3c";
|
|
209
|
+
else if (v >= CONFIG.graphs.reef1) c = "#e67e22";
|
|
210
|
+
// Lo spessore ora è rimosso da qui e gestito dal CSS tramite la classe
|
|
211
|
+
cS += `<line x1="${px}" y1="${py}" x2="${x}" y2="${y}" stroke="${c}" class="${lClass}" />`;
|
|
263
212
|
}
|
|
264
213
|
});
|
|
265
214
|
|
|
266
|
-
|
|
215
|
+
// 3. Rendering finale
|
|
267
216
|
const aP = pD + ` L ${((d.length-1)/(CONFIG.graphs.samples-1))*w} ${h} L 0 ${h} Z`;
|
|
268
|
-
|
|
217
|
+
const clrs = { 'stw-graph': '#2ecc71', 'sog-graph': '#f39c12', 'depth-graph': '#3498db', 'tws-graph': '#f1c40f' };
|
|
218
|
+
|
|
219
|
+
if (isTws) {
|
|
220
|
+
svg.innerHTML = `${grids}<path d="${aP}" fill="rgba(241,196,15,0.12)" stroke="none" />${cS}`;
|
|
221
|
+
} else {
|
|
222
|
+
svg.innerHTML = `${grids}<path d="${aP}" fill="${clrs[id]}22" stroke="none" /><path d="${pD}" class="${lClass}" fill="none" stroke="${clrs[id]}" />`;
|
|
223
|
+
}
|
|
269
224
|
}
|
|
270
225
|
|
|
271
226
|
// ==========================================================================
|
|
272
|
-
//
|
|
227
|
+
// 5. EVENTI E INTERAZIONI
|
|
273
228
|
// ==========================================================================
|
|
229
|
+
|
|
230
|
+
function toggleFocusMode(type, element) {
|
|
231
|
+
const container = document.querySelector('.main-container');
|
|
232
|
+
const parentPanel = element.closest('.side-panel');
|
|
233
|
+
const isLeft = parentPanel.classList.contains('left-panel');
|
|
234
|
+
isFocusActive = !isFocusActive;
|
|
235
|
+
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; }
|
|
236
|
+
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')); }
|
|
237
|
+
}
|
|
238
|
+
|
|
274
239
|
['stw', 'sog', 'tws', 'depth'].forEach(type => {
|
|
275
240
|
const el = document.getElementById(type + '-graph').closest('.data-box');
|
|
276
|
-
el.addEventListener('dblclick', (e) => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
241
|
+
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); });
|
|
242
|
+
const startPress = (e) => { if (!isFocusActive) pressTimer = setTimeout(() => { toggleFocusMode(type, el); if (e.cancelable) e.preventDefault(); }, 1000); };
|
|
243
|
+
const cancelPress = () => { clearTimeout(pressTimer); };
|
|
244
|
+
el.addEventListener('mousedown', startPress); el.addEventListener('touchstart', startPress, {passive: false});
|
|
245
|
+
['mouseup', 'mouseleave', 'touchend'].forEach(evt => el.addEventListener(evt, cancelPress));
|
|
246
|
+
el.addEventListener('click', (e) => { if (blockNextClick) { blockNextClick = false; return; } if (isFocusActive && el.classList.contains('is-focused')) toggleFocusMode(type, el); });
|
|
247
|
+
el.addEventListener('contextmenu', (e) => { if (isFocusActive) e.preventDefault(); });
|
|
281
248
|
});
|
|
282
249
|
|
|
283
|
-
if (ui.hotspot) {
|
|
284
|
-
ui.hotspot.addEventListener('click', (e) => {
|
|
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
|
-
}
|
|
290
|
-
|
|
291
|
-
(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); } } })();
|
|
250
|
+
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(); } }); }
|
|
292
251
|
|
|
252
|
+
// ==========================================================================
|
|
253
|
+
// 6. INIZIALIZZAZIONE
|
|
254
|
+
// ==========================================================================
|
|
255
|
+
(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); } } })();
|
|
256
|
+
function connect() { if (simulationMode) return; let addr = (window.location.protocol.includes("http")) ? window.location.host : CONFIG.server.fallbackIp; try { socket = new WebSocket(`ws://${addr}/signalk/v1/stream?subscribe=self`); socket.onopen = () => { ui.status.className = "online"; ui.status.innerText = "ONLINE"; }; socket.onmessage = (e) => { const d = JSON.parse(e.data); if (d.updates) d.updates.forEach(u => u.values && u.values.forEach(v => processIncomingData(v.path, v.value))); }; socket.onclose = () => !simulationMode && setTimeout(connect, 5000); } catch (e) { setTimeout(connect, 5000); } }
|
|
293
257
|
async function init() { await fetchServerConfig(); startDisplayLoop(); connect(); }
|
|
294
258
|
window.addEventListener('load', init);
|
|
295
|
-
|
|
296
259
|
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'); }
|
|
297
260
|
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
261
|
|
|
262
|
+
// Simulatore (Triple click su Depth)
|
|
299
263
|
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
|
-
};
|
|
264
|
+
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
265
|
})());
|
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,135 +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
|
-
<circle cx="20" cy="20" r="
|
|
191
|
-
<
|
|
192
|
-
<g
|
|
154
|
+
<circle cx="20" cy="20" r="19" fill="#151515" stroke="#444" stroke-width="1.5"/>
|
|
155
|
+
<text x="20" y="8" fill="#e74c3c" font-size="6" text-anchor="middle" font-weight="900">N</text>
|
|
156
|
+
<g stroke="#555" stroke-width="0.8">
|
|
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"/>
|
|
158
|
+
</g>
|
|
159
|
+
<g id="twd-arrow" transform="rotate(0, 20, 20)">
|
|
160
|
+
<path d="M20,10 L16,26 L20,23 L24,26 Z" fill="#ffff00" stroke="#000" stroke-width="0.5"/>
|
|
161
|
+
</g>
|
|
162
|
+
<circle cx="20" cy="20" r="1.5" fill="#555" />
|
|
193
163
|
</svg>
|
|
194
164
|
<span class="value value-large" id="twd-avg">---°</span>
|
|
195
165
|
</div>
|
|
196
166
|
</div>
|
|
197
167
|
</div>
|
|
168
|
+
|
|
198
169
|
</div>
|
|
199
|
-
|
|
200
170
|
<script src="app.js"></script>
|
|
201
171
|
</body>
|
|
202
172
|
</html>
|
|
203
|
-
---Fine Codice ---
|
package/package.json
CHANGED
package/style.css
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
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
|
+
/* Impedisce selezioni accidentali su tutto il corpo dell'app */
|
|
14
|
+
-webkit-user-select: none;
|
|
15
|
+
user-select: none;
|
|
16
|
+
}
|
|
5
17
|
|
|
6
18
|
.main-container {
|
|
7
19
|
display: grid;
|
|
@@ -9,68 +21,39 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
9
21
|
height: 100%;
|
|
10
22
|
padding: 5px;
|
|
11
23
|
box-sizing: border-box;
|
|
12
|
-
/* Default Desktop (es. 4:3 o schermi medi) */
|
|
13
|
-
grid-template-columns: 1fr 3.5fr 1fr;
|
|
14
|
-
grid-template-rows: 100%;
|
|
15
24
|
gap: 8px;
|
|
16
|
-
|
|
25
|
+
/* Desktop Default: 3 colonne */
|
|
26
|
+
grid-template-columns: 1.5fr 3fr 1.5fr;
|
|
27
|
+
grid-template-rows: 100%;
|
|
28
|
+
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
17
29
|
}
|
|
18
30
|
|
|
19
|
-
/*
|
|
20
|
-
@media (min-aspect-ratio: 1.
|
|
31
|
+
/* Ottimizzazione per schermi molto larghi (16:10, 16:9) */
|
|
32
|
+
@media (min-aspect-ratio: 1.5) {
|
|
21
33
|
.main-container {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
gap: 15px; /* Aumentiamo anche il distacco per eleganza */
|
|
34
|
+
grid-template-columns: 2fr 3fr 2fr;
|
|
35
|
+
gap: 15px;
|
|
25
36
|
}
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
/* ==========================================================================
|
|
29
|
-
2. PANNELLI
|
|
40
|
+
2. PANNELLI E DATA-BOX
|
|
30
41
|
========================================================================== */
|
|
31
42
|
.side-panel {
|
|
32
43
|
display: flex;
|
|
33
44
|
flex-direction: column;
|
|
34
|
-
background: rgba(255, 255, 255, 0.
|
|
35
|
-
border-radius:
|
|
45
|
+
background: rgba(255, 255, 255, 0.03);
|
|
46
|
+
border-radius: 12px;
|
|
36
47
|
z-index: 10;
|
|
37
48
|
height: 100%;
|
|
49
|
+
overflow: hidden;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
text-align: right;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/* Riquadro singolo: agisce da contenitore per i font dinamici (cqh) */
|
|
46
|
-
.data-box {
|
|
47
|
-
position: relative;
|
|
48
|
-
border-bottom: 1px solid #222;
|
|
49
|
-
padding: 8px 10px;
|
|
50
|
-
display: flex;
|
|
51
|
-
flex-direction: column;
|
|
52
|
-
width: 100%;
|
|
53
|
-
box-sizing: border-box;
|
|
54
|
-
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
|
-
}
|
|
63
|
-
|
|
64
|
-
.data-box:nth-child(3),
|
|
65
|
-
.data-box:nth-child(4),
|
|
66
|
-
.data-box:nth-child(5) {
|
|
67
|
-
height: 16.666vh;
|
|
68
|
-
}
|
|
52
|
+
.left-panel { grid-column: 1; }
|
|
53
|
+
.right-panel { grid-column: 3; align-items: flex-end; text-align: right; }
|
|
69
54
|
|
|
70
|
-
/* ==========================================================================
|
|
71
|
-
3. STRUMENTO CENTRALE (WIND GAUGE)
|
|
72
|
-
========================================================================== */
|
|
73
55
|
.center-panel {
|
|
56
|
+
grid-column: 2;
|
|
74
57
|
display: flex;
|
|
75
58
|
flex-direction: column;
|
|
76
59
|
align-items: center;
|
|
@@ -79,278 +62,150 @@ body { background-color: #000; color: #fff; font-family: -apple-system, BlinkMac
|
|
|
79
62
|
overflow: hidden;
|
|
80
63
|
}
|
|
81
64
|
|
|
82
|
-
|
|
65
|
+
.data-box {
|
|
66
|
+
position: relative;
|
|
67
|
+
border-bottom: 1px solid #222;
|
|
68
|
+
padding: 8px 12px;
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-direction: column;
|
|
83
71
|
width: 100%;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
box-sizing: border-box;
|
|
73
|
+
container-type: size;
|
|
74
|
+
flex: 1;
|
|
75
|
+
overflow: hidden; /* Evita sconfinamenti su iPhone */
|
|
87
76
|
}
|
|
88
77
|
|
|
78
|
+
/* Altezze Proporzionali Desktop */
|
|
79
|
+
.data-box:nth-child(1), .data-box:nth-child(2) { height: 25vh; }
|
|
80
|
+
.data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 16.666vh; }
|
|
81
|
+
|
|
89
82
|
/* ==========================================================================
|
|
90
|
-
|
|
83
|
+
3. TACTICAL FOCUS MODE (50/50 SPLIT)
|
|
91
84
|
========================================================================== */
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
justify-content: space-between;
|
|
95
|
-
align-items: baseline;
|
|
96
|
-
margin-bottom: 2px;
|
|
97
|
-
width: 100%;
|
|
98
|
-
}
|
|
85
|
+
.focus-active { grid-template-columns: 1fr 1fr !important; }
|
|
86
|
+
.focus-active .side-panel:not(.has-focus) { display: none !important; }
|
|
99
87
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
88
|
+
/* Simmetria: se il focus è a sx, il vento va a dx e viceversa */
|
|
89
|
+
.focus-active.focus-side-left .side-panel.has-focus { grid-column: 1; }
|
|
90
|
+
.focus-active.focus-side-left .center-panel { grid-column: 2; }
|
|
91
|
+
.focus-active.focus-side-right .center-panel { grid-column: 1; }
|
|
92
|
+
.focus-active.focus-side-right .side-panel.has-focus { grid-column: 2; }
|
|
106
93
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
/* Box Focalizzato a tutto schermo */
|
|
95
|
+
.focus-active .has-focus .data-box:not(.is-focused) { display: none !important; }
|
|
96
|
+
.focus-active .has-focus .data-box.is-focused {
|
|
97
|
+
height: 100vh !important;
|
|
98
|
+
border: none;
|
|
99
|
+
background: rgba(255, 255, 255, 0.05);
|
|
111
100
|
}
|
|
112
101
|
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
color: #fff;
|
|
116
|
-
/* 22% dell'altezza del box, minimo 1.2rem */
|
|
117
|
-
font-size: clamp(1.2rem, 22cqh, 3rem);
|
|
118
|
-
font-weight: 600;
|
|
119
|
-
line-height: 0.9;
|
|
120
|
-
letter-spacing: -1px;
|
|
121
|
-
transition: color 0.3s ease;
|
|
122
|
-
padding-bottom: 5px;
|
|
123
|
-
}
|
|
102
|
+
.focus-active .is-focused .value { font-size: clamp(3rem, 18cqh, 8rem) !important; margin-top: 10px; }
|
|
103
|
+
.focus-active .is-focused .scale-labels { font-size: 22px !important; min-width: 40px !important; }
|
|
124
104
|
|
|
125
|
-
/*
|
|
126
|
-
.
|
|
127
|
-
margin-top: auto;
|
|
128
|
-
margin-bottom: auto;
|
|
129
|
-
/* 35% dell'altezza del box, minimo 1.5rem */
|
|
130
|
-
font-size: clamp(1.5rem, 35cqh, 4rem);
|
|
131
|
-
line-height: 0.85;
|
|
132
|
-
}
|
|
105
|
+
/* Grafica Linee Sottili in Focus */
|
|
106
|
+
.focus-active .is-focused .sparkline path { stroke-width: 0.8px !important; }
|
|
133
107
|
|
|
134
108
|
/* ==========================================================================
|
|
135
|
-
|
|
109
|
+
4. TIPOGRAFIA DINAMICA (ELASTICA CQH)
|
|
136
110
|
========================================================================== */
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
justify-content: space-between;
|
|
141
|
-
align-items: flex-end;
|
|
142
|
-
width: 100%;
|
|
143
|
-
margin-top: auto;
|
|
144
|
-
margin-bottom: auto; /* Centratura verticale */
|
|
145
|
-
padding-bottom: 5px;
|
|
146
|
-
}
|
|
111
|
+
.label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; width: 100%; }
|
|
112
|
+
.label { color: #666; font-size: 0.65rem; font-weight: bold; text-transform: uppercase; }
|
|
113
|
+
.unit { color: #888; font-size: 0.6rem; font-weight: bold; }
|
|
147
114
|
|
|
148
|
-
.
|
|
149
|
-
display: flex;
|
|
150
|
-
flex-direction: column;
|
|
151
|
-
justify-content: space-between;
|
|
152
|
-
width: 48%;
|
|
153
|
-
height: 100%;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
.dual-value-col.right-col {
|
|
157
|
-
align-items: flex-end;
|
|
158
|
-
text-align: right;
|
|
159
|
-
}
|
|
115
|
+
.value { color: #fff; font-size: clamp(1.2rem, 22cqh, 3rem); font-weight: 600; line-height: 0.9; letter-spacing: -1px; transition: color 0.3s ease; padding-bottom: 5px; }
|
|
160
116
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
font-weight: bold;
|
|
165
|
-
text-transform: uppercase;
|
|
117
|
+
/* Allineamento centrato per box senza grafico */
|
|
118
|
+
.value-large, .dual-value-container, .value-with-compass {
|
|
119
|
+
margin-top: auto;
|
|
166
120
|
margin-bottom: auto;
|
|
167
121
|
}
|
|
168
122
|
|
|
169
|
-
.value.
|
|
170
|
-
font-size: clamp(1rem, 20cqh, 1.8rem);
|
|
171
|
-
padding-bottom: 0;
|
|
172
|
-
}
|
|
123
|
+
.value-large { font-size: clamp(1.5rem, 35cqh, 4.5rem); line-height: 0.85; }
|
|
173
124
|
|
|
174
|
-
/*
|
|
175
|
-
.value-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
width: 100%;
|
|
180
|
-
margin-top: auto;
|
|
181
|
-
margin-bottom: auto; /* Centratura verticale */
|
|
182
|
-
}
|
|
125
|
+
/* TACK E TWD BUSSOLA */
|
|
126
|
+
.dual-value-container { display: flex; justify-content: space-between; width: 100%; }
|
|
127
|
+
.dual-value-col { display: flex; flex-direction: column; width: 48%; }
|
|
128
|
+
.dual-label { color: #666; font-size: 0.55rem; font-weight: bold; text-transform: uppercase; margin-bottom: 2px; }
|
|
129
|
+
.value.dual-val { font-size: clamp(1rem, 22cqh, 2rem); padding-bottom: 0; line-height: 0.9; }
|
|
183
130
|
|
|
131
|
+
.value-with-compass { display: flex; justify-content: space-between; align-items: center; width: 100%; gap: 10px; }
|
|
184
132
|
.mini-compass {
|
|
185
|
-
width:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
border-radius: 50%;
|
|
189
|
-
flex-shrink: 0;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.value-with-compass .value-large {
|
|
193
|
-
margin-top: 0;
|
|
194
|
-
margin-bottom: 0;
|
|
133
|
+
width: clamp(40px, 60cqh, 120px); height: clamp(40px, 60cqh, 120px);
|
|
134
|
+
background: #000; border-radius: 50%; flex-shrink: 0; border: 1.5px solid #333;
|
|
135
|
+
box-shadow: inset 0 0 10px rgba(0,0,0,0.8); transition: all 0.4s ease;
|
|
195
136
|
}
|
|
137
|
+
.value-with-compass .value-large { margin: 0; flex-grow: 1; text-align: right; font-size: clamp(1.2rem, 35cqh, 4rem); }
|
|
196
138
|
|
|
197
139
|
/* ==========================================================================
|
|
198
|
-
|
|
140
|
+
5. GRAFICI E SCALE
|
|
199
141
|
========================================================================== */
|
|
200
|
-
.graph-wrapper {
|
|
201
|
-
|
|
202
|
-
width: 100%;
|
|
203
|
-
flex-grow: 1;
|
|
204
|
-
min-height: 20px;
|
|
205
|
-
margin-top: 4px;
|
|
206
|
-
display: flex;
|
|
207
|
-
align-items: stretch;
|
|
208
|
-
gap: 4px; /* Ridotto: spazio minimo tra numeri e grafico */
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.sparkline {
|
|
212
|
-
flex-grow: 1;
|
|
213
|
-
height: 100%;
|
|
214
|
-
background: rgba(255, 255, 255, 0.03);
|
|
215
|
-
border-radius: 4px;
|
|
216
|
-
}
|
|
142
|
+
.graph-wrapper { position: relative; width: 100%; flex-grow: 1; min-height: 0; margin-top: 4px; display: flex; align-items: stretch; gap: 6px; }
|
|
143
|
+
.sparkline { flex-grow: 1; height: 100%; background: rgba(255, 255, 255, 0.03); border-radius: 4px; }
|
|
217
144
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
color: #888; /* Leggermente più chiaro per leggibilità */
|
|
224
|
-
font-weight: bold;
|
|
225
|
-
min-width: 16px; /* Allargato per il nuovo font */
|
|
226
|
-
height: 100%;
|
|
227
|
-
line-height: 1;
|
|
145
|
+
/* Uniforma lo spessore di TUTTI i tipi di linea nei grafici (Standard) */
|
|
146
|
+
.sparkline path,
|
|
147
|
+
.sparkline line {
|
|
148
|
+
stroke-width: 1px !important;
|
|
149
|
+
transition: stroke-width 0.3s ease;
|
|
228
150
|
}
|
|
229
151
|
|
|
230
|
-
|
|
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
|
-
}
|
|
152
|
+
.scale-labels { display: flex; flex-direction: column; justify-content: space-between; font-size: 10px; color: #777; font-weight: bold; min-width: 16px; height: 100%; line-height: 1; transition: all 0.3s ease; }
|
|
241
153
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
.right-panel .sparkline {
|
|
247
|
-
order: 2;
|
|
248
|
-
}
|
|
154
|
+
/* Simmetria interna */
|
|
155
|
+
.left-panel .scale-labels { order: 2; text-align: left; }
|
|
156
|
+
.left-panel .sparkline { order: 1; }
|
|
157
|
+
.right-panel .scale-labels { order: 1; text-align: right; }
|
|
158
|
+
.right-panel .sparkline { order: 2; }
|
|
249
159
|
|
|
250
|
-
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#tws-graph { stroke: #f1c40f; fill: rgba(241, 196, 15, 0.15); }
|
|
160
|
+
#stw-graph { stroke: #2ecc71; fill: rgba(46, 204, 113, 0.12); }
|
|
161
|
+
#sog-graph { stroke: #f39c12; fill: rgba(243, 156, 18, 0.12); }
|
|
162
|
+
#depth-graph { stroke: #3498db; fill: rgba(52, 152, 219, 0.12); }
|
|
163
|
+
#tws-graph { stroke: #f1c40f; fill: rgba(241, 196, 15, 0.12); }
|
|
255
164
|
|
|
256
|
-
/*
|
|
165
|
+
/* HERCULES MODE */
|
|
257
166
|
.line-hercules {
|
|
258
|
-
filter: drop-shadow(0 0 5px #ff0000);
|
|
259
|
-
stroke-width:
|
|
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
|
-
transition: all 0.3s ease;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
.box-hercules .sparkline {
|
|
270
|
-
background: rgba(255, 0, 0, 0.05); /* Sfondo grafico leggermente rosso */
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/* Indicatore "H" più evidente */
|
|
274
|
-
.box-hercules::after {
|
|
275
|
-
content: "HERCULES";
|
|
276
|
-
position: absolute;
|
|
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;
|
|
167
|
+
filter: drop-shadow(0 0 5px #ff0000);
|
|
168
|
+
stroke-width: 1.8px !important;
|
|
284
169
|
}
|
|
170
|
+
.box-hercules { background: rgba(255, 0, 0, 0.08) !important; transition: all 0.3s ease; }
|
|
171
|
+
.box-hercules::after { content: "HERCULES"; position: absolute; bottom: 4px; right: 35px; font-size: 7px; color: #ff4444; font-weight: 900; opacity: 0.8; }
|
|
285
172
|
|
|
286
173
|
/* ==========================================================================
|
|
287
|
-
|
|
174
|
+
6. STATI E ANIMAZIONI
|
|
288
175
|
========================================================================== */
|
|
289
|
-
#status {
|
|
290
|
-
position: absolute;
|
|
291
|
-
top: 5px;
|
|
292
|
-
right: 15px;
|
|
293
|
-
font-size: 0.5rem;
|
|
294
|
-
text-transform: uppercase;
|
|
295
|
-
z-index: 1000;
|
|
296
|
-
}
|
|
297
|
-
|
|
176
|
+
#status { position: absolute; top: 5px; right: 15px; font-size: 0.5rem; text-transform: uppercase; z-index: 1000; }
|
|
298
177
|
.online { color: #2ecc71; opacity: 0.5; }
|
|
299
178
|
.offline { color: #e74c3c; font-weight: bold; }
|
|
300
|
-
|
|
301
|
-
/* Fluidità lancette SVG */
|
|
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
|
-
|
|
179
|
+
#awa-pointer, #twa-pointer, #track-pointer, #twd-arrow { transition: all 0.6s cubic-bezier(0.1, 0.7, 0.1, 1); }
|
|
306
180
|
#leeway-mask-rect { transition: none; }
|
|
307
|
-
|
|
308
|
-
/* Allarmi Profondità */
|
|
309
181
|
.alarm-warning { color: #f1c40f !important; }
|
|
310
|
-
.alarm-danger {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
}
|
|
322
|
-
|
|
323
|
-
.unstable-data {
|
|
324
|
-
animation: blink-unstable 1.5s infinite ease-in-out;
|
|
325
|
-
color: #f39c12 !important;
|
|
326
|
-
}
|
|
182
|
+
.alarm-danger { color: #e74c3c !important; font-weight: 900; animation: blink-unstable 1s infinite; }
|
|
183
|
+
@keyframes blink-unstable { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } }
|
|
184
|
+
.unstable-data { animation: blink-unstable 1.5s infinite ease-in-out; color: #f39c12 !important; }
|
|
185
|
+
#wind-gauge { width: 100%; height: 100%; max-height: 100%; object-fit: contain; }
|
|
327
186
|
|
|
328
187
|
/* ==========================================================================
|
|
329
|
-
|
|
188
|
+
7. RESPONSIVE (PORTRAIT)
|
|
330
189
|
========================================================================== */
|
|
331
190
|
@media (max-aspect-ratio: 0.9 / 1) {
|
|
332
191
|
.main-container {
|
|
333
|
-
grid-template-columns: 1fr 1fr;
|
|
334
|
-
grid-template-rows:
|
|
192
|
+
grid-template-columns: 1fr 1fr !important;
|
|
193
|
+
grid-template-rows: 45vh 55vh !important; /* Calibrato per iPhone */
|
|
335
194
|
}
|
|
336
195
|
|
|
337
|
-
|
|
338
|
-
.
|
|
339
|
-
|
|
340
|
-
grid-row: 1;
|
|
341
|
-
height: 50vh;
|
|
342
|
-
}
|
|
196
|
+
.center-panel { grid-row: 1; grid-column: 1 / span 2; padding: 5px 0; }
|
|
197
|
+
.left-panel { grid-row: 2; grid-column: 1; }
|
|
198
|
+
.right-panel { grid-row: 2; grid-column: 2; }
|
|
343
199
|
|
|
344
|
-
/*
|
|
345
|
-
.
|
|
346
|
-
.
|
|
200
|
+
/* Focus Mode Verticale */
|
|
201
|
+
.main-container.focus-active { grid-template-columns: 100% !important; }
|
|
202
|
+
.focus-active .center-panel { grid-row: 1; height: 45vh !important; }
|
|
203
|
+
.focus-active .side-panel.has-focus { grid-row: 2; height: 55vh !important; }
|
|
347
204
|
|
|
348
|
-
|
|
205
|
+
/* Altezze box iPhone */
|
|
206
|
+
.data-box:nth-child(1), .data-box:nth-child(2) { height: 13.5vh; }
|
|
207
|
+
.data-box:nth-child(3), .data-box:nth-child(4), .data-box:nth-child(5) { height: 9.3vh; }
|
|
349
208
|
|
|
350
|
-
|
|
351
|
-
.
|
|
352
|
-
.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
|
-
.value-large { font-size: clamp(1.2rem, 35cqh, 2.5rem); }
|
|
209
|
+
.value-large { font-size: clamp(1.2rem, 35cqh, 2.5rem); margin: auto 0; }
|
|
210
|
+
.value.dual-val { font-size: clamp(0.9rem, 30cqh, 1.4rem); }
|
|
356
211
|
}
|