@jjlmoya/utils-nautical 1.22.0 → 1.23.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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/data.ts +1 -0
  4. package/src/entries.ts +4 -1
  5. package/src/index.ts +1 -0
  6. package/src/tests/i18n_coverage.test.ts +6 -1
  7. package/src/tests/locale_completeness.test.ts +2 -2
  8. package/src/tests/tool_validation.test.ts +7 -4
  9. package/src/tool/hullSpeed/bibliography.astro +14 -0
  10. package/src/tool/hullSpeed/bibliography.ts +12 -0
  11. package/src/tool/hullSpeed/component.astro +116 -0
  12. package/src/tool/hullSpeed/controller.ts +204 -0
  13. package/src/tool/hullSpeed/dom-views.ts +141 -0
  14. package/src/tool/hullSpeed/entry.ts +32 -0
  15. package/src/tool/hullSpeed/evaluator.ts +20 -0
  16. package/src/tool/hullSpeed/hull-draw.ts +185 -0
  17. package/src/tool/hullSpeed/i18n/de.ts +199 -0
  18. package/src/tool/hullSpeed/i18n/en.ts +199 -0
  19. package/src/tool/hullSpeed/i18n/es.ts +199 -0
  20. package/src/tool/hullSpeed/i18n/fr.ts +199 -0
  21. package/src/tool/hullSpeed/i18n/id.ts +199 -0
  22. package/src/tool/hullSpeed/i18n/it.ts +199 -0
  23. package/src/tool/hullSpeed/i18n/ja.ts +199 -0
  24. package/src/tool/hullSpeed/i18n/ko.ts +199 -0
  25. package/src/tool/hullSpeed/i18n/nl.ts +199 -0
  26. package/src/tool/hullSpeed/i18n/pl.ts +199 -0
  27. package/src/tool/hullSpeed/i18n/pt.ts +199 -0
  28. package/src/tool/hullSpeed/i18n/ru.ts +199 -0
  29. package/src/tool/hullSpeed/i18n/sv.ts +199 -0
  30. package/src/tool/hullSpeed/i18n/tr.ts +199 -0
  31. package/src/tool/hullSpeed/i18n/zh.ts +199 -0
  32. package/src/tool/hullSpeed/index.ts +11 -0
  33. package/src/tool/hullSpeed/logic.test.ts +105 -0
  34. package/src/tool/hullSpeed/logic.ts +116 -0
  35. package/src/tool/hullSpeed/presets.ts +17 -0
  36. package/src/tool/hullSpeed/sailboat-hull-speed-calculator.css +504 -0
  37. package/src/tool/hullSpeed/seo.astro +15 -0
  38. package/src/tool/hullSpeed/storage.test.ts +23 -0
  39. package/src/tool/hullSpeed/storage.ts +65 -0
  40. package/src/tool/hullSpeed/ui.ts +43 -0
  41. package/src/tools.ts +3 -0
@@ -0,0 +1,504 @@
1
+ :root {
2
+ --n-sky: #dce8ee;
3
+ --n-deep: #2f6f7d;
4
+ --n-sea: #4a97a6;
5
+ --n-foam: #f7fbfc;
6
+ --n-ink: #1b333c;
7
+ --n-mute: #4e6872;
8
+ --n-line: #ddd6c8;
9
+ --n-topside: #fbf7ef;
10
+ --n-deck: #e3d6c0;
11
+ --n-wet: #3a342e;
12
+ --n-keel: #2a2622;
13
+ --n-cabin: #8a4d2c;
14
+ --n-glass: #7ec8dc;
15
+ --n-board: #fffcf6;
16
+ --n-sole: #f2eee4;
17
+ --n-sole-ink: #1b333c;
18
+ --n-sole-mute: #5b7078;
19
+ --n-track: #e5dfd2;
20
+ --n-field: #fffcf6;
21
+ --n-on: #fff;
22
+ --n-focus: #c45a2a;
23
+ --n-rail: #2f6f7d;
24
+ --n-spar: #1c1814;
25
+ }
26
+
27
+ .theme-dark {
28
+ --n-sky: #0c1822;
29
+ --n-deep: #06232c;
30
+ --n-sea: #0f5366;
31
+ --n-foam: #6ed9ce;
32
+ --n-ink: #e6f0f3;
33
+ --n-mute: #9db4be;
34
+ --n-line: #2a3c44;
35
+ --n-topside: #e6dfd2;
36
+ --n-deck: #b9ab94;
37
+ --n-wet: #1a1714;
38
+ --n-keel: #0c0b0a;
39
+ --n-cabin: #c47848;
40
+ --n-glass: #f0c56e;
41
+ --n-board: #1a2228;
42
+ --n-sole: #12181c;
43
+ --n-sole-ink: #e6f0f3;
44
+ --n-sole-mute: #8ea3ac;
45
+ --n-track: #1c282e;
46
+ --n-field: #0e1418;
47
+ --n-on: #24353c;
48
+ --n-focus: #e08a62;
49
+ --n-rail: #6ed9ce;
50
+ --n-spar: #f0ebe3;
51
+ }
52
+
53
+ .n-hull {
54
+ --n-sky: #dce8ee;
55
+ --n-deep: #2f6f7d;
56
+ --n-sea: #4a97a6;
57
+ --n-foam: #f7fbfc;
58
+ --n-ink: #1b333c;
59
+ --n-mute: #4e6872;
60
+ --n-line: #ddd6c8;
61
+ --n-topside: #fbf7ef;
62
+ --n-deck: #e3d6c0;
63
+ --n-wet: #3a342e;
64
+ --n-keel: #2a2622;
65
+ --n-cabin: #8a4d2c;
66
+ --n-glass: #7ec8dc;
67
+ --n-board: #fffcf6;
68
+ --n-sole: #f2eee4;
69
+ --n-sole-ink: #1b333c;
70
+ --n-sole-mute: #5b7078;
71
+ --n-track: #e5dfd2;
72
+ --n-field: #fffcf6;
73
+ --n-on: #fff;
74
+ --n-focus: #c45a2a;
75
+ --n-rail: #2f6f7d;
76
+ --n-spar: #1c1814;
77
+
78
+ background: var(--n-sky);
79
+ color: var(--n-ink);
80
+ overflow: hidden;
81
+ }
82
+
83
+ .theme-dark .n-hull {
84
+ --n-sky: #0c1822;
85
+ --n-deep: #06232c;
86
+ --n-sea: #0f5366;
87
+ --n-foam: #6ed9ce;
88
+ --n-ink: #e6f0f3;
89
+ --n-mute: #9db4be;
90
+ --n-line: #2a3c44;
91
+ --n-topside: #e6dfd2;
92
+ --n-deck: #b9ab94;
93
+ --n-wet: #1a1714;
94
+ --n-keel: #0c0b0a;
95
+ --n-cabin: #c47848;
96
+ --n-glass: #f0c56e;
97
+ --n-board: #1a2228;
98
+ --n-sole: #12181c;
99
+ --n-sole-ink: #e6f0f3;
100
+ --n-sole-mute: #8ea3ac;
101
+ --n-track: #1c282e;
102
+ --n-field: #0e1418;
103
+ --n-on: #24353c;
104
+ --n-focus: #e08a62;
105
+ --n-rail: #6ed9ce;
106
+ --n-spar: #f0ebe3;
107
+ }
108
+
109
+ .n-hull-stage {
110
+ position: relative;
111
+ background: var(--n-sky);
112
+ }
113
+
114
+ .n-hull-scene {
115
+ cursor: ew-resize;
116
+ touch-action: none;
117
+ }
118
+
119
+ .n-hull-svg {
120
+ display: block;
121
+ width: 100%;
122
+ height: 26rem;
123
+ }
124
+
125
+ .n-hull-sky {
126
+ fill: var(--n-sky);
127
+ }
128
+
129
+ .n-hull-deep {
130
+ fill: var(--n-deep);
131
+ }
132
+
133
+ .n-hull-sea {
134
+ fill: var(--n-sea);
135
+ }
136
+
137
+ .n-hull-foamline {
138
+ fill: none;
139
+ stroke: var(--n-foam);
140
+ stroke-width: 2.2;
141
+ stroke-linejoin: round;
142
+ }
143
+
144
+ .n-hull-foam {
145
+ fill: var(--n-foam);
146
+ }
147
+
148
+ .n-hull-topside {
149
+ fill: var(--n-topside);
150
+ }
151
+
152
+ .n-hull-deck {
153
+ fill: var(--n-deck);
154
+ }
155
+
156
+ .n-hull-transom {
157
+ fill: var(--n-deck);
158
+ }
159
+
160
+ .n-hull-gunwale {
161
+ fill: var(--n-keel);
162
+ }
163
+
164
+ .n-hull-bootfill {
165
+ fill: var(--n-ink);
166
+ }
167
+
168
+ .n-hull-body {
169
+ fill: var(--n-wet);
170
+ }
171
+
172
+ .n-hull-keel {
173
+ fill: var(--n-keel);
174
+ }
175
+
176
+ .n-hull-cabin {
177
+ fill: var(--n-cabin);
178
+ }
179
+
180
+ .n-hull-glass {
181
+ fill: var(--n-glass);
182
+ }
183
+
184
+ .n-hull-spar {
185
+ fill: none;
186
+ stroke: var(--n-spar);
187
+ stroke-width: 3;
188
+ stroke-linecap: square;
189
+ }
190
+
191
+ .n-hull-boot {
192
+ stroke: var(--n-ink);
193
+ stroke-width: 1.5;
194
+ }
195
+
196
+ .n-hull-dim {
197
+ stroke: var(--n-ink);
198
+ stroke-width: 1.6;
199
+ }
200
+
201
+ .n-hull-dimlabel {
202
+ fill: var(--n-ink);
203
+ font-size: 18px;
204
+ font-weight: 700;
205
+ }
206
+
207
+ .n-hull-chine {
208
+ fill: none;
209
+ stroke: var(--n-keel);
210
+ stroke-width: 2.2;
211
+ }
212
+
213
+ .n-hull-band {
214
+ position: absolute;
215
+ top: 0.9rem;
216
+ left: 1rem;
217
+ z-index: 2;
218
+ margin: 0;
219
+ max-width: 20rem;
220
+ padding: 0.28rem 0.7rem;
221
+ background: var(--n-board);
222
+ color: var(--n-ink);
223
+ font-size: 0.92rem;
224
+ font-weight: 600;
225
+ border-radius: 999px;
226
+ }
227
+
228
+ .n-hull-units {
229
+ position: absolute;
230
+ top: 0.75rem;
231
+ right: 0.75rem;
232
+ z-index: 2;
233
+ }
234
+
235
+ .n-hull-seg {
236
+ display: flex;
237
+ gap: 0.15rem;
238
+ padding: 0.18rem;
239
+ background: var(--n-track);
240
+ border-radius: 999px;
241
+ }
242
+
243
+ .n-hull-seg button {
244
+ appearance: none;
245
+ flex: 1;
246
+ border: 0;
247
+ background: transparent;
248
+ color: var(--n-mute);
249
+ min-height: 2.15rem;
250
+ padding: 0.25rem 0.85rem;
251
+ font: inherit;
252
+ font-size: 0.82rem;
253
+ border-radius: 999px;
254
+ cursor: pointer;
255
+ }
256
+
257
+ .n-hull-seg button.is-on {
258
+ background: var(--n-on);
259
+ color: var(--n-ink);
260
+ }
261
+
262
+ .n-hull-forms {
263
+ border-radius: 1.1rem;
264
+ }
265
+
266
+ .n-hull-forms button {
267
+ display: grid;
268
+ justify-items: center;
269
+ gap: 0.15rem;
270
+ font-size: 0.75rem;
271
+ padding: 0.4rem 0.3rem 0.45rem;
272
+ border-radius: 0.9rem;
273
+ }
274
+
275
+ .n-hull-forms svg {
276
+ width: 3.2rem;
277
+ height: 1.1rem;
278
+ }
279
+
280
+ .n-hull-plate {
281
+ position: absolute;
282
+ left: 1rem;
283
+ bottom: 1.2rem;
284
+ z-index: 2;
285
+ background: transparent;
286
+ color: var(--n-ink);
287
+ border: 0;
288
+ padding: 0;
289
+ min-width: 9rem;
290
+ }
291
+
292
+ .n-hull-plate-k,
293
+ .n-hull-alt,
294
+ .n-hull-nums {
295
+ margin: 0;
296
+ color: var(--n-mute);
297
+ font-size: 0.78rem;
298
+ }
299
+
300
+ .n-hull-kn {
301
+ margin: 0.05rem 0 0.1rem;
302
+ font-size: 3.2rem;
303
+ font-weight: 800;
304
+ line-height: 0.92;
305
+ letter-spacing: -0.04em;
306
+ font-variant-numeric: tabular-nums;
307
+ }
308
+
309
+ .n-hull-kn small {
310
+ margin-left: 0.3rem;
311
+ font-size: 1rem;
312
+ font-weight: 600;
313
+ letter-spacing: 0;
314
+ color: var(--n-mute);
315
+ }
316
+
317
+ .n-hull-nums span {
318
+ color: var(--n-ink);
319
+ font-weight: 700;
320
+ margin-right: 0.15rem;
321
+ }
322
+
323
+ .n-hull-nums span + span {
324
+ margin-left: 0.7rem;
325
+ }
326
+
327
+ .n-hull-sole {
328
+ background: var(--n-sole);
329
+ color: var(--n-sole-ink);
330
+ padding: 1.05rem 1.15rem 1.2rem;
331
+ display: grid;
332
+ gap: 0.9rem;
333
+ }
334
+
335
+ .n-hull-meters {
336
+ display: grid;
337
+ gap: 0.85rem;
338
+ }
339
+
340
+ .n-hull-field {
341
+ display: grid;
342
+ gap: 0.4rem;
343
+ }
344
+
345
+ .n-hull-field > span:first-of-type {
346
+ color: var(--n-sole-mute);
347
+ font-size: 0.8rem;
348
+ }
349
+
350
+ .n-hull-row {
351
+ display: grid;
352
+ grid-template-columns: 1fr auto;
353
+ align-items: center;
354
+ gap: 0.45rem;
355
+ }
356
+
357
+ .n-hull-row > span {
358
+ color: var(--n-sole-ink);
359
+ font-size: 0.92rem;
360
+ }
361
+
362
+ .n-hull input[type='number'] {
363
+ width: 100%;
364
+ min-height: 2.6rem;
365
+ padding: 0.35rem 0.75rem;
366
+ border: 0;
367
+ border-radius: 0.7rem;
368
+ background: var(--n-field);
369
+ color: var(--n-ink);
370
+ font: inherit;
371
+ font-size: 1.12rem;
372
+ font-variant-numeric: tabular-nums;
373
+ }
374
+
375
+ .n-hull-rail {
376
+ appearance: none;
377
+ width: 100%;
378
+ height: 0.35rem;
379
+ background: var(--n-track);
380
+ border: 0;
381
+ border-radius: 99px;
382
+ }
383
+
384
+ .n-hull-rail::-webkit-slider-thumb {
385
+ appearance: none;
386
+ width: 1.05rem;
387
+ height: 1.05rem;
388
+ background: var(--n-ink);
389
+ border: 0;
390
+ border-radius: 99px;
391
+ cursor: pointer;
392
+ }
393
+
394
+ .n-hull-rail::-moz-range-thumb {
395
+ width: 1.05rem;
396
+ height: 1.05rem;
397
+ background: var(--n-ink);
398
+ border: 0;
399
+ border-radius: 99px;
400
+ cursor: pointer;
401
+ }
402
+
403
+ .n-hull-hint,
404
+ .n-hull-note {
405
+ margin: 0;
406
+ color: var(--n-sole-mute);
407
+ font-size: 0.88rem;
408
+ max-width: 46rem;
409
+ }
410
+
411
+ .n-hull-fleet {
412
+ display: flex;
413
+ flex-wrap: wrap;
414
+ gap: 0.15rem 0.1rem;
415
+ }
416
+
417
+ .n-hull-fleet button {
418
+ appearance: none;
419
+ display: grid;
420
+ justify-items: start;
421
+ gap: 0.12rem;
422
+ border: 0;
423
+ background: transparent;
424
+ color: var(--n-sole-mute);
425
+ min-height: 2.4rem;
426
+ padding: 0.3rem 0.7rem;
427
+ font: inherit;
428
+ font-size: 0.78rem;
429
+ text-align: left;
430
+ cursor: pointer;
431
+ }
432
+
433
+ .n-hull-fleet button.is-on {
434
+ color: var(--n-sole-ink);
435
+ }
436
+
437
+ .n-hull-fleet b {
438
+ font-size: 0.8rem;
439
+ font-weight: 650;
440
+ font-variant-numeric: tabular-nums;
441
+ }
442
+
443
+ .n-hull-fleet button::after {
444
+ content: '';
445
+ display: block;
446
+ height: 2px;
447
+ background: currentcolor;
448
+ }
449
+
450
+ .n-hull-fleet [data-boat='dinghy']::after {
451
+ width: 1.1rem;
452
+ }
453
+
454
+ .n-hull-fleet [data-boat='daysailer']::after {
455
+ width: 1.55rem;
456
+ }
457
+
458
+ .n-hull-fleet [data-boat='cruiser']::after {
459
+ width: 2.1rem;
460
+ }
461
+
462
+ .n-hull-fleet [data-boat='bluewater']::after {
463
+ width: 2.7rem;
464
+ }
465
+
466
+ .n-hull-fleet [data-boat='workboat']::after {
467
+ width: 3.4rem;
468
+ }
469
+
470
+ .n-hull-units button:focus-visible,
471
+ .n-hull-forms button:focus-visible,
472
+ .n-hull-fleet button:focus-visible,
473
+ .n-hull input:focus-visible {
474
+ outline: 2px solid var(--n-focus);
475
+ outline-offset: 2px;
476
+ }
477
+
478
+ @media (min-width: 720px) {
479
+ .n-hull-meters {
480
+ grid-template-columns: 1fr 1fr;
481
+ }
482
+
483
+ .n-hull-kn {
484
+ font-size: 3.5rem;
485
+ }
486
+
487
+ .n-hull-svg {
488
+ height: 28rem;
489
+ }
490
+ }
491
+
492
+ @media (max-width: 640px) {
493
+ .n-hull-svg {
494
+ height: 18.5rem;
495
+ }
496
+
497
+ .n-hull-kn {
498
+ font-size: 2.4rem;
499
+ }
500
+
501
+ .n-hull-forms {
502
+ flex-wrap: wrap;
503
+ }
504
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { hullSpeed } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await hullSpeed.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DEFAULT_STATE, parseState } from './storage';
3
+
4
+ describe('hull speed storage', () => {
5
+ it('returns defaults for invalid json', () => {
6
+ expect(parseState('{')).toEqual(DEFAULT_STATE);
7
+ });
8
+
9
+ it('clamps saved waterline and speed', () => {
10
+ const parsed = parseState(
11
+ JSON.stringify({ lwlMetres: 90, hullKind: 'semi', observedKnots: -2, unit: 'imperial' }),
12
+ );
13
+ expect(parsed.lwlMetres).toBe(40);
14
+ expect(parsed.hullKind).toBe('semi');
15
+ expect(parsed.observedKnots).toBe(0);
16
+ expect(parsed.unit).toBe('imperial');
17
+ });
18
+
19
+ it('rejects unknown hull kinds', () => {
20
+ const parsed = parseState(JSON.stringify({ hullKind: 'foil' }));
21
+ expect(parsed.hullKind).toBe('displacement');
22
+ });
23
+ });
@@ -0,0 +1,65 @@
1
+ import type { HullKind, UnitSystem } from './logic';
2
+ import { clampLwlMetres, clampObservedKnots } from './logic';
3
+
4
+ export interface HullSpeedState {
5
+ lwlMetres: number;
6
+ hullKind: HullKind;
7
+ observedKnots: number;
8
+ unit: UnitSystem;
9
+ }
10
+
11
+ const KEY = 'n-hull-speed-v1';
12
+
13
+ const KINDS: HullKind[] = ['displacement', 'semi', 'planing'];
14
+
15
+ export const DEFAULT_STATE: HullSpeedState = {
16
+ lwlMetres: 10,
17
+ hullKind: 'displacement',
18
+ observedKnots: 0,
19
+ unit: 'metric',
20
+ };
21
+
22
+ function isKind(value: string): value is HullKind {
23
+ return KINDS.includes(value as HullKind);
24
+ }
25
+
26
+ function asKind(value: unknown): HullKind {
27
+ if (typeof value === 'string' && isKind(value)) return value;
28
+ return DEFAULT_STATE.hullKind;
29
+ }
30
+
31
+ function readUnit(value: string): UnitSystem {
32
+ if (value === 'imperial') return 'imperial';
33
+ return 'metric';
34
+ }
35
+
36
+ export function loadState(): HullSpeedState {
37
+ try {
38
+ const raw = localStorage.getItem(KEY);
39
+ if (!raw) return { ...DEFAULT_STATE };
40
+ return parseState(raw);
41
+ } catch {
42
+ return { ...DEFAULT_STATE };
43
+ }
44
+ }
45
+
46
+ export function parseState(raw: string): HullSpeedState {
47
+ try {
48
+ const data = JSON.parse(raw) as Partial<HullSpeedState>;
49
+ const hullKind = asKind(data.hullKind);
50
+ return {
51
+ lwlMetres: clampLwlMetres(Number(data.lwlMetres)),
52
+ hullKind,
53
+ observedKnots: clampObservedKnots(Number(data.observedKnots)),
54
+ unit: readUnit(String(data.unit)),
55
+ };
56
+ } catch {
57
+ return { ...DEFAULT_STATE };
58
+ }
59
+ }
60
+
61
+ export function saveState(state: HullSpeedState): void {
62
+ try {
63
+ localStorage.setItem(KEY, JSON.stringify(state));
64
+ } catch {}
65
+ }
@@ -0,0 +1,43 @@
1
+ export interface HullSpeedUI {
2
+ [key: string]: string;
3
+ metric: string;
4
+ imperial: string;
5
+ unitGroup: string;
6
+ sceneLabel: string;
7
+ hullSpeedLabel: string;
8
+ knotsUnit: string;
9
+ kmhUnit: string;
10
+ mphUnit: string;
11
+ ratioLabel: string;
12
+ froudeLabel: string;
13
+ lwlLabelMetric: string;
14
+ lwlLabelImperial: string;
15
+ lwlUnitM: string;
16
+ lwlUnitFt: string;
17
+ observedLabel: string;
18
+ observedHint: string;
19
+ stretchHint: string;
20
+ hullKindLabel: string;
21
+ displacement: string;
22
+ semi: string;
23
+ planing: string;
24
+ boatsLabel: string;
25
+ dinghy: string;
26
+ daysailer: string;
27
+ cruiser: string;
28
+ bluewater: string;
29
+ workboat: string;
30
+ bandBelow: string;
31
+ bandNear: string;
32
+ bandAt: string;
33
+ bandAbove: string;
34
+ bandPlane: string;
35
+ planingNote: string;
36
+ displacementNote: string;
37
+ semiNote: string;
38
+ waveLegend: string;
39
+ waterLegend: string;
40
+ markLegend: string;
41
+ faqTitle: string;
42
+ bibliographyTitle: string;
43
+ }
package/src/tools.ts CHANGED
@@ -6,6 +6,7 @@ import { SAIL_AREA_TOOL } from './tool/sailArea';
6
6
  import { SPEED_CONVERTER_TOOL } from './tool/speedConverter';
7
7
  import { ENDURANCE_TOOL } from './tool/endurance';
8
8
  import { ANCHOR_SCOPE_TOOL } from './tool/anchorScope';
9
+ import { HULL_SPEED_TOOL } from './tool/hullSpeed';
9
10
  import type { ToolDefinition } from './types';
10
11
 
11
12
  export const ALL_TOOLS: ToolDefinition[] = [
@@ -16,6 +17,7 @@ export const ALL_TOOLS: ToolDefinition[] = [
16
17
  SPEED_CONVERTER_TOOL,
17
18
  ENDURANCE_TOOL,
18
19
  ANCHOR_SCOPE_TOOL,
20
+ HULL_SPEED_TOOL,
19
21
  ];
20
22
 
21
23
  export {
@@ -26,5 +28,6 @@ export {
26
28
  SPEED_CONVERTER_TOOL,
27
29
  ENDURANCE_TOOL,
28
30
  ANCHOR_SCOPE_TOOL,
31
+ HULL_SPEED_TOOL,
29
32
  };
30
33