Compare commits

...

4 Commits

Author SHA1 Message Date
Matt Walsh
ec1169e07b redraw on entering/leaving enhanced close #198 2026-04-08 09:24:54 -05:00
Matt Walsh
eee4519095 almanac #188 #196 #194 #193 2026-04-07 14:17:26 -05:00
Matt Walsh
38cdb46c85 enhance current weather, latest observations #188 #196 #193 #194 2026-04-07 13:50:37 -05:00
Matt Walsh
e70639d7a6 local forecast enhanced-wide #188 #196 #193 #194 2026-04-07 10:38:12 -05:00
22 changed files with 162 additions and 50 deletions

View File

@@ -15,7 +15,7 @@ import { readFile } from 'fs/promises';
import file from 'gulp-file';
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront';
import log from 'fancy-log';
import dartSass from 'sass';
import * as dartSass from 'sass';
import gulpSass from 'gulp-sass';
import sourceMaps from 'gulp-sourcemaps';
import OVERRIDES from '../src/overrides.mjs';
@@ -222,7 +222,7 @@ const buildDist = series(clean, parallel(buildJs, compressJsVendor, buildCss, co
// upload_images could be in parallel with upload, but _images logs a lot and has little changes
// by running upload last the majority of the changes will be at the bottom of the log for easy viewing
const publishFrontend = series(buildDist, uploadImages, upload, invalidate, logVersion);
const stageFrontend = series(previewVersion, buildDist, uploadImagesPreview, uploadPreview, invalidatePreview);
const stageFrontend = series(previewVersion, buildDist, uploadImagesPreview, uploadPreview, invalidatePreview, logVersion);
export default publishFrontend;

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

BIN
server/images/gimp/1.xcf Normal file

Binary file not shown.

View File

@@ -69,7 +69,7 @@ class Almanac extends WeatherDisplay {
// stop after 30 days or 4 moon phases
iterations += 1;
} while (iterations <= 30 && moon.length < 4);
} while (iterations <= 45 && moon.length < 5);
return {
sun,
@@ -123,21 +123,16 @@ class Almanac extends WeatherDisplay {
// Set day names
const Today = DateTime.local();
const Tomorrow = Today.plus({ days: 1 });
this.elem.querySelector('.day-1').textContent = Today.toLocaleString({ weekday: 'long' });
this.elem.querySelector('.day-2').textContent = Tomorrow.toLocaleString({ weekday: 'long' });
// fill all three days, even if some are hidden by non-enhanced
for (let i = 0; i < 3; i += 1) {
this.elem.querySelector(`.day-${i}`).textContent = Today.plus({ days: i }).toLocaleString({ weekday: 'long' });
const todaySunrise = DateTime.fromJSDate(info.sun[0].sunrise);
const todaySunset = DateTime.fromJSDate(info.sun[0].sunset);
const [todaySunriseFormatted, todaySunsetFormatted] = formatTimesForColumn([todaySunrise, todaySunset]);
this.elem.querySelector('.rise-1').textContent = todaySunriseFormatted;
this.elem.querySelector('.set-1').textContent = todaySunsetFormatted;
const tomorrowSunrise = DateTime.fromJSDate(info.sun[1].sunrise);
const tomorrowSunset = DateTime.fromJSDate(info.sun[1].sunset);
const [tomorrowSunriseFormatted, tomorrowSunsetformatted] = formatTimesForColumn([tomorrowSunrise, tomorrowSunset]);
this.elem.querySelector('.rise-2').textContent = tomorrowSunriseFormatted;
this.elem.querySelector('.set-2').textContent = tomorrowSunsetformatted;
const sunrise = DateTime.fromJSDate(info.sun[i].sunrise);
const sunset = DateTime.fromJSDate(info.sun[i].sunset);
const [sunriseFormatted, sunsetFormatted] = formatTimesForColumn([sunrise, sunset]);
this.elem.querySelector(`.rise-${i}`).textContent = sunriseFormatted;
this.elem.querySelector(`.set-${i}`).textContent = sunsetFormatted;
}
// Moon data
const days = info.moon.map((MoonPhase) => {

View File

@@ -14,6 +14,7 @@ import {
import { debugFlag } from './utils/debug.mjs';
import { isDataStale, enhanceObservationWithMapClick } from './utils/mapclick.mjs';
import { DateTime } from '../vendor/auto/luxon.mjs';
import settings from './settings.mjs';
// some stations prefixed do not provide all the necessary data
const skipStations = ['U', 'C', 'H', 'W', 'Y', 'T', 'S', 'M', 'O', 'L', 'A', 'F', 'B', 'N', 'V', 'R', 'D', 'E', 'I', 'G', 'J'];
@@ -192,7 +193,9 @@ class CurrentWeather extends WeatherDisplay {
const wind = (typeof this.data.WindSpeed === 'number') ? this.data.WindDirection.padEnd(3, '') + this.data.WindSpeed.toString().padStart(3, ' ') : this.data.WindSpeed;
// get location (city name) from StationInfo if available (allows for overrides)
const location = (StationInfo[this.data.station.properties.stationIdentifier]?.city ?? locationCleanup(this.data.station.properties.name)).substr(0, 20);
// longer name allowed if in wide-enhanced
const locationLimit = (settings.wide?.value && settings.enhancedScreens?.value) ? 25 : 20;
const location = (StationInfo[this.data.station.properties.stationIdentifier]?.city ?? locationCleanup(this.data.station.properties.name)).substr(0, locationLimit);
const fill = {
temp: this.data.Temperature + String.fromCharCode(176),

View File

@@ -17,7 +17,7 @@ class ExtendedForecast extends WeatherDisplay {
super(navId, elemId, 'Extended Forecast', true);
// set timings
if (settings.enhancedScreens?.value) {
if (settings.portrait?.value) {
this.timing.totalScreens = 1;
this.perPage = 4;
} else {

View File

@@ -159,12 +159,17 @@ class LatestObservations extends WeatherDisplay {
const windDirection = directionToNSEW(condition.windDirection.value);
const Temperature = temperatureConverter(condition.temperature.value);
const Like = likeTemperature(condition.heatIndex?.value, condition.windChill?.value, Temperature, temperatureConverter);
const WindSpeed = windConverter(condition.windSpeed.value);
const locationLimit = (settings.wide?.value && settings.enhancedScreens?.value) ? 20 : 14;
const weatherLimit = (settings.wide?.value && settings.enhancedScreens?.value) ? 10 : 9;
const fill = {
location: locationCleanup(condition.city).substr(0, 14),
location: locationCleanup(condition.city).substr(0, locationLimit),
temp: Temperature,
weather: shortenCurrentConditions(condition.textDescription).substr(0, 9),
like: Like.value,
weather: shortenCurrentConditions(condition.textDescription).substr(0, weatherLimit),
};
if (WindSpeed > 0) {
@@ -175,7 +180,12 @@ class LatestObservations extends WeatherDisplay {
fill.wind = 'Calm';
}
return this.fillTemplate('observation-row', fill);
const filledRow = this.fillTemplate('observation-row', fill);
// add the feels like class
filledRow.querySelector('.like').classList.add(Like.cssClass);
return filledRow;
});
const linesContainer = this.elem.querySelector('.observation-lines');
@@ -186,6 +196,25 @@ class LatestObservations extends WeatherDisplay {
}
}
// generate a "feels like" temperature from heat index and wind chill.
const likeTemperature = (heat, wind, actual, converter) => {
// figure out the feels like value
let value = '';
if (heat) value = converter(heat);
if (wind) value = converter(wind);
// determine if there's a red/blue color class to add
let cssClass;
if (value !== '') {
if (value > actual) cssClass = 'heat-index';
if (value < actual) cssClass = 'wind-chill';
}
return {
value,
cssClass,
};
};
const shortenCurrentConditions = (_condition) => {
let condition = _condition;
condition = condition.replace(/Light/, 'L');

View File

@@ -37,6 +37,11 @@ const init = async () => {
resizeTimeout = setTimeout(() => resize(), 100);
});
// redraw current screen (typically from enhanced setting change)
window.addEventListener('redraw', () => {
currentDisplay()?.drawCanvas();
});
// Handle orientation changes (Mobile Safari doesn't always fire resize events on orientation change)
window.addEventListener('orientationchange', () => {
if (debugFlag('resize')) {

View File

@@ -37,7 +37,7 @@ const enhancedScreenChange = (value) => {
if (!container) {
// DOM not ready; defer enabling if set
if (value) {
deferredDomSettings.add('enhanced');
deferredDomSettings.add('enhancedScreens');
}
return;
}
@@ -48,7 +48,7 @@ const enhancedScreenChange = (value) => {
container.classList.remove('enhanced');
}
// Trigger resize to recalculate scaling for new width
window.dispatchEvent(new Event('resize'));
window.dispatchEvent(new Event('redraw'));
};
const kioskChange = (value) => {

View File

@@ -1,8 +1,13 @@
@use 'shared/_colors' as c;
@use 'shared/_utils' as u;
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
#almanac-html.weather-display {
background-image: url('../images/backgrounds/3.png');
// repeat the background if wide-enhanced
.wide.enhanced & {
background-repeat: repeat-x;
}
}
.weather-display .main.almanac {
@@ -14,13 +19,17 @@
// Use CSS Grid for cross-browser consistency
// Grid is populated in reading order (left-to-right, top-to-bottom):
display: grid;
grid-template-columns: auto auto auto;
grid-template-rows: auto auto auto;
grid-template-columns: repeat(3, auto);
grid-template-rows: repeat(3, auto);
gap: 0px 90px;
margin: 3px auto 5px auto; // align the bottom of the div with the background
width: fit-content;
line-height: 30px;
.wide.enhanced & {
grid-template-columns: repeat(4, auto);
}
.grid-item {
// Reset inherited styles that interfere with grid layout
width: auto;
@@ -45,6 +54,14 @@
&.time {
text-align: center;
}
&.wide-enhanced {
display: none;
.wide.enhanced & {
display: block;
}
}
}
}
@@ -58,6 +75,10 @@
padding-left: 13px;
}
.days {
text-align: center;
}
.day {
display: inline-block;
text-align: center;
@@ -77,4 +98,4 @@
}
}

View File

@@ -14,6 +14,12 @@
padding-top: 10px;
position: absolute;
.wide.enhanced & {
width: 300px;
margin-left: 25px;
margin-right: 25px;
}
@include u.text-shadow();
&.left {

View File

@@ -84,11 +84,11 @@
left: 425px;
&.heat-index {
color: #e00;
color: c.$heat-index;
}
&.wind-chill {
color: c.$extended-low;
color: c.$wind-chill;
}
}

View File

@@ -46,6 +46,39 @@
left: 430px;
}
.like {
display: none;
}
// wide and enhanced moves the columns and enables the like column
.wide.enhanced & {
.temp {
left: 320px;
}
.like {
left: 380px;
display: block;
&.heat-index {
color: c.$heat-index;
}
&.wind-chill {
color: c.$wind-chill;
}
}
.weather {
left: 470px;
}
.wind {
left: 630px;
}
}
.observation-lines {
min-height: 338px;
padding-top: 10px;

View File

@@ -16,6 +16,13 @@
background-repeat: no-repeat;
}
.wide.enhanced & {
&:has(.can-enhance) {
background-image: url(../images/backgrounds/1-wide-enhanced.png);
background-position-x: 0px;
}
}
/* this method is required to hide blocks so they can be measured while off screen */
height: 0px;
@@ -135,6 +142,12 @@
margin-left: p.$blue-box-margin;
margin-right: p.$blue-box-margin;
width: calc(100% - 128px);
.wide.enhanced & {
&.can-enhance {
width: calc(p.$wide-width - p.$blue-box-margin - p.$blue-box-margin)
}
}
}
}

View File

@@ -13,5 +13,7 @@ $gradient-loading-3: #4f99f9;
$gradient-loading-4: #8ffdfa;
$extended-low: #8080FF;
$wind-chill: #8080FF;
$heat-index: #e00;
$blue-box: #26235a;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,18 @@
<%- include('header.ejs', {title:'Almanac', hasTime: true}) %>
<div class="main has-scroll almanac">
<div class="main has-scroll almanac can-enhance">
<div class="sun">
<div class="grid-item empty"></div>
<div class="grid-item header day-0"></div>
<div class="grid-item header day-1"></div>
<div class="grid-item header day-2"></div>
<div class="grid-item header day-2 wide-enhanced"></div>
<div class="grid-item row-label">Sunrise:</div>
<div class="grid-item time rise-0"></div>
<div class="grid-item time rise-1"></div>
<div class="grid-item time rise-2"></div>
<div class="grid-item time rise-2 wide-enhanced"></div>
<div class="grid-item row-label">Sunset:</div>
<div class="grid-item time set-0"></div>
<div class="grid-item time set-1"></div>
<div class="grid-item time set-2"></div>
<div class="grid-item time set-2 wide-enhanced"></div>
</div>
<div class="moon">
<div class="title">Moon Data:</div>

View File

@@ -1,5 +1,5 @@
<%- include('header.ejs', {titleDual:{ top: 'Current' , bottom: 'Conditions' }, noaaLogo: true, hasTime: true}) %>
<div class="main has-scroll has-box current-weather">
<div class="main has-scroll has-box current-weather can-enhance">
<div class="weather template">
<div class="left col">
<div class="temp center"></div>

View File

@@ -1,9 +1,10 @@
<%- include('header.ejs', {titleDual:{ top: 'Latest' , bottom: 'Observations' }, noaaLogo: true, hasTime: true }) %>
<div class="main has-scroll latest-observations has-box">
<div class="main has-scroll latest-observations has-box can-enhance">
<div class="container">
<div class="column-headers">
<div class="temp english">&deg;F</div>
<div class="temp metric">&deg;C</div>
<div class="like">Like</div>
<div class="weather">Weather</div>
<div class="wind">Wind</div>
</div>
@@ -11,6 +12,7 @@
<div class="observation-row template">
<div class="location"></div>
<div class="temp"></div>
<div class="like"></div>
<div class="weather"></div>
<div class="wind"></div>
</div>

View File

@@ -1,12 +1,12 @@
<%- include('header.ejs', {titleDual:{ top: 'Local' , bottom: 'Forecast' }, hasTime: true, noaaLogo: true}) %>
<div class="main has-scroll has-box local-forecast">
<div class="container">
<div class="forecasts">
<div class="forecast template">
<div class="text">
</div>
</div>
</div>
</div>
</div>
<%- include('scroll.ejs') %>
<div class="main has-scroll has-box local-forecast can-enhance">
<div class="container">
<div class="forecasts">
<div class="forecast template">
<div class="text">
</div>
</div>
</div>
</div>
</div>
<%- include('scroll.ejs') %>