// ==UserScript==
// @name BF - Clima Atual
// @name:pt-BR BF - Clima Atual
// @namespace https://github.com/BrunoFortunatto
// @version 1.0
// @description Mostra o clima atual, previsão de 5 dias, tema escuro automático, cidade fixa se GPS falhar, botão de atualização e alternância C/F.
// @description:pt-BR Mostra o clima atual, previsão de 5 dias, tema escuro automático, cidade fixa se GPS falhar, botão de atualização e alternância C/F.
// @author Bruno Fortunato
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
/*
* MIT License
*
* Copyright (c) 2025 Bruno Fortunato
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
(function () {
'use strict';
if (window.top !== window.self) return;
// POR FAVOR, COLOQUE SUA CHAVE DE API DO OPENWEATHERMAP AQUI!
// Você pode obter uma chave gratuita em: https://openweathermap.org/api
const apiKey = 'SUA_CHAVE_API_AQUI'; // <-- Substitua 'SUA_CHAVE_API_AQUI' pela sua chave real
if (apiKey === 'SUA_CHAVE_API_AQUI' || !apiKey) {
const errorDiv = document.createElement('div');
errorDiv.style.cssText = `
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
background: #ffcccc; color: #cc0000; padding: 20px; border: 2px solid #cc0000;
border-radius: 8px; font-family: sans-serif; font-size: 16px; text-align: center;
z-index: 9999999; box-shadow: 0 4px 8px rgba(0,0,0,0.2); max-width: 80vw;
`;
errorDiv.innerHTML = `
<h3>Erro no Script BF - Clima Atual</h3>
<p>Por favor, adicione sua chave de API do OpenWeatherMap para que o script funcione corretamente.</p>
<p>Edite o script no seu gerenciador (Tampermonkey/Greasemonkey) e substitua 'SUA_CHAVE_API_AQUI' pela sua chave.</p>
<p>Você pode obter uma chave gratuita em: <a href="https://openweathermap.org/api" target="_blank" rel="noopener noreferrer" style="color: #cc0000; text-decoration: underline;">openweathermap.org/api</a></p>
<button style="margin-top: 15px; padding: 8px 15px; background: #cc0000; color: white; border: none; border-radius: 5px; cursor: pointer;">Entendi</button>
`;
document.body.appendChild(errorDiv);
errorDiv.querySelector('button').addEventListener('click', () => errorDiv.remove());
return;
}
const fallbackCoords = { latitude: -30.0346, longitude: -51.2177 };
let coords = null;
let usingFallbackCoords = false;
let isCelsius = GM_getValue('weather_is_celsius', true);
const CACHE_KEY_WEATHER_DATA = 'weather_data_cache';
const CACHE_KEY_LAST_UPDATE = 'weather_last_update';
const CACHE_KEY_COORDS_TYPE = 'weather_coords_type';
const CACHE_EXPIRATION_MS = 5 * 60 * 1000;
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const colorPrimary = prefersDark ? '#333' : '#2196f3';
const colorText = prefersDark ? '#eee' : '#fff';
const colorPanel = prefersDark ? '#1c1c1c' : '#fff';
const colorTextPanel = prefersDark ? '#eee' : '#333';
const colorBorderLight = prefersDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)';
const tempMaxColor = prefersDark ? '#FF8C00' : '#FF6347';
const tempMinColor = prefersDark ? '#87CEEB' : '#4682B4';
const style = document.createElement('style');
style.textContent = `
.weather-btn {
position: fixed; bottom: 20px; right: 20px; z-index: 999999; display: flex;
align-items: center; gap: 8px; background: ${colorPrimary}; color: ${colorText};
padding: 8px 12px; border-radius: 10px; border: none; font-family: sans-serif;
font-size: 14px; box-shadow: 0 6px 12px rgba(0,0,0,0.4); cursor: pointer;
transition: background 0.2s, opacity 0.3s ease; opacity: 0.85;
}
.weather-btn:hover { background: #1976d2; }
.weather-btn .weather-icon { width: 28px; height: 28px; vertical-align: middle; }
.weather-panel {
position: fixed; bottom: 20px; right: 20px; max-height: calc(100vh - 40px);
overflow-y: auto; background: ${colorPanel}; color: ${colorTextPanel};
padding: 14px 18px; border-radius: 12px; box-shadow: 0 6px 12px rgba(0,0,0,0.4);
font-family: sans-serif; font-size: 14px; max-width: 320px; width: 92vw;
line-height: 1.5; display: none; animation: fadeIn 0.3s ease-in-out;
z-index: 999999; box-sizing: border-box;
}
@media (max-width: 480px) {
.weather-panel { right: 15px; left: 15px; max-width: calc(100vw - 30px); width: auto; bottom: 80px; }
.weather-btn { right: 15px; bottom: 15px; }
}
.refresh-btn, .unit-toggle-btn {
display: block; margin-top: 10px; padding: 6px 12px; border-radius: 8px;
background: ${colorPrimary}; color: ${colorText}; cursor: pointer;
transition: background 0.2s; font-size: 14px; width: 100%; box-sizing: border-box; border: none;
}
.refresh-btn:hover, .unit-toggle-btn:hover { background: #1976d2; }
.forecast {
margin-top: 12px; display: grid; grid-template-columns: repeat(5, 1fr);
gap: 5px; border-top: 1px solid ${colorBorderLight}; padding-top: 15px; overflow-x: auto;
}
.forecast-day {
text-align: center; font-size: 12px; padding: 5px 0; display: flex;
flex-direction: column; align-items: center; justify-content: flex-start;
min-width: 60px; box-sizing: border-box;
}
.forecast-day strong { font-size: 12px; margin-bottom: 5px; opacity: 0.9; }
.forecast-day img { width: 38px; height: 38px; margin-bottom: 5px; }
.forecast-temp-max { font-weight: bold; color: ${tempMaxColor}; font-size: 13px; }
.forecast-temp-min { opacity: 0.8; color: ${tempMinColor}; font-size: 12px; margin-right: 2px; }
.forecast-temps { display: flex; align-items: center; justify-content: center; margin-top: 3px; }
.forecast-rain-info { font-size: 10px; opacity: 0.8; margin-top: 2px; min-height: 15px; }
.current-day-rain-info { font-size: 13px; font-weight: bold; color: ${prefersDark ? '#ADD8E6' : '#007bff'}; margin-top: 5px; }
.weather-close-btn {
position: absolute; top: 8px; right: 8px; background: none; border: none;
color: ${colorTextPanel}; font-size: 18px; font-weight: bold; cursor: pointer;
padding: 0 5px; line-height: 1; transition: color 0.2s;
}
.weather-close-btn:hover { color: #f00; }
.full-forecast-link {
display: block; margin-top: 10px; text-align: center; font-size: 13px;
color: ${prefersDark ? '#ADD8E6' : '#007bff'}; text-decoration: underline;
cursor: pointer; transition: color 0.2s; padding: 6px; border-top: 1px dashed ${colorBorderLight};
}
.full-forecast-link:hover { color: ${prefersDark ? '#87CEEB' : '#0056b3'}; }
.wind-info, .pressure-info, .humidity-info {
font-size: 13px; font-weight: bold; margin-top: 5px;
}
.wind-info.calm { color: ${prefersDark ? '#98FB98' : '#32CD32'}; }
.wind-info.light { color: ${prefersDark ? '#ADD8E6' : '#007bff'}; }
.wind-info.moderate { color: ${prefersDark ? '#FFD700' : '#FFA500'}; }
.wind-info.strong { color: ${prefersDark ? '#FF4500' : '#DC143C'}; }
.wind-info.dangerous { color: ${prefersDark ? '#FF0000' : '#8B0000'}; }
.pressure-info.high-pressure { color: ${prefersDark ? '#98FB98' : '#32CD32'}; }
.pressure-info.normal-pressure { color: ${prefersDark ? '#ADD8E6' : '#007bff'}; }
.pressure-info.low-pressure { color: ${prefersDark ? '#FFD700' : '#FFA500'}; }
.pressure-info.very-low-pressure { color: ${prefersDark ? '#FF0000' : '#8B0000'}; }
.pressure-info.unknown-pressure { color: ${prefersDark ? '#A9A9A9' : '#808080'}; }
.humidity-info.very-dry { color: ${prefersDark ? '#FF6347' : '#DC143C'}; }
.humidity-info.dry { color: ${prefersDark ? '#FFD700' : '#FFA500'}; }
.humidity-info.comfortable { color: ${prefersDark ? '#98FB98' : '#32CD32'}; }
.humidity-info.humid { color: ${prefersDark ? '#ADD8E6' : '#007bff'}; }
.humidity-info.very-humid { color: ${prefersDark ? '#4682B4' : '#191970'}; }
`;
document.head.appendChild(style);
const button = document.createElement('button');
button.className = 'weather-btn';
button.innerHTML = `<img class="weather-icon" src="https://openweathermap.org/img/wn/01d.png" alt="Carregando"> <span class="weather-text">Carregando...</span>`;
const panel = document.createElement('div');
panel.className = 'weather-panel';
const refreshButton = document.createElement('button');
refreshButton.className = 'refresh-btn';
refreshButton.textContent = '🔄 Atualizar';
refreshButton.disabled = true;
const unitToggleButton = document.createElement('button');
unitToggleButton.className = 'unit-toggle-btn';
unitToggleButton.textContent = `Mudar para ${isCelsius ? 'Fahrenheit' : 'Celsius'}`;
const weatherDetailsContainer = document.createElement('div');
weatherDetailsContainer.className = 'weather-details-container';
const closeButton = document.createElement('button');
closeButton.className = 'weather-close-btn';
closeButton.innerHTML = '×';
closeButton.title = 'Fechar';
const fullForecastLink = document.createElement('a');
fullForecastLink.className = 'full-forecast-link';
fullForecastLink.textContent = 'Ver previsão completa no OpenWeatherMap';
fullForecastLink.target = '_blank';
fullForecastLink.rel = 'noopener noreferrer';
panel.append(closeButton, weatherDetailsContainer, refreshButton, unitToggleButton, fullForecastLink);
document.body.append(button, panel);
const closeWeatherPanel = () => panel.style.display = 'none';
button.addEventListener('click', () => panel.style.display = panel.style.display === 'block' ? 'none' : 'block');
closeButton.addEventListener('click', closeWeatherPanel);
document.addEventListener('click', (event) => {
if (panel.style.display === 'block' && !button.contains(event.target) && !panel.contains(event.target)) {
closeWeatherPanel();
}
});
const handleRefreshOrUnitToggle = async (isUnitToggle = false) => {
if (!coords) {
console.warn("Coordenadas não disponíveis, usando fallback.");
coords = fallbackCoords;
usingFallbackCoords = true;
}
refreshButton.textContent = '⏳ Atualizando...';
refreshButton.disabled = true;
unitToggleButton.disabled = true;
try {
await updateWeather(coords.latitude, coords.longitude);
} catch (error) {
console.error(`Erro ao ${isUnitToggle ? 'alternar unidade' : 'atualizar'} clima:`, error);
weatherDetailsContainer.innerHTML = `
<p style="color: red; text-align: center; padding: 10px;">
Não foi possível ${isUnitToggle ? 'alterar a unidade' : 'atualizar o clima'}.
<br>Verifique sua conexão ou tente novamente mais tarde.
<br>Detalhes: ${error.message}
</p>`;
panel.style.height = 'auto';
} finally {
refreshButton.textContent = '🔄 Atualizar';
refreshButton.disabled = false;
unitToggleButton.disabled = false;
}
};
refreshButton.addEventListener('click', handleRefreshOrUnitToggle);
unitToggleButton.addEventListener('click', () => {
isCelsius = !isCelsius;
GM_setValue('weather_is_celsius', isCelsius);
unitToggleButton.textContent = `Mudar para ${isCelsius ? 'Fahrenheit' : 'Celsius'}`;
handleRefreshOrUnitToggle(true);
});
const formatTime = (unix) => new Date(unix * 1000).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
const convertTemp = (temp) => isCelsius ? temp : (temp * 9/5) + 32;
const getUnitSymbol = () => isCelsius ? '°C' : '°F';
const getWindDirectionCardinal = (degrees) => {
const directions = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"];
return directions[Math.round((degrees % 360) / 22.5)];
};
const getHumidityFeedback = (humidityPercent) => {
if (humidityPercent < 30) return { description: 'Ar Muito Seco', className: 'very-dry' };
if (humidityPercent < 50) return { description: 'Ar Seco', className: 'dry' };
if (humidityPercent < 70) return { description: 'Umidade Confortável', className: 'comfortable' };
if (humidityPercent < 85) return { description: 'Ar Úmido', className: 'humid' };
return { description: 'Ar Muito Úmido/Saturado', className: 'very-humid' };
};
const getWindFeedback = (speedKmH) => {
if (speedKmH < 1) return { description: 'Vento Calmo', className: 'calm' };
if (speedKmH < 6) return { description: 'Vento Muito Leve', className: 'light' };
if (speedKmH < 12) return { description: 'Vento Leve', className: 'light' };
if (speedKmH < 20) return { description: 'Vento Moderado', className: 'moderate' };
if (speedKmH < 29) return { description: 'Vento Forte', className: 'strong' };
if (speedKmH < 39) return { description: 'Vento Muito Forte', className: 'strong' };
if (speedKmH < 50) return { description: 'Vento Perigoso! Cuidado!', className: 'dangerous' };
return { description: 'Vento Tempestuoso! Alerta!', className: 'dangerous' };
};
const getPressureFeedback = (pressureHPa, humidity, clouds, hasRainToday) => {
let description = 'Pressão: Dado indisponível ou incomum.';
let className = 'unknown-pressure';
if (pressureHPa >= 1022) {
description = 'Pressão Alta: Tempo estável, céu claro.';
className = 'high-pressure';
if (humidity > 70 || clouds > 60) {
description = 'Pressão Alta: Tempo estável, mas pode ter umidade ou nuvens.';
className = 'normal-pressure';
}
} else if (pressureHPa >= 1013 && pressureHPa < 1022) {
description = 'Pressão Normal: Tempo geralmente bom.';
className = 'normal-pressure';
if (hasRainToday || humidity > 75 || clouds > 70) {
description = 'Pressão Normal: Possibilidade de tempo instável.';
className = 'low-pressure';
}
} else if (pressureHPa >= 1005 && pressureHPa < 1013) {
description = 'Pressão Baixa: Possibilidade de tempo instável e chuva.';
className = 'low-pressure';
if (hasRainToday || humidity > 80 || clouds > 80) {
description = 'Pressão Baixa: Risco elevado de chuva e tempo adverso!';
className = 'very-low-pressure';
}
} else if (pressureHPa < 1005) {
description = 'Pressão Muito Baixa: Alto risco de chuva e tempestades!';
className = 'very-low-pressure';
if (!hasRainToday && humidity < 70 && clouds < 50) {
description = 'Pressão Muito Baixa: Fique atento, tempo pode piorar.';
className = 'dangerous';
}
}
return { description, className };
};
async function fetchForecast(lat, lon) {
try {
const url = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric&lang=pt_br`;
const res = await fetch(url);
if (!res.ok) throw new Error(`Erro HTTP na previsão! Status: ${res.status}`);
const data = await res.json();
const daily = {};
let currentDayRainPeriods = { morning: false, afternoon: false, night: false };
let hasRainToday = false;
let firstDateString = null;
data.list.forEach(item => {
const date = item.dt_txt.split(' ')[0];
const weatherId = item.weather[0].id;
const weatherIcon = item.weather[0].icon;
const hour = new Date(item.dt * 1000).getHours();
if (!firstDateString) firstDateString = date;
if (!daily[date]) {
daily[date] = { min: item.main.temp_min, max: item.main.temp_max, icon: weatherIcon, hasRainOrSnow: false, rainPeriods: { morning: false, afternoon: false, night: false } };
} else {
daily[date].min = Math.min(daily[date].min, item.main.temp_min);
daily[date].max = Math.max(daily[date].max, item.main.temp_max);
}
const isRainingOrSnowing = (weatherId >= 200 && weatherId < 700);
if (isRainingOrSnowing) {
daily[date].hasRainOrSnow = true;
if (date === firstDateString) hasRainToday = true;
if (hour >= 6 && hour < 12) daily[date].rainPeriods.morning = true;
else if (hour >= 12 && hour < 18) daily[date].rainPeriods.afternoon = true;
else if (hour >= 18 || hour < 6) daily[date].rainPeriods.night = true;
}
});
if (firstDateString && daily[firstDateString]) currentDayRainPeriods = daily[firstDateString].rainPeriods;
const forecastHtml = Object.entries(daily).slice(1, 6).map(([date, val]) => {
const d = new Date(date + 'T12:00:00');
const dayOfWeek = d.toLocaleDateString('pt-BR', { weekday: 'short' });
let displayIcon = val.icon.endsWith('n') && !val.icon.startsWith('50') ? val.icon.replace('n', 'd') : val.icon;
if (val.hasRainOrSnow && !['09', '10', '11'].some(id => displayIcon.startsWith(id)) && !displayIcon.startsWith('13')) displayIcon = '09d';
const periods = [];
if (val.rainPeriods.morning) periods.push('manhã');
if (val.rainPeriods.afternoon) periods.push('tarde');
if (val.rainPeriods.night) periods.push('noite');
const rainMessage = periods.length > 0 ? `Prob. chuva ${periods.join(' e ')}` : 'Não deve chover';
return `
<div class="forecast-day">
<strong>${dayOfWeek.charAt(0).toUpperCase() + dayOfWeek.slice(1)}</strong>
<img src="https://openweathermap.org/img/wn/${displayIcon}.png" alt="Ícone de clima">
<div class="forecast-temps">
<span class="forecast-temp-min">${convertTemp(val.min).toFixed(0)}${getUnitSymbol()}</span>
<span class="forecast-temp-max">${convertTemp(val.max).toFixed(0)}${getUnitSymbol()}</span>
</div>
<div class="forecast-rain-info">${rainMessage}</div>
</div>`;
}).join('');
return { forecastHtml, currentDayRainPeriods, hasRainToday };
} catch (error) {
console.error("Erro ao buscar previsão:", error);
return { forecastHtml: '<div style="text-align: center; margin-top: 10px;">Não foi possível carregar a previsão.</div>', currentDayRainPeriods: { morning: false, afternoon: false, night: false }, hasRainToday: false };
}
}
function updateButtonDisplay(temp, icon, description) {
const weatherIconElem = button.querySelector('.weather-icon');
const weatherTextElem = button.querySelector('.weather-text');
if (weatherIconElem && weatherTextElem) {
let displayIcon = icon.endsWith('n') && !icon.startsWith('50') ? icon.replace('n', 'd') : icon;
weatherIconElem.src = `https://openweathermap.org/img/wn/${displayIcon}.png`;
weatherIconElem.alt = description;
weatherTextElem.textContent = `${convertTemp(temp).toFixed(1)}${getUnitSymbol()}`;
}
}
async function updateWeather(lat, lon, initialLoad = false) {
try {
const weatherUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}&units=metric&lang=pt_br`;
const weatherRes = await fetch(weatherUrl);
if (!weatherRes.ok) throw new Error(`Erro HTTP: ${weatherRes.status}`);
const weatherData = await weatherRes.json();
updateButtonDisplay(weatherData.main.temp, weatherData.weather[0].icon, weatherData.weather[0].description);
if (typeof GM_setValue !== 'undefined') {
GM_setValue(CACHE_KEY_WEATHER_DATA, JSON.stringify({ temp: weatherData.main.temp, icon: weatherData.weather[0].icon, description: weatherData.weather[0].description, latitude: lat, longitude: lon }));
GM_setValue(CACHE_KEY_LAST_UPDATE, Date.now());
GM_setValue(CACHE_KEY_COORDS_TYPE, usingFallbackCoords ? 'fallback' : 'gps');
}
const { forecastHtml, currentDayRainPeriods, hasRainToday } = await fetchForecast(lat, lon);
const rainPeriods = [];
if (currentDayRainPeriods.morning) rainPeriods.push('manhã');
if (currentDayRainPeriods.afternoon) rainPeriods.push('tarde');
if (currentDayRainPeriods.night) rainPeriods.push('noite');
const currentDayRainMessage = rainPeriods.length > 0 ? `Prob. de chuva ${rainPeriods.join(' e ')}` : 'Não deve chover hoje';
fullForecastLink.href = `https://openweathermap.org/city/${weatherData.id}`;
let displayCityName = usingFallbackCoords ? 'Porto Alegre' : weatherData.name;
let displayCountry = usingFallbackCoords ? 'BR' : weatherData.sys.country;
const windSpeedKmH = (weatherData.wind.speed * 3.6).toFixed(1);
const { description: windFeedbackText, className: windFeedbackClass } = getWindFeedback(parseFloat(windSpeedKmH));
const { description: pressureFeedbackText, className: pressureFeedbackClass } = getPressureFeedback(weatherData.main.pressure, weatherData.main.humidity, weatherData.clouds.all, hasRainToday);
const { description: humidityFeedbackText, className: humidityFeedbackClass } = getHumidityFeedback(weatherData.main.humidity);
weatherDetailsContainer.innerHTML = `
<strong>🌍 ${displayCityName}, ${displayCountry}</strong><br>
🌡️ ${convertTemp(weatherData.main.temp).toFixed(1)}${getUnitSymbol()} (mín: ${convertTemp(weatherData.main.temp_min).toFixed(1)}${getUnitSymbol()}, máx: ${convertTemp(weatherData.main.temp_max).toFixed(1)}${getUnitSymbol()})<br>
🤗 Sensação: ${convertTemp(weatherData.main.feels_like).toFixed(1)}${getUnitSymbol()}<br>
💧 Umidade: ${weatherData.main.humidity}%<br>
<div class="humidity-info ${humidityFeedbackClass}">💧 ${humidityFeedbackText}</div>
🌬️ Vento: ${windSpeedKmH} km/h (${weatherData.wind.deg}º ${getWindDirectionCardinal(weatherData.wind.deg)})<br>
<div class="wind-info ${windFeedbackClass}">💨 ${windFeedbackText}</div>
🌥️ ${weatherData.weather[0].description}<br>
<div class="current-day-rain-info">🌧️ ${currentDayRainMessage}</div>
☁️ Nuvens: ${weatherData.clouds.all}%<br>
📈 Pressão: ${weatherData.main.pressure} hPa<br>
<div class="pressure-info ${pressureFeedbackClass}">${pressureFeedbackText}</div>
🌅 Nascer: ${formatTime(weatherData.sys.sunrise)} | 🌇 Pôr: ${formatTime(weatherData.sys.sunset)}<br>
<div class="forecast">${forecastHtml}</div>`;
refreshButton.disabled = false;
unitToggleButton.disabled = false;
panel.style.height = 'auto';
} catch (error) {
console.error("Erro ao carregar os dados do clima:", error);
weatherDetailsContainer.innerHTML = `
<p style="color: red; text-align: center; padding: 10px;">
Não foi possível carregar os dados do clima.
<br>Verifique sua conexão ou tente novamente mais tarde.
<br>Detalhes: ${error.message}
</p>`;
panel.style.height = 'auto';
refreshButton.disabled = false;
unitToggleButton.disabled = false;
if (initialLoad) updateButtonDisplay(0.0, '50d', 'Erro!');
panel.querySelector('.refresh-btn')?.addEventListener('click', handleRefreshOrUnitToggle);
}
}
async function initializeWeather() {
let useCached = false;
if (typeof GM_setValue !== 'undefined') {
const cachedWeatherData = GM_getValue(CACHE_KEY_WEATHER_DATA);
const lastUpdate = GM_getValue(CACHE_KEY_LAST_UPDATE, 0);
const cachedCoordsType = GM_getValue(CACHE_KEY_COORDS_TYPE, 'gps');
if (cachedWeatherData && (Date.now() - lastUpdate < CACHE_EXPIRATION_MS)) {
try {
const parsedData = JSON.parse(cachedWeatherData);
coords = { latitude: parsedData.latitude, longitude: parsedData.longitude };
usingFallbackCoords = (cachedCoordsType === 'fallback');
updateButtonDisplay(parsedData.temp, parsedData.icon, parsedData.description);
refreshButton.disabled = false;
unitToggleButton.disabled = false;
weatherDetailsContainer.innerHTML = `Carregando detalhes do painel...`;
useCached = true;
updateWeather(parsedData.latitude, parsedData.longitude).catch(err => console.error("Erro ao atualizar o clima em segundo plano do cache:", err));
} catch (e) {
console.error("Erro ao parsear dados do cache:", e);
weatherDetailsContainer.innerHTML = 'Carregando...';
}
}
}
if (!useCached) {
weatherDetailsContainer.innerHTML = 'Carregando...';
navigator.geolocation.getCurrentPosition(
async (pos) => { coords = pos.coords; usingFallbackCoords = false; await updateWeather(coords.latitude, coords.longitude, true); },
async (error) => {
console.warn("Erro de geolocalização, usando coordenadas de fallback:", error);
coords = fallbackCoords; usingFallbackCoords = true; await updateWeather(coords.latitude, coords.longitude, true);
}
);
}
}
initializeWeather();
})();