@jjlmoya/utils-nautical 1.2.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/package.json +75 -0
- package/src/category/i18n/en.ts +79 -0
- package/src/category/i18n/es.ts +79 -0
- package/src/category/i18n/fr.ts +79 -0
- package/src/category/index.ts +13 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +12 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +22 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +249 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +30 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/seo_length.test.ts +29 -0
- package/src/tests/tool_validation.test.ts +51 -0
- package/src/tool/endurance/bibliography.astro +14 -0
- package/src/tool/endurance/component.astro +310 -0
- package/src/tool/endurance/i18n/en.ts +243 -0
- package/src/tool/endurance/i18n/es.ts +249 -0
- package/src/tool/endurance/i18n/fr.ts +217 -0
- package/src/tool/endurance/index.ts +56 -0
- package/src/tool/endurance/logic.ts +36 -0
- package/src/tool/endurance/seo.astro +55 -0
- package/src/tool/endurance/style.css +333 -0
- package/src/tool/nauticalConverter/bibliography.astro +14 -0
- package/src/tool/nauticalConverter/component.astro +481 -0
- package/src/tool/nauticalConverter/i18n/en.ts +239 -0
- package/src/tool/nauticalConverter/i18n/es.ts +239 -0
- package/src/tool/nauticalConverter/i18n/fr.ts +239 -0
- package/src/tool/nauticalConverter/index.ts +57 -0
- package/src/tool/nauticalConverter/logic.ts +52 -0
- package/src/tool/nauticalConverter/seo.astro +52 -0
- package/src/tool/nauticalConverter/style.css +205 -0
- package/src/tool/sailArea/bibliography.astro +14 -0
- package/src/tool/sailArea/component.astro +418 -0
- package/src/tool/sailArea/i18n/en.ts +275 -0
- package/src/tool/sailArea/i18n/es.ts +275 -0
- package/src/tool/sailArea/i18n/fr.ts +275 -0
- package/src/tool/sailArea/index.ts +53 -0
- package/src/tool/sailArea/logic.ts +55 -0
- package/src/tool/sailArea/seo.astro +52 -0
- package/src/tool/sailArea/style.css +351 -0
- package/src/tool/speedConverter/bibliography.astro +14 -0
- package/src/tool/speedConverter/component.astro +125 -0
- package/src/tool/speedConverter/i18n/en.ts +271 -0
- package/src/tool/speedConverter/i18n/es.ts +271 -0
- package/src/tool/speedConverter/i18n/fr.ts +271 -0
- package/src/tool/speedConverter/index.ts +46 -0
- package/src/tool/speedConverter/logic.ts +48 -0
- package/src/tool/speedConverter/seo.astro +43 -0
- package/src/tool/speedConverter/style.css +239 -0
- package/src/tool/tideCalculator/bibliography.astro +14 -0
- package/src/tool/tideCalculator/component.astro +314 -0
- package/src/tool/tideCalculator/i18n/en.ts +203 -0
- package/src/tool/tideCalculator/i18n/es.ts +225 -0
- package/src/tool/tideCalculator/i18n/fr.ts +190 -0
- package/src/tool/tideCalculator/index.ts +50 -0
- package/src/tool/tideCalculator/logic.ts +73 -0
- package/src/tool/tideCalculator/seo.astro +61 -0
- package/src/tool/tideCalculator/style.css +310 -0
- package/src/tool/underKeel/bibliography.astro +14 -0
- package/src/tool/underKeel/component.astro +240 -0
- package/src/tool/underKeel/i18n/en.ts +193 -0
- package/src/tool/underKeel/i18n/es.ts +206 -0
- package/src/tool/underKeel/i18n/fr.ts +193 -0
- package/src/tool/underKeel/index.ts +49 -0
- package/src/tool/underKeel/logic.ts +83 -0
- package/src/tool/underKeel/seo.astro +46 -0
- package/src/tool/underKeel/style.css +312 -0
- package/src/tools.ts +25 -0
- package/src/types.ts +69 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
---
|
|
2
|
+
import './style.css';
|
|
3
|
+
import type { EnduranceUI } from './index';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
ui: EnduranceUI;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { ui } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<section id="endurance-calculator" class="endurance-calculator" data-ui={JSON.stringify(ui)}>
|
|
13
|
+
<div class="ec-main-container">
|
|
14
|
+
<aside class="ec-sidebar-inputs">
|
|
15
|
+
<div class="ec-compact-group">
|
|
16
|
+
<label>{ui.tankCapacityLabel}</label>
|
|
17
|
+
<div class="ec-dual-row">
|
|
18
|
+
<div>
|
|
19
|
+
<span class="ec-sub-label">{ui.mainTankLabel}</span>
|
|
20
|
+
<div class="ec-field-wrapper">
|
|
21
|
+
<span class="ec-field-icon icon-fuel"></span>
|
|
22
|
+
<input type="number" id="tank1" class="ec-compact-input" value="200" min="0" step="10" />
|
|
23
|
+
<span class="ec-unit-tag">L</span>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
<span class="ec-sub-label">{ui.auxTankLabel}</span>
|
|
28
|
+
<div class="ec-field-wrapper">
|
|
29
|
+
<span class="ec-field-icon icon-fuel"></span>
|
|
30
|
+
<input type="number" id="tank2" class="ec-compact-input" value="0" min="0" step="10" />
|
|
31
|
+
<span class="ec-unit-tag">L</span>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="ec-compact-group">
|
|
38
|
+
<label>{ui.currentFuelLabel}</label>
|
|
39
|
+
<div class="ec-field-wrapper">
|
|
40
|
+
<span class="ec-field-icon icon-bucket"></span>
|
|
41
|
+
<input type="number" id="f-current" class="ec-compact-input" value="150" min="0" step="5" />
|
|
42
|
+
<span class="ec-unit-tag">L</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="ec-compact-group">
|
|
47
|
+
<label>{ui.seaConditionsLabel}</label>
|
|
48
|
+
<div class="ec-field-wrapper">
|
|
49
|
+
<span class="ec-field-icon icon-wave"></span>
|
|
50
|
+
<select id="sea-s" class="ec-compact-select">
|
|
51
|
+
<option value="1.0">{ui.seaCalm}</option>
|
|
52
|
+
<option value="1.15">{ui.seaLight}</option>
|
|
53
|
+
<option value="1.30">{ui.seaModerate}</option>
|
|
54
|
+
<option value="1.60">{ui.seaStorm}</option>
|
|
55
|
+
</select>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="ec-compact-group">
|
|
60
|
+
<label>{ui.consumptionLabel}</label>
|
|
61
|
+
<div class="ec-field-wrapper">
|
|
62
|
+
<span class="ec-field-icon icon-fire"></span>
|
|
63
|
+
<input type="number" id="f-cons" class="ec-compact-input" value="25" min="0.1" step="1" />
|
|
64
|
+
<span class="ec-unit-tag">L/h</span>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="ec-compact-group">
|
|
69
|
+
<label>{ui.cruiseSpeedLabel}</label>
|
|
70
|
+
<div class="ec-field-wrapper">
|
|
71
|
+
<span class="ec-field-icon icon-wind"></span>
|
|
72
|
+
<input type="number" id="v-cruiser" class="ec-compact-input" value="8" min="0.1" step="0.5" />
|
|
73
|
+
<span class="ec-unit-tag">kn</span>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="ec-compact-group">
|
|
78
|
+
<label>{ui.reserveLabel}</label>
|
|
79
|
+
<div class="ec-field-wrapper">
|
|
80
|
+
<span class="ec-field-icon icon-shield"></span>
|
|
81
|
+
<input type="number" id="f-res" class="ec-compact-input" value="20" min="0" max="50" step="5" />
|
|
82
|
+
<span class="ec-unit-tag">%</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="ec-compact-group">
|
|
87
|
+
<label>{ui.fuelPriceLabel}</label>
|
|
88
|
+
<div class="ec-field-wrapper">
|
|
89
|
+
<span class="ec-field-icon">€</span>
|
|
90
|
+
<input type="number" id="f-price" class="ec-compact-input" value="1.8" min="0" step="0.05" />
|
|
91
|
+
<span class="ec-unit-tag">€/L</span>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</aside>
|
|
95
|
+
|
|
96
|
+
<div class="ec-dashboard-panel">
|
|
97
|
+
<div class="ec-hero-display">
|
|
98
|
+
<div class="ec-hero-top">
|
|
99
|
+
<div>
|
|
100
|
+
<div class="ec-display-title">{ui.maxRangeLabel}</div>
|
|
101
|
+
<div class="ec-main-value-row">
|
|
102
|
+
<span class="ec-main-value" id="out-dist">--</span>
|
|
103
|
+
<span class="ec-main-unit">MN</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="ec-fuel-badge" id="out-fuel-pct">--%</div>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="ec-progress-track">
|
|
109
|
+
<div class="ec-progress-fill" id="progress-fill"></div>
|
|
110
|
+
<div class="ec-marker" id="reserve-marker" style="left: 80%"></div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="ec-stats-grid">
|
|
115
|
+
<div class="ec-stat-card">
|
|
116
|
+
<span class="ec-stat-label">{ui.realPerformanceLabel}</span>
|
|
117
|
+
<span class="ec-stat-value" id="out-perf">--</span>
|
|
118
|
+
<span class="ec-stat-label">L/MN</span>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="ec-stat-card">
|
|
121
|
+
<span class="ec-stat-label">{ui.hoursLabel}</span>
|
|
122
|
+
<span class="ec-stat-value" id="out-hours">--</span>
|
|
123
|
+
<span class="ec-stat-label">h</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="ec-stat-card">
|
|
126
|
+
<span class="ec-stat-label">{ui.safeMilesLabel}</span>
|
|
127
|
+
<span class="ec-stat-value danger" id="out-safe">--</span>
|
|
128
|
+
<span class="ec-stat-label">MN</span>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="ec-stat-card hidden" id="card-cost">
|
|
131
|
+
<span class="ec-stat-label">{ui.tankValueLabel}</span>
|
|
132
|
+
<span class="ec-stat-value" id="out-cost">--</span>
|
|
133
|
+
<span class="ec-stat-label">€</span>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<div class="ec-inverter-card">
|
|
138
|
+
<div class="ec-inverter-header">{ui.inverseCalcLabel}</div>
|
|
139
|
+
<div class="ec-inverter-row">
|
|
140
|
+
<div>
|
|
141
|
+
<div class="ec-inverter-label">{ui.desiredDistLabel}</div>
|
|
142
|
+
<div class="ec-field-wrapper">
|
|
143
|
+
<input type="number" id="inv-dist" class="ec-compact-input" value="50" min="0" step="5" />
|
|
144
|
+
<span class="ec-unit-tag">MN</span>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
<div>
|
|
148
|
+
<div class="ec-inverter-label">{ui.minFuelLabel}</div>
|
|
149
|
+
<div class="ec-inverter-value" id="inv-res">-- <span class="ec-inverter-unit">L</span></div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div class="ec-warning-bar">
|
|
155
|
+
<span class="ec-warning-icon">!</span>
|
|
156
|
+
<span>{ui.warningLabel}</span>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</section>
|
|
161
|
+
|
|
162
|
+
<script>
|
|
163
|
+
const section = document.getElementById('endurance-calculator') as HTMLElement;
|
|
164
|
+
JSON.parse(section.dataset.ui as string);
|
|
165
|
+
|
|
166
|
+
const SEA_FACTORS = [1.0, 1.15, 1.30, 1.60];
|
|
167
|
+
|
|
168
|
+
class EnduranceCalculator {
|
|
169
|
+
private t1: HTMLInputElement;
|
|
170
|
+
private t2: HTMLInputElement;
|
|
171
|
+
private fCur: HTMLInputElement;
|
|
172
|
+
private seaSel: HTMLSelectElement;
|
|
173
|
+
private fCons: HTMLInputElement;
|
|
174
|
+
private vCruise: HTMLInputElement;
|
|
175
|
+
private fRes: HTMLInputElement;
|
|
176
|
+
private fPrice: HTMLInputElement;
|
|
177
|
+
private invDist: HTMLInputElement;
|
|
178
|
+
|
|
179
|
+
constructor() {
|
|
180
|
+
this.t1 = document.getElementById('tank1') as HTMLInputElement;
|
|
181
|
+
this.t2 = document.getElementById('tank2') as HTMLInputElement;
|
|
182
|
+
this.fCur = document.getElementById('f-current') as HTMLInputElement;
|
|
183
|
+
this.seaSel = document.getElementById('sea-s') as HTMLSelectElement;
|
|
184
|
+
this.fCons = document.getElementById('f-cons') as HTMLInputElement;
|
|
185
|
+
this.vCruise = document.getElementById('v-cruiser') as HTMLInputElement;
|
|
186
|
+
this.fRes = document.getElementById('f-res') as HTMLInputElement;
|
|
187
|
+
this.fPrice = document.getElementById('f-price') as HTMLInputElement;
|
|
188
|
+
this.invDist = document.getElementById('inv-dist') as HTMLInputElement;
|
|
189
|
+
this.init();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private init() {
|
|
193
|
+
const inputs = [this.t1, this.t2, this.fCur, this.fCons, this.vCruise, this.fRes, this.fPrice];
|
|
194
|
+
inputs.forEach((el) => el.addEventListener('input', () => { this.calculate(); this.save(); }));
|
|
195
|
+
this.seaSel.addEventListener('change', () => { this.calculate(); this.save(); });
|
|
196
|
+
this.invDist.addEventListener('input', () => { this.updateInverse(); this.save(); });
|
|
197
|
+
this.load();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private save() {
|
|
201
|
+
localStorage.setItem('jjlmoya-nautical-endurance', JSON.stringify({
|
|
202
|
+
t1: this.t1.value, t2: this.t2.value, fuel: this.fCur.value,
|
|
203
|
+
cons: this.fCons.value, speed: this.vCruise.value,
|
|
204
|
+
state: this.seaSel.value, res: this.fRes.value,
|
|
205
|
+
price: this.fPrice.value, inv: this.invDist.value,
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private restoreInputs(d: Record<string, string>) {
|
|
210
|
+
if (d.t1) this.t1.value = d.t1;
|
|
211
|
+
if (d.t2) this.t2.value = d.t2;
|
|
212
|
+
if (d.fuel) this.fCur.value = d.fuel;
|
|
213
|
+
if (d.cons) this.fCons.value = d.cons;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private restoreSettings(d: Record<string, string>) {
|
|
217
|
+
if (d.speed) this.vCruise.value = d.speed;
|
|
218
|
+
if (d.state) this.seaSel.value = d.state;
|
|
219
|
+
if (d.res) this.fRes.value = d.res;
|
|
220
|
+
if (d.price) this.fPrice.value = d.price;
|
|
221
|
+
if (d.inv) this.invDist.value = d.inv;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private restoreFields(d: Record<string, string>) {
|
|
225
|
+
this.restoreInputs(d);
|
|
226
|
+
this.restoreSettings(d);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private load() {
|
|
230
|
+
try {
|
|
231
|
+
const raw = localStorage.getItem('jjlmoya-nautical-endurance');
|
|
232
|
+
if (raw) this.restoreFields(JSON.parse(raw) as Record<string, string>);
|
|
233
|
+
} catch { }
|
|
234
|
+
this.calculate();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private readTanks() {
|
|
238
|
+
return {
|
|
239
|
+
tank1: parseFloat(this.t1.value) || 0,
|
|
240
|
+
tank2: parseFloat(this.t2.value) || 0,
|
|
241
|
+
fuelCurrent: parseFloat(this.fCur.value) || 0,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private readInputs() {
|
|
246
|
+
const tanks = this.readTanks();
|
|
247
|
+
return {
|
|
248
|
+
...tanks,
|
|
249
|
+
seaFactor: parseFloat(this.seaSel.value) || 1.0,
|
|
250
|
+
consumption: parseFloat(this.fCons.value) || 0,
|
|
251
|
+
speed: parseFloat(this.vCruise.value) || 0,
|
|
252
|
+
reserve: parseFloat(this.fRes.value) || 0,
|
|
253
|
+
price: parseFloat(this.fPrice.value) || 0,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private calculate() {
|
|
258
|
+
const p = this.readInputs();
|
|
259
|
+
const realCons = p.consumption * p.seaFactor;
|
|
260
|
+
const lpmn = p.speed > 0 ? realCons / p.speed : 0;
|
|
261
|
+
const hTotal = realCons > 0 ? p.fuelCurrent / realCons : 0;
|
|
262
|
+
const maxDist = hTotal * p.speed;
|
|
263
|
+
const safeFuel = p.fuelCurrent * (1 - p.reserve / 100);
|
|
264
|
+
const safeHours = realCons > 0 ? safeFuel / realCons : 0;
|
|
265
|
+
const safeDist = safeHours * p.speed;
|
|
266
|
+
const tCap = p.tank1 + p.tank2;
|
|
267
|
+
const fuelPct = tCap > 0 ? (p.fuelCurrent / tCap) * 100 : 0;
|
|
268
|
+
this.updateDisplay({ maxDist, safeHours, safeDist, lpmn, fuelPct, price: p.price, fuelCurrent: p.fuelCurrent, reserve: p.reserve });
|
|
269
|
+
this.updateInverse();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private updateDisplay(r: { maxDist: number; safeHours: number; safeDist: number; lpmn: number; fuelPct: number; price: number; fuelCurrent: number; reserve: number }) {
|
|
273
|
+
this.setText('out-dist', r.maxDist.toFixed(1));
|
|
274
|
+
this.setText('out-fuel-pct', `${r.fuelPct.toFixed(0)}%`);
|
|
275
|
+
this.setText('out-perf', r.lpmn.toFixed(2));
|
|
276
|
+
this.setText('out-hours', r.safeHours.toFixed(1));
|
|
277
|
+
this.setText('out-safe', r.safeDist.toFixed(1));
|
|
278
|
+
const fill = document.getElementById('progress-fill') as HTMLElement;
|
|
279
|
+
fill.style.width = `${Math.min(100, r.fuelPct)}%`;
|
|
280
|
+
const marker = document.getElementById('reserve-marker') as HTMLElement;
|
|
281
|
+
marker.style.left = `${Math.max(0, Math.min(100, 100 - r.reserve))}%`;
|
|
282
|
+
const cost = r.price > 0 ? r.fuelCurrent * r.price : 0;
|
|
283
|
+
const cardCost = document.getElementById('card-cost') as HTMLElement;
|
|
284
|
+
if (r.price > 0) {
|
|
285
|
+
cardCost.classList.remove('hidden');
|
|
286
|
+
this.setText('out-cost', `${cost.toFixed(2)} €`);
|
|
287
|
+
} else {
|
|
288
|
+
cardCost.classList.add('hidden');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private updateInverse() {
|
|
293
|
+
const p = this.readInputs();
|
|
294
|
+
const realCons = p.consumption * p.seaFactor;
|
|
295
|
+
const lpmn = p.speed > 0 ? realCons / p.speed : 0;
|
|
296
|
+
const dist = parseFloat(this.invDist.value) || 0;
|
|
297
|
+
const reserveFactor = 1 - p.reserve / 100;
|
|
298
|
+
const needed = reserveFactor > 0 ? (dist * lpmn) / reserveFactor : 0;
|
|
299
|
+
this.setText('inv-res', `${needed.toFixed(1)} L`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private setText(id: string, val: string) {
|
|
303
|
+
const el = document.getElementById(id);
|
|
304
|
+
if (el) el.textContent = val;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
void SEA_FACTORS;
|
|
309
|
+
new EnduranceCalculator();
|
|
310
|
+
</script>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { EnduranceUI, EnduranceLocaleContent } from '../index';
|
|
3
|
+
|
|
4
|
+
const slug = 'nautical-endurance-calculator';
|
|
5
|
+
const title = 'Nautical Endurance Calculator';
|
|
6
|
+
const description =
|
|
7
|
+
'Calculate your maximum range and safe distance based on fuel consumption, tank capacity and cruise speed. Fuel management for motorboats.';
|
|
8
|
+
|
|
9
|
+
const ui: EnduranceUI = {
|
|
10
|
+
tankCapacityLabel: 'Tank Capacity',
|
|
11
|
+
mainTankLabel: 'Main',
|
|
12
|
+
auxTankLabel: 'Aux',
|
|
13
|
+
currentFuelLabel: 'Current Fuel',
|
|
14
|
+
seaConditionsLabel: 'Sea / Wind Conditions',
|
|
15
|
+
consumptionLabel: 'Theoretical Consumption',
|
|
16
|
+
cruiseSpeedLabel: 'Cruise Speed',
|
|
17
|
+
reserveLabel: 'Reserve',
|
|
18
|
+
fuelPriceLabel: 'Price per Litre',
|
|
19
|
+
maxRangeLabel: 'Maximum Range',
|
|
20
|
+
realPerformanceLabel: 'Real Performance',
|
|
21
|
+
hoursLabel: 'Endurance Hours',
|
|
22
|
+
safeMilesLabel: 'Safe Miles',
|
|
23
|
+
tankValueLabel: 'Tank Value',
|
|
24
|
+
inverseCalcLabel: 'Reverse Calculation: How much do I need to refuel?',
|
|
25
|
+
desiredDistLabel: 'Desired Distance',
|
|
26
|
+
minFuelLabel: 'Minimum Fuel Required',
|
|
27
|
+
warningLabel: 'Reminder: A 20% reserve is the absolute minimum recommended for nautical safety.',
|
|
28
|
+
seaCalm: 'Calm (1.0x)',
|
|
29
|
+
seaLight: 'Light swell (+15%)',
|
|
30
|
+
seaModerate: 'Moderate swell (+30%)',
|
|
31
|
+
seaStorm: 'Storm (+60%)',
|
|
32
|
+
faqTitle: 'Frequently Asked Questions',
|
|
33
|
+
bibliographyTitle: 'Bibliography',
|
|
34
|
+
};
|
|
35
|
+
const faq: EnduranceLocaleContent['faq'] = [
|
|
36
|
+
{
|
|
37
|
+
question: 'How is the endurance of a motorboat calculated?',
|
|
38
|
+
answer: 'Endurance is calculated by dividing the available fuel by the hourly consumption to obtain total hours, then multiplying that time by the cruise speed. The tool also applies a sea state factor to reflect real navigating conditions.',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
question: 'Why is a 20% reserve recommended in nautical navigation?',
|
|
42
|
+
answer: 'The 20% reserve is a nautical safety standard that ensures enough fuel in case of unforeseen events: contrary currents, detours due to bad weather, errors in consumption estimates, or the need to assist another vessel. Many insurers and harbour masters consider it mandatory.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
question: 'What is the L/NM performance and how does it affect planning?',
|
|
46
|
+
answer: 'Performance in litres per nautical mile (L/NM) is the real efficiency of the engine at sea conditions. Unlike hourly consumption, it lets you calculate exactly how much fuel you need to cover a specific distance, regardless of your speed.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
question: 'How does sea state affect actual consumption?',
|
|
50
|
+
answer: 'Sea state increases consumption because the hull works harder against water and wind resistance. In light swell consumption rises 15%, in moderate swell 30%, and in a storm it can spike 60% or more. Ignoring this factor can leave a vessel without fuel before reaching port.',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
const howTo: EnduranceLocaleContent['howTo'] = [
|
|
54
|
+
{
|
|
55
|
+
name: 'Enter your tank capacity',
|
|
56
|
+
text: 'Enter the litres of the main tank and the auxiliary one if you have it. The tool will calculate the current fill percentage.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Enter current fuel level',
|
|
60
|
+
text: 'Enter the actual litres you have right now. You can measure them with the boat level or estimate them from the last refuel.',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'Adjust navigation conditions',
|
|
64
|
+
text: 'Select the expected sea state for your passage. The sea factor corrects the theoretical consumption to the expected real consumption.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Enter consumption and cruise speed',
|
|
68
|
+
text: 'Use manufacturer data or your own records for hourly consumption and the speed at which you usually navigate.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'Review the safe distance and reverse calculation',
|
|
72
|
+
text: 'The tool shows how many miles you can safely cover and how much fuel you need to reach a specific destination.',
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const bibliography: EnduranceLocaleContent['bibliography'] = [
|
|
77
|
+
{
|
|
78
|
+
name: 'UK Maritime and Coastguard Agency',
|
|
79
|
+
url: 'https://www.gov.uk/government/organisations/maritime-and-coastguard-agency',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'RYA - Royal Yachting Association',
|
|
83
|
+
url: 'https://www.rya.org.uk/',
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
const seo: EnduranceLocaleContent['seo'] = [
|
|
87
|
+
{
|
|
88
|
+
type: 'title',
|
|
89
|
+
text: 'Fuel Management and Endurance in Motor Navigation',
|
|
90
|
+
level: 2,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
type: 'paragraph',
|
|
94
|
+
html: '<strong>Nautical endurance</strong> is one of the most critical calculations before setting sail on a motorboat. Knowing precisely how many miles you can cover with the available fuel is the difference between a planned passage and an emergency at sea.',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'paragraph',
|
|
98
|
+
html: 'This endurance calculator integrates the <strong>sea conditions factor</strong>, the safety reserve percentage, and the reverse fuel calculation so that fuel management is complete and reliable in any situation.',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'title',
|
|
102
|
+
text: 'The Golden Rule: The Rule of Thirds',
|
|
103
|
+
level: 3,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: 'paragraph',
|
|
107
|
+
html: 'The classic fuel management rule in nautical navigation divides the tank into three equal parts to always guarantee a safety margin:',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: 'list',
|
|
111
|
+
items: [
|
|
112
|
+
'<strong>One third for the outward leg:</strong> The fuel needed to reach the planned destination.',
|
|
113
|
+
'<strong>One third for the return:</strong> The fuel to get back to the departure port or the nearest one.',
|
|
114
|
+
'<strong>One third as reserve:</strong> The safety margin for unforeseen events, currents or adverse conditions.',
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: 'paragraph',
|
|
119
|
+
html: 'In the calculator you can set the reserve percentage according to your own criteria, although <strong>going below 20% is never recommended</strong>. With larger tanks or long passages, many experienced skippers raise this margin to 30% or even 33%.',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'title',
|
|
123
|
+
text: 'Factors affecting Consumption (l/h)',
|
|
124
|
+
level: 3,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: 'paragraph',
|
|
128
|
+
html: "The manufacturer's theoretical consumption is calculated under ideal laboratory conditions. In practice, multiple factors alter the <strong>real fuel consumption</strong>. Hull speed is the most decisive: navigating above the economical speed can double or triple consumption per mile covered.",
|
|
129
|
+
}, {
|
|
130
|
+
type: 'table',
|
|
131
|
+
headers: ['Navigation State', 'Effect on Endurance', 'Advice'],
|
|
132
|
+
rows: [
|
|
133
|
+
['<strong>Fouled Hull</strong>', 'Reduces endurance by up to 20%', 'Clean the hull before the season'],
|
|
134
|
+
['<strong>Head Current</strong>', 'Can reduce effective speed by 30%', 'Always calculate with speed over ground'],
|
|
135
|
+
['<strong>Head Wind</strong>', 'Increases resistance and consumption by 10-25%', 'Use the light or moderate swell factor'],
|
|
136
|
+
['<strong>Overloading</strong>', 'Increases draught and resistance', 'Weigh gear and provisions before calculating'],
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
type: 'title',
|
|
141
|
+
text: 'Endurance Technical Glossary',
|
|
142
|
+
level: 3,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'glossary',
|
|
146
|
+
items: [
|
|
147
|
+
{ term: 'Performance (L/NM)', definition: 'Litres of fuel consumed per nautical mile covered. The most useful indicator for planning passages because it links consumption to actual distance.' },
|
|
148
|
+
{ term: 'Cruise Speed', definition: 'The optimum speed at which the engine offers the best balance between speed and consumption. Generally between 70% and 80% of maximum power.' },
|
|
149
|
+
{ term: 'Critical Reserve', definition: 'The percentage of the tank that must never be consumed. A minimum of 20% is recommended to cover contingencies and prevent air from entering the fuel circuit.' },
|
|
150
|
+
{ term: 'Specific Consumption', definition: 'Grams of fuel per kilowatt-hour of power produced (g/kWh). Technical engine measure that allows comparing efficiency between different propulsion systems.' },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'title',
|
|
155
|
+
text: 'The Danger of Sludge and Movement',
|
|
156
|
+
level: 3,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: 'paragraph',
|
|
160
|
+
html: 'A common source of problems on vessels with low fuel levels is <strong>tank bottom sludge</strong>. When the level drops too low, sediments accumulated over years can reach the filter and clog it, leaving the engine without fuel even though the gauge shows some litres.',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: 'paragraph',
|
|
164
|
+
html: 'The movement of the boat at sea with little fuel can also cause problems: the liquid oscillates with the waves and can momentarily starve the fuel pump. Maintaining the reserve ensures this does not happen at a critical moment such as a harbour entry manoeuvre.',
|
|
165
|
+
}, {
|
|
166
|
+
type: 'tip',
|
|
167
|
+
title: 'Fuel Saving Tip',
|
|
168
|
+
html: 'Reducing speed by 10-15% below your usual cruise speed can improve efficiency by up to 30%. If you have time and the weather is favourable, going slower is always the most economical and safe decision.',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: 'title',
|
|
172
|
+
text: 'Difference between Nautical Miles (NM) and Kilometres',
|
|
173
|
+
level: 3,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
type: 'paragraph',
|
|
177
|
+
html: 'A <strong>nautical mile</strong> equals 1,852 metres (1.852 km), and is the universal distance unit in maritime and aeronautical navigation. It is based on one arc-minute of a geographic degree, making it ideal for navigation with charts in geographic coordinates. Speed in nautical miles per hour is called a knot (kn).',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: 'comparative',
|
|
181
|
+
items: [
|
|
182
|
+
{
|
|
183
|
+
title: 'Needle Analogue Gauge',
|
|
184
|
+
description: 'Traditional float system',
|
|
185
|
+
points: [
|
|
186
|
+
'Low cost and simple installation',
|
|
187
|
+
'Requires no electricity to operate',
|
|
188
|
+
'Inaccurate in rough seas due to fuel movement',
|
|
189
|
+
'Does not show consumption or estimated endurance',
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
title: 'Digital Flow Calculator',
|
|
194
|
+
description: 'Electronic flow sensors',
|
|
195
|
+
highlight: true,
|
|
196
|
+
points: [
|
|
197
|
+
'1-2% accuracy on real consumption',
|
|
198
|
+
'Shows endurance, consumption and cost in real time',
|
|
199
|
+
'Integrable with chartplotters and NMEA 2000',
|
|
200
|
+
'Requires initial calibration and maintenance',
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
const schemas: EnduranceLocaleContent['schemas'] = [
|
|
207
|
+
{
|
|
208
|
+
'@context': 'https://schema.org',
|
|
209
|
+
'@type': 'SoftwareApplication',
|
|
210
|
+
name: title,
|
|
211
|
+
description,
|
|
212
|
+
applicationCategory: 'UtilityApplication',
|
|
213
|
+
operatingSystem: 'Web',
|
|
214
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
215
|
+
} as WithContext<SoftwareApplication>,
|
|
216
|
+
{
|
|
217
|
+
'@context': 'https://schema.org',
|
|
218
|
+
'@type': 'FAQPage',
|
|
219
|
+
mainEntity: faq.map((item) => ({
|
|
220
|
+
'@type': 'Question',
|
|
221
|
+
name: item.question,
|
|
222
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
223
|
+
})),
|
|
224
|
+
} as WithContext<FAQPage>,
|
|
225
|
+
{
|
|
226
|
+
'@context': 'https://schema.org',
|
|
227
|
+
'@type': 'HowTo',
|
|
228
|
+
name: `How to use: ${title}`,
|
|
229
|
+
step: howTo.map((s) => ({ '@type': 'HowToStep', name: s.name, text: s.text })),
|
|
230
|
+
} as WithContext<HowTo>,
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
export const content: EnduranceLocaleContent = {
|
|
234
|
+
slug,
|
|
235
|
+
title,
|
|
236
|
+
description,
|
|
237
|
+
ui,
|
|
238
|
+
seo,
|
|
239
|
+
faq,
|
|
240
|
+
bibliography,
|
|
241
|
+
howTo,
|
|
242
|
+
schemas,
|
|
243
|
+
};
|