/*!
* Widget Clima compacto — Folha de Alphaville
* Tag:
* Bundle IIFE autocontido, sem dependências externas.
*/
(function () {
"use strict";
var TAG = "clima-widget-2026-mini";
var WORKER = "https://clima.api-copa.uk";
var PORTAL = "https://folhadealphaville.com.br";
var TTL = 600000;
var DEFAULT_CITY = "alphaville";
var TIMEOUT = 5000;
var MAX_ATTEMPTS = 3;
var MAX_TOTAL_TIME = 10000;
// O guard deixa possível incluir o script mais de uma vez sem redefinir o elemento.
try {
if (typeof customElements === "undefined" || customElements.get(TAG)) return;
} catch (_guardError) {
return;
}
var CITIES = [
{ slug: "alphaville", name: "Alphaville" },
{ slug: "barueri", name: "Barueri" },
{ slug: "santana-de-parnaiba", name: "Santana de Parnaíba" },
{ slug: "osasco", name: "Osasco" },
{ slug: "sao-paulo", name: "São Paulo" }
];
var CITY_BY_SLUG = {};
CITIES.forEach(function (city) { CITY_BY_SLUG[city.slug] = city; });
var WMO = {
0: { text: "Céu limpo", icon: "☀️", alt: "Céu limpo, ensolarado" },
1: { text: "Parcialmente nublado", icon: "⛅", alt: "Céu com nuvens" },
2: { text: "Parcialmente nublado", icon: "⛅", alt: "Céu com nuvens" },
3: { text: "Parcialmente nublado", icon: "⛅", alt: "Céu com nuvens" },
45: { text: "Nevoeiro", icon: "🌫️", alt: "Nevoeiro ou neblina" },
48: { text: "Nevoeiro", icon: "🌫️", alt: "Nevoeiro ou neblina" },
51: { text: "Garoa", icon: "🌦️", alt: "Garoa ou chuvisco" },
53: { text: "Garoa", icon: "🌦️", alt: "Garoa ou chuvisco" },
55: { text: "Garoa", icon: "🌦️", alt: "Garoa ou chuvisco" },
56: { text: "Garoa", icon: "🌦️", alt: "Garoa ou chuvisco" },
57: { text: "Garoa", icon: "🌦️", alt: "Garoa ou chuvisco" },
61: { text: "Chuva", icon: "🌧️", alt: "Chuva" },
63: { text: "Chuva", icon: "🌧️", alt: "Chuva" },
65: { text: "Chuva", icon: "🌧️", alt: "Chuva" },
66: { text: "Chuva", icon: "🌧️", alt: "Chuva" },
67: { text: "Chuva", icon: "🌧️", alt: "Chuva" },
71: { text: "Neve", icon: "🌨️", alt: "Neve" },
73: { text: "Neve", icon: "🌨️", alt: "Neve" },
75: { text: "Neve", icon: "🌨️", alt: "Neve" },
77: { text: "Neve", icon: "🌨️", alt: "Neve" },
80: { text: "Pancadas de chuva", icon: "🌦️", alt: "Pancadas de chuva" },
81: { text: "Pancadas de chuva", icon: "🌦️", alt: "Pancadas de chuva" },
82: { text: "Pancadas de chuva", icon: "🌦️", alt: "Pancadas de chuva" },
95: { text: "Trovoada", icon: "⛈️", alt: "Trovoada ou tempestade" },
96: { text: "Trovoada", icon: "⛈️", alt: "Trovoada ou tempestade" },
99: { text: "Trovoada", icon: "⛈️", alt: "Trovoada ou tempestade" }
};
var UNKNOWN_WMO = { text: "Condição indisponível", icon: "❓", alt: "Condição climática não identificada" };
function weather(code) {
return WMO[code] || UNKNOWN_WMO;
}
function safeString(value, fallback) {
return value === null || value === undefined || value === "" ? (fallback || "") : String(value);
}
function escapeHtml(value) {
return safeString(value, "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'");
}
function rounded(value, fallback) {
var number = Number(value);
return Number.isFinite(number) ? Math.round(number) : (fallback === undefined ? "—" : fallback);
}
function storage() {
try { return globalThis.sessionStorage || null; } catch (_storageError) { return null; }
}
function cacheKey(slug) { return "clima-mini-v2-" + slug; }
function readCache(slug) {
var store = storage();
if (!store) return null;
try {
var raw = store.getItem(cacheKey(slug));
if (!raw) return null;
var entry = JSON.parse(raw);
if (!entry || typeof entry.ts !== "number" || !entry.dados ||
!entry.dados.detalhes || typeof entry.dados.detalhes !== "object") return null;
if (Date.now() - entry.ts >= TTL) return null;
return entry.dados;
} catch (_readError) {
return null;
}
}
function writeCache(slug, data) {
var store = storage();
if (!store) return;
try { store.setItem(cacheKey(slug), JSON.stringify({ dados: data, ts: Date.now() })); } catch (_writeError) {}
}
function readSelectedCity() {
var store = storage();
if (!store) return DEFAULT_CITY;
try {
var slug = store.getItem("clima-mini-cidade");
return slug && CITY_BY_SLUG[slug] ? slug : DEFAULT_CITY;
} catch (_readCityError) {
return DEFAULT_CITY;
}
}
function writeSelectedCity(slug) {
var store = storage();
if (!store) return;
try { store.setItem("clima-mini-cidade", slug); } catch (_writeCityError) {}
}
function workerUrl(slug) {
try {
var url = new URL(WORKER);
url.searchParams.set("city", slug);
return url.toString();
} catch (_urlError) {
return WORKER + "?city=" + encodeURIComponent(slug);
}
}
function requestOnce(url, timeout) {
return new Promise(function (resolve) {
if (typeof fetch !== "function") { resolve(null); return; }
var controller = typeof AbortController === "function" ? new AbortController() : null;
var timer = setTimeout(function () {
if (controller) controller.abort();
resolve(null);
}, timeout);
var options = controller ? { signal: controller.signal } : undefined;
try {
fetch(url, options).then(function (response) {
if (!response || !response.ok) throw new Error("Resposta indisponível");
return response.json();
}).then(function (data) {
clearTimeout(timer);
resolve(data && typeof data === "object" && !data.erro ? data : null);
}).catch(function () {
clearTimeout(timer);
resolve(null);
});
} catch (_fetchError) {
clearTimeout(timer);
resolve(null);
}
});
}
async function loadData(slug) {
var started = Date.now();
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
var remaining = MAX_TOTAL_TIME - (Date.now() - started);
if (remaining <= 0) break;
var data = await requestOnce(workerUrl(slug), Math.min(TIMEOUT, remaining));
if (data) return data;
}
return null;
}
function formatHour(iso) {
var match = /T(\d{2}):\d{2}/.exec(safeString(iso, ""));
return match ? match[1] + "h" : "—";
}
function currentLocalHour() {
try {
var hour = Number(new Intl.DateTimeFormat("en-US", {
timeZone: "America/Sao_Paulo", hour: "2-digit", hour12: false
}).format(new Date()));
return hour === 24 ? 0 : hour;
} catch (_hourError) {
return new Date().getHours();
}
}
function currentLocalDate() {
try {
var parts = new Intl.DateTimeFormat("en-CA", {
timeZone: "America/Sao_Paulo", year: "numeric", month: "2-digit", day: "2-digit"
}).formatToParts(new Date());
var values = {};
parts.forEach(function (part) { values[part.type] = part.value; });
return values.year + "-" + values.month + "-" + values.day;
} catch (_dateError) {
return new Date().toISOString().slice(0, 10);
}
}
function nextHours(data) {
var hours = Array.isArray(data && data.horaria) ? data.horaria : [];
if (!hours.length) return [];
var today = currentLocalDate();
var current = currentLocalHour();
var index = hours.findIndex(function (item) {
var match = /^(\d{4}-\d{2}-\d{2})T(\d{2}):/.exec(safeString(item && item.hora, ""));
return match && match[1] === today && Number(match[2]) >= current;
});
if (index < 0) index = 0;
return hours.slice(index, index + 4);
}
function cityName(data, slug) {
return safeString(data && data.cidade && data.cidade.nome, CITY_BY_SLUG[slug] ? CITY_BY_SLUG[slug].name : slug);
}
function renderSelect(active) {
return '
Previsão em ' +
'
' +
CITIES.map(function (city) {
return '' + escapeHtml(city.name) + ' ';
}).join("") + ' ';
}
function renderLoading() {
return '
Carregando previsão...
';
}
function renderError(hasPrevious) {
return '
' +
(hasPrevious ? 'Não foi possível atualizar' : 'Falha ao carregar a previsão do tempo') +
' Tentar novamente
';
}
function detailValue(value, suffix) {
if (value === null || value === undefined || value === "") return "—";
var number = Number(value);
if (!Number.isFinite(number)) return "—";
return String(Math.round(number * 10) / 10).replace(".", ",") + (suffix || "");
}
function tabButton(id, label, active) {
return '
' + label + ' ';
}
function renderNextPanel(data) {
var hours = nextHours(data);
var hoursHtml = hours.length ? hours.map(function (item) {
var itemWeather = weather(item && item.codigoWmo);
return '
' + escapeHtml(formatHour(item && item.hora)) + ' ' +
'' +
escapeHtml(safeString(item && item.icone, itemWeather.icon)) + ' ' + rounded(item && item.temperatura) + '° ';
}).join("") : '
Previsão horária indisponível ';
return '
Próximas 4 horas
' + hoursHtml + ' ';
}
function renderUvPanel(details) {
var uv = details && details.uv;
var level = safeString(details && details.uvNivel, "Indisponível");
var recommendation = level === "Baixo" ? "Proteção usual é suficiente." :
level === "Indisponível" ? "Índice UV indisponível no momento." :
"Use protetor solar, chapéu e procure sombra.";
return '
Índice UV
' +
'
' + escapeHtml(detailValue(uv, "")) + '
' +
'
' + escapeHtml(level) + '
' +
'
' + escapeHtml(recommendation) + '
';
}
function renderAirPanel(details) {
var air = details && details.qualidadeAr ? details.qualidadeAr : {};
var available = (typeof air.aqi === "number" && Number.isFinite(air.aqi)) ||
(typeof air.pm25 === "number" && Number.isFinite(air.pm25)) ||
(typeof air.pm10 === "number" && Number.isFinite(air.pm10));
if (!available) {
return '
Qualidade do ar
' +
'
Qualidade do ar indisponível no momento.
';
}
return '
Qualidade do ar
' +
'
' + escapeHtml(detailValue(air.aqi, "")) + ' AQI
' +
'
' + escapeHtml(safeString(air.nivel, "Indisponível")) + '
' +
'
PM2.5 ' + escapeHtml(detailValue(air.pm25, " µg/m³")) + ' ' +
'PM10 ' + escapeHtml(detailValue(air.pm10, " µg/m³")) + '
';
}
function renderDetailsPanel(details) {
details = details || {};
return '
Detalhes do momento
' +
'
Umidade ' + escapeHtml(detailValue(details.umidade, "%")) + ' ' +
'Vento ' + escapeHtml(detailValue(details.vento, " km/h")) + ' ' +
'Chance de chuva ' + escapeHtml(detailValue(details.chanceChuva, "%")) + '
';
}
function renderForecast(data, slug, activeTab) {
var actual = data && data.atual ? data.atual : {};
var details = data && data.detalhes ? data.detalhes : {};
var mapped = weather(actual.codigoWmo);
var icon = safeString(actual.icone, mapped.icon);
var alt = safeString(actual.alt, mapped.alt);
var condition = safeString(actual.condicao, mapped.text);
var tab = activeTab === "uv" || activeTab === "ar" || activeTab === "detalhes" ? activeTab : "proximas";
var panel = tab === "uv" ? renderUvPanel(details) : tab === "ar" ? renderAirPanel(details) :
tab === "detalhes" ? renderDetailsPanel(details) : renderNextPanel(data);
return '
' +
'
' + escapeHtml(icon) + ' ' +
'
' + rounded(actual.temperatura) + '°C
' +
'
' + escapeHtml(condition) + '
Sensação: ' + rounded(actual.sensacao) + '°C
' +
'
' +
tabButton("proximas", "Próximas", tab) + tabButton("uv", "UV", tab) + tabButton("ar", "Ar", tab) + tabButton("detalhes", "Detalhes", tab) +
'
' + panel +
'
Ver previsão completa › ' +
'
';
}
function injectHostStyle() {
try {
if (!globalThis.document || !globalThis.document.head || globalThis.document.getElementById("clima-widget-mini-host-style")) return;
var style = globalThis.document.createElement("style");
style.id = "clima-widget-mini-host-style";
style.textContent = "@media (max-width: 768px){.fundoBanner{height:auto !important;min-height:0 !important;}}";
globalThis.document.head.appendChild(style);
} catch (_styleError) {}
}
var CSS = "*{box-sizing:border-box}" +
":host{display:block;width:300px;height:250px;max-width:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Arial,sans-serif;color:#fff}" +
".card{width:300px;max-width:100%;height:250px;overflow:hidden;border-radius:14px;padding:13px 14px 12px;background:linear-gradient(155deg,#0d2c62 0%,#071a3a 100%);box-shadow:0 10px 24px rgba(5,18,45,.22);position:relative}" +
".header{display:flex;align-items:center;justify-content:space-between;gap:8px;height:29px}" +
".city-label{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}" +
".city-select{width:154px;min-width:0;border:1px solid rgba(255,255,255,.28);border-radius:6px;background:#15386f;color:#fff;padding:5px 22px 5px 7px;font:600 11px/1.2 inherit;outline:none}" +
".city-select:focus{box-shadow:0 0 0 2px rgba(126,207,255,.75)}" +
".brand{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:#8fcfff;white-space:nowrap}" +
".content{height:calc(100% - 29px);display:flex;flex-direction:column}" +
".forecast{display:flex;flex-direction:column;height:100%;min-height:0}" +
".current{display:flex;align-items:center;gap:9px;height:68px;padding-top:2px}" +
".tabs{display:flex;gap:4px;height:27px;border-bottom:1px solid rgba(255,255,255,.15);margin-bottom:5px}" +
".tab{flex:1;min-width:0;border:0;border-bottom:2px solid transparent;background:transparent;color:#a9c7e8;padding:4px 2px 5px;border-radius:4px 4px 0 0;cursor:pointer;font:700 10px/1 inherit}" +
".tab:hover,.tab:focus{color:#fff;background:rgba(255,255,255,.08);outline:none}.tab:focus-visible{box-shadow:0 0 0 2px #8fd3ff inset}.tab.active{color:#fff;border-bottom-color:#8fd3ff;background:rgba(255,255,255,.1)}" +
".panel{min-height:57px;flex:1;overflow:hidden}.panel-title{font-size:10px;font-weight:700;color:#9fc5ed;text-transform:uppercase;letter-spacing:.08em;margin:0 0 5px}" +
".info-panel{padding:1px 2px 0}.metric-label{font-size:10px;color:#b9d5f4;text-transform:uppercase;letter-spacing:.07em}.metric-value{font-size:25px;font-weight:800;line-height:1.05;margin-top:2px}.metric-value small{font-size:10px;font-weight:700;color:#b9d5f4}.metric-level{font-size:12px;font-weight:700;color:#8fd3ff;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.info-panel p{margin:4px 0 0;font-size:11px;color:#d2e3f6}.unavailable{padding-top:9px}.metric-grid{display:flex;gap:12px;margin-top:7px;font-size:10px;color:#b9d5f4}.metric-grid span{display:flex;flex-direction:column;gap:2px}.metric-grid b{font-size:11px;color:#fff}.detail-grid{gap:16px;flex-wrap:wrap}.detail-grid span{min-width:65px}" +
".weather-icon{font-size:48px;line-height:1;width:58px;text-align:center;filter:drop-shadow(0 3px 4px rgba(0,0,0,.22))}" +
".current-copy{min-width:0}.temperature{font-size:37px;font-weight:800;line-height:.95;letter-spacing:-.05em}.temperature sup{font-size:15px;letter-spacing:0;vertical-align:top;position:relative;top:3px;margin-left:2px}" +
".condition{font-size:12px;font-weight:600;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:183px;margin-top:3px}" +
".feels{font-size:10px;color:#b9d5f4;margin-top:3px}" +
".hours-title{font-size:10px;font-weight:700;color:#9fc5ed;text-transform:uppercase;letter-spacing:.08em;margin:2px 0 5px}" +
".hours{display:flex;list-style:none;margin:0;padding:0;gap:5px;height:57px}" +
".hour-item{flex:1;min-width:0;border-radius:7px;background:rgba(255,255,255,.1);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;font-size:10px;color:#b9d5f4}" +
".hour-icon{font-size:17px;line-height:18px}.hour-item b{font-size:11px;color:#fff}.hours-empty{font-size:10px;color:#c3d8ee;padding-top:18px}" +
".full-link{margin-top:auto;color:#8fd3ff;font-size:11px;text-decoration:none;border-top:1px solid rgba(255,255,255,.12);padding-top:7px;white-space:nowrap}" +
".full-link:hover{text-decoration:underline}.state{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;text-align:center;font-size:13px;color:#e2efff;padding:20px}.spinner{width:27px;height:27px;border:3px solid rgba(255,255,255,.25);border-top-color:#8fd3ff;border-radius:50%;animation:spin .75s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}" +
".error strong{font-size:13px;line-height:1.35}.retry{border:0;border-radius:6px;background:#fff;color:#12376d;font:700 12px inherit;padding:7px 12px;cursor:pointer}.retry:focus{outline:2px solid #8fd3ff;outline-offset:2px}";
class MiniWeather extends HTMLElement {
constructor() {
super();
this.root = null;
this.active = DEFAULT_CITY;
this.data = null;
this.activeTab = "proximas";
this.requestId = 0;
try { this.root = this.attachShadow({ mode: "open" }); } catch (_shadowError) { this.root = null; }
}
connectedCallback() {
try {
this.active = readSelectedCity();
injectHostStyle();
this.mount();
this.load(this.active);
} catch (_connectedError) {
// O widget nunca deve propagar uma falha para a página hospedeira.
}
}
mount() {
if (!this.root) return;
this.root.innerHTML = '
';
var select = this.root.querySelector(".city-select");
if (select) select.addEventListener("change", this.onCityChange.bind(this));
}
area() { return this.root ? this.root.querySelector(".content") : null; }
setContent(html) {
var target = this.area();
if (target) target.innerHTML = html;
}
bindTabs() {
var self = this;
if (!this.root) return;
this.root.querySelectorAll(".tab").forEach(function (tab) {
tab.addEventListener("click", function () {
try {
var nextTab = tab.getAttribute("data-tab");
if (!self.data || !nextTab) return;
self.activeTab = nextTab;
self.setContent(renderForecast(self.data, self.active, self.activeTab));
self.bindTabs();
} catch (_tabError) {}
});
});
}
showLoading() { this.setContent(renderLoading()); }
showError() {
var previous = this.data;
if (previous) {
var previousSlug = previous.cidade && previous.cidade.slug ? previous.cidade.slug : this.active;
this.setContent(renderForecast(previous, previousSlug, this.activeTab) + renderError(true));
} else {
this.setContent(renderError(false));
}
this.bindTabs();
var retry = this.root && this.root.querySelector(".retry");
if (retry) retry.addEventListener("click", this.load.bind(this, this.active));
}
showData(data, slug) {
this.data = data;
this.active = slug;
this.setContent(renderForecast(data, slug, this.activeTab));
this.bindTabs();
}
onCityChange(event) {
try {
var slug = event && event.target ? event.target.value : "";
if (!CITY_BY_SLUG[slug]) return;
writeSelectedCity(slug);
this.active = slug;
this.load(slug);
} catch (_changeError) {}
}
async load(slug) {
var id = ++this.requestId;
try {
var cached = readCache(slug);
if (cached) { this.showData(cached, slug); return; }
this.showLoading();
var data = await loadData(slug);
if (id !== this.requestId) return;
if (!data) { this.showError(); return; }
writeCache(slug, data);
this.showData(data, slug);
} catch (_loadError) {
if (id === this.requestId) this.showError();
}
}
}
try { customElements.define(TAG, MiniWeather); } catch (_defineError) {}
})();