signalk-race-control 0.1.0
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/LICENSE +21 -0
- package/README.md +138 -0
- package/index.js +1133 -0
- package/package.json +18 -0
- package/public/app.js +1540 -0
- package/public/index.html +114 -0
- package/public/style.css +535 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Race Control</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header>
|
|
11
|
+
<h1>Race Control</h1>
|
|
12
|
+
<div class="race-select-row">
|
|
13
|
+
<select id="raceSelect"></select>
|
|
14
|
+
<button id="newRaceBtn" class="secondary">+ New Race</button>
|
|
15
|
+
<button id="exportBtn" class="secondary" hidden>Export to Excel</button>
|
|
16
|
+
<button id="deleteRaceBtn" class="secondary danger" hidden>Delete Race</button>
|
|
17
|
+
</div>
|
|
18
|
+
<div id="newRaceForm" class="new-race-form" hidden>
|
|
19
|
+
<input type="text" id="newRaceName" placeholder="Race name, e.g. Onsdagsseilas 3" autocomplete="off" />
|
|
20
|
+
<button id="createRaceBtn">Create</button>
|
|
21
|
+
<button id="cancelNewRaceBtn" class="secondary">Cancel</button>
|
|
22
|
+
</div>
|
|
23
|
+
<div id="raceArea">
|
|
24
|
+
<div id="clock" class="clock">00:00:00</div>
|
|
25
|
+
<div class="controls">
|
|
26
|
+
<button id="startBtn">Start Race</button>
|
|
27
|
+
<button id="stopBtn" class="secondary" hidden>Stop</button>
|
|
28
|
+
<button id="resumeBtn" class="secondary" hidden>Resume</button>
|
|
29
|
+
<button id="resetBtn" class="secondary">Reset</button>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="schedule-row">
|
|
32
|
+
<input type="datetime-local" id="scheduleInput" step="1" />
|
|
33
|
+
<button id="scheduleBtn" class="secondary">Schedule Start</button>
|
|
34
|
+
<button id="cancelScheduleBtn" class="secondary" hidden>Cancel Schedule</button>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="schedule-row">
|
|
37
|
+
<input type="datetime-local" id="callOffInput" step="1" />
|
|
38
|
+
<button id="scheduleCallOffBtn" class="secondary">Schedule Call-off</button>
|
|
39
|
+
<button id="cancelCallOffBtn" class="secondary" hidden>Cancel Call-off</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div id="statusLine" class="status"></div>
|
|
43
|
+
<div id="vetStatusLine" class="status vet-status">
|
|
44
|
+
<span id="vetStatusText"></span>
|
|
45
|
+
<button id="vetRefreshBtn" class="link-btn">Refresh VET register</button>
|
|
46
|
+
</div>
|
|
47
|
+
</header>
|
|
48
|
+
<main>
|
|
49
|
+
<p id="noRacesMsg" class="empty" hidden>No races yet. Click "+ New Race" to plan one.</p>
|
|
50
|
+
|
|
51
|
+
<div id="courseSection" class="course-section" hidden>
|
|
52
|
+
<button id="courseToggleBtn" type="button" class="link-btn">▸ Course & chart</button>
|
|
53
|
+
<div id="courseBody" hidden>
|
|
54
|
+
<div class="course-grid">
|
|
55
|
+
<div class="course-group">
|
|
56
|
+
<h3>Start line</h3>
|
|
57
|
+
<div id="startLineRows"></div>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="course-group">
|
|
60
|
+
<h3>Marks <span class="hint">(in rounding order)</span></h3>
|
|
61
|
+
<div id="marksRows"></div>
|
|
62
|
+
<button id="addMarkBtn" type="button" class="secondary">+ Add Mark</button>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="course-group">
|
|
65
|
+
<h3>Finish line</h3>
|
|
66
|
+
<div id="finishLineRows"></div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="course-actions">
|
|
70
|
+
<button id="saveCourseBtn" type="button">Save Course</button>
|
|
71
|
+
<span id="courseStatusText" class="status"></span>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="chart-wrap">
|
|
74
|
+
<svg id="courseChart" viewBox="0 0 800 500" preserveAspectRatio="xMidYMid meet"></svg>
|
|
75
|
+
<div id="replayControls" class="replay-controls" hidden>
|
|
76
|
+
<input type="range" id="replaySlider" min="0" max="100" value="100" />
|
|
77
|
+
<span id="replayTimeLabel">Live</span>
|
|
78
|
+
<button id="replayLiveBtn" type="button" class="secondary">Live</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div id="addBoatRow" class="add-boat-row" hidden>
|
|
85
|
+
<div class="autocomplete">
|
|
86
|
+
<input type="text" id="addBoatName" placeholder="Add boat by name…" autocomplete="off" />
|
|
87
|
+
<div id="addBoatSuggestions" class="suggestions" hidden></div>
|
|
88
|
+
</div>
|
|
89
|
+
<button id="addBoatBtn">Add Boat</button>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="table-scroll">
|
|
92
|
+
<table id="boatsTable">
|
|
93
|
+
<thead>
|
|
94
|
+
<tr>
|
|
95
|
+
<th>Boat</th>
|
|
96
|
+
<th>MMSI</th>
|
|
97
|
+
<th>TCF</th>
|
|
98
|
+
<th id="vetAlternativesTh">VET alternatives</th>
|
|
99
|
+
<th>Elapsed</th>
|
|
100
|
+
<th>Corrected</th>
|
|
101
|
+
<th>Est. finish</th>
|
|
102
|
+
<th>vs Self</th>
|
|
103
|
+
<th>Finish time</th>
|
|
104
|
+
<th></th>
|
|
105
|
+
</tr>
|
|
106
|
+
</thead>
|
|
107
|
+
<tbody id="boatsBody"></tbody>
|
|
108
|
+
</table>
|
|
109
|
+
</div>
|
|
110
|
+
<p id="emptyMsg" class="empty" hidden>No boats added yet. Use "Add Boat" above to enter the fleet.</p>
|
|
111
|
+
</main>
|
|
112
|
+
<script src="app.js"></script>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
package/public/style.css
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #0f172a;
|
|
3
|
+
--panel: #1e293b;
|
|
4
|
+
--border: #334155;
|
|
5
|
+
--text: #e2e8f0;
|
|
6
|
+
--muted: #94a3b8;
|
|
7
|
+
--accent: #38bdf8;
|
|
8
|
+
--good: #22c55e;
|
|
9
|
+
--bad: #f87171;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
* {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Elements that set their own `display` (flex rows, etc.) would otherwise
|
|
17
|
+
override the `hidden` attribute's UA-stylesheet `display: none`, since
|
|
18
|
+
both rules target the element at the same specificity. */
|
|
19
|
+
[hidden] {
|
|
20
|
+
display: none !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
margin: 0;
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
26
|
+
background: var(--bg);
|
|
27
|
+
color: var(--text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
header {
|
|
31
|
+
padding: 1.25rem 1.5rem;
|
|
32
|
+
border-bottom: 1px solid var(--border);
|
|
33
|
+
text-align: center;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
h1 {
|
|
37
|
+
margin: 0 0 0.5rem;
|
|
38
|
+
font-size: 1.25rem;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
letter-spacing: 0.02em;
|
|
41
|
+
color: var(--muted);
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.clock {
|
|
46
|
+
font-size: 3rem;
|
|
47
|
+
font-weight: 700;
|
|
48
|
+
font-variant-numeric: tabular-nums;
|
|
49
|
+
letter-spacing: 0.05em;
|
|
50
|
+
color: var(--accent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.clock.stopped {
|
|
54
|
+
color: var(--bad);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.race-select-row {
|
|
58
|
+
display: flex;
|
|
59
|
+
gap: 0.5rem;
|
|
60
|
+
justify-content: center;
|
|
61
|
+
align-items: center;
|
|
62
|
+
flex-wrap: wrap;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.race-select-row select {
|
|
66
|
+
padding: 0.4rem 0.5rem;
|
|
67
|
+
background: var(--panel);
|
|
68
|
+
color: var(--text);
|
|
69
|
+
border: 1px solid var(--border);
|
|
70
|
+
border-radius: 6px;
|
|
71
|
+
font-size: 0.85rem;
|
|
72
|
+
max-width: 16rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
button.danger {
|
|
76
|
+
border-color: var(--bad);
|
|
77
|
+
color: var(--bad);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
button.confirming {
|
|
81
|
+
background: var(--bad);
|
|
82
|
+
color: #2a0a0a;
|
|
83
|
+
border-color: var(--bad);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.new-race-form {
|
|
87
|
+
margin-top: 0.6rem;
|
|
88
|
+
display: flex;
|
|
89
|
+
gap: 0.5rem;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
align-items: center;
|
|
92
|
+
flex-wrap: wrap;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.new-race-form input[type='text'] {
|
|
96
|
+
padding: 0.4rem 0.5rem;
|
|
97
|
+
background: var(--panel);
|
|
98
|
+
color: var(--text);
|
|
99
|
+
border: 1px solid var(--border);
|
|
100
|
+
border-radius: 6px;
|
|
101
|
+
font-size: 0.85rem;
|
|
102
|
+
width: 16rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.controls {
|
|
106
|
+
margin-top: 0.75rem;
|
|
107
|
+
display: flex;
|
|
108
|
+
gap: 0.5rem;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.schedule-row {
|
|
113
|
+
margin-top: 0.6rem;
|
|
114
|
+
display: flex;
|
|
115
|
+
gap: 0.5rem;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
align-items: center;
|
|
118
|
+
flex-wrap: wrap;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.schedule-row input[type='datetime-local'] {
|
|
122
|
+
padding: 0.4rem 0.5rem;
|
|
123
|
+
background: var(--panel);
|
|
124
|
+
color: var(--text);
|
|
125
|
+
border: 1px solid var(--border);
|
|
126
|
+
border-radius: 6px;
|
|
127
|
+
font-size: 0.85rem;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.clock.countdown {
|
|
131
|
+
color: var(--muted);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
button {
|
|
135
|
+
font-size: 0.95rem;
|
|
136
|
+
padding: 0.5rem 1.1rem;
|
|
137
|
+
border-radius: 6px;
|
|
138
|
+
border: 1px solid var(--border);
|
|
139
|
+
background: var(--accent);
|
|
140
|
+
color: #04202e;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
button.secondary {
|
|
146
|
+
background: transparent;
|
|
147
|
+
color: var(--text);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
button:disabled {
|
|
151
|
+
opacity: 0.5;
|
|
152
|
+
cursor: not-allowed;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.status {
|
|
156
|
+
min-height: 1.2em;
|
|
157
|
+
margin-top: 0.5rem;
|
|
158
|
+
font-size: 0.85rem;
|
|
159
|
+
color: var(--muted);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.status.error {
|
|
163
|
+
color: var(--bad);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.vet-status {
|
|
167
|
+
display: flex;
|
|
168
|
+
gap: 0.6rem;
|
|
169
|
+
justify-content: center;
|
|
170
|
+
align-items: center;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.link-btn {
|
|
174
|
+
background: none;
|
|
175
|
+
border: none;
|
|
176
|
+
padding: 0;
|
|
177
|
+
font-size: 0.8rem;
|
|
178
|
+
color: var(--accent);
|
|
179
|
+
text-decoration: underline;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
main {
|
|
184
|
+
padding: 1rem 1.5rem 2rem;
|
|
185
|
+
max-width: 1000px;
|
|
186
|
+
margin: 0 auto;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.course-section {
|
|
190
|
+
margin-bottom: 1rem;
|
|
191
|
+
border-bottom: 1px solid var(--border);
|
|
192
|
+
padding-bottom: 1rem;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.course-grid {
|
|
196
|
+
margin-top: 0.75rem;
|
|
197
|
+
display: grid;
|
|
198
|
+
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
|
199
|
+
gap: 1.5rem;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.course-group h3 {
|
|
203
|
+
margin: 0 0 0.5rem;
|
|
204
|
+
font-size: 0.85rem;
|
|
205
|
+
color: var(--muted);
|
|
206
|
+
text-transform: uppercase;
|
|
207
|
+
letter-spacing: 0.03em;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.course-group h3 .hint {
|
|
211
|
+
text-transform: none;
|
|
212
|
+
letter-spacing: normal;
|
|
213
|
+
font-weight: 400;
|
|
214
|
+
color: var(--muted);
|
|
215
|
+
opacity: 0.8;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.course-point-row {
|
|
219
|
+
display: flex;
|
|
220
|
+
gap: 0.35rem;
|
|
221
|
+
margin-bottom: 0.4rem;
|
|
222
|
+
flex-wrap: wrap;
|
|
223
|
+
align-items: center;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.course-point-row .course-name-input {
|
|
227
|
+
width: 7rem;
|
|
228
|
+
padding: 0.3rem 0.4rem;
|
|
229
|
+
background: var(--panel);
|
|
230
|
+
color: var(--text);
|
|
231
|
+
border: 1px solid var(--border);
|
|
232
|
+
border-radius: 4px;
|
|
233
|
+
font-size: 0.8rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.course-point-row .course-coord-input {
|
|
237
|
+
width: 6.5rem;
|
|
238
|
+
padding: 0.3rem 0.4rem;
|
|
239
|
+
background: var(--panel);
|
|
240
|
+
color: var(--text);
|
|
241
|
+
border: 1px solid var(--border);
|
|
242
|
+
border-radius: 4px;
|
|
243
|
+
font-size: 0.8rem;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.course-point-row button {
|
|
247
|
+
padding: 0.3rem 0.5rem;
|
|
248
|
+
font-size: 0.75rem;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.course-actions {
|
|
252
|
+
margin-top: 0.75rem;
|
|
253
|
+
display: flex;
|
|
254
|
+
align-items: center;
|
|
255
|
+
gap: 0.75rem;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.chart-wrap {
|
|
259
|
+
margin-top: 1rem;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#courseChart {
|
|
263
|
+
width: 100%;
|
|
264
|
+
max-width: 800px;
|
|
265
|
+
height: auto;
|
|
266
|
+
background: var(--panel);
|
|
267
|
+
border: 1px solid var(--border);
|
|
268
|
+
border-radius: 8px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.replay-controls {
|
|
272
|
+
margin-top: 0.5rem;
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
gap: 0.6rem;
|
|
276
|
+
max-width: 800px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.replay-controls input[type='range'] {
|
|
280
|
+
flex: 1;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.replay-controls span {
|
|
284
|
+
font-size: 0.8rem;
|
|
285
|
+
color: var(--muted);
|
|
286
|
+
font-variant-numeric: tabular-nums;
|
|
287
|
+
min-width: 5rem;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.est-finish {
|
|
291
|
+
font-size: 0.85rem;
|
|
292
|
+
color: var(--muted);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.est-finish .est-detail {
|
|
296
|
+
display: block;
|
|
297
|
+
font-size: 0.7rem;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.self-btn {
|
|
301
|
+
background: none;
|
|
302
|
+
border: none;
|
|
303
|
+
padding: 0 0.3rem 0 0;
|
|
304
|
+
font-size: 1rem;
|
|
305
|
+
color: var(--muted);
|
|
306
|
+
cursor: pointer;
|
|
307
|
+
vertical-align: middle;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.self-btn.active {
|
|
311
|
+
color: var(--accent);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.vs-self {
|
|
315
|
+
font-variant-numeric: tabular-nums;
|
|
316
|
+
font-size: 0.85rem;
|
|
317
|
+
color: var(--muted);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.vs-self.ahead {
|
|
321
|
+
color: var(--good);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.vs-self.behind {
|
|
325
|
+
color: var(--bad);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.vs-self .self-tag {
|
|
329
|
+
font-size: 0.7rem;
|
|
330
|
+
letter-spacing: 0.04em;
|
|
331
|
+
color: var(--muted);
|
|
332
|
+
font-style: italic;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.add-boat-row {
|
|
336
|
+
display: flex;
|
|
337
|
+
gap: 0.5rem;
|
|
338
|
+
margin-bottom: 1rem;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.autocomplete {
|
|
342
|
+
position: relative;
|
|
343
|
+
flex: 1;
|
|
344
|
+
max-width: 20rem;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.add-boat-row input[type='text'] {
|
|
348
|
+
width: 100%;
|
|
349
|
+
padding: 0.45rem 0.6rem;
|
|
350
|
+
background: var(--panel);
|
|
351
|
+
color: var(--text);
|
|
352
|
+
border: 1px solid var(--border);
|
|
353
|
+
border-radius: 6px;
|
|
354
|
+
font-size: 0.9rem;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.suggestions {
|
|
358
|
+
position: absolute;
|
|
359
|
+
top: calc(100% + 2px);
|
|
360
|
+
left: 0;
|
|
361
|
+
right: 0;
|
|
362
|
+
z-index: 10;
|
|
363
|
+
max-height: 16rem;
|
|
364
|
+
overflow-y: auto;
|
|
365
|
+
background: var(--panel);
|
|
366
|
+
border: 1px solid var(--border);
|
|
367
|
+
border-radius: 6px;
|
|
368
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.suggestions .suggestion-item {
|
|
372
|
+
padding: 0.45rem 0.6rem;
|
|
373
|
+
font-size: 0.85rem;
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
color: var(--text);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.suggestions .suggestion-item mark {
|
|
379
|
+
background: none;
|
|
380
|
+
color: var(--accent);
|
|
381
|
+
font-weight: 700;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.suggestions .suggestion-item:hover,
|
|
385
|
+
.suggestions .suggestion-item.active {
|
|
386
|
+
background: rgba(56, 189, 248, 0.15);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.table-scroll {
|
|
390
|
+
overflow-x: auto;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
table {
|
|
394
|
+
width: 100%;
|
|
395
|
+
min-width: 60rem;
|
|
396
|
+
border-collapse: collapse;
|
|
397
|
+
font-variant-numeric: tabular-nums;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
thead th {
|
|
401
|
+
text-align: left;
|
|
402
|
+
font-size: 0.75rem;
|
|
403
|
+
text-transform: uppercase;
|
|
404
|
+
letter-spacing: 0.05em;
|
|
405
|
+
color: var(--muted);
|
|
406
|
+
padding: 0.5rem 0.6rem;
|
|
407
|
+
border-bottom: 1px solid var(--border);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
tbody td {
|
|
411
|
+
padding: 0.55rem 0.6rem;
|
|
412
|
+
border-bottom: 1px solid var(--border);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
tbody tr.finished td {
|
|
416
|
+
color: var(--good);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
tbody tr.dnf td {
|
|
420
|
+
color: var(--muted);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.dnf-tag {
|
|
424
|
+
color: var(--bad);
|
|
425
|
+
font-weight: 700;
|
|
426
|
+
font-size: 0.85rem;
|
|
427
|
+
letter-spacing: 0.03em;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.dnf-pos {
|
|
431
|
+
display: block;
|
|
432
|
+
font-size: 0.7rem;
|
|
433
|
+
color: var(--muted);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.mmsi-input {
|
|
437
|
+
width: 7rem;
|
|
438
|
+
padding: 0.3rem 0.4rem;
|
|
439
|
+
background: var(--panel);
|
|
440
|
+
color: var(--text);
|
|
441
|
+
border: 1px solid var(--border);
|
|
442
|
+
border-radius: 4px;
|
|
443
|
+
font-variant-numeric: tabular-nums;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.tcf-input {
|
|
447
|
+
width: 5.5rem;
|
|
448
|
+
padding: 0.3rem 0.4rem;
|
|
449
|
+
background: var(--panel);
|
|
450
|
+
color: var(--text);
|
|
451
|
+
border: 1px solid var(--border);
|
|
452
|
+
border-radius: 4px;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* Handicap edits must be deliberate text entry, not an accidental
|
|
456
|
+
scroll-wheel or spinner click (see tcf-input wheel/keydown guards in
|
|
457
|
+
app.js) — hide the native number-input spinner entirely. */
|
|
458
|
+
.tcf-input::-webkit-outer-spin-button,
|
|
459
|
+
.tcf-input::-webkit-inner-spin-button {
|
|
460
|
+
-webkit-appearance: none;
|
|
461
|
+
margin: 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.tcf-input[type='number'] {
|
|
465
|
+
-moz-appearance: textfield;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.vet-cell {
|
|
469
|
+
display: flex;
|
|
470
|
+
flex-wrap: wrap;
|
|
471
|
+
gap: 0.3rem;
|
|
472
|
+
align-items: center;
|
|
473
|
+
min-width: 11rem;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.vet-select {
|
|
477
|
+
max-width: 13rem;
|
|
478
|
+
padding: 0.3rem 0.4rem;
|
|
479
|
+
background: var(--panel);
|
|
480
|
+
color: var(--text);
|
|
481
|
+
border: 1px solid var(--border);
|
|
482
|
+
border-radius: 4px;
|
|
483
|
+
font-size: 0.8rem;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.vet-badge {
|
|
487
|
+
font-size: 0.7rem;
|
|
488
|
+
color: var(--muted);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.vet-badge.warn {
|
|
492
|
+
color: var(--bad);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.remove-boat-btn {
|
|
496
|
+
padding: 0.3rem 0.6rem;
|
|
497
|
+
font-size: 0.8rem;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.finish-cell {
|
|
501
|
+
display: flex;
|
|
502
|
+
flex-wrap: wrap;
|
|
503
|
+
gap: 0.3rem;
|
|
504
|
+
align-items: center;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.finish-time-input {
|
|
508
|
+
padding: 0.3rem 0.4rem;
|
|
509
|
+
background: var(--panel);
|
|
510
|
+
color: var(--text);
|
|
511
|
+
border: 1px solid var(--border);
|
|
512
|
+
border-radius: 4px;
|
|
513
|
+
font-variant-numeric: tabular-nums;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.finish-now-btn,
|
|
517
|
+
.finish-clear-btn,
|
|
518
|
+
.finish-dnf-btn,
|
|
519
|
+
.undo-dnf-btn {
|
|
520
|
+
padding: 0.3rem 0.6rem;
|
|
521
|
+
font-size: 0.8rem;
|
|
522
|
+
background: transparent;
|
|
523
|
+
color: var(--text);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.finish-dnf-btn {
|
|
527
|
+
border-color: var(--bad);
|
|
528
|
+
color: var(--bad);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.empty {
|
|
532
|
+
color: var(--muted);
|
|
533
|
+
text-align: center;
|
|
534
|
+
margin-top: 2rem;
|
|
535
|
+
}
|