speedandcurrent 1.6.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -59
- package/correctionTable.js +1 -1
- package/index.js +392 -264
- package/package.json +5 -2
- package/public/TableRenderer.js +114 -210
- package/public/app.js +804 -447
- package/public/index.html +257 -96
- package/public/main.css +79 -158
- package/public/vectors.html +0 -30
- package/public/vectors.js +0 -145
package/public/index.html
CHANGED
|
@@ -4,127 +4,288 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Speed and
|
|
7
|
+
<title>Speed and Current</title>
|
|
8
|
+
<script>
|
|
9
|
+
(async () => {
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch('/');
|
|
12
|
+
const html = await res.text();
|
|
13
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
14
|
+
const link = doc.querySelector('link[rel="stylesheet"]');
|
|
15
|
+
if (link) {
|
|
16
|
+
const el = document.createElement('link');
|
|
17
|
+
el.rel = 'stylesheet';
|
|
18
|
+
el.href = new URL(link.getAttribute('href'), res.url).href;
|
|
19
|
+
document.head.appendChild(el);
|
|
20
|
+
}
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.warn('[webapp] Could not load SignalK admin stylesheet:', e);
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
25
|
+
</script>
|
|
8
26
|
<link rel="stylesheet" href="main.css">
|
|
9
27
|
</head>
|
|
10
28
|
|
|
11
|
-
<body>
|
|
12
|
-
|
|
13
|
-
<header class="menu-bar">
|
|
14
|
-
<!-- Speed Unit Selection -->
|
|
15
|
-
<div class="menu-item dropdown">
|
|
16
|
-
<label for="speed-unit">Speed:</label>
|
|
17
|
-
<select id="speed-unit" onchange="handleSpeedUnitChange(this.value)">
|
|
18
|
-
<option value="ms">m/s</option>
|
|
19
|
-
<option value="knots" selected>knots</option>
|
|
20
|
-
<option value="kmh">km/h</option>
|
|
21
|
-
</select>
|
|
22
|
-
</div>
|
|
29
|
+
<body class="app header-fixed sidebar-fixed aside-menu-fixed aside-menu-hidden">
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
<div class="
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
<header class="app-header navbar navbar-light bg-white">
|
|
32
|
+
<div class="d-flex align-items-center">
|
|
33
|
+
<a class="d-flex align-items-center gap-2 me-0 text-decoration-none" href="#">
|
|
34
|
+
<img src="icon.png" alt="" style="height:48px;width:auto">
|
|
35
|
+
<span class="app-title">Speed & Current</span>
|
|
36
|
+
</a>
|
|
37
|
+
<button class="navbar-toggler sidebar-toggler d-md-down-none ms-2" type="button" id="sidebarToggler">
|
|
38
|
+
<span class="navbar-toggler-icon"></span>
|
|
39
|
+
</button>
|
|
31
40
|
</div>
|
|
41
|
+
<span id="message" class="small ms-auto me-3"></span>
|
|
42
|
+
</header>
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
<div class="menu-item" id="metrics-group">
|
|
35
|
-
<span>Show:</span>
|
|
36
|
-
<label><input type="checkbox" id="show-correction" onchange="handleMetricsChange()"> correction</label>
|
|
37
|
-
<label><input type="checkbox" id="show-factor" onchange="handleMetricsChange()"> factor</label>
|
|
38
|
-
<label><input type="checkbox" id="show-leeway" onchange="handleMetricsChange()"> leeway</label>
|
|
39
|
-
<label><input type="checkbox" id="show-trace" onchange="handleMetricsChange()"> trace</label>
|
|
40
|
-
<label><input type="checkbox" id="show-N" onchange="handleMetricsChange()"> N</label>
|
|
41
|
-
</div>
|
|
44
|
+
<div class="app-body">
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
<div class="sidebar">
|
|
47
|
+
<nav class="sidebar-nav">
|
|
48
|
+
<ul id="main-nav" class="nav">
|
|
49
|
+
<li class="nav-item">
|
|
50
|
+
<a class="nav-link active" href="#" data-section="inputs">Inputs</a>
|
|
51
|
+
</li>
|
|
52
|
+
<li class="nav-item">
|
|
53
|
+
<a class="nav-link" href="#" data-section="estimation">Boatspeed Estimation</a>
|
|
54
|
+
</li>
|
|
55
|
+
<li class="nav-item">
|
|
56
|
+
<a class="nav-link" href="#" data-section="learning">Correction Table Learning</a>
|
|
57
|
+
</li>
|
|
58
|
+
<li class="nav-item">
|
|
59
|
+
<a class="nav-link" href="#" data-section="table">Correction Table</a>
|
|
60
|
+
</li>
|
|
61
|
+
</ul>
|
|
62
|
+
</nav>
|
|
63
|
+
<button class="sidebar-minimizer brand-minimizer" type="button" id="sidebarMinimizer"></button>
|
|
53
64
|
</div>
|
|
54
65
|
|
|
66
|
+
<main class="main">
|
|
67
|
+
<div class="container-fluid pt-3">
|
|
55
68
|
|
|
69
|
+
<!-- ===== INPUTS SECTION ===== -->
|
|
70
|
+
<div id="section-inputs" class="app-section">
|
|
71
|
+
<div class="card">
|
|
72
|
+
<div class="card-header fw-bold text-uppercase">Inputs</div>
|
|
73
|
+
<div class="card-body">
|
|
74
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Signal K Sources</h6>
|
|
75
|
+
<p class="text-muted small mb-2">Leave blank / select (any) to accept all sources.</p>
|
|
76
|
+
<table class="table table-sm table-borderless mb-0" id="inputs-sources-table">
|
|
77
|
+
<tbody></tbody>
|
|
78
|
+
</table>
|
|
79
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Live Values</h6>
|
|
80
|
+
<div id="inputs-values"></div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
56
84
|
|
|
85
|
+
<!-- ===== BOATSPEED ESTIMATION SECTION ===== -->
|
|
86
|
+
<div id="section-estimation" class="app-section" style="display:none">
|
|
87
|
+
<div class="card">
|
|
88
|
+
<div class="card-header d-flex align-items-center justify-content-between">
|
|
89
|
+
<span class="fw-bold text-uppercase">Boatspeed Estimation</span>
|
|
90
|
+
<label class="switch switch-text switch-primary mb-0" id="toggle-estimateBoatSpeed-label" title="Enable boatspeed estimation">
|
|
91
|
+
<input type="checkbox" class="switch-input form-check-input" id="toggle-estimateBoatSpeed">
|
|
92
|
+
<span class="switch-label" data-on="On" data-off="Off"></span>
|
|
93
|
+
<span class="switch-handle"></span>
|
|
94
|
+
</label>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="card-body">
|
|
97
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
98
|
+
<table class="table table-sm table-borderless mb-0" id="estimation-settings-table">
|
|
99
|
+
<tbody></tbody>
|
|
100
|
+
</table>
|
|
101
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Inputs</h6>
|
|
102
|
+
<div id="estimation-inputs"></div>
|
|
103
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Intermediates</h6>
|
|
104
|
+
<div id="estimation-intermediates"></div>
|
|
105
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Outputs</h6>
|
|
106
|
+
<div id="estimation-outputs"></div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
57
110
|
|
|
58
|
-
|
|
59
|
-
|
|
111
|
+
<!-- ===== CORRECTION TABLE LEARNING SECTION ===== -->
|
|
112
|
+
<div id="section-learning" class="app-section" style="display:none">
|
|
113
|
+
<div class="card">
|
|
114
|
+
<div class="card-header d-flex align-items-center justify-content-between">
|
|
115
|
+
<span class="fw-bold text-uppercase">Correction Table Learning</span>
|
|
116
|
+
<label class="switch switch-text switch-primary mb-0" id="toggle-updateCorrectionTable-label" title="Enable correction table learning">
|
|
117
|
+
<input type="checkbox" class="switch-input form-check-input" id="toggle-updateCorrectionTable">
|
|
118
|
+
<span class="switch-label" data-on="On" data-off="Off"></span>
|
|
119
|
+
<span class="switch-handle"></span>
|
|
120
|
+
</label>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="card-body">
|
|
123
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mb-1 small">Settings</h6>
|
|
124
|
+
<table class="table table-sm table-borderless mb-0" id="learning-settings-table">
|
|
125
|
+
<tbody></tbody>
|
|
126
|
+
</table>
|
|
127
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Smoother</h6>
|
|
128
|
+
<table class="table table-sm table-borderless mb-0" id="smoother-settings-table">
|
|
129
|
+
<tbody></tbody>
|
|
130
|
+
</table>
|
|
131
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Inputs</h6>
|
|
132
|
+
<div id="learning-inputs"></div>
|
|
133
|
+
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-1 mt-3 mb-1 small">Intermediates</h6>
|
|
134
|
+
<div id="learning-intermediates"></div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
60
138
|
|
|
61
|
-
|
|
139
|
+
<!-- ===== CORRECTION TABLE SECTION ===== -->
|
|
140
|
+
<div id="section-table" class="app-section" style="display:none">
|
|
62
141
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
142
|
+
<div class="card">
|
|
143
|
+
<div class="card-header" style="position:relative">
|
|
144
|
+
<span class="fw-bold text-uppercase">Correction Table</span>
|
|
145
|
+
<span id="active-table-name" class="ms-2 text-muted small"></span>
|
|
146
|
+
<div style="position:absolute;right:1rem;top:0;bottom:0;display:flex;align-items:center;gap:0.5rem;">
|
|
147
|
+
<button id="btn-tbl-new" class="btn btn-sm btn-outline-secondary">New</button>
|
|
148
|
+
<button id="btn-tbl-load" class="btn btn-sm btn-outline-secondary">Load</button>
|
|
149
|
+
<button id="btn-tbl-copy" class="btn btn-sm btn-outline-secondary">Copy</button>
|
|
150
|
+
<button id="btn-tbl-resize" class="btn btn-sm btn-outline-secondary">Resize</button>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="card-body">
|
|
154
|
+
<div id="table-container"></div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
67
158
|
|
|
68
|
-
<!-- Content below the fixed menu -->
|
|
69
|
-
<main class="content">
|
|
70
|
-
|
|
71
|
-
<section class="graph">
|
|
72
|
-
<svg height="300px" width="300px" xmlns="http://www.w3.org/2000/svg" viewbox="-100 -100 200 200" id="canvas">
|
|
73
|
-
|
|
74
|
-
<!-- Draw the paths -->
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<path id="hull" d="M -45 10 Q 15 25 30 0 Q 15 -25 -45 -10 z" stroke="black" fill="none" stroke-width="1"
|
|
78
|
-
transform="rotate(-90), scale(1, 1)" />
|
|
79
|
-
|
|
80
|
-
<line id="mast" x1="0" y1="0" x2="1" y2="8" stroke="brown" stroke-width="2" transform="rotate(-90)" />
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Sorry, your browser does not support inline SVG.
|
|
84
|
-
</svg>
|
|
85
|
-
</section>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<section class=" data ">
|
|
89
|
-
<h2>Deltas</h2>
|
|
90
|
-
<div id="delta-container">
|
|
91
|
-
<!-- Speeds table will be populated here -->
|
|
92
159
|
</div>
|
|
93
|
-
</
|
|
160
|
+
</main>
|
|
161
|
+
</div>
|
|
94
162
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
163
|
+
<script>
|
|
164
|
+
document.getElementById('sidebarMinimizer').addEventListener('click', () => {
|
|
165
|
+
document.body.classList.toggle('sidebar-minimized');
|
|
166
|
+
document.body.classList.toggle('brand-minimized');
|
|
167
|
+
});
|
|
168
|
+
document.getElementById('sidebarToggler').addEventListener('click', () => {
|
|
169
|
+
document.body.classList.toggle('sidebar-hidden');
|
|
170
|
+
});
|
|
171
|
+
document.querySelectorAll('#main-nav .nav-link').forEach(link => {
|
|
172
|
+
link.addEventListener('click', e => {
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
document.querySelectorAll('#main-nav .nav-link').forEach(l => l.classList.remove('active'));
|
|
175
|
+
link.classList.add('active');
|
|
176
|
+
const target = link.dataset.section;
|
|
177
|
+
document.querySelectorAll('.app-section').forEach(s => s.style.display = 'none');
|
|
178
|
+
document.getElementById('section-' + target).style.display = '';
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
</script>
|
|
108
182
|
|
|
183
|
+
<!-- ===== TABLE MANAGER MODALS ===== -->
|
|
109
184
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
185
|
+
<!-- Create table -->
|
|
186
|
+
<div class="modal fade" id="modal-create" tabindex="-1" role="dialog">
|
|
187
|
+
<div class="modal-dialog" role="document">
|
|
188
|
+
<div class="modal-content">
|
|
189
|
+
<div class="modal-header">
|
|
190
|
+
<h5 class="modal-title">New Correction Table</h5>
|
|
191
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="modal-body">
|
|
194
|
+
<table class="table table-sm table-borderless">
|
|
195
|
+
<tbody>
|
|
196
|
+
<tr><td>Name</td><td><input type="text" id="create-name" class="form-control form-control-sm" placeholder="e.g. table-2026" style="width:180px"></td></tr>
|
|
197
|
+
<tr><td id="create-maxSpeed-label">Max speed (kn)</td><td><input type="number" id="create-maxSpeed" class="form-control form-control-sm" value="9" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
198
|
+
<tr><td id="create-speedStep-label">Speed step (kn)</td><td><input type="number" id="create-speedStep" class="form-control form-control-sm" value="1" min="0.1" max="5" step="0.1" style="width:80px"></td></tr>
|
|
199
|
+
<tr><td id="create-maxHeel-label">Max heel (°)</td><td><input type="number" id="create-maxHeel" class="form-control form-control-sm" value="32" min="1" max="90" step="1" style="width:80px"></td></tr>
|
|
200
|
+
<tr><td id="create-heelStep-label">Heel step (°)</td><td><input type="number" id="create-heelStep" class="form-control form-control-sm" value="8" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
201
|
+
</tbody>
|
|
202
|
+
</table>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="modal-footer">
|
|
205
|
+
<span id="modal-create-status" class="small text-danger mr-auto"></span>
|
|
206
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
207
|
+
<button type="button" class="btn btn-primary" id="btn-create-confirm">Create</button>
|
|
208
|
+
</div>
|
|
114
209
|
</div>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
</main>
|
|
120
|
-
|
|
121
|
-
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
122
212
|
|
|
213
|
+
<!-- Load table -->
|
|
214
|
+
<div class="modal fade" id="modal-load" tabindex="-1" role="dialog">
|
|
215
|
+
<div class="modal-dialog" role="document">
|
|
216
|
+
<div class="modal-content">
|
|
217
|
+
<div class="modal-header">
|
|
218
|
+
<h5 class="modal-title">Load Correction Table</h5>
|
|
219
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
220
|
+
</div>
|
|
221
|
+
<div class="modal-body">
|
|
222
|
+
<p class="text-muted small">Select a saved table to load and activate.</p>
|
|
223
|
+
<ul id="table-list" class="list-group"></ul>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="modal-footer">
|
|
226
|
+
<span id="modal-load-status" class="small text-danger mr-auto"></span>
|
|
227
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
228
|
+
<button type="button" class="btn btn-primary" id="btn-load-confirm" disabled>Load</button>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
123
233
|
|
|
234
|
+
<!-- Copy table -->
|
|
235
|
+
<div class="modal fade" id="modal-copy" tabindex="-1" role="dialog">
|
|
236
|
+
<div class="modal-dialog" role="document">
|
|
237
|
+
<div class="modal-content">
|
|
238
|
+
<div class="modal-header">
|
|
239
|
+
<h5 class="modal-title">Copy Active Table</h5>
|
|
240
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
241
|
+
</div>
|
|
242
|
+
<div class="modal-body">
|
|
243
|
+
<p class="text-muted small">Save the active table under a new name and activate it.</p>
|
|
244
|
+
<table class="table table-sm table-borderless">
|
|
245
|
+
<tbody>
|
|
246
|
+
<tr><td>New name</td><td><input type="text" id="copy-name" class="form-control form-control-sm" placeholder="e.g. backup-2026" style="width:180px"></td></tr>
|
|
247
|
+
</tbody>
|
|
248
|
+
</table>
|
|
249
|
+
</div>
|
|
250
|
+
<div class="modal-footer">
|
|
251
|
+
<span id="modal-copy-status" class="small text-danger mr-auto"></span>
|
|
252
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
253
|
+
<button type="button" class="btn btn-primary" id="btn-copy-confirm">Copy</button>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
124
258
|
|
|
259
|
+
<!-- Resize table -->
|
|
260
|
+
<div class="modal fade" id="modal-resize" tabindex="-1" role="dialog">
|
|
261
|
+
<div class="modal-dialog" role="document">
|
|
262
|
+
<div class="modal-content">
|
|
263
|
+
<div class="modal-header">
|
|
264
|
+
<h5 class="modal-title">Resize Active Table</h5>
|
|
265
|
+
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="modal-body">
|
|
268
|
+
<p class="text-muted small">Resamples learned corrections onto a new grid. The active table file is overwritten.</p>
|
|
269
|
+
<table class="table table-sm table-borderless">
|
|
270
|
+
<tbody>
|
|
271
|
+
<tr><td id="resize-maxSpeed-label">Max speed (kn)</td><td><input type="number" id="resize-maxSpeed" class="form-control form-control-sm" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
272
|
+
<tr><td id="resize-speedStep-label">Speed step (kn)</td><td><input type="number" id="resize-speedStep" class="form-control form-control-sm" min="0.1" max="5" step="0.1" style="width:80px"></td></tr>
|
|
273
|
+
<tr><td id="resize-maxHeel-label">Max heel (°)</td><td><input type="number" id="resize-maxHeel" class="form-control form-control-sm" min="1" max="90" step="1" style="width:80px"></td></tr>
|
|
274
|
+
<tr><td id="resize-heelStep-label">Heel step (°)</td><td><input type="number" id="resize-heelStep" class="form-control form-control-sm" min="1" max="30" step="1" style="width:80px"></td></tr>
|
|
275
|
+
</tbody>
|
|
276
|
+
</table>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="modal-footer">
|
|
279
|
+
<span id="modal-resize-status" class="small mr-auto"></span>
|
|
280
|
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
281
|
+
<button type="button" class="btn btn-primary" id="btn-resize-confirm">Resize</button>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
125
286
|
|
|
126
287
|
<script type="module" src="app.js"></script>
|
|
127
|
-
|
|
288
|
+
|
|
128
289
|
</body>
|
|
129
290
|
|
|
130
291
|
</html>
|
package/public/main.css
CHANGED
|
@@ -1,84 +1,36 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/* ─── CSS Variables for instance colours (used by SVG strokes and table rows) ─ */
|
|
2
2
|
:root {
|
|
3
|
-
|
|
4
|
-
--
|
|
5
|
-
--
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--correctedBoatSpeed-color: #27632a; /* even darker green */
|
|
17
|
-
--boatSpeedRefGround-color: #00575b; /* darker teal */
|
|
18
|
-
--groundSpeed-color: #5d4037; /* darker brown */
|
|
19
|
-
--groundSpeedDamped-color: #5d4037; /* darker brown */
|
|
20
|
-
--speedCorrection-color: #ad1457; /* darker pink/red */
|
|
21
|
-
--residual-color: #6a1b9a; /* darker purple */
|
|
22
|
-
--correctionTable-color: #4e342e; /* darker brown */
|
|
3
|
+
--heading-color: #000000;
|
|
4
|
+
--attitude-color-instance: #000000;
|
|
5
|
+
--current-color: #0d47a1;
|
|
6
|
+
--currentDamped-color: #0d47a1;
|
|
7
|
+
--boatSpeed-color: #388e3c;
|
|
8
|
+
--boatSpeedDamped-color: #388e3c;
|
|
9
|
+
--correctedBoatSpeed-color: #27632a;
|
|
10
|
+
--boatSpeedRefGround-color: #00575b;
|
|
11
|
+
--groundSpeed-color: #5d4037;
|
|
12
|
+
--groundSpeedDamped-color: #5d4037;
|
|
13
|
+
--speedCorrection-color: #ad1457;
|
|
14
|
+
--residual-color: #6a1b9a;
|
|
15
|
+
--correctionTable-color: #4e342e;
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
/*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
18
|
+
/* ─── Instance ID colours ──────────────────────────────────────────────────── */
|
|
19
|
+
#heading { color: var(--heading-color); stroke: var(--heading-color); }
|
|
20
|
+
#attitude { color: var(--attitude-color-instance);stroke: var(--attitude-color-instance); }
|
|
21
|
+
#current,
|
|
22
|
+
#currentDamped { color: var(--current-color); stroke: var(--current-color); }
|
|
23
|
+
#boatSpeed,
|
|
24
|
+
#boatSpeedDamped { color: var(--boatSpeed-color); stroke: var(--boatSpeed-color); }
|
|
25
|
+
#correctedBoatSpeed { color: var(--correctedBoatSpeed-color); stroke: var(--correctedBoatSpeed-color); }
|
|
26
|
+
#boatSpeedRefGround { color: var(--boatSpeedRefGround-color); stroke: var(--boatSpeedRefGround-color); }
|
|
27
|
+
#groundSpeed,
|
|
28
|
+
#groundSpeedDamped { color: var(--groundSpeed-color); stroke: var(--groundSpeed-color); }
|
|
29
|
+
#speedCorrection { color: var(--speedCorrection-color); stroke: var(--speedCorrection-color); }
|
|
30
|
+
#residual { color: var(--residual-color); stroke: var(--residual-color); }
|
|
31
|
+
#correctionTable { color: var(--correctionTable-color); stroke: var(--correctionTable-color); }
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
/* --- Instance IDs for Specific Colors --- */
|
|
33
|
-
#heading {
|
|
34
|
-
color: var(--heading-color);
|
|
35
|
-
stroke: var(--heading-color);
|
|
36
|
-
}
|
|
37
|
-
#attitude {
|
|
38
|
-
color: var(--attitude-color-instance);
|
|
39
|
-
stroke: var(--attitude-color-instance);
|
|
40
|
-
}
|
|
41
|
-
#current, #currentDamped {
|
|
42
|
-
color: var(--current-color);
|
|
43
|
-
stroke: var(--current-color);
|
|
44
|
-
}
|
|
45
|
-
#boatSpeed, #boatSpeedDamped {
|
|
46
|
-
color: var(--boatSpeed-color);
|
|
47
|
-
stroke: var(--boatSpeed-color);
|
|
48
|
-
}
|
|
49
|
-
#correctedBoatSpeed {
|
|
50
|
-
color: var(--correctedBoatSpeed-color);
|
|
51
|
-
stroke: var(--correctedBoatSpeed-color);
|
|
52
|
-
}
|
|
53
|
-
#boatSpeedRefGround {
|
|
54
|
-
color: var(--boatSpeedRefGround-color);
|
|
55
|
-
stroke: var(--boatSpeedRefGround-color);
|
|
56
|
-
}
|
|
57
|
-
#groundSpeed, #groundSpeedDamped {
|
|
58
|
-
color: var(--groundSpeed-color);
|
|
59
|
-
stroke: var(--groundSpeed-color);
|
|
60
|
-
}
|
|
61
|
-
#speedCorrection {
|
|
62
|
-
color: var(--speedCorrection-color);
|
|
63
|
-
stroke: var(--speedCorrection-color);
|
|
64
|
-
}
|
|
65
|
-
#residual {
|
|
66
|
-
color: var(--residual-color);
|
|
67
|
-
stroke: var(--residual-color);
|
|
68
|
-
}
|
|
69
|
-
#correctionTable {
|
|
70
|
-
color: var(--correctionTable-color);
|
|
71
|
-
stroke: var(--correctionTable-color);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/* --- Table and Cell Styling --- */
|
|
75
|
-
table {
|
|
76
|
-
border-collapse: collapse;
|
|
77
|
-
}
|
|
78
|
-
th, td {
|
|
79
|
-
border: 1px solid black;
|
|
80
|
-
padding: 5px;
|
|
81
|
-
}
|
|
33
|
+
/* ─── Correction table cell sizing ────────────────────────────────────────── */
|
|
82
34
|
.TableCell {
|
|
83
35
|
width: 60px;
|
|
84
36
|
height: 60px;
|
|
@@ -86,92 +38,61 @@ th, td {
|
|
|
86
38
|
vertical-align: middle;
|
|
87
39
|
font-size: 11px;
|
|
88
40
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
border: 1px solid #ccc;
|
|
98
|
-
height: auto;
|
|
99
|
-
aspect-ratio: 1 / 1;
|
|
100
|
-
svg {
|
|
101
|
-
width: 100vmin;
|
|
102
|
-
height: 100vmin;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
line {
|
|
106
|
-
stroke-width: 2;
|
|
107
|
-
}
|
|
108
|
-
#hull, #mast {
|
|
109
|
-
stroke: purple;
|
|
110
|
-
stroke-width: 1;
|
|
111
|
-
stroke-linecap: round;
|
|
112
|
-
}
|
|
41
|
+
/* corner cell: show axis units */
|
|
42
|
+
.TableCorner { font-size: 10px; color: #888; font-weight: 400; text-align: center; }
|
|
43
|
+
/* cell states */
|
|
44
|
+
.cell--empty { background-color: #f0f0f0; }
|
|
45
|
+
.cell--active { outline: 3px solid #1565c0; outline-offset: -3px; }
|
|
46
|
+
.cell--neighbour { outline: 1px dashed #90a4ae; outline-offset: -2px; }
|
|
47
|
+
.cell-factor { font-weight: 600; font-size: 12px; }
|
|
48
|
+
.cell-leeway { font-weight: 600; font-size: 12px; margin-top: 2px; }
|
|
113
49
|
|
|
114
|
-
/*
|
|
115
|
-
body {
|
|
116
|
-
|
|
117
|
-
display: flex;
|
|
118
|
-
flex-direction: column;
|
|
119
|
-
margin: 15px 15px 15px 15px;
|
|
120
|
-
}
|
|
121
|
-
.menu-bar {
|
|
122
|
-
position: fixed;
|
|
123
|
-
top: 0;
|
|
124
|
-
left: 0;
|
|
50
|
+
/* ─── Stable table column widths inside cards ──────────────────────────────── */
|
|
51
|
+
.card-body .table {
|
|
52
|
+
table-layout: fixed;
|
|
125
53
|
width: 100%;
|
|
126
|
-
background: #6a6a6a;
|
|
127
|
-
color: black;
|
|
128
|
-
display: flex;
|
|
129
|
-
align-items: center;
|
|
130
|
-
padding: 10px 20px;
|
|
131
|
-
gap: 20px;
|
|
132
|
-
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.2);
|
|
133
|
-
z-index: 1000;
|
|
134
|
-
}
|
|
135
|
-
.menu-item {
|
|
136
|
-
display: flex;
|
|
137
|
-
align-items: center;
|
|
138
|
-
gap: 5px;
|
|
139
|
-
cursor: pointer;
|
|
140
54
|
}
|
|
141
|
-
.
|
|
142
|
-
|
|
143
|
-
|
|
55
|
+
.card-body .table td:first-child {
|
|
56
|
+
width: 55%;
|
|
57
|
+
white-space: nowrap;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
text-overflow: ellipsis;
|
|
144
60
|
}
|
|
145
|
-
.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
appearance: none;
|
|
151
|
-
padding-right: 15px;
|
|
152
|
-
cursor: pointer;
|
|
61
|
+
.card-body .table td:first-child small {
|
|
62
|
+
white-space: normal;
|
|
63
|
+
overflow: visible;
|
|
64
|
+
text-overflow: clip;
|
|
65
|
+
display: block;
|
|
153
66
|
}
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
67
|
+
.card-body .table td:last-child { width: 45%; }
|
|
68
|
+
|
|
69
|
+
/* ─── SVG stroke rules ─────────────────────────────────────────────────────── */
|
|
70
|
+
line { stroke-width: 2; }
|
|
71
|
+
#hull, #mast { stroke: purple; stroke-width: 1; stroke-linecap: round; }
|
|
72
|
+
|
|
73
|
+
/* ─── Surface plot ─────────────────────────────────────────────────────────── */
|
|
74
|
+
.surfacePlot { width: 100%; height: 100%; border: 1px solid black; padding: 0; margin: 0; position: relative; }
|
|
75
|
+
|
|
76
|
+
/* ─── SignalK navbar-brand suppression ─────────────────────────────────────── */
|
|
77
|
+
.app-header .navbar-brand {
|
|
78
|
+
background-image: none !important;
|
|
79
|
+
width: auto !important;
|
|
163
80
|
}
|
|
164
|
-
|
|
165
|
-
|
|
81
|
+
.app-header .navbar-brand::before,
|
|
82
|
+
.app-header .navbar-brand::after {
|
|
83
|
+
display: none !important;
|
|
84
|
+
content: none !important;
|
|
166
85
|
}
|
|
167
|
-
|
|
168
|
-
|
|
86
|
+
|
|
87
|
+
/* ─── App title ────────────────────────────────────────────────────────────── */
|
|
88
|
+
.app-title {
|
|
89
|
+
font-size: 1.1rem;
|
|
90
|
+
font-weight: 500;
|
|
91
|
+
color: #29363D;
|
|
169
92
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
position: relative;
|
|
177
|
-
}
|
|
93
|
+
|
|
94
|
+
/* ─── Sidebar accent ───────────────────────────────────────────────────────── */
|
|
95
|
+
.sidebar .nav-link.active { border-left-color: #00bcd4 !important; }
|
|
96
|
+
|
|
97
|
+
/* ─── Stale data rows ──────────────────────────────────────────────────────── */
|
|
98
|
+
tr.stale td { color: #aaa; font-style: italic; }
|