<div id="awn-weather-widget">
<div class="awn-header">
<div>
<div id="awn-station-name" class="awn-station-name">
Local Weather
</div>
<div id="awn-station-location" class="awn-location"></div>
</div>
<button
id="awn-refresh-button"
class="awn-refresh"
type="button"
aria-label="Refresh weather data"
title="Refresh weather data"
>
↻
</button>
</div>
<div id="awn-loading" class="awn-message">
Loading weather data…
</div>
<div
id="awn-error"
class="awn-message awn-error"
hidden>
</div>
<div id="awn-weather-content" hidden>
<div class="awn-temperature-panel">
<div
id="awn-weather-symbol"
class="awn-weather-symbol"
title="Current station conditions"
></div>
<div class="awn-temp-block">
<div class="awn-primary-temperature">
<span id="awn-temperature">--</span><span class="awn-degree">°F</span>
</div>
<div class="awn-feels-like">
Feels like
<span id="awn-feels-like">--</span>°F
</div>
</div>
</div>
<div class="awn-grid">
<!-- RAIN -->
<div class="awn-card">
<div class="awn-icon">
Rain
</div>
<div class="awn-label">
Rain Today
</div>
<div class="awn-value">
<span id="awn-rain">--</span>
<span class="awn-unit">in</span>
</div>
<div class="awn-secondary">
Hourly:
<span id="awn-hourly-rain">--</span> in
</div>
</div>
<!-- PRESSURE -->
<div class="awn-card">
<div class="awn-icon">
Pressure
</div>
<div class="awn-label">
Pressure
</div>
<div class="awn-value">
<span id="awn-pressure">--</span>
<span class="awn-unit">inHg</span>
</div>
<div class="awn-secondary">
<span id="awn-pressure-trend">
Relative pressure
</span>
</div>
</div>
<!-- WIND -->
<div class="awn-card">
<div class="awn-icon">
Wind
</div>
<div class="awn-label">
Wind
</div>
<div class="awn-value">
<span id="awn-wind-speed">--</span>
<span class="awn-unit">mph</span>
</div>
<div class="awn-secondary">
<span id="awn-wind-direction">--</span>
·
<span id="awn-wind-degrees">--</span>°
</div>
</div>
<!-- HUMIDITY -->
<div class="awn-card">
<div class="awn-icon">
Humidity
</div>
<div class="awn-label">
Humidity
</div>
<div class="awn-value">
<span id="awn-humidity">--</span>
<span class="awn-unit">%</span>
</div>
<div class="awn-secondary">
Gust:
<span id="awn-wind-gust">--</span> mph
</div>
</div>
</div>
<div class="awn-footer">
Updated:
<span id="awn-updated-time">--</span>
</div>
</div>
</div>
<style>
#awn-weather-widget {
box-sizing: border-box;
width: 100%;
max-width: 620px;
margin: 0 auto;
padding: 14px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 14px;
background: linear-gradient(145deg, #f5fbff, #e9f4fc);
color: #203040;
font-family: Arial, Helvetica, sans-serif;
box-shadow: 0 4px 14px rgba(0, 40, 70, 0.12);
}
#awn-weather-widget *,
#awn-weather-widget *::before,
#awn-weather-widget *::after {
box-sizing: border-box;
}
.awn-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 9px;
}
.awn-station-name {
font-size: 14px;
font-weight: 700;
}
.awn-location {
margin-top: 2px;
color: #607080;
font-size: 9px;
}
.awn-refresh {
width: 30px;
height: 30px;
padding: 0;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 50%;
background: rgba(255, 255, 255, 0.7);
color: #264b65;
font-size: 16px;
line-height: 28px;
cursor: pointer;
}
.awn-refresh:hover {
background: #ffffff;
}
.awn-refresh:disabled {
cursor: wait;
opacity: 0.6;
}
.awn-message {
padding: 12px 6px;
text-align: center;
color: #536575;
font-size: 10px;
}
.awn-error {
color: #a12626;
}
/* TOP TEMPERATURE */
.awn-temperature-panel {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
gap: 7px;
width: 100%;
margin: 8px auto 14px;
text-align: center;
white-space: nowrap;
}
.awn-temp-block {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-width: 0;
text-align: center;
}
.awn-weather-symbol {
min-width: 30px;
font-size: 23px;
line-height: 1;
text-align: center;
white-space: nowrap;
flex-shrink: 0;
}
.awn-primary-temperature {
font-size: 26px;
font-weight: 700;
line-height: 1;
white-space: nowrap;
text-align: center;
}
.awn-degree {
font-size: 12px;
vertical-align: top;
}
.awn-feels-like {
margin-top: 4px;
color: #607080;
font-size: 8px;
white-space: nowrap;
text-align: center;
width: 100%;
}
/* WEATHER GRID */
.awn-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 7px;
}
.awn-card {
min-width: 0;
padding: 10px 8px;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 9px;
background: rgba(255, 255, 255, 0.72);
text-align: center;
overflow: hidden;
}
.awn-icon {
margin-bottom: 3px;
font-size: 9px;
color: #617382;
font-weight: 700;
text-transform: uppercase;
}
.awn-label {
color: #617382;
font-size: 8px;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
white-space: nowrap;
}
.awn-value {
margin-top: 4px;
font-size: 15px;
font-weight: 700;
white-space: nowrap;
}
.awn-unit {
font-size: 8px;
font-weight: 400;
}
.awn-secondary {
min-height: 11px;
margin-top: 4px;
color: #6b7c89;
font-size: 8px;
white-space: nowrap;
}
.awn-footer {
margin-top: 10px;
color: #6b7c89;
font-size: 8px;
text-align: center;
white-space: nowrap;
}
/* MOBILE */
@media (max-width: 420px) {
#awn-weather-widget {
padding: 11px;
}
.awn-temperature-panel {
gap: 5px;
flex-wrap: nowrap;
justify-content: center;
}
.awn-primary-temperature {
font-size: 23px;
}
.awn-weather-symbol {
min-width: 27px;
font-size: 20px;
}
.awn-degree {
font-size: 11px;
}
.awn-feels-like {
font-size: 7px;
}
.awn-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.awn-card {
padding: 8px 5px;
}
.awn-value {
font-size: 13px;
}
.awn-secondary {
font-size: 7px;
}
}
@media (max-width: 280px) {
.awn-grid {
grid-template-columns: 1fr;
}
}
</style>
<script>
(function () {
"use strict";
/*
* GOOGLE APPS SCRIPT PROXY
*/
const PROXY_URL =
"https://script.google.com/macros/s/AKfycbxAwHorXlDJJ8u2akJaTfV4aE8muF8F6xBQgVGyGwrHcVSRmYweCj-zf1WKBXwSTf83/exec";
/*
* Refresh every five minutes.
*/
const REFRESH_INTERVAL = 300000;
/*
* Weather thresholds.
*/
const WIND_THRESHOLD_MPH = 5;
const HUMIDITY_THRESHOLD = 95;
const PRESSURE_DROP_THRESHOLD = 0.01;
/*
* Day/night settings.
*
* Day: 7:00 AM - 6:59 PM
* Night: 7:00 PM - 6:59 AM
*/
const DAY_START_HOUR = 7;
const NIGHT_START_HOUR = 19;
/*
* MOBILE-SAFE WEATHER SYMBOLS
*
* Unicode escape sequences are used instead
* of literal emoji characters.
*
* This prevents Blogger from converting
* the characters into visible HTML entities.
*/
const ICON_RAIN_DROP =
"\uD83D\uDCA7";
const ICON_RAIN =
"\uD83C\uDF27";
const ICON_WIND =
"\uD83D\uDCA8";
const ICON_CLOUD =
"\u2601";
const ICON_SUN =
"\u2600";
const ICON_SHOOTING_STAR =
"\uD83C\uDF20";
let activeRequest = null;
const elements = {
loading:
document.getElementById("awn-loading"),
error:
document.getElementById("awn-error"),
content:
document.getElementById("awn-weather-content"),
refreshButton:
document.getElementById("awn-refresh-button"),
stationName:
document.getElementById("awn-station-name"),
stationLocation:
document.getElementById("awn-station-location"),
weatherSymbol:
document.getElementById("awn-weather-symbol"),
temperature:
document.getElementById("awn-temperature"),
feelsLike:
document.getElementById("awn-feels-like"),
rain:
document.getElementById("awn-rain"),
hourlyRain:
document.getElementById("awn-hourly-rain"),
pressure:
document.getElementById("awn-pressure"),
pressureTrend:
document.getElementById("awn-pressure-trend"),
windSpeed:
document.getElementById("awn-wind-speed"),
windGust:
document.getElementById("awn-wind-gust"),
windDirection:
document.getElementById("awn-wind-direction"),
windDegrees:
document.getElementById("awn-wind-degrees"),
humidity:
document.getElementById("awn-humidity"),
updatedTime:
document.getElementById("awn-updated-time")
};
/*
* LOAD WEATHER
*/
function loadWeather() {
if (
activeRequest &&
typeof activeRequest.cleanup === "function"
) {
activeRequest.cleanup();
}
setLoading(true);
const callbackName =
"awnWeatherCallback_" +
Date.now() +
"_" +
Math.floor(Math.random() * 100000);
const script =
document.createElement("script");
let requestFinished = false;
const timeout =
window.setTimeout(
function () {
if (requestFinished) {
return;
}
requestFinished = true;
cleanup();
showError(
"Weather request timed out. Please try again."
);
setLoading(false);
},
15000
);
function cleanup() {
window.clearTimeout(timeout);
if (script.parentNode) {
script.parentNode.removeChild(script);
}
try {
delete window[callbackName];
} catch (error) {
window[callbackName] = undefined;
}
if (
activeRequest &&
activeRequest.callbackName === callbackName
) {
activeRequest = null;
}
}
activeRequest = {
callbackName: callbackName,
cleanup: cleanup
};
window[callbackName] =
function (result) {
if (requestFinished) {
return;
}
requestFinished = true;
cleanup();
try {
if (
!result ||
!result.success
) {
throw new Error(
result && result.error
? result.error
: "The weather service returned an error."
);
}
displayWeather(result);
} catch (error) {
console.error(
"AWN weather widget:",
error
);
showError(
"Weather data is temporarily unavailable. " +
error.message
);
} finally {
setLoading(false);
}
};
script.onerror =
function () {
if (requestFinished) {
return;
}
requestFinished = true;
cleanup();
showError(
"Weather data is temporarily unavailable. " +
"The proxy request could not be loaded."
);
setLoading(false);
};
const separator =
PROXY_URL.indexOf("?") === -1
? "?"
: "&";
script.src =
PROXY_URL +
separator +
"callback=" +
encodeURIComponent(callbackName) +
"&time=" +
Date.now();
script.async = true;
document.body.appendChild(script);
}
/*
* DISPLAY WEATHER
*/
function displayWeather(result) {
const station =
result.station || {};
const weather =
result.weather || {};
elements.stationName.textContent =
station.name || "Local Weather";
elements.stationLocation.textContent =
station.location || "";
elements.temperature.textContent =
formatNumber(
weather.temperatureF,
1
);
elements.feelsLike.textContent =
formatNumber(
weather.feelsLikeF,
1
);
elements.rain.textContent =
formatNumber(
weather.dailyRainIn,
2
);
elements.hourlyRain.textContent =
formatNumber(
weather.hourlyRainIn,
2
);
elements.pressure.textContent =
formatNumber(
weather.relativePressureInHg,
2
);
elements.windSpeed.textContent =
formatNumber(
weather.windSpeedMph,
1
);
elements.windGust.textContent =
formatNumber(
weather.windGustMph,
1
);
elements.humidity.textContent =
formatNumber(
weather.humidity,
0
);
const degrees =
normalizeDegrees(
weather.windDirectionDegrees
);
elements.windDegrees.textContent =
degrees === null
? "--"
: Math.round(degrees);
elements.windDirection.textContent =
degrees === null
? "--"
: degreesToCompass(degrees);
elements.updatedTime.textContent =
formatObservationTime(
weather.observationTime
);
const pressureTrend =
determinePressureTrend(
weather.relativePressureInHg
);
updatePressureTrendText(
pressureTrend
);
updateWeatherSymbol(
weather,
pressureTrend
);
storeCurrentPressure(
weather.relativePressureInHg
);
elements.error.hidden = true;
elements.content.hidden = false;
}
/*
* DYNAMIC WEATHER ICON
*/
function updateWeatherSymbol(
weather,
pressureTrend
) {
const icons = [];
const rain =
Number(weather.hourlyRainIn);
const wind =
Number(weather.windSpeedMph);
const humidity =
Number(weather.humidity);
/*
* Rain detected during
* the past hour.
*/
if (
Number.isFinite(rain) &&
rain > 0
) {
icons.push(
ICON_RAIN_DROP
);
}
/*
* Humidity above 95%.
*/
if (
Number.isFinite(humidity) &&
humidity > HUMIDITY_THRESHOLD
) {
icons.push(
ICON_RAIN
);
}
/*
* Average wind above 5 MPH.
*/
if (
Number.isFinite(wind) &&
wind > WIND_THRESHOLD_MPH
) {
icons.push(
ICON_WIND
);
}
/*
* Falling pressure.
*/
if (
pressureTrend === "falling"
) {
icons.push(
ICON_CLOUD
);
}
/*
* If none of the special weather
* conditions apply, show the
* daytime or nighttime symbol.
*/
if (
icons.length === 0
) {
if (isDaytime()) {
icons.push(
ICON_SUN
);
} else {
icons.push(
ICON_SHOOTING_STAR
);
}
}
/*
* Remove duplicate symbols.
*/
const uniqueIcons =
icons.filter(
function (
icon,
index,
array
) {
return (
array.indexOf(icon) ===
index
);
}
);
elements.weatherSymbol.textContent =
uniqueIcons.join(" ");
}
/*
* PRESSURE TREND
*/
function determinePressureTrend(
currentPressure
) {
const current =
Number(currentPressure);
if (
!Number.isFinite(current)
) {
return "unknown";
}
let previous = null;
try {
previous =
Number(
localStorage.getItem(
"awnPreviousPressure"
)
);
} catch (error) {
return "unknown";
}
if (
!Number.isFinite(previous) ||
previous === 0
) {
return "unknown";
}
const difference =
current - previous;
if (
difference <=
-PRESSURE_DROP_THRESHOLD
) {
return "falling";
}
if (
difference >=
PRESSURE_DROP_THRESHOLD
) {
return "rising";
}
return "steady";
}
/*
* STORE PRESSURE
*/
function storeCurrentPressure(
pressure
) {
const number =
Number(pressure);
if (
Number.isFinite(number)
) {
try {
localStorage.setItem(
"awnPreviousPressure",
String(number)
);
localStorage.setItem(
"awnPreviousPressureTime",
String(Date.now())
);
} catch (error) {
/*
* Continue if browser storage
* is unavailable.
*/
}
}
}
/*
* PRESSURE TREND DISPLAY
*
* IMPORTANT:
*
* Unicode escape sequences are used here
* instead of literal arrow characters.
*
* This prevents Blogger from displaying
* things such as:
*
* →
*
* instead of the actual arrow.
*/
function updatePressureTrendText(
trend
) {
if (
trend === "falling"
) {
elements.pressureTrend.textContent =
"\u2193 Falling";
return;
}
if (
trend === "rising"
) {
elements.pressureTrend.textContent =
"\u2191 Rising";
return;
}
if (
trend === "steady"
) {
elements.pressureTrend.textContent =
"\u2192 Steady";
return;
}
elements.pressureTrend.textContent =
"Relative pressure";
}
/*
* DAY / NIGHT
*/
function isDaytime() {
const hour =
new Date().getHours();
return (
hour >= DAY_START_HOUR &&
hour < NIGHT_START_HOUR
);
}
/*
* LOADING DISPLAY
*/
function setLoading(
isLoading
) {
elements.refreshButton.disabled =
isLoading;
if (
isLoading &&
elements.content.hidden
) {
elements.loading.hidden =
false;
} else {
elements.loading.hidden =
true;
}
}
/*
* ERROR DISPLAY
*/
function showError(
message
) {
elements.loading.hidden =
true;
elements.error.textContent =
message;
elements.error.hidden =
false;
if (
elements.updatedTime.textContent ===
"--"
) {
elements.content.hidden =
true;
}
}
/*
* NUMBER FORMATTER
*/
function formatNumber(
value,
decimalPlaces
) {
if (
value === null ||
value === undefined ||
value === ""
) {
return "--";
}
const number =
Number(value);
if (
!Number.isFinite(number)
) {
return "--";
}
return number.toFixed(
decimalPlaces
);
}
/*
* NORMALIZE WIND DIRECTION
*/
function normalizeDegrees(
value
) {
if (
value === null ||
value === undefined ||
value === ""
) {
return null;
}
const degrees =
Number(value);
if (
!Number.isFinite(degrees)
) {
return null;
}
return (
(
degrees % 360
) +
360
) % 360;
}
/*
* WIND DIRECTION
*/
function degreesToCompass(
degrees
) {
const directions = [
"N",
"NNE",
"NE",
"ENE",
"E",
"ESE",
"SE",
"SSE",
"S",
"SSW",
"SW",
"WSW",
"W",
"WNW",
"NW",
"NNW"
];
const index =
Math.round(
degrees / 22.5
) %
directions.length;
return directions[index];
}
/*
* OBSERVATION TIME
*/
function formatObservationTime(
value
) {
if (!value) {
return "Time unavailable";
}
let date;
if (
typeof value === "number" ||
/^\d+$/.test(
String(value)
)
) {
let timestamp =
Number(value);
if (
timestamp <
100000000000
) {
timestamp *= 1000;
}
date =
new Date(timestamp);
} else {
date =
new Date(value);
}
if (
Number.isNaN(
date.getTime()
)
) {
return String(value);
}
return date.toLocaleString(
[],
{
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit"
}
);
}
/*
* MANUAL REFRESH BUTTON
*/
elements.refreshButton
.addEventListener(
"click",
loadWeather
);
/*
* INITIAL LOAD
*/
loadWeather();
/*
* AUTOMATIC REFRESH
*/
window.setInterval(
loadWeather,
REFRESH_INTERVAL
);
})();
</script>
Words and images from a professional bureaucrat, cubicle occupant and amateur thinker
Weather Station Widget HTML
Subscribe to:
Posts (Atom)
Popular Items
-
The radio The Modular "Cubbyhole" rig As a rookie member of the amateur radio community, and recent technician licensee, I’ve be...
-
If you've read my previous article, " Flight Tracking: I Guess I'm a Plane Guy Now... ", you're well aware of my inter...
-
As we officially end week one of the college football season, I thought I'd take a look back at an article I wrote about the new rules a...
-
When I first began printing I was happy to download and print existing 3D models, but my ultimate goal was always to design my own. The prob...
-
"They have the radio on computers now?" That was my first thought when I learned about Software Defined Radio (SDR) after I start...
No comments:
Post a Comment