document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.bkbg-ww-app').forEach(function (root) {
var opts = JSON.parse(root.dataset.opts || '{}');
new WeatherWidget(root, opts);
});
});
function WeatherWidget(root, opts) {
var self = this;
self.root = root;
self.opts = opts;
self.apiKey = opts.apiKey || '';
self.city = opts.city || 'London';
self.units = opts.units || 'metric';
self.lang = opts.lang || 'en';
self.refreshMs = (opts.refreshInterval || 0) * 60 * 1000;
self.timer = null;
self.lat = null;
self.lon = null;
self.current = null;
if (!self.apiKey) {
root.innerHTML = '
β οΈ No OpenWeatherMap API key set. Add it in the block settings.
';
return;
}
if (opts.autoLocation && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
function (pos) {
self.lat = pos.coords.latitude;
self.lon = pos.coords.longitude;
self.fetchWeather();
},
function () { self.fetchWeather(); }
);
} else {
self.fetchWeather();
}
}
WeatherWidget.prototype.fetchWeather = function () {
var self = this;
var base = 'https://api.openweathermap.org/data/2.5/';
var unitStr = '&units=' + self.units + '&lang=' + self.lang;
var locStr = self.lat ? ('lat=' + self.lat + '&lon=' + self.lon) : ('q=' + encodeURIComponent(self.city));
var currentUrl = base + 'weather?' + locStr + unitStr + '&appid=' + self.apiKey;
var forecastUrl = base + 'forecast?' + locStr + unitStr + '&cnt=40&appid=' + self.apiKey;
self.root.innerHTML = 'π Loading weatherβ¦
';
Promise.all([
fetch(currentUrl).then(function (r) { return r.json(); }),
self.opts.showForecast ? fetch(forecastUrl).then(function (r) { return r.json(); }) : Promise.resolve(null)
]).then(function (data) {
var current = data[0];
var forecast = data[1];
if (current.cod && current.cod !== 200) {
self.root.innerHTML = 'β ' + (current.message || 'API error') + '
';
return;
}
self.current = current;
self.render(current, forecast);
if (self.refreshMs > 0) {
clearInterval(self.timer);
self.timer = setInterval(function () { self.fetchWeather(); }, self.refreshMs);
}
}).catch(function (err) {
self.root.innerHTML = 'β Failed to load weather data. Check your API key and city name.
';
});
};
WeatherWidget.prototype.render = function (current, forecast) {
var self = this;
var opts = self.opts;
var a = opts;
var deg = a.units === 'imperial' ? 'Β°F' : a.units === 'standard' ? 'K' : 'Β°C';
var temp = Math.round(current.main.temp);
var feelsLike = Math.round(current.main.feels_like);
var tempMax = Math.round(current.main.temp_max);
var tempMin = Math.round(current.main.temp_min);
var humidity = current.main.humidity;
var windSpeed = Math.round(current.wind.speed * (a.units === 'metric' ? 3.6 : 1));
var windUnit = a.units === 'metric' ? 'km/h' : a.units === 'imperial' ? 'mph' : 'm/s';
var pressure = current.main.pressure;
var visibility = current.visibility ? Math.round(current.visibility / 1000) + ' km' : '--';
var condition = current.weather[0].description;
var iconCode = current.weather[0].icon;
var cityName = current.name + (current.sys && current.sys.country ? ', ' + current.sys.country : '');
var isNight = iconCode.endsWith('n');
/* determine bg class */
var mainId = current.weather[0].id;
var bgClass;
if (a.backgroundStyle === 'custom') {
bgClass = 'bkbg-ww-bg-custom';
} else if (a.backgroundStyle === 'transparent') {
bgClass = '';
} else {
if (isNight) bgClass = 'bkbg-ww-bg-night';
else if (mainId >= 200 && mainId < 300) bgClass = 'bkbg-ww-bg-thunderstorm';
else if (mainId >= 300 && mainId < 600) bgClass = 'bkbg-ww-bg-rain';
else if (mainId >= 600 && mainId < 700) bgClass = 'bkbg-ww-bg-snow';
else if (mainId >= 700 && mainId < 800) bgClass = 'bkbg-ww-bg-mist';
else if (mainId === 800) bgClass = 'bkbg-ww-bg-clear';
else bgClass = 'bkbg-ww-bg-clouds';
}
var weatherEmoji = iconToEmoji(iconCode);
/* CSS vars */
var wrapStyle = 'color:' + a.textColor + ';padding:' + a.padding + 'px;border-radius:' + a.borderRadius + 'px;';
if (a.backgroundStyle === 'custom') wrapStyle += '--ww-bg:' + a.customBg + ';';
if (a.layout === 'card') wrapStyle += 'max-width:' + a.maxWidth + 'px;margin:0 auto;';
var cardStyle = 'background:' + a.cardBg + ';backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-radius:' + a.cardRadius + 'px;padding:24px;border:1px solid rgba(255,255,255,0.2);';
/* forecast daily groups (pick one item per day ~noon) */
var forecastHTML = '';
if (a.showForecast && forecast && forecast.list) {
var days = [];
var seen = {};
forecast.list.forEach(function (item) {
var d = new Date(item.dt * 1000);
var dayKey = d.toLocaleDateString('en', { weekday: 'short' });
var dayDate = d.toDateString();
if (!seen[dayDate] && days.length < a.forecastDays) {
seen[dayDate] = true;
days.push({
day: dayKey,
emoji: iconToEmoji(item.weather[0].icon),
hi: Math.round(item.main.temp_max),
lo: Math.round(item.main.temp_min),
});
}
});
var fcDays = days.map(function (f) {
return '' +
'
' + f.day + '
' +
'
' + f.emoji + '
' +
'
' + f.hi + deg + '
' +
'
' + f.lo + deg + '
' +
'
';
}).join('');
forecastHTML = '5-Day Forecast
' +
'' + fcDays + '
';
}
var statsHTML = '';
if (a.showHumidity || a.showWind || a.showPressure || a.showVisibility) {
var stats = [];
if (a.showHumidity) stats.push({ icon: 'π§', val: humidity + '%', lbl: 'Humidity' });
if (a.showWind) stats.push({ icon: 'π¨', val: windSpeed + ' ' + windUnit, lbl: 'Wind' });
if (a.showPressure) stats.push({ icon: 'π‘', val: pressure + ' hPa', lbl: 'Pressure' });
if (a.showVisibility) stats.push({ icon: 'π', val: visibility, lbl: 'Visibility' });
statsHTML = '' +
stats.map(function (s) {
return '
' +
'
' + s.icon + '
' +
'
' + s.val + '
' +
'
' + s.lbl + '
' +
'
';
}).join('') + '
';
}
var feelsLikeHTML = a.showFeelsLike
? 'Feels like ' + feelsLike + deg + ' Β· H: ' + tempMax + deg + ' L: ' + tempMin + deg + '
'
: '';
var refreshBtn = 'β» Refresh ';
var html = '' +
'
' +
'
' +
'
' +
'
π ' + cityName + '
' +
'
' + temp + deg + '
' +
'
' + condition + '
' +
'
' +
(a.showAnimatedIcon ? '
' + weatherEmoji + '
' : '') +
'
' +
feelsLikeHTML +
statsHTML +
forecastHTML +
'
' + refreshBtn + '
' +
'
' +
'
';
self.root.innerHTML = html;
self.root.querySelector('[data-ww-refresh]').addEventListener('click', function () {
self.fetchWeather();
});
};
function iconToEmoji(code) {
if (!code) return 'π€';
var c = String(code);
if (c.startsWith('01d')) return 'βοΈ';
if (c.startsWith('01n')) return 'π';
if (c.startsWith('02')) return 'β
';
if (c.startsWith('03') || c.startsWith('04')) return 'βοΈ';
if (c.startsWith('09') || c.startsWith('10')) return 'π§';
if (c.startsWith('11')) return 'β';
if (c.startsWith('13')) return 'βοΈ';
if (c.startsWith('50')) return 'π«';
return 'π€';
}