BF - Clima Atual

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.

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==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 = '&times;';
    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();
})();