@jjlmoya/utils-drones 1.4.0 → 1.6.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.
@@ -3,7 +3,6 @@ import GpsInputs from "./components/GpsInputs.astro";
3
3
  import GpsResults from "./components/GpsResults.astro";
4
4
  import GpsMap from "./components/GpsMap.astro";
5
5
  import GpsHistory from "./components/GpsHistory.astro";
6
- import "./GpsCoordinatesConverter.css";
7
6
 
8
7
  const { ui } = Astro.props;
9
8
  ---
@@ -31,246 +30,149 @@ const { ui } = Astro.props;
31
30
  </div>
32
31
  </div>
33
32
 
34
- <script>
35
- let mode = 'DD';
36
- let mapInstance = null;
37
- let L = null;
38
-
39
- const elements = {
40
- latDD: document.getElementById('latDD'),
41
- lngDD: document.getElementById('lngDD'),
42
- latG: document.getElementById('latG'),
43
- latM: document.getElementById('latM'),
44
- latS: document.getElementById('latS'),
45
- latH: document.getElementById('latH'),
46
- lngG: document.getElementById('lngG'),
47
- lngM: document.getElementById('lngM'),
48
- lngS: document.getElementById('lngS'),
49
- lngH: document.getElementById('lngH'),
50
- valDD: document.getElementById('valDD'),
51
- valGMS: document.getElementById('valGMS'),
52
- valDM: document.getElementById('valDM'),
53
- valMaps: document.getElementById('valMaps'),
54
- historyList: document.getElementById('historyList'),
55
- modeDD: document.getElementById('modeDD'),
56
- modeGMS: document.getElementById('modeGMS'),
57
- ddInputs: document.getElementById('dd-inputs'),
58
- gmsInputs: document.getElementById('gms-inputs'),
59
- btnMyLocation: document.getElementById('btnMyLocation'),
60
- };
61
- const container = document.querySelector('.gps-converter-ui');
62
- const uiLocal = {
63
- copied: container?.dataset.uiCopied || 'Copied',
64
- noHistory: container?.dataset.uiNoHistory || 'No history',
65
- load: container?.dataset.uiLoad || 'Load',
66
- delete: container?.dataset.uiDelete || 'Delete'
67
- };
68
-
69
- function init() {
70
- import('leaflet').then(module => {
71
- L = module.default;
72
- initMap();
73
- setupListeners();
74
- loadHistory();
75
- updateFromDD(40.4168, -3.7038);
76
- });
77
- }
78
-
79
- function initMap() {
80
- const mapContainer = document.getElementById('gpsMap');
81
- if (!mapContainer) return;
82
-
83
- mapInstance = L.map('gpsMap').setView([40.4168, -3.7038], 13);
84
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
85
- attribution: '© OpenStreetMap contributors',
86
- maxZoom: 19
87
- }).addTo(mapInstance);
88
-
89
- mapInstance.on('click', (e) => {
90
- updateFromDD(e.latlng.lat, e.latlng.lng);
91
- });
92
- }
33
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
34
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="" is:inline></script>
93
35
 
94
- function parseCoordValue(val) {
95
- return parseFloat(val.replace(',', '.'));
96
- }
97
-
98
- function setupModeListeners() {
99
- elements.modeDD?.addEventListener('click', () => switchMode('DD'));
100
- elements.modeGMS?.addEventListener('click', () => switchMode('GMS'));
36
+ <script is:inline>
37
+ function getHemisphere(val, isLat) {
38
+ if (val >= 0) return isLat ? 'N' : 'E';
39
+ return isLat ? 'S' : 'W';
101
40
  }
102
-
103
- function setupLocationListener() {
104
- elements.btnMyLocation?.addEventListener('click', () => {
105
- if ("geolocation" in navigator) {
106
- navigator.geolocation.getCurrentPosition((position) => {
107
- updateFromDD(position.coords.latitude, position.coords.longitude);
108
- });
109
- }
110
- });
41
+ function decimalToGMS(d, isLat) {
42
+ const h = getHemisphere(d, isLat);
43
+ const absD = Math.abs(d);
44
+ const g = Math.floor(absD);
45
+ const m = Math.floor((absD - g) * 60);
46
+ const s = ((absD - g) * 60 - m) * 60;
47
+ return {g, m, s, h, mDec: (absD - g) * 60};
111
48
  }
112
-
113
- function setupDDInputListeners() {
114
- [elements.latDD, elements.lngDD].forEach(el => {
115
- el?.addEventListener('input', () => {
116
- if (mode === 'DD') {
117
- const lat = parseCoordValue(elements.latDD.value);
118
- const lng = parseCoordValue(elements.lngDD.value);
119
- updateFromDD(lat, lng, false);
120
- }
121
- });
122
- });
49
+ function formatGMS(obj) {
50
+ return `${obj.g}° ${obj.m}' ${obj.s.toFixed(2)}" ${obj.h}`;
123
51
  }
124
-
125
- function setupGMSInputListeners() {
126
- const gmsElements = [elements.latG, elements.latM, elements.latS, elements.latH,
127
- elements.lngG, elements.lngM, elements.lngS, elements.lngH];
128
- gmsElements.forEach(el => {
129
- el?.addEventListener('input', () => {
130
- if (mode === 'GMS') {
131
- const lat = gmsToDecimal(
132
- parseFloat(elements.latG.value) || 0,
133
- parseFloat(elements.latM.value) || 0,
134
- parseFloat(elements.latS.value) || 0,
135
- elements.latH.value
136
- );
137
- const lng = gmsToDecimal(
138
- parseFloat(elements.lngG.value) || 0,
139
- parseFloat(elements.lngM.value) || 0,
140
- parseFloat(elements.lngS.value) || 0,
141
- elements.lngH.value
142
- );
143
- updateFromDD(lat, lng, false);
144
- }
145
- });
146
- });
52
+ function formatDM(obj) {
53
+ return `${obj.g}° ${obj.mDec.toFixed(3)}' ${obj.h}`;
147
54
  }
148
-
149
- function setupCopyListeners() {
150
- document.querySelectorAll('.copy-btn').forEach(btn => {
151
- btn.addEventListener('click', () => {
152
- const targetId = btn.getAttribute('data-copy');
153
- const text = document.getElementById(targetId)?.textContent;
154
- if (text) {
155
- navigator.clipboard.writeText(text);
156
- const original = btn.innerHTML;
157
- btn.innerHTML = uiLocal.copied;
158
- setTimeout(() => btn.innerHTML = original, 2000);
159
- }
160
- });
161
- });
55
+ function id(name) {
56
+ return document.getElementById(name);
162
57
  }
163
-
164
- function setupClearHistoryListener() {
165
- document.getElementById('clearHistory')?.addEventListener('click', () => {
166
- localStorage.removeItem('gps_history');
167
- loadHistory();
168
- });
58
+ const state={el:{}, ui:{}, mode:'DD', map:null, L:window.L};
59
+ function initElements() {
60
+ state.el = {
61
+ latDD:id('latDD'),lngDD:id('lngDD'),latG:id('latG'),latM:id('latM'),
62
+ latS:id('latS'),latH:id('latH'),lngG:id('lngG'),lngM:id('lngM'),
63
+ lngS:id('lngS'),lngH:id('lngH'),valDD:id('valDD'),valGMS:id('valGMS'),
64
+ valDM:id('valDM'),valMaps:id('valMaps'),historyList:id('historyList'),
65
+ modeDD:id('modeDD'),modeGMS:id('modeGMS'),ddInputs:id('dd-inputs'),
66
+ gmsInputs:id('gms-inputs'),btnMyLocation:id('btnMyLocation')
67
+ };
169
68
  }
170
-
171
- function setupListeners() {
172
- setupModeListeners();
173
- setupLocationListener();
174
- setupDDInputListeners();
175
- setupGMSInputListeners();
176
- setupCopyListeners();
177
- setupClearHistoryListener();
69
+ function getUIValue(attr, def) {
70
+ const ct = document.querySelector('.gps-converter-ui');
71
+ return ct?.dataset[attr] || def;
178
72
  }
179
-
180
- function setSwitchModeDisplay(isDD) {
181
- if (isDD) {
182
- if (elements.ddInputs) elements.ddInputs.style.display = 'grid';
183
- if (elements.gmsInputs) elements.gmsInputs.style.display = 'none';
184
- } else {
185
- if (elements.ddInputs) elements.ddInputs.style.display = 'none';
186
- if (elements.gmsInputs) elements.gmsInputs.style.display = 'flex';
187
- }
73
+ function initUI() {
74
+ state.ui = {
75
+ copied: getUIValue('uiCopied', 'Copied'),
76
+ noHistory: getUIValue('uiNoHistory', 'No history'),
77
+ load: getUIValue('uiLoad', 'Load'),
78
+ delete: getUIValue('uiDelete', 'Delete')
79
+ };
188
80
  }
189
-
190
- function setSwitchModeActive(isDD) {
191
- if (isDD) {
192
- elements.modeDD?.classList.add('active');
193
- elements.modeGMS?.classList.remove('active');
194
- } else {
195
- elements.modeDD?.classList.remove('active');
196
- elements.modeGMS?.classList.add('active');
197
- }
81
+ function initState() {
82
+ initElements();
83
+ initUI();
198
84
  }
199
-
200
- function switchMode(newMode) {
201
- mode = newMode;
202
- const isDD = mode === 'DD';
203
- setSwitchModeActive(isDD);
204
- setSwitchModeDisplay(isDD);
85
+ function setLatGMS(gms) {
86
+ if (state.el.latG) state.el.latG.value = gms.g.toString();
87
+ if (state.el.latM) state.el.latM.value = gms.m.toString();
88
+ if (state.el.latS) state.el.latS.value = gms.s.toFixed(2);
89
+ if (state.el.latH) state.el.latH.value = gms.h;
205
90
  }
206
-
207
- function updateGMSInputs(lat, lng) {
208
- const latGMS = decimalToGMS(lat, true);
209
- const lngGMS = decimalToGMS(lng, false);
210
- elements.latG.value = latGMS.g.toString();
211
- elements.latM.value = latGMS.m.toString();
212
- elements.latS.value = latGMS.s.toFixed(2);
213
- elements.latH.value = latGMS.h;
214
- elements.lngG.value = lngGMS.g.toString();
215
- elements.lngM.value = lngGMS.m.toString();
216
- elements.lngS.value = lngGMS.s.toFixed(2);
217
- elements.lngH.value = lngGMS.h;
91
+ function setLngGMS(gms) {
92
+ if (state.el.lngG) state.el.lngG.value = gms.g.toString();
93
+ if (state.el.lngM) state.el.lngM.value = gms.m.toString();
94
+ if (state.el.lngS) state.el.lngS.value = gms.s.toFixed(2);
95
+ if (state.el.lngH) state.el.lngH.value = gms.h;
218
96
  }
219
-
220
- function updateResultsDisplay(lat, lng) {
221
- const latGMS = decimalToGMS(lat, true);
222
- const lngGMS = decimalToGMS(lng, false);
223
- if (elements.valDD) elements.valDD.textContent = `${lat.toFixed(6)}, ${lng.toFixed(6)}`;
224
- if (elements.valGMS) elements.valGMS.textContent = `${formatGMS(latGMS)}, ${formatGMS(lngGMS)}`;
225
- if (elements.valDM) elements.valDM.textContent = `${formatDM(latGMS)}, ${formatDM(lngGMS)}`;
226
- if (elements.valMaps) elements.valMaps.textContent = `${lat.toFixed(6)},${lng.toFixed(6)}`;
97
+ function updateGMSDisplay(lat, lng) {
98
+ const lGMS = decimalToGMS(lat, true);
99
+ const nGMS = decimalToGMS(lng, false);
100
+ setLatGMS(lGMS);
101
+ setLngGMS(nGMS);
227
102
  }
228
-
229
- function updateFromDD(lat, lng, updateInputs = true) {
230
- if (isNaN(lat) || isNaN(lng)) return;
231
-
232
- if (updateInputs) {
233
- elements.latDD.value = lat.toFixed(6);
234
- elements.lngDD.value = lng.toFixed(6);
235
- updateGMSInputs(lat, lng);
236
- } else if (mode === 'DD') {
237
- updateGMSInputs(lat, lng);
103
+ function updateResultDisplay(lat, lng) {
104
+ const lGMS = decimalToGMS(lat, true);
105
+ const nGMS = decimalToGMS(lng, false);
106
+ if (state.el.valDD) {
107
+ state.el.valDD.textContent = `${lat.toFixed(6)}, ${lng.toFixed(6)}`;
108
+ }
109
+ if (state.el.valGMS) {
110
+ state.el.valGMS.textContent = `${formatGMS(lGMS)}, ${formatGMS(nGMS)}`;
111
+ }
112
+ if (state.el.valDM) {
113
+ state.el.valDM.textContent = `${formatDM(lGMS)}, ${formatDM(nGMS)}`;
114
+ }
115
+ if (state.el.valMaps) {
116
+ state.el.valMaps.textContent = `${lat.toFixed(6)},${lng.toFixed(6)}`;
238
117
  }
239
-
240
- updateResultsDisplay(lat, lng);
241
- saveToHistory(lat, lng);
242
118
  }
243
-
244
- function getHemisphere(value, isLat) {
245
- if (value >= 0) return isLat ? 'N' : 'E';
246
- return isLat ? 'S' : 'W';
119
+ function setDDInputs(lat, lng) {
120
+ if (state.el.latDD) state.el.latDD.value = lat.toFixed(6);
121
+ if (state.el.lngDD) state.el.lngDD.value = lng.toFixed(6);
247
122
  }
248
-
249
- function decimalToGMS(d, isLat) {
250
- const h = getHemisphere(d, isLat);
251
- const absD = Math.abs(d);
252
- const g = Math.floor(absD);
253
- const mDecimal = (absD - g) * 60;
254
- const m = Math.floor(mDecimal);
255
- const s = (mDecimal - m) * 60;
256
- return { g, m, s, h, mDec: mDecimal };
123
+ function updateFromDD(lat, lng, upd=true) {
124
+ if (isNaN(lat) || isNaN(lng)) return;
125
+ if (upd) setDDInputs(lat, lng);
126
+ if (!upd && state.mode !== 'DD') return;
127
+ updateGMSDisplay(lat, lng);
128
+ updateResultDisplay(lat, lng);
129
+ saveHistory(lat, lng);
257
130
  }
258
-
259
- function gmsToDecimal(g, m, s, h) {
260
- let d = g + (m / 60) + (s / 3600);
261
- if (h === 'S' || h === 'W') d = -d;
262
- return d;
131
+ function createHistoryItem(item) {
132
+ return `<div class="history-item" data-id="${item.id}"><div class="hist-info" data-lat="${item.lat}" data-lng="${item.lng}">
133
+ <span class="hist-coords">${item.lat}, ${item.lng}</span><span class="hist-time">${item.time}</span></div><div class="hist-actions">
134
+ <button class="action-btn load-btn" data-lat="${item.lat}" data-lng="${item.lng}"><svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
135
+ <button class="action-btn delete-btn" data-id="${item.id}"><svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button></div></div>`;
263
136
  }
264
-
265
- function formatGMS(obj) {
266
- return `${obj.g}° ${obj.m}' ${obj.s.toFixed(2)}" ${obj.h}`;
137
+ function setupHistoryListeners() {
138
+ document.querySelectorAll('.load-btn').forEach(btn => {
139
+ btn.addEventListener('click', (evt) => {
140
+ evt.stopPropagation();
141
+ const lat = btn.getAttribute('data-lat');
142
+ const lng = btn.getAttribute('data-lng');
143
+ if (lat && lng) updateFromDD(parseFloat(lat), parseFloat(lng));
144
+ });
145
+ });
146
+ document.querySelectorAll('.delete-btn').forEach(btn => {
147
+ btn.addEventListener('click', (evt) => {
148
+ evt.stopPropagation();
149
+ const itemId = btn.getAttribute('data-id');
150
+ if (itemId) {
151
+ let history = JSON.parse(localStorage.getItem('gps_history') || '[]');
152
+ history = history.filter(it => it.id !== parseInt(itemId));
153
+ localStorage.setItem('gps_history', JSON.stringify(history));
154
+ renderHistory(history);
155
+ }
156
+ });
157
+ });
158
+ document.querySelectorAll('.hist-info').forEach(info => {
159
+ info.addEventListener('click', () => {
160
+ const lat = info.getAttribute('data-lat');
161
+ const lng = info.getAttribute('data-lng');
162
+ if (lat && lng) updateFromDD(parseFloat(lat), parseFloat(lng));
163
+ });
164
+ });
267
165
  }
268
-
269
- function formatDM(obj) {
270
- return `${obj.g}° ${obj.mDec.toFixed(3)}' ${obj.h}`;
166
+ function renderHistory(history) {
167
+ if (!state.el.historyList) return;
168
+ if (history.length === 0) {
169
+ state.el.historyList.innerHTML = `<div class="no-history">${state.ui.noHistory}</div>`;
170
+ } else {
171
+ state.el.historyList.innerHTML = history.map(createHistoryItem).join('');
172
+ }
173
+ setupHistoryListeners();
271
174
  }
272
-
273
- function saveToHistory(lat, lng) {
175
+ function saveHistory(lat, lng) {
274
176
  let history = JSON.parse(localStorage.getItem('gps_history') || '[]');
275
177
  const entry = {
276
178
  id: Date.now(),
@@ -278,78 +180,403 @@ const { ui } = Astro.props;
278
180
  lng: lng.toFixed(5),
279
181
  time: new Date().toLocaleTimeString()
280
182
  };
281
-
282
183
  if (history.length > 0 && history[0].lat === entry.lat && history[0].lng === entry.lng) return;
283
-
284
184
  history.unshift(entry);
285
185
  history = history.slice(0, 5);
286
186
  localStorage.setItem('gps_history', JSON.stringify(history));
287
187
  renderHistory(history);
288
188
  }
289
-
290
189
  function loadHistory() {
291
190
  const history = JSON.parse(localStorage.getItem('gps_history') || '[]');
292
191
  renderHistory(history);
293
192
  }
294
-
295
- function renderHistoryHTML(history) {
296
- return history.map(item => `
297
- <div class="history-item" data-id="${item.id}">
298
- <div class="hist-info" data-lat="${item.lat}" data-lng="${item.lng}">
299
- <span class="hist-coords">${item.lat}, ${item.lng}</span>
300
- <span class="hist-time">${item.time}</span>
301
- </div>
302
- <div class="hist-actions">
303
- <button class="action-btn load-btn" title="${uiLocal.load}" data-lat="${item.lat}" data-lng="${item.lng}">
304
- <svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
305
- </button>
306
- <button class="action-btn delete-btn" title="${uiLocal.delete}" data-id="${item.id}">
307
- <svg viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
308
- </button>
309
- </div>
310
- </div>
311
- `).join('');
193
+ function initMap() {
194
+ const mapEl = id('gpsMap');
195
+ if (!mapEl || !state.L) return;
196
+ state.map = state.L.map('gpsMap').setView([40.4168, -3.7038], 13);
197
+ state.L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
198
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
199
+ maxZoom: 19
200
+ }).addTo(state.map);
201
+ state.map.on('click', (evt) => updateFromDD(evt.latlng.lat, evt.latlng.lng));
312
202
  }
313
-
314
- function setupHistoryEventListeners() {
315
- document.querySelectorAll('.load-btn').forEach(btn => {
316
- btn.addEventListener('click', (e) => {
317
- e.stopPropagation();
318
- const lat = parseFloat(btn.getAttribute('data-lat')!);
319
- const lng = parseFloat(btn.getAttribute('data-lng')!);
320
- updateFromDD(lat, lng);
321
- });
203
+ function setupModeListeners() {
204
+ state.el.modeDD?.addEventListener('click', () => {
205
+ state.mode = 'DD';
206
+ state.el.modeDD?.classList.add('active');
207
+ state.el.modeGMS?.classList.remove('active');
208
+ if (state.el.ddInputs) state.el.ddInputs.style.display = 'grid';
209
+ if (state.el.gmsInputs) state.el.gmsInputs.style.display = 'none';
322
210
  });
323
-
324
- document.querySelectorAll('.delete-btn').forEach(btn => {
325
- btn.addEventListener('click', (e) => {
326
- e.stopPropagation();
327
- const id = parseInt(btn.getAttribute('data-id')!);
328
- let history = JSON.parse(localStorage.getItem('gps_history') || '[]');
329
- history = history.filter((item) => item.id !== id);
330
- localStorage.setItem('gps_history', JSON.stringify(history));
331
- renderHistory(history);
211
+ state.el.modeGMS?.addEventListener('click', () => {
212
+ state.mode = 'GMS';
213
+ state.el.modeGMS?.classList.add('active');
214
+ state.el.modeDD?.classList.remove('active');
215
+ if (state.el.ddInputs) state.el.ddInputs.style.display = 'none';
216
+ if (state.el.gmsInputs) state.el.gmsInputs.style.display = 'flex';
217
+ });
218
+ }
219
+ function setupInputListeners() {
220
+ state.el.btnMyLocation?.addEventListener('click', () => {
221
+ if ('geolocation' in navigator) {
222
+ navigator.geolocation.getCurrentPosition(pos => {
223
+ updateFromDD(pos.coords.latitude, pos.coords.longitude);
224
+ });
225
+ }
226
+ });
227
+ [state.el.latDD, state.el.lngDD].forEach(el => {
228
+ el?.addEventListener('input', () => {
229
+ if (state.mode === 'DD' && state.el.latDD && state.el.lngDD) {
230
+ updateFromDD(parseFloat(state.el.latDD.value), parseFloat(state.el.lngDD.value), false);
231
+ }
332
232
  });
333
233
  });
334
-
335
- document.querySelectorAll('.hist-info').forEach(info => {
336
- info.addEventListener('click', () => {
337
- const lat = parseFloat(info.getAttribute('data-lat')!);
338
- const lng = parseFloat(info.getAttribute('data-lng')!);
339
- updateFromDD(lat, lng);
234
+ }
235
+ function setupCopyListeners() {
236
+ document.querySelectorAll('.copy-btn').forEach(btn => {
237
+ btn.addEventListener('click', () => {
238
+ const targetId = btn.getAttribute('data-copy');
239
+ const text = targetId ? id(targetId)?.textContent : null;
240
+ if (text) {
241
+ navigator.clipboard.writeText(text);
242
+ const original = btn.innerHTML;
243
+ btn.innerHTML = state.ui.copied;
244
+ setTimeout(() => { btn.innerHTML = original; }, 2000);
245
+ }
340
246
  });
341
247
  });
248
+ id('clearHistory')?.addEventListener('click', () => {
249
+ localStorage.removeItem('gps_history');
250
+ loadHistory();
251
+ });
342
252
  }
343
-
344
- function renderHistory(history) {
345
- if (!elements.historyList) return;
346
- if (history.length === 0) {
347
- elements.historyList.innerHTML = `<div class="no-history">${uiLocal.noHistory}</div>`;
348
- return;
253
+ function setupAll() {
254
+ setupModeListeners();
255
+ setupInputListeners();
256
+ setupCopyListeners();
257
+ setupHistoryListeners();
258
+ }
259
+ function checkLeaflet() {
260
+ if (window.L) {
261
+ state.L = window.L;
262
+ initMap();
263
+ setupAll();
264
+ loadHistory();
265
+ updateFromDD(40.4168, -3.7038);
266
+ } else {
267
+ setTimeout(checkLeaflet, 100);
349
268
  }
350
- elements.historyList.innerHTML = renderHistoryHTML(history);
351
- setupHistoryEventListeners();
352
269
  }
353
-
354
- init();
270
+ initState();
271
+ checkLeaflet();
355
272
  </script>
273
+
274
+ <style>
275
+ .gps-converter-ui {
276
+ --gps-accent: #0ea5e9;
277
+ --gps-accent-rgb: 14, 165, 233;
278
+ --gps-accent-glow: rgba(14, 165, 233, 0.15);
279
+ --gps-bg: #fff;
280
+ --gps-bg-muted: #f8fafc;
281
+ --gps-bg-surface: #f1f5f9;
282
+ --gps-bg-overlay: #fdfdfd;
283
+ --gps-text: #1e293b;
284
+ --gps-text-muted: #64748b;
285
+ --gps-text-dim: #94a3b8;
286
+ --gps-border: #e2e8f0;
287
+ --gps-border-light: #f1f5f9;
288
+ --gps-shadow: 0 40px 100px -20px rgba(0, 0, 0, 0.08);
289
+ --gps-danger: #ef4444;
290
+ --gps-danger-bg: rgba(239, 68, 68, 0.1);
291
+
292
+ width: 100%;
293
+ max-width: 1200px;
294
+ margin: 2rem auto;
295
+ padding: 0 1.5rem;
296
+ }
297
+
298
+ :global([data-theme="dark"]) .gps-converter-ui,
299
+ :global(.theme-dark) .gps-converter-ui {
300
+ --gps-bg: #0f172a;
301
+ --gps-bg-muted: #1e293b;
302
+ --gps-bg-surface: #334155;
303
+ --gps-bg-overlay: #1e293b;
304
+ --gps-text: #f1f5f9;
305
+ --gps-text-muted: #94a3b8;
306
+ --gps-text-dim: #64748b;
307
+ --gps-border: #334155;
308
+ --gps-border-light: #1e293b;
309
+ --gps-shadow: 0 40px 100px -20px rgba(0, 0, 0, 0.4);
310
+ }
311
+
312
+ .gps-converter-ui .tech-mega-card {
313
+ background: var(--gps-bg);
314
+ backdrop-filter: blur(24px) saturate(180%);
315
+ -webkit-backdrop-filter: blur(24px) saturate(180%);
316
+ border: 1px solid var(--gps-border);
317
+ border-radius: 40px;
318
+ box-shadow: var(--gps-shadow);
319
+ position: relative;
320
+ overflow: hidden;
321
+ }
322
+
323
+ .gps-converter-ui .tech-mega-card::before {
324
+ content: '';
325
+ position: absolute;
326
+ top: 0;
327
+ left: 0;
328
+ right: 0;
329
+ height: 6px;
330
+ background: linear-gradient(90deg, #0ea5e9, #6366f1, #0ea5e9);
331
+ background-size: 200% 100%;
332
+ animation: gradient-shift-gps 4s linear infinite;
333
+ z-index: 10;
334
+ }
335
+
336
+ @keyframes gradient-shift-gps {
337
+ 0% { background-position: 0% 50%; }
338
+ 100% { background-position: 200% 50%; }
339
+ }
340
+
341
+ .gps-converter-ui .card-grid {
342
+ display: grid;
343
+ grid-template-columns: 400px 1fr;
344
+ min-height: 700px;
345
+ }
346
+
347
+ .gps-converter-ui .config-sidebar {
348
+ padding: 3.5rem 3rem;
349
+ background: var(--gps-bg-overlay);
350
+ border-right: 1px solid var(--gps-border-light);
351
+ display: flex;
352
+ flex-direction: column;
353
+ gap: 3rem;
354
+ }
355
+
356
+ .gps-converter-ui .main-display {
357
+ padding: 3.5rem 3rem;
358
+ display: flex;
359
+ flex-direction: column;
360
+ gap: 3rem;
361
+ background: var(--gps-bg);
362
+ }
363
+
364
+ .gps-converter-ui .divider {
365
+ height: 1px;
366
+ width: 100%;
367
+ background: linear-gradient(90deg, transparent, var(--gps-border-light), transparent);
368
+ }
369
+
370
+ .gps-converter-ui :global(.tech-section) {
371
+ display: flex;
372
+ flex-direction: column;
373
+ gap: 1.5rem;
374
+ }
375
+
376
+ .gps-converter-ui :global(.section-title) {
377
+ font-size: 0.75rem;
378
+ font-weight: 800;
379
+ color: var(--gps-text-muted);
380
+ text-transform: uppercase;
381
+ letter-spacing: 0.15em;
382
+ }
383
+
384
+ .gps-converter-ui :global(.field-label) {
385
+ font-size: 0.7rem;
386
+ font-weight: 800;
387
+ color: var(--gps-text-dim);
388
+ text-transform: uppercase;
389
+ letter-spacing: 0.05em;
390
+ margin-bottom: 0.5rem;
391
+ display: block;
392
+ }
393
+
394
+ .gps-converter-ui :global(.tech-input) {
395
+ width: 100%;
396
+ padding: 1rem 1.25rem;
397
+ background: var(--gps-bg-surface);
398
+ border: 1px solid var(--gps-border);
399
+ border-radius: 16px;
400
+ font-weight: 700;
401
+ color: var(--gps-text);
402
+ font-size: 1rem;
403
+ transition: all 0.3s ease;
404
+ }
405
+
406
+ .gps-converter-ui :global(.tech-input:focus) {
407
+ outline: none;
408
+ border-color: var(--gps-accent);
409
+ box-shadow: 0 0 0 4px var(--gps-accent-glow);
410
+ background: var(--gps-bg);
411
+ }
412
+
413
+ .gps-converter-ui :global(.mode-switch) {
414
+ display: flex;
415
+ background: var(--gps-bg-surface);
416
+ padding: 0.4rem;
417
+ border-radius: 18px;
418
+ gap: 0.25rem;
419
+ }
420
+
421
+ .gps-converter-ui :global(.mode-btn) {
422
+ flex: 1;
423
+ padding: 0.75rem;
424
+ border-radius: 14px;
425
+ border: none;
426
+ background: transparent;
427
+ font-size: 0.75rem;
428
+ font-weight: 800;
429
+ color: var(--gps-text-muted);
430
+ cursor: pointer;
431
+ transition: all 0.3s ease;
432
+ }
433
+
434
+ .gps-converter-ui :global(.mode-btn.active) {
435
+ background: var(--gps-bg);
436
+ color: var(--gps-accent);
437
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
438
+ }
439
+
440
+ .gps-converter-ui :global(.map-container) {
441
+ max-width: 100%;
442
+ height: 400px;
443
+ border-radius: 32px;
444
+ overflow: hidden;
445
+ background: var(--gps-bg-surface);
446
+ border: 1px solid var(--gps-border-light);
447
+ }
448
+
449
+ .gps-converter-ui :global(.results-area) {
450
+ display: grid;
451
+ grid-template-columns: repeat(2, 1fr);
452
+ gap: 2rem;
453
+ }
454
+
455
+ .gps-converter-ui :global(.result-box) {
456
+ background: var(--gps-bg-overlay);
457
+ border: 1px solid var(--gps-border-light);
458
+ border-radius: 28px;
459
+ padding: 2.5rem;
460
+ display: flex;
461
+ flex-direction: column;
462
+ gap: 1rem;
463
+ transition: all 0.3s ease;
464
+ }
465
+
466
+ .gps-converter-ui :global(.result-box:hover) {
467
+ transform: translateY(-5px);
468
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.04);
469
+ border-color: var(--gps-accent);
470
+ }
471
+
472
+ .gps-converter-ui :global(.res-header) {
473
+ display: flex;
474
+ justify-content: space-between;
475
+ align-items: center;
476
+ }
477
+
478
+ .gps-converter-ui :global(.copy-btn) {
479
+ background: var(--gps-bg-surface);
480
+ border: 1px solid var(--gps-border);
481
+ color: var(--gps-text-muted);
482
+ padding: 0.5rem 1rem;
483
+ border-radius: 12px;
484
+ font-size: 0.7rem;
485
+ font-weight: 800;
486
+ cursor: pointer;
487
+ display: flex;
488
+ align-items: center;
489
+ gap: 0.5rem;
490
+ transition: all 0.3s ease;
491
+ }
492
+
493
+ .gps-converter-ui :global(.copy-btn:hover) {
494
+ background: var(--gps-accent);
495
+ color: white;
496
+ border-color: var(--gps-accent);
497
+ }
498
+
499
+ .gps-converter-ui :global(.val-display) {
500
+ font-size: 1.5rem;
501
+ font-weight: 900;
502
+ color: var(--gps-text);
503
+ word-break: break-all;
504
+ letter-spacing: -0.01em;
505
+ }
506
+
507
+ .gps-converter-ui :global(.history-item) {
508
+ padding: 1.25rem 1.5rem;
509
+ background: var(--gps-bg-surface);
510
+ border-radius: 20px;
511
+ display: flex;
512
+ justify-content: space-between;
513
+ align-items: center;
514
+ transition: all 0.3s ease;
515
+ border: 1px solid transparent;
516
+ }
517
+
518
+ .gps-converter-ui :global(.history-item:hover) {
519
+ background: var(--gps-bg);
520
+ border-color: var(--gps-accent);
521
+ transform: translateX(5px);
522
+ }
523
+
524
+ .gps-converter-ui :global(.hist-info) {
525
+ display: flex;
526
+ flex-direction: column;
527
+ cursor: pointer;
528
+ flex: 1;
529
+ }
530
+
531
+ .gps-converter-ui :global(.hist-coords) {
532
+ font-size: 0.9rem;
533
+ font-weight: 800;
534
+ color: var(--gps-text);
535
+ }
536
+
537
+ .gps-converter-ui :global(.hist-time) {
538
+ font-size: 0.7rem;
539
+ color: var(--gps-text-dim);
540
+ font-weight: 700;
541
+ text-transform: uppercase;
542
+ }
543
+
544
+ .gps-converter-ui :global(.action-btn) {
545
+ background: var(--gps-bg-overlay);
546
+ border: 1px solid var(--gps-border);
547
+ padding: 0.6rem;
548
+ border-radius: 12px;
549
+ color: var(--gps-text-dim);
550
+ cursor: pointer;
551
+ display: flex;
552
+ align-items: center;
553
+ justify-content: center;
554
+ transition: all 0.2s ease;
555
+ }
556
+
557
+ .gps-converter-ui :global(.no-history) {
558
+ text-align: center;
559
+ padding: 3rem;
560
+ color: var(--gps-text-dim);
561
+ font-style: italic;
562
+ font-size: 0.9rem;
563
+ }
564
+
565
+ @media (max-width: 1200px) {
566
+ .gps-converter-ui .card-grid {
567
+ grid-template-columns: 1fr;
568
+ }
569
+ .gps-converter-ui .config-sidebar {
570
+ border-right: none;
571
+ border-bottom: 1px solid var(--gps-border-light);
572
+ }
573
+ }
574
+
575
+ @media (max-width: 600px) {
576
+ .gps-converter-ui { padding: 0.5rem; }
577
+ .gps-converter-ui .config-sidebar,
578
+ .gps-converter-ui .main-display { padding: 2rem 1.5rem; }
579
+ .gps-converter-ui :global(.results-area) { grid-template-columns: 1fr; }
580
+ }
581
+ </style>
582
+