@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.
Files changed (74) hide show
  1. package/package.json +75 -0
  2. package/src/category/i18n/en.ts +79 -0
  3. package/src/category/i18n/es.ts +79 -0
  4. package/src/category/i18n/fr.ts +79 -0
  5. package/src/category/index.ts +13 -0
  6. package/src/components/PreviewNavSidebar.astro +116 -0
  7. package/src/components/PreviewToolbar.astro +143 -0
  8. package/src/data.ts +12 -0
  9. package/src/env.d.ts +5 -0
  10. package/src/index.ts +22 -0
  11. package/src/layouts/PreviewLayout.astro +117 -0
  12. package/src/pages/[locale]/[slug].astro +146 -0
  13. package/src/pages/[locale].astro +249 -0
  14. package/src/pages/index.astro +4 -0
  15. package/src/tests/faq_count.test.ts +30 -0
  16. package/src/tests/mocks/astro_mock.js +2 -0
  17. package/src/tests/seo_length.test.ts +29 -0
  18. package/src/tests/tool_validation.test.ts +51 -0
  19. package/src/tool/endurance/bibliography.astro +14 -0
  20. package/src/tool/endurance/component.astro +310 -0
  21. package/src/tool/endurance/i18n/en.ts +243 -0
  22. package/src/tool/endurance/i18n/es.ts +249 -0
  23. package/src/tool/endurance/i18n/fr.ts +217 -0
  24. package/src/tool/endurance/index.ts +56 -0
  25. package/src/tool/endurance/logic.ts +36 -0
  26. package/src/tool/endurance/seo.astro +55 -0
  27. package/src/tool/endurance/style.css +333 -0
  28. package/src/tool/nauticalConverter/bibliography.astro +14 -0
  29. package/src/tool/nauticalConverter/component.astro +481 -0
  30. package/src/tool/nauticalConverter/i18n/en.ts +239 -0
  31. package/src/tool/nauticalConverter/i18n/es.ts +239 -0
  32. package/src/tool/nauticalConverter/i18n/fr.ts +239 -0
  33. package/src/tool/nauticalConverter/index.ts +57 -0
  34. package/src/tool/nauticalConverter/logic.ts +52 -0
  35. package/src/tool/nauticalConverter/seo.astro +52 -0
  36. package/src/tool/nauticalConverter/style.css +205 -0
  37. package/src/tool/sailArea/bibliography.astro +14 -0
  38. package/src/tool/sailArea/component.astro +418 -0
  39. package/src/tool/sailArea/i18n/en.ts +275 -0
  40. package/src/tool/sailArea/i18n/es.ts +275 -0
  41. package/src/tool/sailArea/i18n/fr.ts +275 -0
  42. package/src/tool/sailArea/index.ts +53 -0
  43. package/src/tool/sailArea/logic.ts +55 -0
  44. package/src/tool/sailArea/seo.astro +52 -0
  45. package/src/tool/sailArea/style.css +351 -0
  46. package/src/tool/speedConverter/bibliography.astro +14 -0
  47. package/src/tool/speedConverter/component.astro +125 -0
  48. package/src/tool/speedConverter/i18n/en.ts +271 -0
  49. package/src/tool/speedConverter/i18n/es.ts +271 -0
  50. package/src/tool/speedConverter/i18n/fr.ts +271 -0
  51. package/src/tool/speedConverter/index.ts +46 -0
  52. package/src/tool/speedConverter/logic.ts +48 -0
  53. package/src/tool/speedConverter/seo.astro +43 -0
  54. package/src/tool/speedConverter/style.css +239 -0
  55. package/src/tool/tideCalculator/bibliography.astro +14 -0
  56. package/src/tool/tideCalculator/component.astro +314 -0
  57. package/src/tool/tideCalculator/i18n/en.ts +203 -0
  58. package/src/tool/tideCalculator/i18n/es.ts +225 -0
  59. package/src/tool/tideCalculator/i18n/fr.ts +190 -0
  60. package/src/tool/tideCalculator/index.ts +50 -0
  61. package/src/tool/tideCalculator/logic.ts +73 -0
  62. package/src/tool/tideCalculator/seo.astro +61 -0
  63. package/src/tool/tideCalculator/style.css +310 -0
  64. package/src/tool/underKeel/bibliography.astro +14 -0
  65. package/src/tool/underKeel/component.astro +240 -0
  66. package/src/tool/underKeel/i18n/en.ts +193 -0
  67. package/src/tool/underKeel/i18n/es.ts +206 -0
  68. package/src/tool/underKeel/i18n/fr.ts +193 -0
  69. package/src/tool/underKeel/index.ts +49 -0
  70. package/src/tool/underKeel/logic.ts +83 -0
  71. package/src/tool/underKeel/seo.astro +46 -0
  72. package/src/tool/underKeel/style.css +312 -0
  73. package/src/tools.ts +25 -0
  74. package/src/types.ts +69 -0
@@ -0,0 +1,271 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { SpeedConverterUI, SpeedConverterLocaleContent } from '../index';
3
+
4
+ const slug = 'convertisseur-vitesse-nautique';
5
+ const title = 'Convertisseur de Vitesse Nautique et Echelle Beaufort';
6
+ const description =
7
+ 'Convertissez entre noeuds, km/h, m/s et mph instantanement. Inclut le tableau Beaufort complet avec description de l\'etat de la mer et effets du vent.';
8
+
9
+ const beaufortData = [
10
+ { force: 0, name: 'Calme', sea: 'Mer comme un miroir', effect: 'La fumee monte verticalement', kn: '0' },
11
+ { force: 1, name: 'Tres legere brise', sea: 'Rides comme des ecailles de poisson', effect: 'La fumee indique la direction du vent', kn: '1-3' },
12
+ { force: 2, name: 'Legere brise', sea: 'Petites vaguelettes sans cretes', effect: 'Le vent est senti sur le visage', kn: '4-6' },
13
+ { force: 3, name: 'Petite brise', sea: 'Moutons epars', effect: 'Les feuilles et les petites branches bougent', kn: '7-10' },
14
+ { force: 4, name: 'Jolie brise', sea: 'Petites vagues, nombreux moutons', effect: 'La poussiere et les papiers s\'envolent', kn: '11-16' },
15
+ { force: 5, name: 'Brise fraiche', sea: 'Vagues moderees, beaucoup de moutons', effect: 'Difficulte a utiliser un parapluie', kn: '17-21' },
16
+ { force: 6, name: 'Vent frais', sea: 'Grandes vagues, ecume blanche', effect: 'Difficulte a marcher contre le vent', kn: '22-27' },
17
+ { force: 7, name: 'Grand frais', sea: 'Mer grosse, ecume en stries', effect: 'Difficulte a marcher', kn: '28-33' },
18
+ { force: 8, name: 'Coup de vent', sea: 'Tres hautes vagues, eclaboussures', effect: 'Grande difficulte a marcher', kn: '34-40' },
19
+ { force: 9, name: 'Fort coup de vent', sea: 'Tres hautes vagues, visibilite reduite', effect: 'Degats aux batiments', kn: '41-47' },
20
+ { force: 10, name: 'Tempete', sea: 'Vagues exceptionnellement hautes, mer blanche', effect: 'Arbres deracines', kn: '48-55' },
21
+ { force: 11, name: 'Violente tempete', sea: 'Enormes vagues, ecume couvre la mer', effect: 'Devastation generalisee', kn: '56-63' },
22
+ { force: 12, name: 'Ouragan', sea: 'Air plein d\'ecume et d\'embruns', effect: 'Catastrophe totale', kn: '64+' },
23
+ ];
24
+
25
+ const ui: SpeedConverterUI = {
26
+ knLabel: 'Noeuds (kt)',
27
+ kmhLabel: 'Kilometres/heure',
28
+ msLabel: 'Metres/seconde',
29
+ mphLabel: 'Miles/heure',
30
+ beaufortTitle: 'Echelle Beaufort',
31
+ forceLabel: 'Force',
32
+ descriptionLabel: 'Description',
33
+ knotsLabel: 'Noeuds',
34
+ effectLabel: 'Effet Visuel',
35
+ seaStateLabel: 'Etat de la Mer',
36
+ windEffectLabel: 'Effet sur Terre',
37
+ faqTitle: 'Questions Frequentes',
38
+ bibliographyTitle: 'Bibliographie',
39
+ beaufortDataJson: JSON.stringify(beaufortData),
40
+ };
41
+
42
+ const faq: SpeedConverterLocaleContent['faq'] = [
43
+ {
44
+ question: 'Pourquoi la vitesse du bateau se mesure-t-elle en noeuds ?',
45
+ answer: "Le noeud est une unite de vitesse equivalant a un mille nautique par heure. Son origine est historique : les marins mesuraient la vitesse du navire en jetant a la mer un morceau de bois attache a une corde avec des noeuds equidistants, en comptant combien de noeuds passaient entre leurs doigts en un temps donne. Aujourd'hui, c'est toujours l'unite standard internationale en navigation maritime et aeronautique.",
46
+ },
47
+ {
48
+ question: "Qu'est-ce que l'echelle Beaufort et comment s'utilise-t-elle en navigation ?",
49
+ answer: "L'echelle Beaufort est une echelle empirique qui relie la vitesse du vent aux effets observes en mer et sur terre. Developpee par l'amiral britannique Francis Beaufort en 1805, elle va de 0 (calme absolu) a 12 (ouragan). Les navigateurs l'utilisent pour estimer l'intensite du vent sans instruments en observant l'etat de la mer.",
50
+ },
51
+ {
52
+ question: 'Quelle est la difference entre SOG et STW en navigation ?',
53
+ answer: "Le SOG (Speed Over Ground) est la vitesse reelle du bateau par rapport au fond marin, mesuree par GPS. Le STW (Speed Through Water) ou vitesse au loch est la vitesse du bateau par rapport a l'eau environnante. La difference entre les deux reflete l'effet des courants marins. Le SOG est utilise pour planifier les arrivees et calculer le carburant ; le STW est utilise pour calculer la derive.",
54
+ },
55
+ {
56
+ question: 'Combien de km/h vaut un noeud ?',
57
+ answer: "Un noeud equivaut exactement a 1,852 kilometres par heure, soit la longueur d'un mille nautique (defini comme une minute d'arc du meridien terrestre). Ainsi, 10 noeuds font 18,52 km/h, 20 noeuds font 37,04 km/h et 30 noeuds font 55,56 km/h.",
58
+ },
59
+ ];
60
+
61
+ const howTo: SpeedConverterLocaleContent['howTo'] = [
62
+ {
63
+ name: 'Saisir la vitesse dans n\'importe quelle unite',
64
+ text: 'Tapez la valeur de vitesse dans le champ correspondant (noeuds, km/h, m/s ou mph) et tous les autres champs se mettront a jour automatiquement.',
65
+ },
66
+ {
67
+ name: 'Consulter la force Beaufort equivalente',
68
+ text: "La carte Beaufort affiche en temps reel la force du vent correspondant a la vitesse saisie, avec l'etat de la mer et les effets sur terre.",
69
+ },
70
+ {
71
+ name: 'Cliquer sur une ligne du tableau Beaufort',
72
+ text: 'Vous pouvez selectionner directement n\'importe quelle echelle Beaufort dans le tableau inferieur pour voir sa vitesse minimale dans tous les formats.',
73
+ },
74
+ {
75
+ name: 'Utiliser les resultats pour planifier votre navigation',
76
+ text: 'Combinez les informations de vitesse et de vent pour decider si les conditions sont appropriees pour votre type d\'embarcation et la traversee prevue.',
77
+ },
78
+ ];
79
+
80
+ const bibliography: SpeedConverterLocaleContent['bibliography'] = [
81
+ {
82
+ name: 'Met Office - The Beaufort Scale',
83
+ url: 'https://www.metoffice.gov.uk/weather/guides/coast-and-sea/beaufort-scale',
84
+ },
85
+ {
86
+ name: 'OMI - Organisation Maritime Internationale',
87
+ url: 'https://www.imo.org/',
88
+ },
89
+ ];
90
+
91
+ const seo: SpeedConverterLocaleContent['seo'] = [
92
+ {
93
+ type: 'title',
94
+ text: 'Guide du Convertisseur de Vitesse Nautique et Echelle Beaufort',
95
+ level: 2,
96
+ },
97
+ {
98
+ type: 'paragraph',
99
+ html: "La vitesse est une donnee fondamentale en navigation maritime. Savoir combien de noeuds fait votre bateau, comment cela se traduit en kilometres par heure pour planifier une traversee, ou quelle force Beaufort represente le vent que vous subissez, sont des calculs que tout navigateur doit maitriser.",
100
+ },
101
+ {
102
+ type: 'paragraph',
103
+ html: "Ce convertisseur de vitesse nautique vous permet de transformer instantanement entre noeuds, km/h, m/s et mph, tout en identifiant automatiquement la force Beaufort correspondante avec sa description de l'etat de la mer et les effets du vent observables.",
104
+ },
105
+ {
106
+ type: 'stats',
107
+ items: [
108
+ { label: 'Unite Standard', value: 'Noeuds (kt)', icon: 'mdi:speedometer' },
109
+ { label: 'Echelle de Vent', value: 'Beaufort (0-12)', icon: 'mdi:weather-windy' },
110
+ { label: 'Conversion Cle', value: '1 kt = 1,85 km/h', icon: 'mdi:swap-horizontal' },
111
+ ],
112
+ },
113
+ {
114
+ type: 'title',
115
+ text: 'Pourquoi la vitesse se mesure-t-elle en Noeuds ?',
116
+ level: 3,
117
+ },
118
+ {
119
+ type: 'paragraph',
120
+ html: "Le <strong>noeud (kt)</strong> est l'unite officielle de vitesse en navigation maritime et aerienne. Sa definition est precise : un noeud equivaut a un mille nautique par heure, ou le mille nautique (1852 m) est defini comme une minute d'arc du meridien terrestre. Cette relation directe avec la geometrie de la Terre est ce qui fait du noeud une unite particulierement pratique pour la navigation, permettant d'estimer les distances directement sur la carte nautique sans aucune conversion.",
121
+ },
122
+ {
123
+ type: 'paragraph',
124
+ html: "Historiquement, les marins mesuraient la vitesse du navire en utilisant le <em>loch a bateau</em> : un morceau de bois triangulaire jete a la mer attache a une corde avec des noeuds equidistants tous les 14,4 metres. En comptant les noeuds passant entre leurs doigts en 30 secondes, ils obtenaient la vitesse du navire, donnant naissance au terme que nous utilisons aujourd'hui.",
125
+ },
126
+ {
127
+ type: 'title',
128
+ text: "L'Echelle Beaufort : L'Oeil du Marin",
129
+ level: 3,
130
+ },
131
+ {
132
+ type: 'paragraph',
133
+ html: "L'<strong>echelle Beaufort</strong> a ete developpee en 1805 par l'amiral britannique Sir Francis Beaufort comme methode empirique pour classifier la force du vent a partir d'effets observables, sans instrumentation. Initialement concue pour estimer la quantite de voilure qu'un navire pouvait porter, elle est aujourd'hui une reference universelle en meteorologie maritime, adoptee par l'Organisation Meteorologique Mondiale (OMM).",
134
+ },
135
+ {
136
+ type: 'paragraph',
137
+ html: "L'echelle va de la <strong>Force 0</strong> (calme absolu, mer comme un miroir) a la <strong>Force 12</strong> (conditions d'ouragan avec des vagues de plus de 14 metres et l'air plein d'ecume). Pour le plaisancier, les limites critiques sont generalement la Force 6 (vent frais, 22-27 noeuds) pour les embarcations legeres et la Force 7-8 pour les voiliers oceaniques.",
138
+ },
139
+ {
140
+ type: 'table',
141
+ headers: ['Force Beaufort', 'Denomination', 'Etat de la Mer'],
142
+ rows: [
143
+ ['0', 'Calme', 'Mer comme un miroir'],
144
+ ['3', 'Petite brise', 'Moutons epars, cretes commencant a se briser'],
145
+ ['6', 'Vent frais', 'Grandes vagues, ecume blanche, embruns possibles'],
146
+ ['9', 'Fort coup de vent', 'Tres hautes vagues, visibilite reduite par ecume'],
147
+ ['12', 'Ouragan', 'Air completement plein d\'ecume, mer totalement blanche'],
148
+ ],
149
+ },
150
+ {
151
+ type: 'title',
152
+ text: 'Glossaire Nautique de Vitesse',
153
+ level: 3,
154
+ },
155
+ {
156
+ type: 'glossary',
157
+ items: [
158
+ { term: 'Noeud (kt)', definition: "Unite de vitesse equivalant a un mille nautique par heure (1852 m/h). Standard international en navigation maritime et aerienne." },
159
+ { term: 'SOG (Speed Over Ground)', definition: "Vitesse reelle du navire sur le fond marin, mesuree par GPS. Reflete l'effet des courants marins sur la vitesse du bateau." },
160
+ { term: 'Vitesse au Loch (STW)', definition: "Vitesse du bateau par rapport a l'eau environnante, mesuree par le loch. N'inclut pas les effets des courants ; essentielle pour calculer la derive." },
161
+ { term: 'Vent Apparent', definition: "Vent ressenti par l'equipage a bord, resultant de la somme vectorielle du vent reel et du vent cree par l'avancement du bateau. Differe toujours du vent reel en magnitude et direction." },
162
+ ],
163
+ },
164
+ {
165
+ type: 'title',
166
+ text: 'Formules de Conversion Rapides',
167
+ level: 3,
168
+ },
169
+ {
170
+ type: 'paragraph',
171
+ html: "Pour convertir les vitesses manuellement sans acces a un convertisseur numerique, utilisez ces approximations rapides que tout navigateur devrait connaitre par coeur :",
172
+ },
173
+ {
174
+ type: 'list',
175
+ items: [
176
+ '<strong>Noeuds en km/h :</strong> Multipliez les noeuds par 1,852. Approximation rapide : noeuds x 2, moins 8 %. Par exemple, 10 kt ≈ 20 - 1,6 = 18,4 km/h.',
177
+ '<strong>Noeuds en mph :</strong> Multipliez les noeuds par 1,15. Approximation : noeuds + 15 %. Par exemple, 20 kt ≈ 23 mph.',
178
+ '<strong>Noeuds en m/s :</strong> Multipliez les noeuds par 0,514. Approximation rapide : divisez les noeuds par 2. Par exemple, 10 kt ≈ 5 m/s (exact : 5,14 m/s).',
179
+ ],
180
+ },
181
+ {
182
+ type: 'tip',
183
+ title: 'Securite et Climatologie',
184
+ html: "Avant d'appareiller, consultez toujours le bulletin meteorologique maritime. Un Beaufort 4-5 est genable pour la plupart des bateaux de plaisance ; a partir de la Force 6, envisagez serieusement de reporter le depart si vous manquez d'experience ou si votre bateau n'est pas prepare a ces conditions.",
185
+ },
186
+ {
187
+ type: 'title',
188
+ text: 'Comparatif des Equipements',
189
+ level: 3,
190
+ },
191
+ {
192
+ type: 'comparative',
193
+ items: [
194
+ {
195
+ title: 'Anemometre Numerique',
196
+ description: 'Instrument electronique pour la mesure precise de la vitesse du vent.',
197
+ icon: 'mdi:speedometer',
198
+ points: [
199
+ 'Precision de ±2% dans des conditions normales',
200
+ 'Necessite une alimentation electrique ou des piles',
201
+ 'Peut tomber en panne dans des conditions extremes',
202
+ 'Lecture instantanee et enregistrement des donnees',
203
+ ],
204
+ },
205
+ {
206
+ title: 'Observation Beaufort',
207
+ description: 'Estimation visuelle de la force du vent par les effets observables en mer et sur terre.',
208
+ icon: 'mdi:eye-outline',
209
+ highlight: true,
210
+ points: [
211
+ "Ne necessite aucun instrument",
212
+ 'Disponible dans toutes les conditions',
213
+ 'Precision suffisante pour les decisions de securite',
214
+ 'Competence fondamentale du navigateur expert',
215
+ ],
216
+ },
217
+ ],
218
+ },
219
+ {
220
+ type: 'title',
221
+ text: 'Navigation et Planification de Route',
222
+ level: 3,
223
+ },
224
+ {
225
+ type: 'paragraph',
226
+ html: "Lors de la planification d'une route, la vitesse de votre embarcation determine les heures d'arrivee estimees (ETA) et la consommation de carburant pour les bateaux a moteur. Pour un voilier, la vitesse depend aussi de l'intensite et de la direction du vent : connaitre la force Beaufort prevue vous permet d'anticiper si vous pourrez naviguer a la voile ou si vous aurez besoin du moteur.",
227
+ },
228
+ {
229
+ type: 'paragraph',
230
+ html: "En regate, la conversion entre noeuds et metres par seconde est particulierement utile pour calculer le VMG (Velocity Made Good) et pour analyser les polaires de voile. Les meteorologues et les modeles numeriques de prevision donnent generalement le vent en m/s ou km/h, la conversion en noeuds est donc une etape habituelle dans la planification tactique.",
231
+ },
232
+ ];
233
+
234
+ const schemas: SpeedConverterLocaleContent['schemas'] = [
235
+ {
236
+ '@context': 'https://schema.org',
237
+ '@type': 'SoftwareApplication',
238
+ name: title,
239
+ description,
240
+ applicationCategory: 'UtilityApplication',
241
+ operatingSystem: 'Web',
242
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
243
+ } as WithContext<SoftwareApplication>,
244
+ {
245
+ '@context': 'https://schema.org',
246
+ '@type': 'FAQPage',
247
+ mainEntity: faq.map((item) => ({
248
+ '@type': 'Question',
249
+ name: item.question,
250
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
251
+ })),
252
+ } as WithContext<FAQPage>,
253
+ {
254
+ '@context': 'https://schema.org',
255
+ '@type': 'HowTo',
256
+ name: `Comment utiliser : ${title}`,
257
+ step: howTo.map((s) => ({ '@type': 'HowToStep', name: s.name, text: s.text })),
258
+ } as WithContext<HowTo>,
259
+ ];
260
+
261
+ export const content: SpeedConverterLocaleContent = {
262
+ slug,
263
+ title,
264
+ description,
265
+ ui,
266
+ seo,
267
+ faq,
268
+ bibliography,
269
+ howTo,
270
+ schemas,
271
+ };
@@ -0,0 +1,46 @@
1
+ import type { NauticalToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
2
+ import SpeedConverterComponent from './component.astro';
3
+ import SpeedConverterSEO from './seo.astro';
4
+ import SpeedConverterBibliography from './bibliography.astro';
5
+
6
+ export interface SpeedConverterUI {
7
+ [key: string]: string;
8
+ knLabel: string;
9
+ kmhLabel: string;
10
+ msLabel: string;
11
+ mphLabel: string;
12
+ beaufortTitle: string;
13
+ forceLabel: string;
14
+ descriptionLabel: string;
15
+ knotsLabel: string;
16
+ effectLabel: string;
17
+ seaStateLabel: string;
18
+ windEffectLabel: string;
19
+ faqTitle: string;
20
+ bibliographyTitle: string;
21
+ beaufortDataJson: string;
22
+ }
23
+
24
+ export type SpeedConverterLocaleContent = ToolLocaleContent<SpeedConverterUI>;
25
+
26
+ export const speedConverter: NauticalToolEntry<SpeedConverterUI> = {
27
+ id: 'speed-converter',
28
+ icons: {
29
+ bg: 'mdi:speedometer',
30
+ fg: 'mdi:weather-windy',
31
+ },
32
+ i18n: {
33
+ es: () => import('./i18n/es').then((m) => m.content),
34
+ en: () => import('./i18n/en').then((m) => m.content),
35
+ fr: () => import('./i18n/fr').then((m) => m.content),
36
+ },
37
+ };
38
+
39
+ export { SpeedConverterComponent, SpeedConverterSEO, SpeedConverterBibliography };
40
+
41
+ export const SPEED_CONVERTER_TOOL: ToolDefinition = {
42
+ entry: speedConverter,
43
+ Component: SpeedConverterComponent,
44
+ SEOComponent: SpeedConverterSEO,
45
+ BibliographyComponent: SpeedConverterBibliography,
46
+ };
@@ -0,0 +1,48 @@
1
+ export interface SpeedResult {
2
+ kn: number;
3
+ kmh: number;
4
+ ms: number;
5
+ mph: number;
6
+ }
7
+
8
+ export interface BeaufortEntry {
9
+ force: number;
10
+ name: string;
11
+ sea: string;
12
+ effect: string;
13
+ knMin: number;
14
+ knMax: number;
15
+ }
16
+
17
+ export const BEAUFORT_SCALE: BeaufortEntry[] = [
18
+ { force: 0, name: 'Calm', sea: 'Sea like a mirror', effect: 'Smoke rises vertically', knMin: 0, knMax: 1 },
19
+ { force: 1, name: 'Light air', sea: 'Ripples with appearance of scales', effect: 'Direction shown by smoke drift', knMin: 1, knMax: 4 },
20
+ { force: 2, name: 'Light breeze', sea: 'Small wavelets, crests not breaking', effect: 'Wind felt on face', knMin: 4, knMax: 7 },
21
+ { force: 3, name: 'Gentle breeze', sea: 'Large wavelets, scattered whitecaps', effect: 'Leaves and twigs in motion', knMin: 7, knMax: 11 },
22
+ { force: 4, name: 'Moderate breeze', sea: 'Small waves, frequent whitecaps', effect: 'Dust and loose paper raised', knMin: 11, knMax: 17 },
23
+ { force: 5, name: 'Fresh breeze', sea: 'Moderate waves, many whitecaps', effect: 'Small trees begin to sway', knMin: 17, knMax: 22 },
24
+ { force: 6, name: 'Strong breeze', sea: 'Large waves, white foam crests', effect: 'Difficulty using umbrella', knMin: 22, knMax: 28 },
25
+ { force: 7, name: 'Near gale', sea: 'Sea heaps up, foam blown in streaks', effect: 'Difficult to walk', knMin: 28, knMax: 34 },
26
+ { force: 8, name: 'Gale', sea: 'High waves, spray reduces visibility', effect: 'Great difficulty walking', knMin: 34, knMax: 41 },
27
+ { force: 9, name: 'Strong gale', sea: 'Very high waves, sea rolls', effect: 'Structural damage occurs', knMin: 41, knMax: 48 },
28
+ { force: 10, name: 'Storm', sea: 'Exceptionally high waves, sea white', effect: 'Trees uprooted', knMin: 48, knMax: 56 },
29
+ { force: 11, name: 'Violent storm', sea: 'Huge waves, foam covers sea', effect: 'Widespread damage', knMin: 56, knMax: 64 },
30
+ { force: 12, name: 'Hurricane', sea: 'Air filled with foam and spray', effect: 'Total catastrophe', knMin: 64, knMax: Infinity },
31
+ ];
32
+
33
+ export function convertFromKnots(kn: number): SpeedResult {
34
+ return {
35
+ kn,
36
+ kmh: kn * 1.852,
37
+ ms: kn * 0.514444,
38
+ mph: kn * 1.15078,
39
+ };
40
+ }
41
+
42
+ export function getBeaufortForce(kn: number): number {
43
+ const thresholds = [1, 4, 7, 11, 17, 22, 28, 34, 41, 48, 56, 64];
44
+ for (let i = 0; i < thresholds.length; i++) {
45
+ if (kn < (thresholds[i] ?? Infinity)) return i;
46
+ }
47
+ return 12;
48
+ }
@@ -0,0 +1,43 @@
1
+ ---
2
+ import {
3
+ SEOTitle,
4
+ SEOTable,
5
+ SEOTip,
6
+ SEOGlossary,
7
+ SEOStats,
8
+ SEOComparative,
9
+ SEOList,
10
+ SEOArticle,
11
+ } from '@jjlmoya/utils-shared';
12
+ import { speedConverter } from './index';
13
+ import type { KnownLocale } from '../../types';
14
+
15
+ interface Props {
16
+ locale?: KnownLocale;
17
+ }
18
+
19
+ const { locale = 'es' } = Astro.props;
20
+ const content = await speedConverter.i18n[locale]?.();
21
+ if (!content) return null;
22
+ const { seo } = content;
23
+ ---
24
+
25
+ <SEOArticle>
26
+ {seo.map((section: any) => {
27
+ switch (section.type) {
28
+ case 'title': return <SEOTitle title={section.text} level={section.level || 2} />;
29
+ case 'paragraph': return <p set:html={section.html} />;
30
+ case 'tip': return <SEOTip title={section.title}><Fragment set:html={section.html} /></SEOTip>;
31
+ case 'table': return (
32
+ <SEOTable headers={section.headers}>
33
+ {section.rows.map((row: string[]) => <tr>{row.map((cell: string) => <td set:html={cell} />)}</tr>)}
34
+ </SEOTable>
35
+ );
36
+ case 'glossary': return <SEOGlossary items={section.items} />;
37
+ case 'stats': return <SEOStats stats={section.items} columns={section.columns} />;
38
+ case 'comparative': return <SEOComparative items={section.items} columns={section.columns} />;
39
+ case 'list': return <SEOList items={section.items} icon={section.icon} />;
40
+ default: return null;
41
+ }
42
+ })}
43
+ </SEOArticle>
@@ -0,0 +1,239 @@
1
+ .speed-converter {
2
+ --n-bg: #fff;
3
+ --n-bg-muted: #f1f5f9;
4
+ --n-text: #0f172a;
5
+ --n-text-muted: #475569;
6
+ --n-text-dim: #64748b;
7
+ --n-border: #e2e8f0;
8
+ --n-shadow: rgba(0, 0, 0, 0.05);
9
+ --n-primary: #3b82f6;
10
+ --n-primary-on: #fff;
11
+ --n-accent: #6366f1;
12
+ --n-cyan: #0891b2;
13
+ --n-success: #10b981;
14
+ --n-warning: #f59e0b;
15
+ --n-error: #f43f5e;
16
+
17
+ max-width: 900px;
18
+ margin: 0 auto;
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 2rem;
22
+ }
23
+
24
+ .theme-dark .speed-converter {
25
+ --n-bg: #0f172a;
26
+ --n-bg-muted: #1e293b;
27
+ --n-text: #f8fafc;
28
+ --n-text-muted: #94a3b8;
29
+ --n-text-dim: #64748b;
30
+ --n-border: #334155;
31
+ --n-shadow: rgba(0, 0, 0, 0.3);
32
+ --n-primary: #60a5fa;
33
+ --n-primary-on: #fff;
34
+ --n-accent: #818cf8;
35
+ --n-cyan: #22d3ee;
36
+ --n-success: #34d399;
37
+ --n-warning: #fbbf24;
38
+ --n-error: #fb7185;
39
+ }
40
+
41
+ .sc-input-grid {
42
+ display: grid;
43
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
44
+ gap: 1rem;
45
+ background: var(--n-bg);
46
+ border: 1px solid var(--n-border);
47
+ border-radius: 1.5rem;
48
+ padding: 2rem;
49
+ }
50
+
51
+ .sc-input-group {
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 0.5rem;
55
+ }
56
+
57
+ .sc-input-group label {
58
+ font-size: 0.8rem;
59
+ font-weight: 600;
60
+ color: var(--n-text-muted);
61
+ text-transform: uppercase;
62
+ }
63
+
64
+ .sc-input-wrapper {
65
+ background: var(--n-bg-muted);
66
+ border: 2px solid var(--n-border);
67
+ border-radius: 0.8rem;
68
+ padding: 0.8rem 1rem;
69
+ transition: border-color 0.2s;
70
+ }
71
+
72
+ .sc-input-wrapper:focus-within {
73
+ border-color: var(--n-cyan);
74
+ }
75
+
76
+ .sc-input-wrapper input {
77
+ background: transparent;
78
+ border: none;
79
+ outline: none;
80
+ color: var(--n-text);
81
+ font-size: 1.5rem;
82
+ font-weight: 800;
83
+ width: 100%;
84
+ }
85
+
86
+ .sc-input-wrapper input::-webkit-outer-spin-button,
87
+ .sc-input-wrapper input::-webkit-inner-spin-button {
88
+ appearance: none;
89
+ }
90
+
91
+ .sc-beaufort-summary {
92
+ background: var(--n-primary);
93
+ color: var(--n-primary-on);
94
+ border-radius: 1.5rem;
95
+ padding: 2rem;
96
+ display: grid;
97
+ grid-template-columns: auto 1fr;
98
+ gap: 1rem 2rem;
99
+ align-items: center;
100
+ }
101
+
102
+ .sc-force-circle {
103
+ width: 80px;
104
+ height: 80px;
105
+ border-radius: 50%;
106
+ background: rgba(255, 255, 255, 0.15);
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ font-size: 2.5rem;
111
+ font-weight: 800;
112
+ color: var(--n-primary-on);
113
+ flex-shrink: 0;
114
+ }
115
+
116
+ .sc-force-text-box {
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: 0.3rem;
120
+ }
121
+
122
+ .sc-force-name {
123
+ margin: 0;
124
+ font-size: 1.4rem;
125
+ font-weight: 800;
126
+ color: var(--n-primary-on);
127
+ }
128
+
129
+ .sc-force-detail {
130
+ margin: 0;
131
+ opacity: 0.8;
132
+ font-size: 0.9rem;
133
+ color: var(--n-primary-on);
134
+ }
135
+
136
+ .sc-sea-state-box {
137
+ grid-column: 1 / -1;
138
+ background: rgba(255, 255, 255, 0.1);
139
+ border-radius: 1rem;
140
+ padding: 1rem 1.5rem;
141
+ }
142
+
143
+ .sc-sea-text .label {
144
+ font-size: 0.7rem;
145
+ text-transform: uppercase;
146
+ opacity: 0.7;
147
+ color: var(--n-primary-on);
148
+ font-weight: 700;
149
+ }
150
+
151
+ .sc-sea-text p {
152
+ margin: 0.2rem 0 0;
153
+ font-weight: 600;
154
+ color: var(--n-primary-on);
155
+ }
156
+
157
+ .sc-table-container {
158
+ background: var(--n-bg);
159
+ border: 1px solid var(--n-border);
160
+ border-radius: 1.5rem;
161
+ overflow: hidden;
162
+ }
163
+
164
+ .sc-table-container h3 {
165
+ padding: 1.5rem;
166
+ margin: 0;
167
+ font-size: 1rem;
168
+ color: var(--n-text-muted);
169
+ border-bottom: 1px solid var(--n-border);
170
+ font-weight: 700;
171
+ }
172
+
173
+ .sc-scroll-area {
174
+ overflow-x: auto;
175
+ }
176
+
177
+ .sc-beaufort-table {
178
+ width: 100%;
179
+ border-collapse: collapse;
180
+ }
181
+
182
+ .sc-beaufort-table th {
183
+ text-align: left;
184
+ padding: 0.8rem 1rem;
185
+ font-size: 0.75rem;
186
+ color: var(--n-text-muted);
187
+ text-transform: uppercase;
188
+ border-bottom: 2px solid var(--n-border);
189
+ }
190
+
191
+ .sc-beaufort-table td {
192
+ padding: 0.8rem 1rem;
193
+ font-size: 0.9rem;
194
+ color: var(--n-text-muted);
195
+ border-bottom: 1px solid var(--n-border);
196
+ }
197
+
198
+ .sc-clickable-row {
199
+ cursor: pointer;
200
+ transition: background 0.15s;
201
+ }
202
+
203
+ .sc-clickable-row:hover {
204
+ background: var(--n-bg-muted);
205
+ }
206
+
207
+ .sc-clickable-row.active {
208
+ background: var(--n-primary);
209
+ }
210
+
211
+ .sc-clickable-row.active td {
212
+ color: var(--n-primary-on);
213
+ }
214
+
215
+ .sc-force-tag {
216
+ background: var(--n-bg-muted);
217
+ padding: 0.2rem 0.5rem;
218
+ border-radius: 0.4rem;
219
+ font-weight: 700;
220
+ font-size: 0.85rem;
221
+ color: var(--n-text);
222
+ }
223
+
224
+ .sc-clickable-row.active .sc-force-tag {
225
+ background: rgba(255, 255, 255, 0.2);
226
+ color: var(--n-primary-on);
227
+ }
228
+
229
+ @media (max-width: 640px) {
230
+ .sc-beaufort-summary {
231
+ grid-template-columns: 1fr;
232
+ }
233
+
234
+ .sc-force-circle {
235
+ width: 60px;
236
+ height: 60px;
237
+ font-size: 2rem;
238
+ }
239
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { tideCalculator } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await tideCalculator.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} title={content.ui.bibliographyTitle} />}