zen-wdg 2.2.7 → 2.3.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/dist/components/UI/Button.vue.d.ts +1 -1
- package/dist/components/UI/Input.vue.d.ts +1 -1
- package/dist/components/ZModal.vue.d.ts +155 -0
- package/dist/widgets/z-calendar-widget.vue.d.ts +84 -0
- package/dist/widgets/z-marked-widget.vue.d.ts +9 -6
- package/dist/widgets/z-notes-widget.vue.d.ts +42 -0
- package/dist/widgets/z-weather-widget.vue.d.ts +22 -0
- package/dist/zen-wdg.css +1 -1
- package/dist/zen-wdg.es.js +2159 -140
- package/dist/zen-wdg.umd.js +1 -1
- package/package.json +1 -1
- package/src/index.js +34 -2
- package/src/widgets/z-calendar-widget.vue +990 -0
- package/src/widgets/z-marked-widget.vue +143 -38
- package/src/widgets/z-notes-widget.vue +1073 -0
- package/src/widgets/z-search-widget.vue +2 -2
- package/src/widgets/z-weather-widget.vue +530 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="weather-container">
|
|
3
|
+
<div class="weather-widget">
|
|
4
|
+
<!-- Loading state -->
|
|
5
|
+
<div v-if="loading" class="weather-loading">
|
|
6
|
+
<div class="spinner"></div>
|
|
7
|
+
<p>Obteniendo ubicación...</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<!-- Error state -->
|
|
11
|
+
<div v-else-if="error" class="weather-error">
|
|
12
|
+
<span class="error-icon">⚠️</span>
|
|
13
|
+
<p>{{ error }}</p>
|
|
14
|
+
<button @click="getLocation" class="retry-btn">Reintentar</button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Weather data -->
|
|
18
|
+
<div v-else-if="weatherData" class="weather-content">
|
|
19
|
+
<div class="weather-header">
|
|
20
|
+
<div class="location">
|
|
21
|
+
<span class="location-icon">📍</span>
|
|
22
|
+
<span class="location-name">{{ locationName }}</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="weather-main">
|
|
27
|
+
<div class="temperature">
|
|
28
|
+
<span class="temp-value">{{ Math.round(weatherData.current.temperature_2m) }}</span>
|
|
29
|
+
<span class="temp-unit">°C</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="weather-icon">
|
|
32
|
+
{{ getWeatherIcon(weatherData.current.weather_code) }}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="weather-description">
|
|
37
|
+
{{ getWeatherDescription(weatherData.current.weather_code) }}
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="weather-details">
|
|
41
|
+
<div class="detail-item">
|
|
42
|
+
<span class="detail-icon">💨</span>
|
|
43
|
+
<span class="detail-label">Viento</span>
|
|
44
|
+
<span class="detail-value">{{ Math.round(weatherData.current.wind_speed_10m) }} km/h</span>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="detail-item">
|
|
47
|
+
<span class="detail-icon">💧</span>
|
|
48
|
+
<span class="detail-label">Humedad</span>
|
|
49
|
+
<span class="detail-value">{{ weatherData.current.relative_humidity_2m }}%</span>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="detail-item">
|
|
52
|
+
<span class="detail-icon">🌡️</span>
|
|
53
|
+
<span class="detail-label">Sensación</span>
|
|
54
|
+
<span class="detail-value">{{ Math.round(weatherData.current.apparent_temperature) }}°C</span>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="weather-footer">
|
|
59
|
+
<span class="last-update">Actualizado: {{ lastUpdate }}</span>
|
|
60
|
+
<button @click="refreshWeather" class="refresh-btn">🔄</button>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script>
|
|
68
|
+
export default {
|
|
69
|
+
name: 'ZWeatherWidget',
|
|
70
|
+
data() {
|
|
71
|
+
return {
|
|
72
|
+
loading: true,
|
|
73
|
+
error: null,
|
|
74
|
+
weatherData: null,
|
|
75
|
+
locationName: '',
|
|
76
|
+
latitude: null,
|
|
77
|
+
longitude: null,
|
|
78
|
+
lastUpdate: '',
|
|
79
|
+
updateInterval: null,
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
mounted() {
|
|
83
|
+
this.getLocation();
|
|
84
|
+
// Actualizar cada 10 minutos
|
|
85
|
+
this.updateInterval = setInterval(() => {
|
|
86
|
+
if (this.latitude && this.longitude) {
|
|
87
|
+
this.fetchWeather(this.latitude, this.longitude);
|
|
88
|
+
}
|
|
89
|
+
}, 600000);
|
|
90
|
+
},
|
|
91
|
+
beforeUnmount() {
|
|
92
|
+
if (this.updateInterval) {
|
|
93
|
+
clearInterval(this.updateInterval);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
methods: {
|
|
97
|
+
getLocation() {
|
|
98
|
+
this.loading = true;
|
|
99
|
+
this.error = null;
|
|
100
|
+
|
|
101
|
+
if (!navigator.geolocation) {
|
|
102
|
+
this.error = 'La geolocalización no está disponible en tu navegador';
|
|
103
|
+
this.loading = false;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
navigator.geolocation.getCurrentPosition(
|
|
108
|
+
(position) => {
|
|
109
|
+
this.latitude = position.coords.latitude;
|
|
110
|
+
this.longitude = position.coords.longitude;
|
|
111
|
+
this.fetchWeather(this.latitude, this.longitude);
|
|
112
|
+
this.fetchLocationName(this.latitude, this.longitude);
|
|
113
|
+
},
|
|
114
|
+
(error) => {
|
|
115
|
+
this.loading = false;
|
|
116
|
+
switch (error.code) {
|
|
117
|
+
case error.PERMISSION_DENIED:
|
|
118
|
+
this.error = 'Permiso de ubicación denegado. Activa la ubicación para usar este widget.';
|
|
119
|
+
break;
|
|
120
|
+
case error.POSITION_UNAVAILABLE:
|
|
121
|
+
this.error = 'Información de ubicación no disponible.';
|
|
122
|
+
break;
|
|
123
|
+
case error.TIMEOUT:
|
|
124
|
+
this.error = 'Tiempo de espera agotado al obtener la ubicación.';
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
this.error = 'Error desconocido al obtener la ubicación.';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async fetchWeather(lat, lon) {
|
|
134
|
+
try {
|
|
135
|
+
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m&timezone=auto`;
|
|
136
|
+
|
|
137
|
+
const response = await fetch(url);
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
throw new Error('Error al obtener datos del clima');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
this.weatherData = await response.json();
|
|
143
|
+
this.updateLastUpdateTime();
|
|
144
|
+
this.loading = false;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
this.error = 'No se pudo obtener la información del clima';
|
|
147
|
+
this.loading = false;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
async fetchLocationName(lat, lon) {
|
|
152
|
+
try {
|
|
153
|
+
// Generar clave de cache basada en coordenadas redondeadas
|
|
154
|
+
const cacheKey = this.generateCacheKey(lat, lon);
|
|
155
|
+
|
|
156
|
+
// Intentar obtener del cache primero
|
|
157
|
+
const cachedLocation = this.getLocationFromCache(cacheKey);
|
|
158
|
+
if (cachedLocation) {
|
|
159
|
+
this.locationName = cachedLocation;
|
|
160
|
+
console.log('📦 Ubicación obtenida del cache:', cachedLocation);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Si no está en cache, hacer request a Nominatim
|
|
165
|
+
console.log('🌐 Consultando Nominatim para ubicación...');
|
|
166
|
+
const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=10`;
|
|
167
|
+
|
|
168
|
+
const response = await fetch(url, {
|
|
169
|
+
headers: {
|
|
170
|
+
'User-Agent': 'ZenWDG-WeatherWidget/1.0'
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (response.ok) {
|
|
175
|
+
const data = await response.json();
|
|
176
|
+
// Extraer ciudad o localidad
|
|
177
|
+
const cityName = data.address.city || data.address.town || data.address.village || data.address.state || 'Tu ubicación';
|
|
178
|
+
this.locationName = cityName;
|
|
179
|
+
|
|
180
|
+
// Guardar en cache
|
|
181
|
+
this.saveLocationToCache(cacheKey, cityName);
|
|
182
|
+
console.log('💾 Ubicación guardada en cache:', cityName);
|
|
183
|
+
} else {
|
|
184
|
+
this.locationName = 'Tu ubicación';
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
this.locationName = 'Tu ubicación';
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
generateCacheKey(lat, lon) {
|
|
192
|
+
// Redondear a 2 decimales para agrupar ubicaciones cercanas (~1km de precisión)
|
|
193
|
+
const roundedLat = Math.round(lat * 100) / 100;
|
|
194
|
+
const roundedLon = Math.round(lon * 100) / 100;
|
|
195
|
+
return `${roundedLat},${roundedLon}`;
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
getLocationFromCache(cacheKey) {
|
|
199
|
+
try {
|
|
200
|
+
const cache = localStorage.getItem('weather_location_cache');
|
|
201
|
+
if (!cache) return null;
|
|
202
|
+
|
|
203
|
+
const cacheData = JSON.parse(cache);
|
|
204
|
+
const entry = cacheData[cacheKey];
|
|
205
|
+
|
|
206
|
+
if (!entry) return null;
|
|
207
|
+
|
|
208
|
+
// Verificar si el cache ha expirado (30 días)
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
const cacheAge = now - entry.timestamp;
|
|
211
|
+
const maxAge = 30 * 24 * 60 * 60 * 1000; // 30 días en milisegundos
|
|
212
|
+
|
|
213
|
+
if (cacheAge > maxAge) {
|
|
214
|
+
// Cache expirado, eliminarlo
|
|
215
|
+
delete cacheData[cacheKey];
|
|
216
|
+
localStorage.setItem('weather_location_cache', JSON.stringify(cacheData));
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return entry.name;
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.warn('Error al leer cache de ubicaciones:', err);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
saveLocationToCache(cacheKey, locationName) {
|
|
228
|
+
try {
|
|
229
|
+
let cacheData = {};
|
|
230
|
+
const existingCache = localStorage.getItem('weather_location_cache');
|
|
231
|
+
|
|
232
|
+
if (existingCache) {
|
|
233
|
+
cacheData = JSON.parse(existingCache);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Guardar con timestamp
|
|
237
|
+
cacheData[cacheKey] = {
|
|
238
|
+
name: locationName,
|
|
239
|
+
timestamp: Date.now()
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Limitar el tamaño del cache a 100 entradas
|
|
243
|
+
const entries = Object.entries(cacheData);
|
|
244
|
+
if (entries.length > 100) {
|
|
245
|
+
// Ordenar por timestamp y mantener solo las 100 más recientes
|
|
246
|
+
entries.sort((a, b) => b[1].timestamp - a[1].timestamp);
|
|
247
|
+
cacheData = Object.fromEntries(entries.slice(0, 100));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
localStorage.setItem('weather_location_cache', JSON.stringify(cacheData));
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.warn('Error al guardar en cache de ubicaciones:', err);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
refreshWeather() {
|
|
257
|
+
if (this.latitude && this.longitude) {
|
|
258
|
+
this.loading = true;
|
|
259
|
+
this.fetchWeather(this.latitude, this.longitude);
|
|
260
|
+
} else {
|
|
261
|
+
this.getLocation();
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
updateLastUpdateTime() {
|
|
266
|
+
const now = new Date();
|
|
267
|
+
this.lastUpdate = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
getWeatherIcon(code) {
|
|
271
|
+
// WMO Weather interpretation codes
|
|
272
|
+
const weatherIcons = {
|
|
273
|
+
0: '☀️', // Clear sky
|
|
274
|
+
1: '🌤️', // Mainly clear
|
|
275
|
+
2: '⛅', // Partly cloudy
|
|
276
|
+
3: '☁️', // Overcast
|
|
277
|
+
45: '🌫️', // Fog
|
|
278
|
+
48: '🌫️', // Depositing rime fog
|
|
279
|
+
51: '🌦️', // Light drizzle
|
|
280
|
+
53: '🌦️', // Moderate drizzle
|
|
281
|
+
55: '🌧️', // Dense drizzle
|
|
282
|
+
61: '🌧️', // Slight rain
|
|
283
|
+
63: '🌧️', // Moderate rain
|
|
284
|
+
65: '🌧️', // Heavy rain
|
|
285
|
+
71: '🌨️', // Slight snow
|
|
286
|
+
73: '🌨️', // Moderate snow
|
|
287
|
+
75: '❄️', // Heavy snow
|
|
288
|
+
77: '🌨️', // Snow grains
|
|
289
|
+
80: '🌦️', // Slight rain showers
|
|
290
|
+
81: '🌧️', // Moderate rain showers
|
|
291
|
+
82: '⛈️', // Violent rain showers
|
|
292
|
+
85: '🌨️', // Slight snow showers
|
|
293
|
+
86: '❄️', // Heavy snow showers
|
|
294
|
+
95: '⛈️', // Thunderstorm
|
|
295
|
+
96: '⛈️', // Thunderstorm with slight hail
|
|
296
|
+
99: '⛈️', // Thunderstorm with heavy hail
|
|
297
|
+
};
|
|
298
|
+
return weatherIcons[code] || '🌤️';
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
getWeatherDescription(code) {
|
|
302
|
+
const descriptions = {
|
|
303
|
+
0: 'Cielo despejado',
|
|
304
|
+
1: 'Principalmente despejado',
|
|
305
|
+
2: 'Parcialmente nublado',
|
|
306
|
+
3: 'Nublado',
|
|
307
|
+
45: 'Niebla',
|
|
308
|
+
48: 'Niebla con escarcha',
|
|
309
|
+
51: 'Llovizna ligera',
|
|
310
|
+
53: 'Llovizna moderada',
|
|
311
|
+
55: 'Llovizna densa',
|
|
312
|
+
61: 'Lluvia ligera',
|
|
313
|
+
63: 'Lluvia moderada',
|
|
314
|
+
65: 'Lluvia fuerte',
|
|
315
|
+
71: 'Nevada ligera',
|
|
316
|
+
73: 'Nevada moderada',
|
|
317
|
+
75: 'Nevada fuerte',
|
|
318
|
+
77: 'Granizo',
|
|
319
|
+
80: 'Chubascos ligeros',
|
|
320
|
+
81: 'Chubascos moderados',
|
|
321
|
+
82: 'Chubascos violentos',
|
|
322
|
+
85: 'Chubascos de nieve ligeros',
|
|
323
|
+
86: 'Chubascos de nieve fuertes',
|
|
324
|
+
95: 'Tormenta eléctrica',
|
|
325
|
+
96: 'Tormenta con granizo ligero',
|
|
326
|
+
99: 'Tormenta con granizo fuerte',
|
|
327
|
+
};
|
|
328
|
+
return descriptions[code] || 'Condición desconocida';
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
</script>
|
|
333
|
+
|
|
334
|
+
<style scoped>
|
|
335
|
+
.weather-container {
|
|
336
|
+
display: flex;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
align-items: center;
|
|
339
|
+
height: 100%;
|
|
340
|
+
width: 100%;
|
|
341
|
+
padding: 10px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.weather-widget {
|
|
345
|
+
width: 100%;
|
|
346
|
+
max-width: 400px;
|
|
347
|
+
background-color: rgba(20, 20, 20, 0.4);
|
|
348
|
+
backdrop-filter: blur(0.2rem);
|
|
349
|
+
border-radius: 15px;
|
|
350
|
+
padding: 20px;
|
|
351
|
+
color: #fff;
|
|
352
|
+
font-family: 'Arial', sans-serif;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.weather-loading,
|
|
356
|
+
.weather-error {
|
|
357
|
+
text-align: center;
|
|
358
|
+
padding: 20px;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.spinner {
|
|
362
|
+
border: 3px solid rgba(255, 255, 255, 0.3);
|
|
363
|
+
border-top: 3px solid #fff;
|
|
364
|
+
border-radius: 50%;
|
|
365
|
+
width: 40px;
|
|
366
|
+
height: 40px;
|
|
367
|
+
animation: spin 1s linear infinite;
|
|
368
|
+
margin: 0 auto 15px;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@keyframes spin {
|
|
372
|
+
0% { transform: rotate(0deg); }
|
|
373
|
+
100% { transform: rotate(360deg); }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.weather-error {
|
|
377
|
+
color: #ffcccc;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.error-icon {
|
|
381
|
+
font-size: 2em;
|
|
382
|
+
display: block;
|
|
383
|
+
margin-bottom: 10px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.retry-btn,
|
|
387
|
+
.refresh-btn {
|
|
388
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
389
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
390
|
+
color: #fff;
|
|
391
|
+
padding: 8px 16px;
|
|
392
|
+
border-radius: 8px;
|
|
393
|
+
cursor: pointer;
|
|
394
|
+
margin-top: 10px;
|
|
395
|
+
transition: background-color 0.3s;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.retry-btn:hover,
|
|
399
|
+
.refresh-btn:hover {
|
|
400
|
+
background-color: rgba(255, 255, 255, 0.3);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.weather-header {
|
|
404
|
+
margin-bottom: 15px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.location {
|
|
408
|
+
display: flex;
|
|
409
|
+
align-items: center;
|
|
410
|
+
gap: 8px;
|
|
411
|
+
font-size: 1.1em;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.location-icon {
|
|
415
|
+
font-size: 1.2em;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.weather-main {
|
|
419
|
+
display: flex;
|
|
420
|
+
justify-content: space-between;
|
|
421
|
+
align-items: center;
|
|
422
|
+
margin: 20px 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.temperature {
|
|
426
|
+
display: flex;
|
|
427
|
+
align-items: flex-start;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.temp-value {
|
|
431
|
+
font-size: 4em;
|
|
432
|
+
font-weight: bold;
|
|
433
|
+
line-height: 1;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.temp-unit {
|
|
437
|
+
font-size: 2em;
|
|
438
|
+
margin-top: 10px;
|
|
439
|
+
margin-left: 5px;
|
|
440
|
+
opacity: 0.8;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.weather-icon {
|
|
444
|
+
font-size: 4em;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.weather-description {
|
|
448
|
+
text-align: center;
|
|
449
|
+
font-size: 1.3em;
|
|
450
|
+
margin-bottom: 20px;
|
|
451
|
+
text-transform: capitalize;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.weather-details {
|
|
455
|
+
display: grid;
|
|
456
|
+
grid-template-columns: repeat(3, 1fr);
|
|
457
|
+
gap: 15px;
|
|
458
|
+
margin-top: 20px;
|
|
459
|
+
padding-top: 20px;
|
|
460
|
+
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.detail-item {
|
|
464
|
+
display: flex;
|
|
465
|
+
flex-direction: column;
|
|
466
|
+
align-items: center;
|
|
467
|
+
gap: 5px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.detail-icon {
|
|
471
|
+
font-size: 1.5em;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.detail-label {
|
|
475
|
+
font-size: 0.8em;
|
|
476
|
+
opacity: 0.7;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.detail-value {
|
|
480
|
+
font-size: 1em;
|
|
481
|
+
font-weight: bold;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.weather-footer {
|
|
485
|
+
display: flex;
|
|
486
|
+
justify-content: space-between;
|
|
487
|
+
align-items: center;
|
|
488
|
+
margin-top: 20px;
|
|
489
|
+
padding-top: 15px;
|
|
490
|
+
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.last-update {
|
|
494
|
+
font-size: 0.85em;
|
|
495
|
+
opacity: 0.7;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
.refresh-btn {
|
|
499
|
+
padding: 6px 12px;
|
|
500
|
+
font-size: 1.2em;
|
|
501
|
+
margin-top: 0;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
@media (max-width: 500px) {
|
|
505
|
+
.temp-value {
|
|
506
|
+
font-size: 3em;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.temp-unit {
|
|
510
|
+
font-size: 1.5em;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.weather-icon {
|
|
514
|
+
font-size: 3em;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.weather-description {
|
|
518
|
+
font-size: 1.1em;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.weather-details {
|
|
522
|
+
grid-template-columns: repeat(3, 1fr);
|
|
523
|
+
gap: 10px;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.detail-item {
|
|
527
|
+
font-size: 0.9em;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
</style>
|