@jjlmoya/utils-drones 1.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.
Files changed (56) hide show
  1. package/package.json +61 -0
  2. package/src/category/i18n/en.ts +107 -0
  3. package/src/category/i18n/es.ts +106 -0
  4. package/src/category/i18n/fr.ts +107 -0
  5. package/src/category/index.ts +15 -0
  6. package/src/category/seo.astro +92 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +22 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/seo_length.test.ts +22 -0
  21. package/src/tests/seo_section_types.test.ts +75 -0
  22. package/src/tests/tool_validation.test.ts +17 -0
  23. package/src/tool/antenna-length-calculator/AntennaLengthCalculator.css +684 -0
  24. package/src/tool/antenna-length-calculator/bibliography.astro +14 -0
  25. package/src/tool/antenna-length-calculator/component.astro +360 -0
  26. package/src/tool/antenna-length-calculator/i18n/en.ts +204 -0
  27. package/src/tool/antenna-length-calculator/i18n/es.ts +204 -0
  28. package/src/tool/antenna-length-calculator/i18n/fr.ts +204 -0
  29. package/src/tool/antenna-length-calculator/index.ts +27 -0
  30. package/src/tool/antenna-length-calculator/seo.astro +39 -0
  31. package/src/tool/drone-flight-time/FlightTimeCalculator.css +363 -0
  32. package/src/tool/drone-flight-time/bibliography.astro +14 -0
  33. package/src/tool/drone-flight-time/component.astro +262 -0
  34. package/src/tool/drone-flight-time/components/AutonomyChart.astro +13 -0
  35. package/src/tool/drone-flight-time/components/BatterySpecs.astro +46 -0
  36. package/src/tool/drone-flight-time/components/ConsumptionStats.astro +33 -0
  37. package/src/tool/drone-flight-time/components/FlightDashboard.astro +33 -0
  38. package/src/tool/drone-flight-time/i18n/en.ts +200 -0
  39. package/src/tool/drone-flight-time/i18n/es.ts +200 -0
  40. package/src/tool/drone-flight-time/i18n/fr.ts +200 -0
  41. package/src/tool/drone-flight-time/index.ts +27 -0
  42. package/src/tool/drone-flight-time/seo.astro +31 -0
  43. package/src/tool/gps-coordinates-converter/GpsCoordinatesConverter.css +310 -0
  44. package/src/tool/gps-coordinates-converter/bibliography.astro +14 -0
  45. package/src/tool/gps-coordinates-converter/component.astro +355 -0
  46. package/src/tool/gps-coordinates-converter/components/GpsHistory.astro +36 -0
  47. package/src/tool/gps-coordinates-converter/components/GpsInputs.astro +92 -0
  48. package/src/tool/gps-coordinates-converter/components/GpsMap.astro +18 -0
  49. package/src/tool/gps-coordinates-converter/components/GpsResults.astro +50 -0
  50. package/src/tool/gps-coordinates-converter/i18n/en.ts +201 -0
  51. package/src/tool/gps-coordinates-converter/i18n/es.ts +201 -0
  52. package/src/tool/gps-coordinates-converter/i18n/fr.ts +201 -0
  53. package/src/tool/gps-coordinates-converter/index.ts +27 -0
  54. package/src/tool/gps-coordinates-converter/seo.astro +39 -0
  55. package/src/tools.ts +16 -0
  56. package/src/types.ts +72 -0
@@ -0,0 +1,200 @@
1
+ import type { DroneFlightTimeLocaleContent } from '../index';
2
+
3
+ const slug = 'calculateur-temps-vol-drone';
4
+ const title = 'Calculateur de Temps de Vol Drone | Estimation Autonomie LiPo/Li-Ion';
5
+ const description = 'Calculez combien de temps votre drone peut voler en fonction de la capacité mAh et de la consommation. Optimisez vos batteries LiPo pour des vols sûrs.';
6
+
7
+ export const content: DroneFlightTimeLocaleContent = {
8
+ slug,
9
+ title,
10
+ description,
11
+ faqTitle: 'Foire Aux Questions',
12
+ bibliographyTitle: 'Références Bibliographiques',
13
+ ui: {
14
+ faqTitle: 'Foire Aux Questions',
15
+ bibliographyTitle: 'Références Bibliographiques',
16
+ batterySpecs: 'Spécifications de la Batterie',
17
+ capacity: 'Capacité',
18
+ voltage: 'Tension (Cellules S)',
19
+ safetyMargin: 'Marge de Sécurité',
20
+ landingHint: 'Atterrir à 3,5V - 3,7V par cellule.',
21
+ consumptionDynamics: 'Dynamique de Consommation',
22
+ averageConsumption: 'Consommation Moyenne',
23
+ powerWatts: 'Puissance (Watts)',
24
+ efficiencyHint: 'En changeant les Ampères, les Watts sont recalculés selon la tension S.',
25
+ estimatedEfficiency: 'Efficacité Estimée',
26
+ typicalEfficiencyHint: 'Typique : 4-6 (Racing), 8-12 (Cinématique/Longue Portée).',
27
+ safeFlight: 'Vol Sécurisé',
28
+ totalEnergy: 'Énergie Totale',
29
+ theoreticalTime: 'Temps Théorique (0%)',
30
+ co2Footprint: 'Empreinte CO2',
31
+ autonomyChart: 'Graphique d\'Autonomie',
32
+ chartSubtitle: 'Ampères (A) vs Temps (min)',
33
+ chartLabel: 'Minutes',
34
+ },
35
+ seo: [
36
+ {
37
+ type: 'title',
38
+ text: 'Calculateur de Temps de Vol pour Drones : Guide Complet de l\'Autonomie',
39
+ level: 2,
40
+ },
41
+ {
42
+ type: 'paragraph',
43
+ html: 'L\'autonomie est probablement le facteur le plus critique dans la conception et l\'utilisation de tout aéronef sans pilote. Que vous soyez un pilote de drone FPV de course, un professionnel de la photographie aérienne ou un passionné de vols longue distance, savoir avec précision combien de temps votre équipement peut rester en l\'air est vital pour la sécurité et le succès de la mission. Notre <strong>calculateur de temps de vol</strong> utilise les variables fondamentales de capacité de batterie et de consommation de courant pour vous offrir une estimation réaliste et sûre.',
44
+ },
45
+ {
46
+ type: 'title',
47
+ text: 'Capacité de la Batterie : Les mAh Expliqués',
48
+ level: 3,
49
+ },
50
+ {
51
+ type: 'paragraph',
52
+ html: 'La capacité d\'une batterie est généralement mesurée en milliampères-heures (mAh). Ce chiffre nous indique quelle charge électrique la batterie est capable de stocker. Par exemple, une batterie de 1500 mAh peut théoriquement fournir 1500 milliampères pendant une heure complète. Dans le monde des drones, où les consommations sont extrêmement élevées, nous parlons souvent en Ampères (A). 1000 mAh équivalent exactement à 1 Ah (Ampère-heure).',
53
+ },
54
+ {
55
+ type: 'paragraph',
56
+ html: 'Cependant, la capacité brute n\'est pas le seul facteur. La tension (déterminée par le nombre de cellules ou \'S\') influence directement la puissance totale (Watts), mais pour le calcul du temps basé sur la consommation des moteurs, le rapport Ah/Ampères est la mesure la plus directe utilisée par les ingénieurs de vol.',
57
+ },
58
+ {
59
+ type: 'title',
60
+ text: 'La Consommation de Courant : Ampérage en Vol',
61
+ level: 3,
62
+ },
63
+ {
64
+ type: 'paragraph',
65
+ html: 'La consommation des moteurs est la variable qui fluctue le plus pendant un vol. Maintenir un drone en vol stationnaire (hover) n\'est pas la même chose que réaliser des manœuvres acrobatiques agressives. Chaque ensemble moteur et hélice a une courbe d\'efficacité. Lorsque vous accélérez au maximum, l\'ampérage monte en flèche, réduisant drastiquement la durée de vie de la batterie.',
66
+ },
67
+ {
68
+ type: 'list',
69
+ items: [
70
+ 'Vol Stationnaire : La consommation est minimale et constante, idéale pour la photographie.',
71
+ 'Vol de Croisière : La consommation augmente légèrement en raison de la traînée aérodynamique.',
72
+ 'Vol Agressif/FPV : Les pics de courant peuvent tripler la consommation moyenne en quelques secondes.',
73
+ 'Poids du Drone : Chaque gramme supplémentaire nécessite plus de tours moteur pour générer de la poussée, augmentant l\'ampérage.',
74
+ ],
75
+ },
76
+ {
77
+ type: 'title',
78
+ text: 'Règle de Sécurité des 80% : Protéger la Chimie LiPo',
79
+ level: 3,
80
+ },
81
+ {
82
+ type: 'paragraph',
83
+ html: 'Décharger une batterie LiPo (Lithium Polymère) jusqu\'à 0% de sa capacité est le moyen le plus rapide de la détruire et, pire encore, de provoquer un accident. Chimiquement, les cellules subissent des dommages irréversibles si leur tension descend en dessous d\'un seuil critique (généralement 3,0V - 3,2V par cellule).',
84
+ },
85
+ {
86
+ type: 'paragraph',
87
+ html: 'C\'est pourquoi nous appliquons toujours une <strong>règle de marge de sécurité</strong>. Notre calculateur permet d\'ajuster cette valeur, mais il est recommandé d\'atterrir lorsqu\'il reste encore 20% de charge. Cela prolonge non seulement la durée de vie de vos batteries coûteuses de plusieurs centaines de cycles, mais vous garantit également une réserve de puissance vitale en cas de rafales de vent inattendues ou si vous devez avorter l\'atterrissage et réessayer.',
88
+ },
89
+ {
90
+ type: 'tip',
91
+ html: 'Les batteries de drones sont très sensibles au froid. En hiver, la résistance interne de la LiPo augmente, ce qui provoque une chute de tension plus rapide. Préchauffez toujours vos batteries avant de décoller si la température ambiante est inférieure à 15 degrés.',
92
+ },
93
+ {
94
+ type: 'title',
95
+ text: 'Formule Mathématique du Vol',
96
+ level: 3,
97
+ },
98
+ {
99
+ type: 'paragraph',
100
+ html: 'Bien que notre outil fasse le travail difficile pour vous, il est intéressant de connaître la logique derrière le calcul. La formule de base est :',
101
+ },
102
+ {
103
+ type: 'paragraph',
104
+ html: '<strong>Temps (min) = ((Capacité mAh / 1000) * Facteur de Sécurité) / Consommation Ampères * 60</strong>',
105
+ },
106
+ {
107
+ type: 'paragraph',
108
+ html: 'Par exemple, si vous avez une batterie de 2200 mAh, que vous voulez atterrir à 20% (sécurité 0,8) et que votre drone consomme une moyenne de 15 Ampères, le calcul serait : (2,2 * 0,8) / 15 * 60 = 7,04 minutes de vol sûr.',
109
+ },
110
+ {
111
+ type: 'title',
112
+ text: 'Optimisation du Poids et Efficacité',
113
+ level: 3,
114
+ },
115
+ {
116
+ type: 'paragraph',
117
+ html: 'Il existe un point de rendement décroissant lors de l\'ajout de batteries plus grandes. Doubler la capacité de la batterie ne double pas le temps de vol, car la batterie elle-même ajoute du poids. Ce poids supplémentaire nécessite que les moteurs tournent plus vite et consomment donc plus de courant. À un certain point, le poids additionnel consomme plus d\'énergie qu\'il n\'en apporte, réduisant l\'efficacité globale du système.',
118
+ },
119
+ {
120
+ type: 'paragraph',
121
+ html: 'Les pilotes expérimentés recherchent l\'équilibre parfait entre le <em>Disc Loading</em> (charge du disque des hélices) et la capacité de la batterie pour maximiser ce que nous appelons le "temps de mission utile".',
122
+ },
123
+ {
124
+ type: 'title',
125
+ text: 'Différences entre les Types de Drones',
126
+ level: 3,
127
+ },
128
+ {
129
+ type: 'paragraph',
130
+ html: '<strong>Micro Drones (Whoops) :</strong> Consomment à peine 2-5 Ampères, mais leurs batteries sont de 300-500 mAh. Le temps est souvent court (3-4 min) en raison de la faible inertie et de la rotation élevée.',
131
+ },
132
+ {
133
+ type: 'paragraph',
134
+ html: '<strong>Drones de Course 5" :</strong> Consommations brutales en course (jusqu\'à 120A en pointe), ce qui épuise une batterie de 1300 mAh en à peine 2 minutes d\'adrénaline pure.',
135
+ },
136
+ {
137
+ type: 'paragraph',
138
+ html: '<strong>Drones Long Range :</strong> Optimisés pour le GPS et le vol efficace. Ils utilisent des cellules Lithium-Ion (Li-Ion) qui ont une densité énergétique plus élevée que les LiPo, permettant des vols de 30 à 60 minutes avec des ampérages très contenus.',
139
+ },
140
+ {
141
+ type: 'tip',
142
+ html: 'Passer à des hélices avec un pas (pitch) plus court peut augmenter votre temps de vol au détriment de la vitesse de pointe et de la réactivité. C\'est le réglage le plus économique et efficace pour gagner 10-15% d\'autonomie.',
143
+ },
144
+ {
145
+ type: 'title',
146
+ text: 'Entretien et Stockage',
147
+ level: 3,
148
+ },
149
+ {
150
+ type: 'paragraph',
151
+ html: 'Pour que les calculs de cet outil soient précis, vos batteries doivent être en bon état. Une batterie avec une résistance interne élevée chauffera excessivement et "mentira" sur sa capacité réelle. Stockez toujours vos batteries à la tension de stockage (3,8V-3,85V par cellule) si vous ne comptez pas voler pendant plus de 48 heures.',
152
+ },
153
+ {
154
+ type: 'paragraph',
155
+ html: 'En conclusion, la gestion de l\'énergie est l\'art d\'équilibrer la physique, la chimie et les mathématiques. Utilisez notre calculateur régulièrement pour planifier vos sessions de vol et n\'oubliez jamais qu\'en l\'air, le temps est la ressource la plus précieuse. Bons vols et atterrissages en toute sécurité !',
156
+ },
157
+ ],
158
+ faq: [
159
+ {
160
+ question: 'Pourquoi le temps réel est-il inférieur au temps calculé ?',
161
+ answer: 'Le calculateur estime une consommation constante. Les manœuvres brusques, le vent de face et l\'usure de la batterie peuvent réduire le temps réel jusqu\'à 30%.',
162
+ },
163
+ {
164
+ question: 'À quelle tension dois-je faire atterrir mon drone ?',
165
+ answer: 'L\'idéal est d\'atterrir lorsque la tension descend à 3,5V - 3,6V par cellule (au repos). Cela équivaut environ aux 20% de capacité restante recommandés.',
166
+ },
167
+ {
168
+ question: 'Les batteries LiPo ou Li-Ion sont-elles meilleures pour les drones ?',
169
+ answer: 'Les LiPo offrent beaucoup de puissance instantanée (idéal pour la course et les acrobaties). Les Li-Ion ont plus d\'endurance mais moins de puissance (idéal pour les vols longs et tranquilles).',
170
+ },
171
+ {
172
+ question: 'Comment le nombre de cellules (S) affecte-t-il le temps de vol ?',
173
+ answer: 'Plus de cellules augmentent la tension et la puissance, mais aussi le poids. Si les moteurs sont optimisés pour cette tension, ils peuvent être plus efficaces, mais cela ne garantit pas plus de temps en soi.',
174
+ },
175
+ ],
176
+ bibliography: [
177
+ { name: 'EASA - Réglementation des Drones', url: 'https://www.easa.europa.eu/en/domains/civil-drones' },
178
+ { name: 'ArduPilot Wiki', url: 'https://ardupilot.org/copter/' },
179
+ { name: 'Battery University', url: 'https://batteryuniversity.com/' },
180
+ ],
181
+ howTo: [
182
+ {
183
+ name: 'Identifiez la capacité',
184
+ text: 'Regardez l\'étiquette de votre batterie et cherchez la valeur en mAh (ex. 1500, 2200, 4500).',
185
+ },
186
+ {
187
+ name: 'Estimez la consommation',
188
+ text: 'Entrez l\'ampérage moyen consommé par votre drone. Vous pouvez le voir dans la télémétrie OSD après un vol d\'essai.',
189
+ },
190
+ {
191
+ name: 'Ajustez la marge',
192
+ text: 'Nous recommandons de laisser 20% (régler à 80%) pour protéger la batterie et avoir une marge d\'atterrissage.',
193
+ },
194
+ {
195
+ name: 'Obtenez le résultat',
196
+ text: 'Visualisez le temps exact en minutes et secondes pendant lequel vous pouvez rester en l\'air en toute sécurité.',
197
+ },
198
+ ],
199
+ schemas: [],
200
+ };
@@ -0,0 +1,27 @@
1
+ import type { DronesToolEntry, ToolDefinition, ToolLocaleContent } from '../../types';
2
+ import DroneFlightTimeComponent from './component.astro';
3
+ import DroneFlightTimeSEO from './seo.astro';
4
+ import DroneFlightTimeBibliography from './bibliography.astro';
5
+
6
+ export interface DroneFlightTimeUI {
7
+ [key: string]: string;
8
+ }
9
+
10
+ export type DroneFlightTimeLocaleContent = ToolLocaleContent<DroneFlightTimeUI>;
11
+
12
+ export const droneFlightTime: DronesToolEntry<DroneFlightTimeUI> = {
13
+ id: 'drone-flight-time',
14
+ icons: { bg: 'mdi:drone', fg: 'mdi:timer-sand' },
15
+ i18n: {
16
+ es: () => import('./i18n/es').then((m) => m.content),
17
+ en: () => import('./i18n/en').then((m) => m.content),
18
+ fr: () => import('./i18n/fr').then((m) => m.content),
19
+ },
20
+ };
21
+
22
+ export const DRONE_FLIGHT_TIME_TOOL: ToolDefinition = {
23
+ entry: droneFlightTime,
24
+ Component: DroneFlightTimeComponent,
25
+ SEOComponent: DroneFlightTimeSEO,
26
+ BibliographyComponent: DroneFlightTimeBibliography,
27
+ };
@@ -0,0 +1,31 @@
1
+ ---
2
+ import { SEOArticle, SEOTitle, SEOList, SEOTip } from '@jjlmoya/utils-shared';
3
+ import { droneFlightTime } 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 droneFlightTime.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ <SEOArticle>
16
+ {content.seo.map((section: any) => {
17
+ if (section.type === 'title') {
18
+ return <SEOTitle title={section.text} level={section.level || 2} />;
19
+ }
20
+ if (section.type === 'paragraph') {
21
+ return <p set:html={section.html} />;
22
+ }
23
+ if (section.type === 'list') {
24
+ return <SEOList items={section.items} />;
25
+ }
26
+ if (section.type === 'tip') {
27
+ return <SEOTip title="Consejos">{section.html}</SEOTip>;
28
+ }
29
+ return null;
30
+ })}
31
+ </SEOArticle>
@@ -0,0 +1,310 @@
1
+ .gps-converter-ui {
2
+ width: 100%;
3
+ max-width: 1100px;
4
+ margin: 0 auto;
5
+ }
6
+
7
+ .tech-mega-card {
8
+ background: rgba(255, 255, 255, 0.7);
9
+ backdrop-filter: blur(20px);
10
+ border: 1px solid rgba(255, 255, 255, 0.4);
11
+ border-radius: 32px;
12
+ box-shadow: 0 30px 60px rgba(0, 0, 0, 0.08);
13
+ overflow: hidden;
14
+ }
15
+
16
+ :global(.dark) .tech-mega-card {
17
+ background: rgba(15, 23, 42, 0.7);
18
+ border-color: rgba(255, 255, 255, 0.05);
19
+ }
20
+
21
+ .card-grid {
22
+ display: grid;
23
+ grid-template-columns: 420px 1fr;
24
+ }
25
+
26
+ .config-sidebar {
27
+ padding: 2rem;
28
+ background: rgba(255, 255, 255, 0.3);
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 2rem;
32
+ border-right: 1px solid rgba(0, 0, 0, 0.05);
33
+ }
34
+
35
+ :global(.dark) .config-sidebar {
36
+ background: rgba(255, 255, 255, 0.02);
37
+ border-right-color: rgba(255, 255, 255, 0.05);
38
+ }
39
+
40
+ .main-display {
41
+ padding: 2rem;
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: 2rem;
45
+ }
46
+
47
+ .tech-section {
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: 1.5rem;
51
+ }
52
+
53
+ .section-title {
54
+ font-size: 0.9rem;
55
+ font-weight: 800;
56
+ color: #1e293b;
57
+ text-transform: uppercase;
58
+ letter-spacing: 0.05em;
59
+ margin: 0;
60
+ }
61
+
62
+ :global(.dark) .section-title {
63
+ color: #f1f5f9;
64
+ }
65
+
66
+ .divider {
67
+ height: 1px;
68
+ width: 100%;
69
+ background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.05), transparent);
70
+ }
71
+
72
+ :global(.dark) .divider {
73
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.05), transparent);
74
+ }
75
+
76
+ .compact-inputs {
77
+ display: grid;
78
+ grid-template-columns: 1fr 1fr;
79
+ gap: 1rem;
80
+ }
81
+
82
+ .input-block {
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: 0.5rem;
86
+ }
87
+
88
+ .field-label {
89
+ font-size: 0.75rem;
90
+ font-weight: 700;
91
+ color: #64748b;
92
+ text-transform: uppercase;
93
+ }
94
+
95
+ .tech-input {
96
+ width: 100%;
97
+ padding: 0.75rem;
98
+ background: white;
99
+ border: 1px solid #e2e8f0;
100
+ border-radius: 12px;
101
+ font-weight: 600;
102
+ color: #1e293b;
103
+ }
104
+
105
+ :global(.dark) .tech-input {
106
+ background: #1e293b;
107
+ border-color: #334155;
108
+ color: #f1f5f9;
109
+ }
110
+
111
+ .mode-switch {
112
+ display: flex;
113
+ background: #f1f5f9;
114
+ padding: 0.3rem;
115
+ border-radius: 14px;
116
+ }
117
+
118
+ :global(.dark) .mode-switch {
119
+ background: #1e293b;
120
+ }
121
+
122
+ .mode-btn {
123
+ flex: 1;
124
+ padding: 0.6rem;
125
+ border-radius: 10px;
126
+ border: none;
127
+ background: transparent;
128
+ font-size: 0.8rem;
129
+ font-weight: 700;
130
+ color: #64748b;
131
+ cursor: pointer;
132
+ transition: all 0.2s;
133
+ }
134
+
135
+ .mode-btn.active {
136
+ background: white;
137
+ color: #0ea5e9;
138
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
139
+ }
140
+
141
+ :global(.dark) .mode-btn.active {
142
+ background: #334155;
143
+ color: #38bdf8;
144
+ }
145
+
146
+ .map-container {
147
+ width: 100%;
148
+ height: 350px;
149
+ border-radius: 20px;
150
+ overflow: hidden;
151
+ background: #f1f5f9;
152
+ border: 1px solid rgba(0, 0, 0, 0.05);
153
+ }
154
+
155
+ .results-area {
156
+ display: grid;
157
+ grid-template-columns: 1fr 1fr;
158
+ gap: 1.5rem;
159
+ }
160
+
161
+ .result-box {
162
+ background: rgba(255, 255, 255, 0.5);
163
+ border: 1px solid rgba(0, 0, 0, 0.03);
164
+ border-radius: 20px;
165
+ padding: 1.5rem;
166
+ display: flex;
167
+ flex-direction: column;
168
+ gap: 0.75rem;
169
+ }
170
+
171
+ :global(.dark) .result-box {
172
+ background: rgba(255, 255, 255, 0.03);
173
+ border-color: rgba(255, 255, 255, 0.05);
174
+ }
175
+
176
+ .res-header {
177
+ display: flex;
178
+ justify-content: space-between;
179
+ align-items: center;
180
+ }
181
+
182
+ .res-label {
183
+ font-size: 0.7rem;
184
+ font-weight: 700;
185
+ color: #94a3b8;
186
+ text-transform: uppercase;
187
+ }
188
+
189
+ .copy-btn {
190
+ background: transparent;
191
+ border: 1px solid rgba(0, 0, 0, 0.1);
192
+ color: #64748b;
193
+ padding: 0.3rem 0.6rem;
194
+ border-radius: 8px;
195
+ font-size: 0.7rem;
196
+ font-weight: 700;
197
+ cursor: pointer;
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 0.4rem;
201
+ transition: all 0.2s;
202
+ }
203
+
204
+ .copy-btn:hover {
205
+ background: #0ea5e9;
206
+ color: white;
207
+ border-color: #0ea5e9;
208
+ }
209
+
210
+ .val-display {
211
+ font-size: 1.35rem;
212
+ font-weight: 800;
213
+ color: #1e293b;
214
+ }
215
+
216
+ :global(.dark) .val-display {
217
+ color: #f8fafc;
218
+ }
219
+
220
+ .history-list {
221
+ display: flex;
222
+ flex-direction: column;
223
+ gap: 0.5rem;
224
+ }
225
+
226
+ .history-item {
227
+ padding: 0.75rem 1rem;
228
+ background: rgba(255, 255, 255, 0.4);
229
+ border-radius: 12px;
230
+ display: flex;
231
+ justify-content: space-between;
232
+ align-items: center;
233
+ transition: all 0.2s;
234
+ }
235
+
236
+ :global(.dark) .history-item {
237
+ background: rgba(255, 255, 255, 0.02);
238
+ }
239
+
240
+ .history-item:hover {
241
+ background: rgba(0, 0, 0, 0.03);
242
+ }
243
+
244
+ .hist-info {
245
+ display: flex;
246
+ flex-direction: column;
247
+ gap: 0.1rem;
248
+ cursor: pointer;
249
+ flex: 1;
250
+ }
251
+
252
+ .hist-coords {
253
+ font-size: 0.85rem;
254
+ font-weight: 700;
255
+ color: #1e293b;
256
+ }
257
+
258
+ :global(.dark) .hist-coords {
259
+ color: #f1f5f9;
260
+ }
261
+
262
+ .hist-time {
263
+ font-size: 0.65rem;
264
+ color: #94a3b8;
265
+ text-transform: uppercase;
266
+ font-weight: 600;
267
+ }
268
+
269
+ .hist-actions {
270
+ display: flex;
271
+ gap: 0.5rem;
272
+ }
273
+
274
+ .action-btn {
275
+ background: transparent;
276
+ border: none;
277
+ padding: 0.4rem;
278
+ border-radius: 8px;
279
+ color: #94a3b8;
280
+ cursor: pointer;
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ transition: all 0.2s;
285
+ }
286
+
287
+ .load-btn:hover {
288
+ color: #0ea5e9;
289
+ background: #0ea5e910;
290
+ }
291
+
292
+ .delete-btn:hover {
293
+ color: #ef4444;
294
+ background: #ef444410;
295
+ }
296
+
297
+ @media (max-width: 992px) {
298
+ .card-grid {
299
+ grid-template-columns: 1fr;
300
+ }
301
+
302
+ .config-sidebar {
303
+ border-right: none;
304
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
305
+ }
306
+
307
+ .results-area {
308
+ grid-template-columns: 1fr;
309
+ }
310
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography } from '@jjlmoya/utils-shared';
3
+ import { gpsCoordinatesConverter } 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 gpsCoordinatesConverter.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <Bibliography links={content.bibliography} />}