diff --git a/datagenerators/output/radarcities.json b/datagenerators/output/radarcities.json
new file mode 100644
index 0000000..adac848
--- /dev/null
+++ b/datagenerators/output/radarcities.json
@@ -0,0 +1,462 @@
+[
+ {
+ "name": "Anchorage",
+ "lat": 61.2181,
+ "lon": -149.9003
+ },
+ {
+ "name": "Vancouver",
+ "lat": 49.2827,
+ "lon": -123.1207
+ },
+ {
+ "name": "Seattle",
+ "lat": 47.6062,
+ "lon": -122.3321
+ },
+ {
+ "name": "San Francisco",
+ "lat": 37.7749,
+ "lon": -122.4194
+ },
+ {
+ "name": "Los Angeles",
+ "lat": 34.0522,
+ "lon": -118.2437
+ },
+ {
+ "name": "Phoenix",
+ "lat": 33.4484,
+ "lon": -112.074
+ },
+ {
+ "name": "Denver",
+ "lat": 39.7392,
+ "lon": -104.9903
+ },
+ {
+ "name": "Dallas",
+ "lat": 32.7767,
+ "lon": -96.797
+ },
+ {
+ "name": "Houston",
+ "lat": 29.7604,
+ "lon": -95.3698
+ },
+ {
+ "name": "Minneapolis",
+ "lat": 44.9778,
+ "lon": -93.265
+ },
+ {
+ "name": "Chicago",
+ "lat": 41.8781,
+ "lon": -87.6298
+ },
+ {
+ "name": "Atlanta",
+ "lat": 33.749,
+ "lon": -84.388
+ },
+ {
+ "name": "Miami",
+ "lat": 25.7617,
+ "lon": -80.1918
+ },
+ {
+ "name": "Toronto",
+ "lat": 43.6532,
+ "lon": -79.3832
+ },
+ {
+ "name": "Montreal",
+ "lat": 45.5019,
+ "lon": -73.5674
+ },
+ {
+ "name": "New York",
+ "lat": 40.7128,
+ "lon": -74.006
+ },
+ {
+ "name": "Boston",
+ "lat": 42.3601,
+ "lon": -71.0589
+ },
+ {
+ "name": "Reykjavik",
+ "lat": 64.1466,
+ "lon": -21.9426
+ },
+ {
+ "name": "Mexico City",
+ "lat": 19.4326,
+ "lon": -99.1332
+ },
+ {
+ "name": "Guatemala City",
+ "lat": 14.6349,
+ "lon": -90.5069
+ },
+ {
+ "name": "Havana",
+ "lat": 23.1136,
+ "lon": -82.3666
+ },
+ {
+ "name": "Santo Domingo",
+ "lat": 18.4861,
+ "lon": -69.9312
+ },
+ {
+ "name": "San Juan",
+ "lat": 18.4655,
+ "lon": -66.1057
+ },
+ {
+ "name": "Bogota",
+ "lat": 4.711,
+ "lon": -74.0721
+ },
+ {
+ "name": "Quito",
+ "lat": -0.1807,
+ "lon": -78.4678
+ },
+ {
+ "name": "Lima",
+ "lat": -12.0464,
+ "lon": -77.0428
+ },
+ {
+ "name": "La Paz",
+ "lat": -16.4897,
+ "lon": -68.1193
+ },
+ {
+ "name": "Santiago",
+ "lat": -33.4489,
+ "lon": -70.6693
+ },
+ {
+ "name": "Buenos Aires",
+ "lat": -34.6037,
+ "lon": -58.3816
+ },
+ {
+ "name": "Montevideo",
+ "lat": -34.9011,
+ "lon": -56.1645
+ },
+ {
+ "name": "Sao Paulo",
+ "lat": -23.5558,
+ "lon": -46.6396
+ },
+ {
+ "name": "Rio",
+ "lat": -22.9068,
+ "lon": -43.1729
+ },
+ {
+ "name": "Recife",
+ "lat": -8.0476,
+ "lon": -34.877
+ },
+ {
+ "name": "London",
+ "lat": 51.5072,
+ "lon": -0.1276
+ },
+ {
+ "name": "Dublin",
+ "lat": 53.3498,
+ "lon": -6.2603
+ },
+ {
+ "name": "Paris",
+ "lat": 48.8566,
+ "lon": 2.3522
+ },
+ {
+ "name": "Amsterdam",
+ "lat": 52.3676,
+ "lon": 4.9041
+ },
+ {
+ "name": "Brussels",
+ "lat": 50.8503,
+ "lon": 4.3517
+ },
+ {
+ "name": "Berlin",
+ "lat": 52.52,
+ "lon": 13.405
+ },
+ {
+ "name": "Hamburg",
+ "lat": 53.5511,
+ "lon": 9.9937
+ },
+ {
+ "name": "Madrid",
+ "lat": 40.4168,
+ "lon": -3.7038
+ },
+ {
+ "name": "Lisbon",
+ "lat": 38.7223,
+ "lon": -9.1393
+ },
+ {
+ "name": "Rome",
+ "lat": 41.9028,
+ "lon": 12.4964
+ },
+ {
+ "name": "Milan",
+ "lat": 45.4642,
+ "lon": 9.19
+ },
+ {
+ "name": "Vienna",
+ "lat": 48.2082,
+ "lon": 16.3738
+ },
+ {
+ "name": "Prague",
+ "lat": 50.0755,
+ "lon": 14.4378
+ },
+ {
+ "name": "Warsaw",
+ "lat": 52.2297,
+ "lon": 21.0122
+ },
+ {
+ "name": "Stockholm",
+ "lat": 59.3293,
+ "lon": 18.0686
+ },
+ {
+ "name": "Oslo",
+ "lat": 59.9139,
+ "lon": 10.7522
+ },
+ {
+ "name": "Helsinki",
+ "lat": 60.1699,
+ "lon": 24.9384
+ },
+ {
+ "name": "Athens",
+ "lat": 37.9838,
+ "lon": 23.7275
+ },
+ {
+ "name": "Istanbul",
+ "lat": 41.0082,
+ "lon": 28.9784
+ },
+ {
+ "name": "Kyiv",
+ "lat": 50.4501,
+ "lon": 30.5234
+ },
+ {
+ "name": "Cairo",
+ "lat": 30.0444,
+ "lon": 31.2357
+ },
+ {
+ "name": "Casablanca",
+ "lat": 33.5731,
+ "lon": -7.5898
+ },
+ {
+ "name": "Lagos",
+ "lat": 6.5244,
+ "lon": 3.3792
+ },
+ {
+ "name": "Accra",
+ "lat": 5.6037,
+ "lon": -0.187
+ },
+ {
+ "name": "Nairobi",
+ "lat": -1.2864,
+ "lon": 36.8172
+ },
+ {
+ "name": "Addis Ababa",
+ "lat": 8.9806,
+ "lon": 38.7578
+ },
+ {
+ "name": "Johannesburg",
+ "lat": -26.2041,
+ "lon": 28.0473
+ },
+ {
+ "name": "Cape Town",
+ "lat": -33.9249,
+ "lon": 18.4241
+ },
+ {
+ "name": "Dubai",
+ "lat": 25.2048,
+ "lon": 55.2708
+ },
+ {
+ "name": "Abu Dhabi",
+ "lat": 24.4539,
+ "lon": 54.3773
+ },
+ {
+ "name": "Riyadh",
+ "lat": 24.7136,
+ "lon": 46.6753
+ },
+ {
+ "name": "Doha",
+ "lat": 25.2854,
+ "lon": 51.531
+ },
+ {
+ "name": "Kuwait City",
+ "lat": 29.3759,
+ "lon": 47.9774
+ },
+ {
+ "name": "Jerusalem",
+ "lat": 31.7683,
+ "lon": 35.2137
+ },
+ {
+ "name": "Amman",
+ "lat": 31.9539,
+ "lon": 35.9106
+ },
+ {
+ "name": "Karachi",
+ "lat": 24.8607,
+ "lon": 67.0011
+ },
+ {
+ "name": "Mumbai",
+ "lat": 19.076,
+ "lon": 72.8777
+ },
+ {
+ "name": "Delhi",
+ "lat": 28.6139,
+ "lon": 77.209
+ },
+ {
+ "name": "Dhaka",
+ "lat": 23.8103,
+ "lon": 90.4125
+ },
+ {
+ "name": "Bangkok",
+ "lat": 13.7563,
+ "lon": 100.5018
+ },
+ {
+ "name": "Hanoi",
+ "lat": 21.0278,
+ "lon": 105.8342
+ },
+ {
+ "name": "Ho Chi Minh City",
+ "lat": 10.8231,
+ "lon": 106.6297
+ },
+ {
+ "name": "Singapore",
+ "lat": 1.3521,
+ "lon": 103.8198
+ },
+ {
+ "name": "Jakarta",
+ "lat": -6.2088,
+ "lon": 106.8456
+ },
+ {
+ "name": "Manila",
+ "lat": 14.5995,
+ "lon": 120.9842
+ },
+ {
+ "name": "Hong Kong",
+ "lat": 22.3193,
+ "lon": 114.1694
+ },
+ {
+ "name": "Taipei",
+ "lat": 25.033,
+ "lon": 121.5654
+ },
+ {
+ "name": "Shanghai",
+ "lat": 31.2304,
+ "lon": 121.4737
+ },
+ {
+ "name": "Beijing",
+ "lat": 39.9042,
+ "lon": 116.4074
+ },
+ {
+ "name": "Seoul",
+ "lat": 37.5665,
+ "lon": 126.978
+ },
+ {
+ "name": "Tokyo",
+ "lat": 35.6762,
+ "lon": 139.6503
+ },
+ {
+ "name": "Osaka",
+ "lat": 34.6937,
+ "lon": 135.5023
+ },
+ {
+ "name": "Sapporo",
+ "lat": 43.0618,
+ "lon": 141.3545
+ },
+ {
+ "name": "Sydney",
+ "lat": -33.8688,
+ "lon": 151.2093
+ },
+ {
+ "name": "Melbourne",
+ "lat": -37.8136,
+ "lon": 144.9631
+ },
+ {
+ "name": "Brisbane",
+ "lat": -27.4698,
+ "lon": 153.0251
+ },
+ {
+ "name": "Perth",
+ "lat": -31.9523,
+ "lon": 115.8613
+ },
+ {
+ "name": "Auckland",
+ "lat": -36.8509,
+ "lon": 174.7645
+ },
+ {
+ "name": "Wellington",
+ "lat": -41.2866,
+ "lon": 174.7756
+ }
+]
\ No newline at end of file
diff --git a/datagenerators/radarcities-raw.json b/datagenerators/radarcities-raw.json
new file mode 100644
index 0000000..2685981
--- /dev/null
+++ b/datagenerators/radarcities-raw.json
@@ -0,0 +1,94 @@
+[
+ { "name": "Anchorage", "lat": 61.2181, "lon": -149.9003 },
+ { "name": "Vancouver", "lat": 49.2827, "lon": -123.1207 },
+ { "name": "Seattle", "lat": 47.6062, "lon": -122.3321 },
+ { "name": "San Francisco", "lat": 37.7749, "lon": -122.4194 },
+ { "name": "Los Angeles", "lat": 34.0522, "lon": -118.2437 },
+ { "name": "Phoenix", "lat": 33.4484, "lon": -112.074 },
+ { "name": "Denver", "lat": 39.7392, "lon": -104.9903 },
+ { "name": "Dallas", "lat": 32.7767, "lon": -96.797 },
+ { "name": "Houston", "lat": 29.7604, "lon": -95.3698 },
+ { "name": "Minneapolis", "lat": 44.9778, "lon": -93.265 },
+ { "name": "Chicago", "lat": 41.8781, "lon": -87.6298 },
+ { "name": "Atlanta", "lat": 33.749, "lon": -84.388 },
+ { "name": "Miami", "lat": 25.7617, "lon": -80.1918 },
+ { "name": "Toronto", "lat": 43.6532, "lon": -79.3832 },
+ { "name": "Montreal", "lat": 45.5019, "lon": -73.5674 },
+ { "name": "New York", "lat": 40.7128, "lon": -74.006 },
+ { "name": "Boston", "lat": 42.3601, "lon": -71.0589 },
+ { "name": "Reykjavik", "lat": 64.1466, "lon": -21.9426 },
+ { "name": "Mexico City", "lat": 19.4326, "lon": -99.1332 },
+ { "name": "Guatemala City", "lat": 14.6349, "lon": -90.5069 },
+ { "name": "Havana", "lat": 23.1136, "lon": -82.3666 },
+ { "name": "Santo Domingo", "lat": 18.4861, "lon": -69.9312 },
+ { "name": "San Juan", "lat": 18.4655, "lon": -66.1057 },
+ { "name": "Bogota", "lat": 4.711, "lon": -74.0721 },
+ { "name": "Quito", "lat": -0.1807, "lon": -78.4678 },
+ { "name": "Lima", "lat": -12.0464, "lon": -77.0428 },
+ { "name": "La Paz", "lat": -16.4897, "lon": -68.1193 },
+ { "name": "Santiago", "lat": -33.4489, "lon": -70.6693 },
+ { "name": "Buenos Aires", "lat": -34.6037, "lon": -58.3816 },
+ { "name": "Montevideo", "lat": -34.9011, "lon": -56.1645 },
+ { "name": "Sao Paulo", "lat": -23.5558, "lon": -46.6396 },
+ { "name": "Rio", "lat": -22.9068, "lon": -43.1729 },
+ { "name": "Recife", "lat": -8.0476, "lon": -34.877 },
+ { "name": "London", "lat": 51.5072, "lon": -0.1276 },
+ { "name": "Dublin", "lat": 53.3498, "lon": -6.2603 },
+ { "name": "Paris", "lat": 48.8566, "lon": 2.3522 },
+ { "name": "Amsterdam", "lat": 52.3676, "lon": 4.9041 },
+ { "name": "Brussels", "lat": 50.8503, "lon": 4.3517 },
+ { "name": "Berlin", "lat": 52.52, "lon": 13.405 },
+ { "name": "Hamburg", "lat": 53.5511, "lon": 9.9937 },
+ { "name": "Madrid", "lat": 40.4168, "lon": -3.7038 },
+ { "name": "Lisbon", "lat": 38.7223, "lon": -9.1393 },
+ { "name": "Rome", "lat": 41.9028, "lon": 12.4964 },
+ { "name": "Milan", "lat": 45.4642, "lon": 9.19 },
+ { "name": "Vienna", "lat": 48.2082, "lon": 16.3738 },
+ { "name": "Prague", "lat": 50.0755, "lon": 14.4378 },
+ { "name": "Warsaw", "lat": 52.2297, "lon": 21.0122 },
+ { "name": "Stockholm", "lat": 59.3293, "lon": 18.0686 },
+ { "name": "Oslo", "lat": 59.9139, "lon": 10.7522 },
+ { "name": "Helsinki", "lat": 60.1699, "lon": 24.9384 },
+ { "name": "Athens", "lat": 37.9838, "lon": 23.7275 },
+ { "name": "Istanbul", "lat": 41.0082, "lon": 28.9784 },
+ { "name": "Kyiv", "lat": 50.4501, "lon": 30.5234 },
+ { "name": "Cairo", "lat": 30.0444, "lon": 31.2357 },
+ { "name": "Casablanca", "lat": 33.5731, "lon": -7.5898 },
+ { "name": "Lagos", "lat": 6.5244, "lon": 3.3792 },
+ { "name": "Accra", "lat": 5.6037, "lon": -0.187 },
+ { "name": "Nairobi", "lat": -1.2864, "lon": 36.8172 },
+ { "name": "Addis Ababa", "lat": 8.9806, "lon": 38.7578 },
+ { "name": "Johannesburg", "lat": -26.2041, "lon": 28.0473 },
+ { "name": "Cape Town", "lat": -33.9249, "lon": 18.4241 },
+ { "name": "Dubai", "lat": 25.2048, "lon": 55.2708 },
+ { "name": "Abu Dhabi", "lat": 24.4539, "lon": 54.3773 },
+ { "name": "Riyadh", "lat": 24.7136, "lon": 46.6753 },
+ { "name": "Doha", "lat": 25.2854, "lon": 51.531 },
+ { "name": "Kuwait City", "lat": 29.3759, "lon": 47.9774 },
+ { "name": "Jerusalem", "lat": 31.7683, "lon": 35.2137 },
+ { "name": "Amman", "lat": 31.9539, "lon": 35.9106 },
+ { "name": "Karachi", "lat": 24.8607, "lon": 67.0011 },
+ { "name": "Mumbai", "lat": 19.076, "lon": 72.8777 },
+ { "name": "Delhi", "lat": 28.6139, "lon": 77.209 },
+ { "name": "Dhaka", "lat": 23.8103, "lon": 90.4125 },
+ { "name": "Bangkok", "lat": 13.7563, "lon": 100.5018 },
+ { "name": "Hanoi", "lat": 21.0278, "lon": 105.8342 },
+ { "name": "Ho Chi Minh City", "lat": 10.8231, "lon": 106.6297 },
+ { "name": "Singapore", "lat": 1.3521, "lon": 103.8198 },
+ { "name": "Jakarta", "lat": -6.2088, "lon": 106.8456 },
+ { "name": "Manila", "lat": 14.5995, "lon": 120.9842 },
+ { "name": "Hong Kong", "lat": 22.3193, "lon": 114.1694 },
+ { "name": "Taipei", "lat": 25.033, "lon": 121.5654 },
+ { "name": "Shanghai", "lat": 31.2304, "lon": 121.4737 },
+ { "name": "Beijing", "lat": 39.9042, "lon": 116.4074 },
+ { "name": "Seoul", "lat": 37.5665, "lon": 126.978 },
+ { "name": "Tokyo", "lat": 35.6762, "lon": 139.6503 },
+ { "name": "Osaka", "lat": 34.6937, "lon": 135.5023 },
+ { "name": "Sapporo", "lat": 43.0618, "lon": 141.3545 },
+ { "name": "Sydney", "lat": -33.8688, "lon": 151.2093 },
+ { "name": "Melbourne", "lat": -37.8136, "lon": 144.9631 },
+ { "name": "Brisbane", "lat": -27.4698, "lon": 153.0251 },
+ { "name": "Perth", "lat": -31.9523, "lon": 115.8613 },
+ { "name": "Auckland", "lat": -36.8509, "lon": 174.7645 },
+ { "name": "Wellington", "lat": -41.2866, "lon": 174.7756 }
+]
diff --git a/datagenerators/radarcities.mjs b/datagenerators/radarcities.mjs
new file mode 100644
index 0000000..4b059af
--- /dev/null
+++ b/datagenerators/radarcities.mjs
@@ -0,0 +1,17 @@
+import { readFile, writeFile } from 'fs/promises';
+
+const radarCities = JSON.parse(await readFile('./datagenerators/radarcities-raw.json'));
+
+const result = radarCities.map((city) => {
+ if (!city?.name || typeof city.lat !== 'number' || typeof city.lon !== 'number') {
+ throw new Error(`Invalid radar city: ${JSON.stringify(city)}`);
+ }
+
+ return {
+ name: city.name,
+ lat: city.lat,
+ lon: city.lon,
+ };
+});
+
+await writeFile('./datagenerators/output/radarcities.json', JSON.stringify(result, null, '\t'));
diff --git a/gulp/publish-frontend.mjs b/gulp/publish-frontend.mjs
index 3ff65cf..e00a868 100644
--- a/gulp/publish-frontend.mjs
+++ b/gulp/publish-frontend.mjs
@@ -144,6 +144,7 @@ const copyDataFiles = () => src([
'datagenerators/output/travelcities.json',
'datagenerators/output/regionalcities.json',
'datagenerators/output/stations.json',
+ 'datagenerators/output/radarcities.json',
]).pipe(dest('./dist/data'));
const s3 = s3Upload({
diff --git a/index.mjs b/index.mjs
index 63c4f7e..a74cd28 100644
--- a/index.mjs
+++ b/index.mjs
@@ -81,6 +81,7 @@ const parseLwnStories = (html) => {
const travelCities = JSON.parse(await readFile('./datagenerators/output/travelcities.json'));
const regionalCities = JSON.parse(await readFile('./datagenerators/output/regionalcities.json'));
const stationInfo = JSON.parse(await readFile('./datagenerators/output/stations.json'));
+const radarCities = JSON.parse(await readFile('./datagenerators/output/radarcities.json'));
const app = express();
const port = process.env.WS4KP_PORT ?? 8080;
@@ -265,6 +266,7 @@ const dataEndpoints = {
travelcities: travelCities,
regionalcities: regionalCities,
stations: stationInfo,
+ radarcities: radarCities,
};
Object.entries(dataEndpoints).forEach(([name, data]) => {
diff --git a/server/scripts/modules/radar.mjs b/server/scripts/modules/radar.mjs
index e9aa948..f22eaf7 100644
--- a/server/scripts/modules/radar.mjs
+++ b/server/scripts/modules/radar.mjs
@@ -3,14 +3,17 @@ import { DateTime } from '../vendor/auto/luxon.mjs';
import { safeJson } from './utils/fetch.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
+import {
+ createMap,
+ addBaseLayers,
+ setPrimaryLocationMarker,
+ loadNearbyObservationMarkers,
+ clearMarkers,
+} from './utils/leaflet-weather-map.mjs';
class Radar extends WeatherDisplay {
static metadataUrl = 'https://api.rainviewer.com/public/weather-maps.json';
- static baseMapUrl = 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}';
-
- static boundaryMapUrl = 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}';
-
constructor(navId, elemId) {
super(navId, elemId, 'Local Radar');
@@ -21,6 +24,7 @@ class Radar extends WeatherDisplay {
this.baseLayer = null;
this.boundaryLayer = null;
this.locationMarker = null;
+ this.nearbyMarkers = [];
this.radarLayers = [];
this.mapFrames = [];
this.radarHost = '';
@@ -42,6 +46,7 @@ class Radar extends WeatherDisplay {
this.map.invalidateSize();
this.map.setView([this.weatherParameters.latitude, this.weatherParameters.longitude], 7);
this.updateLocationMarker();
+ await this.updateNearbyMarkers();
const radarMetadata = await safeJson(Radar.metadataUrl, {
retryCount: 2,
@@ -67,6 +72,7 @@ class Radar extends WeatherDisplay {
} catch (error) {
console.error(`Failed to initialize radar: ${error.message}`);
this.clearRadarLayers();
+ this.clearNearbyMarkers();
this.timing.totalScreens = 0;
if (this.isEnabled) this.setStatus(STATUS.failed);
}
@@ -80,37 +86,8 @@ class Radar extends WeatherDisplay {
throw new Error('Radar map container not found');
}
- this.map = window.L.map(mapElement, {
- zoomControl: false,
- dragging: false,
- touchZoom: false,
- scrollWheelZoom: false,
- doubleClickZoom: false,
- boxZoom: false,
- keyboard: false,
- tap: false,
- attributionControl: false,
- preferCanvas: true,
- });
-
- this.baseLayer = window.L.tileLayer(Radar.baseMapUrl, {
- maxZoom: 10,
- minZoom: 1,
- crossOrigin: true,
- className: 'radar-base-layer',
- });
-
- this.baseLayer.addTo(this.map);
-
- this.boundaryLayer = window.L.tileLayer(Radar.boundaryMapUrl, {
- maxZoom: 10,
- minZoom: 1,
- opacity: 0.6,
- crossOrigin: true,
- className: 'radar-boundary-layer',
- });
-
- this.boundaryLayer.addTo(this.map);
+ this.map = createMap(mapElement);
+ ({ baseLayer: this.baseLayer, boundaryLayer: this.boundaryLayer } = addBaseLayers(this.map));
}
resetRadarLayers() {
@@ -162,23 +139,27 @@ class Radar extends WeatherDisplay {
updateLocationMarker() {
if (!this.map) return;
-
- if (this.locationMarker && this.map.hasLayer(this.locationMarker)) {
- this.map.removeLayer(this.locationMarker);
- }
-
- this.locationMarker = window.L.circleMarker([
+ this.locationMarker = setPrimaryLocationMarker(
+ this.map,
+ this.locationMarker,
this.weatherParameters.latitude,
this.weatherParameters.longitude,
- ], {
- radius: 5,
- color: '#000',
- weight: 2,
- fillColor: '#ff0',
- fillOpacity: 1,
- interactive: false,
- className: 'location-marker',
- }).addTo(this.map);
+ );
+ }
+
+ clearNearbyMarkers() {
+ this.nearbyMarkers = clearMarkers(this.map, this.nearbyMarkers);
+ }
+
+ async updateNearbyMarkers() {
+ if (!this.map) return;
+
+ this.clearNearbyMarkers();
+ this.nearbyMarkers = await loadNearbyObservationMarkers(this.map, {
+ latitude: this.weatherParameters.latitude,
+ longitude: this.weatherParameters.longitude,
+ });
+ this.nearbyMarkers.forEach((marker) => marker.addTo(this.map));
}
showFrame(screenIndex) {
diff --git a/server/scripts/modules/regionalforecast.mjs b/server/scripts/modules/regionalforecast.mjs
index b5a081b..c3dabbf 100644
--- a/server/scripts/modules/regionalforecast.mjs
+++ b/server/scripts/modules/regionalforecast.mjs
@@ -1,245 +1,115 @@
-// regional forecast and observations
-// type 0 = observations, 1 = first forecast, 2 = second forecast
+// regional observations display
import STATUS from './status.mjs';
-import { distance as calcDistance } from './utils/calc.mjs';
-import { safeJson, safePromiseAll } from './utils/fetch.mjs';
-import { temperature as temperatureUnit } from './utils/units.mjs';
-import { getSmallIcon } from './icons.mjs';
-import { preloadImg } from './utils/image.mjs';
-import { DateTime } from '../vendor/auto/luxon.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
-import * as utils from './regionalforecast-utils.mjs';
-import { getPoint } from './utils/weather.mjs';
-import { debugFlag } from './utils/debug.mjs';
-import filterExpiredPeriods from './utils/forecast-utils.mjs';
-
-// map offset
-const mapOffsetXY = {
- x: 240,
- y: 117,
-};
+import {
+ createMap,
+ addBaseLayers,
+ setPrimaryLocationMarker,
+ loadNearbyObservationMarkers,
+ clearMarkers,
+} from './utils/leaflet-weather-map.mjs';
class RegionalForecast extends WeatherDisplay {
constructor(navId, elemId) {
- super(navId, elemId, 'Regional Forecast', true);
-
- // timings
- this.timing.totalScreens = 3;
+ super(navId, elemId, 'Regional Observations', true);
+ this.timing.totalScreens = 1;
+ this.map = null;
+ this.baseLayer = null;
+ this.boundaryLayer = null;
+ this.locationMarker = null;
+ this.nearbyMarkers = [];
+ this.nearbyMarkersKey = '';
}
async getData(weatherParameters, refresh) {
if (!super.getData(weatherParameters, refresh)) return;
- if (!this.weatherParameters?.supportsNoaaDisplays) {
- this.data = [];
- this.timing.totalScreens = 0;
+
+ try {
+ if (!window.L) {
+ throw new Error('Leaflet is not available');
+ }
+
+ await this.ensureMap();
+ this.map.invalidateSize();
+ this.map.setView([this.weatherParameters.latitude, this.weatherParameters.longitude], 6);
+ this.locationMarker = setPrimaryLocationMarker(
+ this.map,
+ this.locationMarker,
+ this.weatherParameters.latitude,
+ this.weatherParameters.longitude,
+ );
+ this.nearbyMarkers = clearMarkers(this.map, this.nearbyMarkers);
+ this.nearbyMarkersKey = '';
+
+ this.timing.totalScreens = 1;
this.setStatus(STATUS.loaded);
- return;
+ } catch (error) {
+ console.error(`Failed to initialize regional observations: ${error.message}`);
+ this.nearbyMarkers = clearMarkers(this.map, this.nearbyMarkers);
+ this.timing.totalScreens = 0;
+ if (this.isEnabled) this.setStatus(STATUS.failed);
}
- this.timing.totalScreens = 3;
- // regional forecast implements a silent reload
- // but it will not fall back to previously loaded data if data can not be loaded
- // there are enough other cities available to populate the map sufficiently even if some do not load
+ }
- // pre-load the base map
- let baseMap = 'images/maps/basemap.webp';
- if (weatherParameters.state === 'HI') {
- baseMap = 'images/maps/radar-hawaii.png';
- } else if (weatherParameters.state === 'AK') {
- baseMap = 'images/maps/radar-alaska.png';
- }
- this.elem.querySelector('.map img').src = baseMap;
+ async refreshNearbyMarkers() {
+ if (!this.map || !this.active) return;
- // get user's location in x/y
- const sourceXY = utils.getXYFromLatitudeLongitude(this.weatherParameters.latitude, this.weatherParameters.longitude, mapOffsetXY.x, mapOffsetXY.y, weatherParameters.state);
+ this.map.invalidateSize(false);
+ this.map.setView([this.weatherParameters.latitude, this.weatherParameters.longitude], 6);
- // get latitude and longitude limits
- const minMaxLatLon = utils.getMinMaxLatitudeLongitude(sourceXY.x, sourceXY.y, mapOffsetXY.x, mapOffsetXY.y, this.weatherParameters.state);
+ const bounds = this.map.getBounds();
+ const markerKey = [
+ this.weatherParameters.latitude.toFixed(2),
+ this.weatherParameters.longitude.toFixed(2),
+ bounds.getSouth().toFixed(2),
+ bounds.getWest().toFixed(2),
+ bounds.getNorth().toFixed(2),
+ bounds.getEast().toFixed(2),
+ ].join(':');
- // get a target distance
- let targetDistance = 2.4;
- if (this.weatherParameters.state === 'HI') targetDistance = 1;
+ if (this.nearbyMarkers.length > 0 && this.nearbyMarkersKey === markerKey) return;
- // make station info into an array
- const stationInfoArray = Object.values(StationInfo).map((station) => ({ ...station, targetDistance }));
- // combine regional cities with station info for additional stations
- // stations are intentionally after cities to allow cities priority when drawing the map
- const combinedCities = [...RegionalCities, ...stationInfoArray];
-
- // Determine which cities are within the max/min latitude/longitude.
- const regionalCities = [];
- combinedCities.forEach((city) => {
- if (city.lat > minMaxLatLon.minLat && city.lat < minMaxLatLon.maxLat
- && city.lon > minMaxLatLon.minLon && city.lon < minMaxLatLon.maxLon - 1) {
- // default to 1 for cities loaded from RegionalCities, use value calculate above for remaining stations
- const targetDist = city.targetDistance || 1;
- // Only add the city as long as it isn't within set distance degree of any other city already in the array.
- const okToAddCity = regionalCities.reduce((acc, testCity) => {
- const distance = calcDistance(city.lon, city.lat, testCity.lon, testCity.lat);
- return acc && distance >= targetDist;
- }, true);
- if (okToAddCity) regionalCities.push(city);
- }
+ this.nearbyMarkers = clearMarkers(this.map, this.nearbyMarkers);
+ this.nearbyMarkers = await loadNearbyObservationMarkers(this.map, {
+ latitude: this.weatherParameters.latitude,
+ longitude: this.weatherParameters.longitude,
});
+ this.nearbyMarkers.forEach((marker) => marker.addTo(this.map));
+ this.nearbyMarkersKey = markerKey;
+ }
- // get a unit converter
- const temperatureConverter = temperatureUnit();
+ async ensureMap() {
+ if (this.map) return;
- // get regional forecasts and observations using centralized safe Promise handling
- const regionalDataAll = await safePromiseAll(regionalCities.map(async (city) => {
- try {
- const point = city?.point ?? (await getAndFormatPoint(city.lat, city.lon));
- if (!point) {
- if (debugFlag('verbose-failures')) {
- console.warn(`Unable to get Points for '${city.Name ?? city.city}'`);
- }
- return false;
- }
-
- // start off the observation task
- const observationPromise = utils.getRegionalObservation(point, city);
-
- const forecast = await safeJson(`https://api.weather.gov/gridpoints/${point.wfo}/${point.x},${point.y}/forecast`);
- if (!forecast) {
- if (debugFlag('verbose-failures')) {
- console.warn(`Regional Forecast request for ${city.Name ?? city.city} failed`);
- }
- return false;
- }
-
- // get XY on map for city
- const cityXY = utils.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, this.weatherParameters.state);
-
- // wait for the regional observation if it's not done yet
- const observation = await observationPromise;
-
- if (!observation) return false;
-
- // format the observation the same as the forecast
- const regionalObservation = {
- daytime: !!/\/day\//.test(observation.icon),
- temperature: temperatureConverter(observation.temperature.value),
- name: utils.formatCity(city.city),
- icon: observation.icon,
- x: cityXY.x,
- y: cityXY.y,
- };
-
- // preload the icon
- preloadImg(getSmallIcon(regionalObservation.icon, !regionalObservation.daytime));
-
- // filter out expired periods first, then use the next two periods for forecast
- const activePeriods = filterExpiredPeriods(forecast.properties.periods);
-
- // ensure we have enough periods for forecast
- if (activePeriods.length < 3) {
- console.warn(`Insufficient active periods for ${city.Name ?? city.city}: only ${activePeriods.length} periods available`);
- return false;
- }
-
- // group together the current observation and next two periods
- return [
- regionalObservation,
- utils.buildForecast(activePeriods[1], city, cityXY),
- utils.buildForecast(activePeriods[2], city, cityXY),
- ];
- } catch (error) {
- console.error(`Unexpected error getting Regional Forecast data for '${city.name ?? city.city}': ${error.message}`);
- return false;
- }
- }));
-
- // filter out any false (unavailable data)
- const regionalData = regionalDataAll.filter((data) => data);
-
- // test for data present
- if (regionalData.length === 0) {
- this.setStatus(STATUS.noData);
- return;
+ const mapElement = this.elem.querySelector('.leaflet-map');
+ if (!mapElement) {
+ throw new Error('Regional observations map container not found');
}
- // return the weather data and offsets
- this.data = {
- regionalData,
- mapOffsetXY,
- sourceXY,
- };
-
- this.setStatus(STATUS.loaded);
+ this.map = createMap(mapElement);
+ ({ baseLayer: this.baseLayer, boundaryLayer: this.boundaryLayer } = addBaseLayers(this.map));
}
drawCanvas() {
super.drawCanvas();
- // break up data into useful values
- const { regionalData: data, sourceXY } = this.data;
-
- // draw the header graphics
-
- // draw the appropriate title
const titleTop = this.elem.querySelector('.title.dual .top');
const titleBottom = this.elem.querySelector('.title.dual .bottom');
- if (this.screenIndex === 0) {
- titleTop.innerHTML = 'Regional';
- titleBottom.innerHTML = 'Observations';
- } else {
- const forecastDate = DateTime.fromISO(data[0][this.screenIndex].time);
+ titleTop.innerHTML = 'Regional';
+ titleBottom.innerHTML = 'Observations';
- // get the name of the day
- const dayName = forecastDate.toLocaleString({ weekday: 'long' });
- titleTop.innerHTML = 'Forecast for';
- // draw the title
- titleBottom.innerHTML = data[0][this.screenIndex].daytime
- ? dayName
- : `${dayName} Night`;
+ if (this.map) {
+ this.map.invalidateSize(false);
}
- // draw the map
- const scale = 640 / (mapOffsetXY.x * 2);
- const map = this.elem.querySelector('.map');
- map.style.transform = `scale(${scale}) translate(-${sourceXY.x}px, -${sourceXY.y}px)`;
-
- const cities = data.map((city) => {
- const fill = {};
- const period = city[this.screenIndex];
-
- fill.icon = { type: 'img', src: getSmallIcon(period.icon, !period.daytime) };
- fill.city = period.name;
- const { temperature } = period;
- fill.temp = temperature;
-
- const { x, y } = period;
-
- const elem = this.fillTemplate('location', fill);
- elem.style.left = `${x}px`;
- elem.style.top = `${y}px`;
-
- return elem;
- });
-
- const locationContainer = this.elem.querySelector('.location-container');
- locationContainer.innerHTML = '';
- locationContainer.append(...cities);
-
this.finishDraw();
}
+
+ async showCanvas(navCmd) {
+ super.showCanvas(navCmd);
+ await this.refreshNearbyMarkers();
+ }
}
-const getAndFormatPoint = async (lat, lon) => {
- try {
- const point = await getPoint(lat, lon);
- if (!point) {
- return null;
- }
- return {
- x: point.properties.gridX,
- y: point.properties.gridY,
- wfo: point.properties.gridId,
- };
- } catch (error) {
- throw new Error(`Unexpected error getting point for ${lat},${lon}: ${error.message}`);
- }
-};
-
-// register display
registerDisplay(new RegionalForecast(6, 'regional-forecast'));
diff --git a/server/scripts/modules/utils/leaflet-weather-map.mjs b/server/scripts/modules/utils/leaflet-weather-map.mjs
new file mode 100644
index 0000000..4918210
--- /dev/null
+++ b/server/scripts/modules/utils/leaflet-weather-map.mjs
@@ -0,0 +1,155 @@
+import { safePromiseAll } from './fetch.mjs';
+import { loadData } from './data-loader.mjs';
+import { getSmallIconFromWmoCode } from '../icons.mjs';
+import { getOpenMeteoObservationSnapshot } from './weather.mjs';
+
+const BASE_MAP_URL = 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}';
+const BOUNDARY_MAP_URL = 'https://services.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}';
+const DEFAULT_MAX_NEARBY_MARKERS = 7;
+const MIN_CITY_DISTANCE_METERS = 25000;
+const MIN_MARKER_PIXEL_DISTANCE = 85;
+
+let radarCitiesCache = null;
+
+const createMap = (mapElement) => window.L.map(mapElement, {
+ zoomControl: false,
+ dragging: false,
+ touchZoom: false,
+ scrollWheelZoom: false,
+ doubleClickZoom: false,
+ boxZoom: false,
+ keyboard: false,
+ tap: false,
+ attributionControl: false,
+ preferCanvas: true,
+});
+
+const addBaseLayers = (map) => {
+ const baseLayer = window.L.tileLayer(BASE_MAP_URL, {
+ maxZoom: 10,
+ minZoom: 1,
+ crossOrigin: true,
+ className: 'radar-base-layer',
+ }).addTo(map);
+
+ const boundaryLayer = window.L.tileLayer(BOUNDARY_MAP_URL, {
+ maxZoom: 10,
+ minZoom: 1,
+ opacity: 0.6,
+ crossOrigin: true,
+ className: 'radar-boundary-layer',
+ }).addTo(map);
+
+ return { baseLayer, boundaryLayer };
+};
+
+const setPrimaryLocationMarker = (map, existingMarker, latitude, longitude) => {
+ if (existingMarker && map.hasLayer(existingMarker)) {
+ map.removeLayer(existingMarker);
+ }
+
+ return window.L.circleMarker([latitude, longitude], {
+ radius: 5,
+ color: '#000',
+ weight: 2,
+ fillColor: '#ff0',
+ fillOpacity: 1,
+ interactive: false,
+ className: 'location-marker',
+ }).addTo(map);
+};
+
+const loadRadarCities = async () => {
+ if (!radarCitiesCache) {
+ radarCitiesCache = await loadData('radarcities');
+ }
+ return radarCitiesCache ?? [];
+};
+
+const selectNearbyCities = (map, sourceLocation, cities, options = {}) => {
+ const {
+ maxMarkers = DEFAULT_MAX_NEARBY_MARKERS,
+ minCityDistanceMeters = MIN_CITY_DISTANCE_METERS,
+ minMarkerPixelDistance = MIN_MARKER_PIXEL_DISTANCE,
+ } = options;
+
+ const bounds = map.getBounds();
+ const currentLatLng = window.L.latLng(sourceLocation.latitude, sourceLocation.longitude);
+ const visibleCities = cities
+ .filter((city) => bounds.contains([city.lat, city.lon]))
+ .filter((city) => currentLatLng.distanceTo([city.lat, city.lon]) > minCityDistanceMeters)
+ .map((city) => ({
+ ...city,
+ distance: currentLatLng.distanceTo([city.lat, city.lon]),
+ point: map.latLngToContainerPoint([city.lat, city.lon]),
+ }))
+ .sort((a, b) => a.distance - b.distance);
+
+ const selected = [];
+ visibleCities.forEach((city) => {
+ if (selected.length >= maxMarkers) return;
+ const overlaps = selected.some((existingCity) => existingCity.point.distanceTo(city.point) < minMarkerPixelDistance);
+ if (!overlaps) selected.push(city);
+ });
+
+ if (selected.length === 0 && visibleCities.length > 0) {
+ selected.push(visibleCities[0]);
+ }
+
+ return selected;
+};
+
+const buildNearbyWeatherMarker = (city, observation) => {
+ const icon = getSmallIconFromWmoCode(observation.weatherCode, observation.isDay);
+ const markerHtml = `
+
\n*/\n\n.scanlines {\n position: relative;\n overflow: hidden;\n isolation: isolate;\n\n /*\n This is the actual rendered weather area in your HTML.\n Applying the softness here affects the maps/text/icons themselves.\n */\n #container {\n position: relative;\n z-index: 1;\n transform: translateZ(0);\n will-change: filter;\n\n filter:\n blur($crt-soft-blur)\n saturate($crt-saturation)\n contrast($crt-contrast)\n brightness($crt-brightness);\n }\n\n /*\n Red fringe overlay\n */\n #container::before,\n #container::after {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 3;\n }\n\n #container::before {\n background:\n linear-gradient(\n to right,\n rgba(255, 0, 0, $crt-rgb-opacity) 0%,\n rgba(255, 0, 0, 0.01) 15%,\n rgba(255, 0, 0, 0.00) 50%,\n rgba(255, 0, 0, 0.01) 85%,\n rgba(255, 0, 0, $crt-rgb-opacity) 100%\n );\n transform: translateX($crt-r-shift);\n filter: blur($crt-bleed-blur);\n mix-blend-mode: screen;\n }\n\n /*\n Blue fringe overlay\n */\n #container::after {\n background:\n linear-gradient(\n to right,\n rgba(0, 140, 255, $crt-rgb-opacity) 0%,\n rgba(0, 140, 255, 0.01) 15%,\n rgba(0, 140, 255, 0.00) 50%,\n rgba(0, 140, 255, 0.01) 85%,\n rgba(0, 140, 255, $crt-rgb-opacity) 100%\n );\n transform: translateX($crt-b-shift);\n filter: blur($crt-bleed-blur);\n mix-blend-mode: screen;\n }\n\n /*\n Moving scanline\n */\n &:before,\n &:after {\n display: block;\n pointer-events: none;\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n }\n\n &:before {\n height: var(--scanline-thickness, $scan-width);\n z-index: $scan-z-index + 2;\n background: $scan-color;\n opacity: $scan-opacity;\n @include scan-moving($scan-moving-line);\n }\n\n /*\n Regular scanline mask\n */\n &:after {\n top: 0;\n bottom: 0;\n z-index: $scan-z-index;\n background: repeating-linear-gradient(\n to bottom,\n transparent 0,\n transparent var(--scanline-thickness, $scan-width),\n $scan-color var(--scanline-thickness, $scan-width),\n $scan-color calc(var(--scanline-thickness, $scan-width) * 2)\n );\n @include scan-crt($scan-crt);\n }\n\n /*\n Vignette layer\n Added as an inset shadow so you don't need extra HTML\n */\n box-shadow:\n inset 0 0 80px rgba(0, 0, 0, $crt-vignette-opacity),\n inset 0 0 18px rgba(255, 255, 255, $crt-glow-opacity);\n}\n\n/* =========================================================\n OPTIONAL: only affect active weather panels, not menus\n ========================================================= */\n\n/*\n If the controls / bottom nav get too blurry, move the blur\n from #container to the weather slides only:\n*/\n.scanlines.crt-panels-only {\n #container {\n filter: none;\n }\n\n .weather-display {\n filter:\n blur($crt-soft-blur)\n saturate($crt-saturation)\n contrast($crt-contrast)\n brightness($crt-brightness);\n\n transform: translateZ(0);\n }\n}\n\n/* =========================================================\n OPTIONAL: make text slightly glow like old TV phosphors\n ========================================================= */\n\n.scanlines {\n .header,\n .main,\n .scroll,\n .date-time,\n .city,\n .temp,\n .condition,\n .location,\n .label,\n .value,\n .title {\n text-shadow:\n 0 0 1px rgba(255, 255, 255, 0.18),\n 0 0 2px rgba(255, 255, 255, 0.06);\n }\n}\n\n/* =========================================================\n ANIMATIONS\n ========================================================= */\n\n@keyframes scanline {\n 0% {\n transform: translate3d(0, 200000%, 0);\n }\n}\n\n@keyframes scanlines {\n 0% {\n background-position: 0 50%;\n }\n}\n\n"],"file":"ws.min.css"}
\ No newline at end of file
+{"version":3,"sources":["_page.scss","shared/_utils.scss","_weather-display.scss","shared/_colors.scss","_current-weather.scss","_extended-forecast.scss","_hourly.scss","_hourly-graph.scss","_travel.scss","_latest-observations.scss","_local-forecast.scss","_progress.scss","_radar.scss","_regional-forecast.scss","_almanac.scss","_hazards.scss","_media.scss","_spc-outlook.scss","_server-observations.scss","_linux-news.scss","shared/_scanlines.scss"],"names":[],"mappings":"AAGA,WACC,uBACA,iDACA,kBAGD,KACC,uBACA,SAEA,mCAJD,KAKE,sBACA,YAIA,mCADD,OAEE,eAIF,WACC,WACA,YACA,gBACA,YAEA,iCAIF,UACC,gBACA,YAEA,mBACC,qBACA,YACA,iBAEA,8BACC,YACA,sBAGD,0BACC,eACA,yBAEA,mCAJD,0BAKE,sBACA,YAQA,uCACC,aAEA,mCAHD,uCAIE,sBAKD,mCADD,wCAEE,cAKH,qCACC,sBAEA,mCAHD,qCAIE,uBAGD,yCACC,iBAMJ,iCAEC,uBAGD,uBACC,yBACA,gBACA,eACA,gBACA,qBAGA,sBACA,WACA,sBAEA,mCAZD,uBAaE,sBACA,WACA,uBAOH,0BACC,sBACA,sBACA,kBACA,aAEA,mCAND,0BAOE,uBAGD,8BAEC,mBACA,gBACA,uBACA,eAEA,uCACC,sBACA,WAMH,QACC,cACA,sBACA,WACA,WACA,gBACA,SAEA,aACC,gBAIF,iBACC,YAGD,YACC,YACA,aACA,kBAEA,kBACC,YAIF,eACC,gBAGD,YACC,aACA,iBACA,sBACA,sBAGD,gBACC,OACA,mBACA,aACA,sBACA,uBAGD,aACC,gBACA,aACA,sBACA,sBAGD,iBACC,OACA,kBACA,aACA,sBACA,uBAGD,cAEC,aACA,mBACA,sBAEA,WACA,YAEA,oBACC,YAGD,mCAbD,cAcE,0BAKF,kBACC,iBACA,kBAIA,yBAND,kBAOE,gBAGD,yBAVD,kBAWE,gBAGD,yBAdD,kBAeE,gBAGD,yBAlBD,kBAmBE,gBAGD,yBAtBD,kBAuBE,gBAIF,kBACC,OACA,gBAID,oBACC,OACA,kBAGD,mBACC,OACA,iBAGD,oBACC,aAGD,WACC,WACA,aACA,mBACA,sBACA,WACA,gBAGD,eACC,iBACA,kBAGD,eACC,OACA,gBAGD,iBACC,OACA,kBAGD,gBACC,OACA,iBAGD,YACC,kBACA,kBAGD,YACC,uBAGD,eACC,YAGD,WACC,gCACA,0DACA,kBAGD,WACC,6BACA,uDACA,kBAGD,WACC,6BACA,uDACA,kBAGD,SACC,uBACA,eACA,WAGD,WACC,kBACA,YACA,aAEA,kDACA,qBACA,4BAGD,iBACC,mBACA,oBACA,iDACA,4BAGD,wDAGC,YACA,aAGD,SACC,YACA,aACA,eACA,yBACA,aACA,mBACA,kBACA,uBAEA,gBACC,2BACA,eACA,WACA,kBAGD,kBACC,mBAGD,uBACC,eAIF,SACC,iBACA,gBAGD,UACC,mBAGD,2BAEC,mBCzXA,4FAEC,WAGD,mDACC,WACA,eAGD,2CACC,UAGD,6CACC,aAGD,+CACC,aDyWD,mDACC,WAGD,oCAEC,4FAEC,WAGD,mDACC,WACA,eAGD,2CACC,WAGD,6CACC,oBAGD,+CACC,qBAIF,uCACC,cACA,sBACA,eAEA,qDACC,aAEA,+DACC,eACA,UAMJ,kBACC,sBAGA,yBACC,wBACC,oBAKH,kCAEC,aACA,mBACA,uBACA,qBAEA,sDACC,YAIF,oDAEC,kBAGD,8DAEC,aACA,mBACA,gCACA,WACA,WACA,kBACA,WAIA,6BACC,aAIF,WACC,eAGD,iBACC,qBAEA,qBACC,aAGD,sBACC,qBAKA,wBACC,qBAGD,yBACC,aAMH,SACC,mBACA,UACA,8BAGD,2BACC,kBACA,UACA,8CAGD,cACC,YACA,6BACA,aACA,6BACA,eAGC,qBACC,qBACA,UAGD,2BACC,qBACA,gBACA,iFACA,YACA,cACA,mBAGD,yDAEC,kBACA,qBACA,oBACA,YACA,gBACA,eACA,gBACA,iBACA,sBACA,eACA,yBACA,sBACA,qBACA,iBACA,2BACA,8BACA,0BACA,iBAGD,wBACC,oBAGD,yCACC,8BAGD,iCACC,cACA,8BAGD,+EAEC,YACA,iBACA,eACA,iBAGD,4BACC,qBACA,wBACA,kBACA,iBAGD,qFAEC,0BACA,oBAGD,wBACC,cACA,yBACA,qBACA,gCACA,8SACA,iEACA,+DACA,sGAGD,8BACC,YAGD,4DAEC,yBACA,6BACA,qBACA,gCACA,8SACA,iEACA,+DACA,sGAGD,wEAEC,YAGD,+BACC,yBACA,qBACA,gCACA,iDACA,sBACA,YAGD,iCACC,cACA,sBACA,qBACA,gCAGD,8EAEC,cAGD,kCACC,cAGD,oCAEC,qFAEC,0BACA,oBAGD,wBACC,cACA,yBACA,qBACA,gCACA,8SACA,iEACA,+DACA,sGAGD,8BACC,YAGD,4DAEC,yBACA,6BACA,qBACA,gCACA,8SACA,iEACA,+DACA,sGAGD,wEAEC,YAGD,+BACC,yBACA,qBACA,gCACA,iDACA,sBACA,YAGD,iCACC,cACA,sBACA,qBACA,gCAGD,8EAEC,cAGD,kCACC,eAIF,mCAEC,qFAEC,0BACA,oBAGD,wBACC,cACA,yBACA,qBACA,kCACA,8SACA,iEACA,+DACA,sGAGD,8BACC,YAGD,4DAEC,yBACA,6BACA,qBACA,8SACA,iEACA,+DACA,sGAGD,wEAEC,YAGD,+BACC,yBACA,qBACA,8CACA,sBACA,YAGD,iCACC,cACA,yBACA,qBACA,kCAGD,8EAEC,cAGD,kCACC,eAMJ,mBACC,WACA,aAGD,yBACC,aAID,kCACC,wBAMA,sBACC,wBAIF,SACC,aACA,8BACA,gBEnzBD,iBACC,YACA,aACA,gBACA,kBACA,kDAGA,WAEA,sBACC,aAGD,2BACC,aAGD,yBACC,YACA,YACA,iBACA,kBACA,WAEA,gCACC,MC7BW,KFMb,YACC,6JCwBC,uBACA,eACA,kBACA,YAEA,uCACC,WACA,SAGD,qCACC,WAEA,yCACC,kBAGD,0CACC,SAGD,6CACC,SAMH,+BACC,SACA,UACA,kBACA,WAGD,oCACC,kBACA,SACA,WAGD,uCACC,SAGD,oCACC,gBACA,MC7ES,KD8ET,6BACA,eD1EF,YACC,6JC2EC,WACA,YACA,iBACA,kBAEA,yCACC,iBAKH,uBACC,kBAEA,kCACC,YACA,aACA,aACA,gBAEA,4CACC,aACA,aAIF,+BACC,iBACA,kBACA,yBAOH,mBACC,aDjHA,YACC,6JCkHD,YACA,YACA,gBACA,eACA,kBACA,WACA,UAEA,0BACC,yBAGD,qCACC,YAEA,gGAEC,iBACA,kBACA,gBACA,mBAGD,oDACC,YACA,6BACA,eACA,iBAGD,4CACC,uBACA,eAEA,yDACC,iBACA,kBAYJ,yBACC,YACA,mBAEA,2CACC,kBExKA,iDACC,YACA,YACA,qBACA,gBACA,iBACA,kBHNF,YACC,6JGSC,sDACC,gCACA,eAID,uDACC,UACA,6BACA,eACA,iBACA,iBAEA,4DACC,mBAEA,sIAEC,qBAGD,mEACC,iBAGD,mEACC,YACA,kBAQJ,oDACC,kBAGD,kDACC,6BACA,eAIA,sDACC,cACA,cAIF,4DACC,iBACA,aAEA,gEACC,UAGD,kEACC,iBAIF,wDACC,iBACA,eAGD,sDACC,MDtFW,KCuFX,gBACA,mBACA,gBACA,gBACA,iBCxFH,wCACC,oDAIA,wDACC,gBACA,iBAGD,8CJPA,YACC,6JIQA,YACA,aACA,YACA,qBACA,gBACA,uBACA,eAEA,oDACC,yBACA,kBACA,MF1BW,KE6BZ,yDACC,kBACA,YACA,eAGD,oDACC,kBACA,YAEA,wDACC,gBAIF,4DACC,WAEA,+EACC,qBACA,UACA,mBAEA,mFACC,kBAGD,sFACC,6BACA,eAGD,yFACC,MFhDU,QEmDX,yFACC,MFlES,KGIb,mCACC,kBAEA,mDACC,iBHJa,QGKb,YACA,kBACA,WAGD,mDACC,gBACA,QACA,UAEA,uDACC,qBACA,6BACA,eACA,MHpBiB,KGqBjB,kBACA,UACA,ULpBH,YACC,6JKuBC,yDACC,WAGD,yDACC,WAGD,yDACC,WAIF,iDACC,iBACA,iBAEA,qGAMA,6DACC,6BACA,eACA,YACA,MHzDU,KFMb,YACC,6JKoDE,kBAEA,iEACC,kBACA,gBACA,QAGD,mEACC,UAGD,mEACC,WACA,WACA,kBACA,UAGD,mEACC,WAGD,mEACC,WAEA,8EACC,WAGD,8EACC,MH5ES,QGgFX,mEACC,WACA,YACA,iBC9FL,mBACC,wDAGC,kCACC,kBACA,SACA,WACA,YACA,6BACA,eNPF,YACC,6JMQC,iBAEA,sCACC,iBAGD,+CACC,UAGD,4CACC,YAGD,yCACC,cAGD,wCACC,WASF,6CACC,kBAGD,gDACC,6BACA,eACA,MJ/CkB,KFGpB,YACC,6JM6CC,iBACA,kBAGD,iDACC,WACA,UACA,YACA,YAEA,wDACC,kBACA,2BACA,mBAEA,4DACC,SAGD,4DACC,WAGD,4DACC,WAGD,4DACC,WAGD,4DACC,WAQH,gDACC,QACA,UAEA,oDACC,YACA,aAIF,iDACC,QACA,SACA,WACA,aAEA,wDACC,iBACA,UAEA,4DACC,QAGD,4DACC,oBAGD,4DACC,uBAGD,4DACC,WAKH,yDACC,iBJ/Ha,QIgIb,YACA,kBACA,WAGD,yDACC,gBACA,QACA,UAGA,+DACC,WAGD,+DACC,WAGD,+DACC,WCpJH,mCACC,kBAEA,mDACC,iBLJa,QKKb,YACA,gBACA,QACA,WACA,UACA,gBAEA,uDACC,qBACA,6BACA,eACA,MLjBiB,KKkBjB,kBACA,UACA,UPjBH,YACC,6JOoBC,yDACC,WACA,kBAEA,6DACC,WAID,8DACC,WACA,WAKH,iDACC,iBACA,iBAEA,qGAMA,6DACC,6BACA,eACA,YACA,MLzDU,KFMb,YACC,6JOoDE,kBAEA,iEACC,kBACA,gBACA,QAGD,mEACC,UAGD,mEACC,WACA,WACA,kBACA,UAEA,uEACC,eAIF,mEACC,WACA,kBAEA,uEACC,WAGD,wEACC,WACA,WCvFL,2CACC,kBAEA,2DACC,YACA,kBACA,WAGD,2DACC,QAEA,+DACC,qBACA,6BACA,eACA,kBACA,URhBH,YACC,6JQmBC,iEAEC,aAEA,sEACC,qBAKH,iDACC,WAGD,oDACC,WAGD,iDACC,WAGD,8DACC,iBACA,iBAEA,+EACC,uBACA,eRhDH,YACC,6JQiDE,kBACA,YAEA,mFACC,kBACA,QAGD,qFACC,gBACA,iBC9DJ,4CACC,kBACA,SACA,gBACA,sBACA,aACA,gBAGD,4CACC,kBAGD,2CACC,uBACA,eACA,yBTdD,YACC,6JSeA,iBACA,iBCpBF,2BVGC,YACC,6JUFD,gCACA,eAEA,sCACC,kBACA,SACA,gBACA,sBACA,aACA,gBACA,iBAEA,4CACC,kBAEA,kDACC,mBAEA,yDACC,mFAIF,mDACC,kBACA,iBACA,UACA,QAEA,uDACC,iBRnBM,QQoBN,aACA,iBVjBJ,yHAEC,WAGD,+DACC,WACA,eAGD,2DACC,UAGD,4DACC,aAGD,6DACC,aUGE,gaAMC,cAYJ,2BACC,GACC,4BAGD,KACC,4BAIF,+DACC,sBACA,sBACA,iBACA,YACA,kBACA,aAEA,oEACC,cAGD,6EACC,YACA,WACA,YACA,6OAiBA,sBACA,6BACA,mCACA,+BACA,wCAGD,sEACC,kBACA,QACA,UACA,sBACA,WACA,YACA,6BClHH,4BACC,oDAEA,oCACC,YAEA,gDACC,WACA,+BACA,iBACA,eACA,WAEA,qDACC,SAGD,wDACC,SAIF,2CACC,kBACA,UACA,YACA,eACA,uBACA,eACA,iBX1BF,YACC,6JW2BC,kBAEA,wDACC,aACA,uBACA,QAEA,8DACC,aACA,sBACA,mBACA,WACA,QAGD,6DACC,cACA,sBACA,WACA,YACA,UAGD,+DACC,yBAGD,+DACC,yBAGD,+DACC,yBAGD,+DACC,yBAGD,+DACC,yBAGD,+DACC,yBAGD,+DACC,6BACA,eACA,cACA,mBAIF,kDACC,gBAGD,iDACC,kBACA,mBACA,UACA,6BACA,eACA,WAMJ,6BACC,gBACA,aAEA,wCACC,kBACA,YAEA,qDACC,kBACA,YAGD,+CACC,YAGD,6CACC,YACA,WAGD,qDACC,YACA,WACA,mBAGD,2DACC,mBACA,oBAGD,kIAEC,oEAGD,0IAEC,kEAGD,sMAGC,aAGD,yDACC,gBACA,sBACA,kBAGD,+DACC,yBACA,SAEA,4FACC,aACA,sBACA,mBACA,eACA,gBACA,8BACA,sBACA,0BACA,WACA,kBAGD,qEACC,6BACA,eACA,cACA,mBACA,kBACA,2BAGD,wEACC,aACA,mBACA,QAGD,qEACC,uBACA,eACA,cACA,WACA,2BAGD,mEACC,WACA,YAMJ,uBACC,iDC9MD,wCACC,oDAGD,yCACC,kBACA,gBACA,UAEA,8CACC,kBACA,QAGD,sDACC,YACA,WACA,mBAGD,4DACC,mBACA,oBAGD,oIAEC,oEAGD,4IAEC,kEAGD,yMAGC,aAGD,0DACC,gBACA,sBACA,kBAGD,gEACC,yBACA,SAEA,6FACC,aACA,sBACA,mBACA,eACA,gBACA,8BACA,sBACA,0BACA,WACA,kBAGD,sEACC,6BACA,eACA,cACA,mBACA,kBZlEF,YACC,6JYqEA,yEACC,aACA,mBACA,QAGD,sEACC,uBACA,eACA,cACA,MVtFW,KFMb,YACC,6JYmFA,oEACC,WACA,YCzFH,8BACC,oDAGD,+BACC,uBACA,ebHA,YACC,6JaKD,oCAGC,aACA,qCACA,kCACA,aACA,yBACA,kBACA,iBAEA,+CAEC,WACA,YACA,UACA,SACA,kBAGA,sDACC,MX9BiB,KW+BjB,kBAID,yDAEC,iBAID,oDACC,kBAKH,qCACC,kBACA,iBACA,iBAEA,4CACC,MXrDkB,KWsDlB,kBAGD,0CACC,qBACA,kBACA,YAEA,gDAEC,kBAGD,gDACC,kBACA,UCrEJ,8BACC,oDAIA,oCACC,kBACA,aACA,yBAGA,kDACC,iBACA,iBAEA,0DACC,uBACA,eACA,WdfH,YACC,6JcgBE,kBACA,yBACA,gBACA,iBACA,kBACA,oBAMJ,yBACC,iDCnCD,OACC,aAGD,sBACC,aACA,kBAEA,gCACC,qBAEA,uCACC,aAGD,wCACC,cAKA,+CACC,cAGD,gDACC,aAQH,qCACC,aACA,kBACA,QACA,4BACA,WACA,sBACA,kBACA,YAEA,mCAVD,qCAWE,0BAGD,uDACC,yBACA,cACA,gBACA,mBAGD,0CACC,cCrDH,kCACC,oDAKA,yCACC,kBACA,QACA,gBACA,sBACA,aACA,gBAGD,2CACC,kBACA,WACA,6BACA,ehBhBD,YACC,6JgBmBA,uDACC,kBACA,UACA,YAEA,oEACC,WAGD,oEACC,UAGD,oEACC,UAGD,oEACC,UAGD,oEACC,UAGD,oEACC,SAKH,oCACC,kBACA,UAEA,yCACC,YAEA,mDACC,kBACA,uBACA,eACA,YACA,iBhB/DH,YACC,6JgBgEE,iBAGD,mDACC,kBACA,YACA,YACA,WACA,gBACA,gCACA,gGC7EH,gEACC,eAMD,2CACC,uBACA,iBAGD,iDACC,kBACA,SACA,sBACA,aACA,gBAGD,qDACC,kBACA,uBACA,eACA,iBACA,WACA,yBACA,kBjBzBD,YACC,6JiB2BA,kEACC,aACA,cACA,sBAGD,kEACC,mBACA,uBACA,sBACA,kBCzCF,kCACC,uBACA,iBAGD,wCACC,kBACA,SACA,gBACA,sBACA,aACA,gBAGD,0CACC,kBAEA,qDACC,aACA,sBACA,cACA,aACA,sBACA,8BAGD,iDACC,aACA,gBAGD,oDACC,uBACA,eACA,iBACA,WACA,yBlBjCF,YACC,6JkBkCC,kBAGD,iDACC,uBACA,eACA,iBACA,WlB1CF,YACC,6JkB2CC,gBCiBH,WACE,kBACA,gBACA,kBAMA,sBACE,kBACA,UACA,wBACA,mBAEA,OACE,4DASJ,2DAEE,WACA,kBACA,QACA,oBACA,UAGF,8BACE,WACE,8JAQF,6BACA,mBACA,sBAMF,6BACE,WACE,wKAQF,4BACA,mBACA,sBAMF,mCAEE,cACA,oBACA,WACA,kBACA,OACA,QAGF,kBACE,sCACA,mBACA,WA/IS,eAgJT,QA5IW,IAwCX,sCA2GF,iBACE,MACA,SACA,QAzJW,WA0JX,2NArHA,eAsBJ,WA6GE,WACE,kEAaF,sCACE,YAGF,4CACE,OACE,4DAKF,wBASF,gNAWE,YACE,wDASN,oBACE,GACE,sCAIJ,qBACE,GACE","sourcesContent":["@use 'shared/_utils'as u;\n@use 'shared/_colors'as c;\n\n@font-face {\n\tfont-family: \"Star4000\";\n\tsrc: url('../fonts/Star4000.woff') format('woff');\n\tfont-display: swap;\n}\n\nbody {\n\tfont-family: \"Star4000\";\n\tmargin: 0;\n\n\t@media (prefers-color-scheme: dark) {\n\t\tbackground-color: #000000;\n\t\tcolor: white;\n\t}\n\n\ta {\n\t\t@media (prefers-color-scheme: dark) {\n\t\t\tcolor: lightblue;\n\t\t}\n\t}\n\n\t&.kiosk {\n\t\tmargin: 0px;\n\t\tpadding: 0px;\n\t\toverflow: hidden;\n\t\twidth: 100vw;\n\t\t// Always use black background in kiosk mode, regardless of light/dark preference\n\t\tbackground-color: #000000 !important;\n\t}\n}\n\n#divQuery {\n\tmax-width: 640px;\n\tpadding: 8px;\n\n\t.buttons {\n\t\tdisplay: inline-block;\n\t\twidth: 150px;\n\t\ttext-align: right;\n\n\t\t#imgGetGps {\n\t\t\theight: 13px;\n\t\t\tvertical-align: middle;\n\t\t}\n\n\t\tbutton {\n\t\t\tfont-size: 16pt;\n\t\t\tborder: 1px solid darkgray;\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbackground-color: #000000;\n\t\t\t\tcolor: white;\n\t\t\t}\n\n\t\t}\n\n\t\t#btnGetGps {\n\t\t\timg {\n\n\t\t\t\t&.dark {\n\t\t\t\t\tdisplay: none;\n\n\t\t\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&.light {\n\t\t\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\t\t\tdisplay: none;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&.active {\n\t\t\t\tbackground-color: black;\n\n\t\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\t\tbackground-color: white;\n\t\t\t\t}\n\n\t\t\t\timg {\n\t\t\t\t\tfilter: invert(1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tinput,\n\tbutton {\n\t\tfont-family: \"Star4000\";\n\t}\n\n\t#txtLocation {\n\t\twidth: calc(100% - 170px);\n\t\tmax-width: 490px;\n\t\tfont-size: 16pt;\n\t\tmin-width: 200px;\n\t\tdisplay: inline-block;\n\n\t\t// Ensure consistent styling across light and dark modes\n\t\tbackground-color: white;\n\t\tcolor: black;\n\t\tborder: 2px inset #808080;\n\n\t\t@media (prefers-color-scheme: dark) {\n\t\t\tbackground-color: #000000;\n\t\t\tcolor: white;\n\t\t\tborder: 2px inset #808080;\n\t\t}\n\t}\n\n\n}\n\n.autocomplete-suggestions {\n\tbackground-color: #ffffff;\n\tborder: 1px solid #000000;\n\tposition: absolute;\n\tz-index: 9999;\n\n\t@media (prefers-color-scheme: dark) {\n\t\tbackground-color: #000000;\n\t}\n\n\tdiv {\n\t\t/*padding: 2px 5px;*/\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tfont-size: 16pt;\n\n\t\t&.selected {\n\t\t\tbackground-color: #0000ff;\n\t\t\tcolor: #ffffff;\n\t\t}\n\t}\n\n}\n\n#divTwc {\n\tdisplay: block;\n\tbackground-color: #000000;\n\tcolor: #ffffff;\n\twidth: 100%;\n\tmax-width: 640px;\n\tmargin: 0; // Ensure edge-to-edge display\n\n\t&.wide {\n\t\tmax-width: 854px;\n\t}\n}\n\n.content-wrapper {\n\tpadding: 8px;\n}\n\n#divTwcMain {\n\twidth: 640px;\n\theight: 480px;\n\tposition: relative;\n\n\t.wide & {\n\t\twidth: 854px;\n\t}\n}\n\n.kiosk #divTwc {\n\tmax-width: unset;\n}\n\n#divTwcLeft {\n\tdisplay: none;\n\ttext-align: right;\n\tflex-direction: column;\n\tvertical-align: middle;\n}\n\n#divTwcLeft>div {\n\tflex: 1;\n\tpadding-right: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n}\n\n#divTwcRight {\n\ttext-align: left;\n\tdisplay: none;\n\tflex-direction: column;\n\tvertical-align: middle;\n}\n\n#divTwcRight>div {\n\tflex: 1;\n\tpadding-left: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n}\n\n#divTwcBottom {\n\t/* visibility: hidden; */\n\tdisplay: flex;\n\tflex-direction: row;\n\tbackground-color: #000000;\n\n\tcolor: #ffffff;\n\twidth: 640px;\n\n\t.wide & {\n\t\twidth: 854px;\n\t}\n\n\t@media (prefers-color-scheme: dark) {\n\t\tbackground-color: rgb(48, 48, 48);\n\t}\n\n}\n\n#divTwcBottom>div {\n\tpadding-left: 6px;\n\tpadding-right: 6px;\n\n\t// Use font-size scaling instead of zoom/transform to avoid layout gaps and preserve icon tap targets.\n\t// While not semantically ideal, it works well for our fixed-layout design.\n\t@media (max-width: 550px) {\n\t\tfont-size: 0.90em;\n\t}\n\n\t@media (max-width: 500px) {\n\t\tfont-size: 0.80em;\n\t}\n\n\t@media (max-width: 450px) {\n\t\tfont-size: 0.70em;\n\t}\n\n\t@media (max-width: 400px) {\n\t\tfont-size: 0.60em;\n\t}\n\n\t@media (max-width: 350px) {\n\t\tfont-size: 0.50em;\n\t}\n}\n\n#divTwcBottomLeft {\n\tflex: 1;\n\ttext-align: left;\n\n}\n\n#divTwcBottomMiddle {\n\tflex: 0;\n\ttext-align: center;\n}\n\n#divTwcBottomRight {\n\tflex: 1;\n\ttext-align: right;\n}\n\n#divTwcNavContainer {\n\tdisplay: none;\n}\n\n#divTwcNav {\n\twidth: 100%;\n\tdisplay: flex;\n\tflex-direction: row;\n\tbackground-color: #000000;\n\tcolor: #ffffff;\n\tmax-width: 640px;\n}\n\n#divTwcNav>div {\n\tpadding-left: 6px;\n\tpadding-right: 6px;\n}\n\n#divTwcNavLeft {\n\tflex: 1;\n\ttext-align: left;\n}\n\n#divTwcNavMiddle {\n\tflex: 0;\n\ttext-align: center;\n}\n\n#divTwcNavRight {\n\tflex: 1;\n\ttext-align: right;\n}\n\n#imgPause1x {\n\tvisibility: hidden;\n\tposition: absolute;\n}\n\n.HideCursor {\n\tcursor: none !important;\n}\n\n#txtScrollText {\n\twidth: 475px;\n}\n\n@font-face {\n\tfont-family: 'Star4000 Extended';\n\tsrc: url('../fonts/Star4000 Extended.woff') format('woff');\n\tfont-display: swap;\n}\n\n@font-face {\n\tfont-family: 'Star4000 Large';\n\tsrc: url('../fonts/Star4000 Large.woff') format('woff');\n\tfont-display: swap;\n}\n\n@font-face {\n\tfont-family: 'Star4000 Small';\n\tsrc: url('../fonts/Star4000 Small.woff') format('woff');\n\tfont-display: swap;\n}\n\n#display {\n\tfont-family: \"Star4000\";\n\tmargin: 0 0 0 0;\n\twidth: 100%;\n}\n\n#container {\n\tposition: relative;\n\twidth: 640px;\n\theight: 480px;\n\t// overflow: hidden;\n\tbackground-image: url(../images/backgrounds/1.png);\n\ttransform-origin: 0 0;\n\tbackground-repeat: no-repeat;\n}\n\n.wide #container {\n\tpadding-left: 107px;\n\tpadding-right: 107px;\n\tbackground: url(../images/backgrounds/1-wide.png);\n\tbackground-repeat: no-repeat;\n}\n\n#divTwc:fullscreen #container,\n.kiosk #divTwc #container {\n\t// background-image: none;\n\twidth: unset;\n\theight: unset;\n}\n\n#loading {\n\twidth: 640px;\n\theight: 480px;\n\tmax-width: 100%;\n\ttext-shadow: 4px 4px black;\n\tdisplay: flex;\n\talign-items: center;\n\ttext-align: center;\n\tjustify-content: center;\n\n\t.title {\n\t\tfont-family: Star4000 Large;\n\t\tfont-size: 36px;\n\t\tcolor: yellow;\n\t\tmargin-bottom: 0px;\n\t}\n\n\t.version {\n\t\tmargin-bottom: 35px;\n\t}\n\n\t.instructions {\n\t\tfont-size: 18pt;\n\t}\n}\n\n.heading {\n\tfont-weight: bold;\n\tmargin-top: 15px;\n}\n\n#settings {\n\tmargin-bottom: 15px;\n}\n\n#enabledDisplays,\n#settings {\n\tmargin-bottom: 15px;\n\t@include u.status-colors();\n\n\t.press-here {\n\t\tcolor: white;\n\t}\n\n\t@media (prefers-color-scheme: light) {\n\n\t\t.loading,\n\t\t.retrying {\n\t\t\tcolor: hsl(60, 100%, 30%);\n\t\t}\n\n\t\t.press-here {\n\t\t\tcolor: black;\n\t\t\tcursor: pointer;\n\t\t}\n\n\t\t.failed {\n\t\t\tcolor: hsl(0, 100%, 30%);\n\t\t}\n\n\t\t.no-data {\n\t\t\tcolor: hsl(0, 0%, 30%);\n\t\t}\n\n\t\t.disabled {\n\t\t\tcolor: hsl(0, 0%, 30%);\n\t\t}\n\t}\n\n\tlabel {\n\t\tdisplay: block;\n\t\tmax-width: fit-content;\n\t\tcursor: pointer;\n\n\t\t.alert {\n\t\t\tdisplay: none;\n\n\t\t\t&.show {\n\t\t\t\tdisplay: inline;\n\t\t\t\tcolor: red;\n\t\t\t}\n\t\t}\n\t}\n}\n\n#divTwcBottom img {\n\ttransform: scale(0.75);\n\n\t// Make icons larger in widescreen mode on mobile\n\t@media (max-width: 550px) {\n\t\t.wide & {\n\t\t\ttransform: scale(1.0); // Larger icons in widescreen\n\t\t}\n\t}\n}\n\n#divTwc:fullscreen,\n.kiosk #divTwc {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\talign-content: center;\n\n\t&.no-cursor {\n\t\tcursor: none;\n\t}\n}\n\n#divTwc:fullscreen #display,\n.kiosk #divTwc #display {\n\tposition: relative;\n}\n\n#divTwc:fullscreen #divTwcBottom,\n.kiosk #divTwc #divTwcBottom {\n\tdisplay: flex;\n\tflex-direction: row;\n\tbackground-color: rgb(0 0 0 / 0.5);\n\tcolor: #ffffff;\n\twidth: 100%;\n\tposition: absolute;\n\tbottom: 0px;\n}\n\n.kiosk {\n\t#divTwc #divTwcBottom {\n\t\tdisplay: none;\n\t}\n}\n\n.navButton {\n\tcursor: pointer;\n}\n\n#ToggleScanlines {\n\tdisplay: inline-block;\n\n\t.on {\n\t\tdisplay: none;\n\t}\n\n\t.off {\n\t\tdisplay: inline-block;\n\t}\n\n\n\t&.on {\n\t\t.on {\n\t\t\tdisplay: inline-block;\n\t\t}\n\n\t\t.off {\n\t\t\tdisplay: none;\n\t\t}\n\n\t}\n}\n\n.visible {\n\tvisibility: visible;\n\topacity: 1;\n\ttransition: opacity 0.1s linear;\n}\n\n#divTwc:fullscreen .hidden {\n\tvisibility: hidden;\n\topacity: 0;\n\ttransition: visibility 0s 1s, opacity 1s linear\n}\n\n.github-links {\n\twidth: 610px;\n\tmax-width: calc(100vw - 30px);\n\tdisplay: flex;\n\tjustify-content: space-evenly;\n\tflex-wrap: wrap;\n\n\tspan {\n\t\ta {\n\t\t\ttext-decoration: none;\n\t\t\toutline: 0\n\t\t}\n\n\t\t.widget {\n\t\t\tdisplay: inline-block;\n\t\t\toverflow: hidden;\n\t\t\tfont-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;\n\t\t\tfont-size: 0;\n\t\t\tline-height: 0;\n\t\t\twhite-space: nowrap\n\t\t}\n\n\t\t.btn,\n\t\t.social-count {\n\t\t\tposition: relative;\n\t\t\tdisplay: inline-block;\n\t\t\tdisplay: inline-flex;\n\t\t\theight: 14px;\n\t\t\tpadding: 2px 5px;\n\t\t\tfont-size: 11px;\n\t\t\tfont-weight: 600;\n\t\t\tline-height: 14px;\n\t\t\tvertical-align: bottom;\n\t\t\tcursor: pointer;\n\t\t\t-webkit-user-select: none;\n\t\t\t-moz-user-select: none;\n\t\t\t-ms-user-select: none;\n\t\t\tuser-select: none;\n\t\t\tbackground-repeat: repeat-x;\n\t\t\tbackground-position: -1px -1px;\n\t\t\tbackground-size: 110% 110%;\n\t\t\tborder: 1px solid\n\t\t}\n\n\t\t.btn {\n\t\t\tborder-radius: .25em\n\t\t}\n\n\t\t.btn:not(:last-child) {\n\t\t\tborder-radius: .25em 0 0 .25em\n\t\t}\n\n\t\t.social-count {\n\t\t\tborder-left: 0;\n\t\t\tborder-radius: 0 .25em .25em 0\n\t\t}\n\n\t\t.widget-lg .btn,\n\t\t.widget-lg .social-count {\n\t\t\theight: 16px;\n\t\t\tpadding: 5px 10px;\n\t\t\tfont-size: 12px;\n\t\t\tline-height: 16px\n\t\t}\n\n\t\t.octicon {\n\t\t\tdisplay: inline-block;\n\t\t\tvertical-align: text-top;\n\t\t\tfill: currentColor;\n\t\t\toverflow: visible\n\t\t}\n\n\t\t.btn:focus-visible,\n\t\t.social-count:focus-visible {\n\t\t\toutline: 2px solid #0969da;\n\t\t\toutline-offset: -2px\n\t\t}\n\n\t\t.btn {\n\t\t\tcolor: #24292f;\n\t\t\tbackground-color: #ebf0f4;\n\t\t\tborder-color: #ccd1d5;\n\t\t\tborder-color: rgba(27, 31, 36, .15);\n\t\t\tbackground-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23f6f8fa'/%3e%3cstop offset='90%25' stop-color='%23ebf0f4'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");\n\t\t\tbackground-image: -moz-linear-gradient(top, #f6f8fa, #ebf0f4 90%);\n\t\t\tbackground-image: linear-gradient(180deg, #f6f8fa, #ebf0f4 90%);\n\t\t\tfilter: progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FFF6F8FA', endColorstr='#FFEAEFF3')\n\t\t}\n\n\t\t:root .btn {\n\t\t\tfilter: none\n\t\t}\n\n\t\t.btn:hover,\n\t\t.btn:focus {\n\t\t\tbackground-color: #e9ebef;\n\t\t\tbackground-position: 0 -0.5em;\n\t\t\tborder-color: #caccd1;\n\t\t\tborder-color: rgba(27, 31, 36, .15);\n\t\t\tbackground-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23f3f4f6'/%3e%3cstop offset='90%25' stop-color='%23e9ebef'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");\n\t\t\tbackground-image: -moz-linear-gradient(top, #f3f4f6, #e9ebef 90%);\n\t\t\tbackground-image: linear-gradient(180deg, #f3f4f6, #e9ebef 90%);\n\t\t\tfilter: progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FFF3F4F6', endColorstr='#FFE8EAEE')\n\t\t}\n\n\t\t:root .btn:hover,\n\t\t:root .btn:focus {\n\t\t\tfilter: none\n\t\t}\n\n\t\t.btn:active {\n\t\t\tbackground-color: #e5e9ed;\n\t\t\tborder-color: #c7cbcf;\n\t\t\tborder-color: rgba(27, 31, 36, .15);\n\t\t\tbox-shadow: inset 0 .15em .3em rgba(27, 31, 36, .15);\n\t\t\tbackground-image: none;\n\t\t\tfilter: none\n\t\t}\n\n\t\t.social-count {\n\t\t\tcolor: #24292f;\n\t\t\tbackground-color: #fff;\n\t\t\tborder-color: #ddddde;\n\t\t\tborder-color: rgba(27, 31, 36, .15)\n\t\t}\n\n\t\t.social-count:hover,\n\t\t.social-count:focus {\n\t\t\tcolor: #0969da\n\t\t}\n\n\t\t.octicon-heart {\n\t\t\tcolor: #bf3989\n\t\t}\n\n\t\t@media(prefers-color-scheme:light) {\n\n\t\t\t.btn:focus-visible,\n\t\t\t.social-count:focus-visible {\n\t\t\t\toutline: 2px solid #0969da;\n\t\t\t\toutline-offset: -2px\n\t\t\t}\n\n\t\t\t.btn {\n\t\t\t\tcolor: #24292f;\n\t\t\t\tbackground-color: #ebf0f4;\n\t\t\t\tborder-color: #ccd1d5;\n\t\t\t\tborder-color: rgba(27, 31, 36, .15);\n\t\t\t\tbackground-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23f6f8fa'/%3e%3cstop offset='90%25' stop-color='%23ebf0f4'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");\n\t\t\t\tbackground-image: -moz-linear-gradient(top, #f6f8fa, #ebf0f4 90%);\n\t\t\t\tbackground-image: linear-gradient(180deg, #f6f8fa, #ebf0f4 90%);\n\t\t\t\tfilter: progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FFF6F8FA', endColorstr='#FFEAEFF3')\n\t\t\t}\n\n\t\t\t:root .btn {\n\t\t\t\tfilter: none\n\t\t\t}\n\n\t\t\t.btn:hover,\n\t\t\t.btn:focus {\n\t\t\t\tbackground-color: #e9ebef;\n\t\t\t\tbackground-position: 0 -0.5em;\n\t\t\t\tborder-color: #caccd1;\n\t\t\t\tborder-color: rgba(27, 31, 36, .15);\n\t\t\t\tbackground-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%23f3f4f6'/%3e%3cstop offset='90%25' stop-color='%23e9ebef'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");\n\t\t\t\tbackground-image: -moz-linear-gradient(top, #f3f4f6, #e9ebef 90%);\n\t\t\t\tbackground-image: linear-gradient(180deg, #f3f4f6, #e9ebef 90%);\n\t\t\t\tfilter: progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FFF3F4F6', endColorstr='#FFE8EAEE')\n\t\t\t}\n\n\t\t\t:root .btn:hover,\n\t\t\t:root .btn:focus {\n\t\t\t\tfilter: none\n\t\t\t}\n\n\t\t\t.btn:active {\n\t\t\t\tbackground-color: #e5e9ed;\n\t\t\t\tborder-color: #c7cbcf;\n\t\t\t\tborder-color: rgba(27, 31, 36, .15);\n\t\t\t\tbox-shadow: inset 0 .15em .3em rgba(27, 31, 36, .15);\n\t\t\t\tbackground-image: none;\n\t\t\t\tfilter: none\n\t\t\t}\n\n\t\t\t.social-count {\n\t\t\t\tcolor: #24292f;\n\t\t\t\tbackground-color: #fff;\n\t\t\t\tborder-color: #ddddde;\n\t\t\t\tborder-color: rgba(27, 31, 36, .15)\n\t\t\t}\n\n\t\t\t.social-count:hover,\n\t\t\t.social-count:focus {\n\t\t\t\tcolor: #0969da\n\t\t\t}\n\n\t\t\t.octicon-heart {\n\t\t\t\tcolor: #bf3989\n\t\t\t}\n\t\t}\n\n\t\t@media(prefers-color-scheme:dark) {\n\n\t\t\t.btn:focus-visible,\n\t\t\t.social-count:focus-visible {\n\t\t\t\toutline: 2px solid #58a6ff;\n\t\t\t\toutline-offset: -2px\n\t\t\t}\n\n\t\t\t.btn {\n\t\t\t\tcolor: #c9d1d9;\n\t\t\t\tbackground-color: #1a1e23;\n\t\t\t\tborder-color: #2f3439;\n\t\t\t\tborder-color: rgba(240, 246, 252, .1);\n\t\t\t\tbackground-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%2321262d'/%3e%3cstop offset='90%25' stop-color='%231a1e23'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");\n\t\t\t\tbackground-image: -moz-linear-gradient(top, #21262d, #1a1e23 90%);\n\t\t\t\tbackground-image: linear-gradient(180deg, #21262d, #1a1e23 90%);\n\t\t\t\tfilter: progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FF21262D', endColorstr='#FF191D22')\n\t\t\t}\n\n\t\t\t:root .btn {\n\t\t\t\tfilter: none\n\t\t\t}\n\n\t\t\t.btn:hover,\n\t\t\t.btn:focus {\n\t\t\t\tbackground-color: #292e33;\n\t\t\t\tbackground-position: 0 -0.5em;\n\t\t\t\tborder-color: #8b949e;\n\t\t\t\tbackground-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg'%3e%3clinearGradient id='o' x2='0' y2='1'%3e%3cstop stop-color='%2330363d'/%3e%3cstop offset='90%25' stop-color='%23292e33'/%3e%3c/linearGradient%3e%3crect width='100%25' height='100%25' fill='url(%23o)'/%3e%3c/svg%3e\");\n\t\t\t\tbackground-image: -moz-linear-gradient(top, #30363d, #292e33 90%);\n\t\t\t\tbackground-image: linear-gradient(180deg, #30363d, #292e33 90%);\n\t\t\t\tfilter: progid:DXImageTransform.Microsoft.Gradient(startColorstr='#FF30363D', endColorstr='#FF282D32')\n\t\t\t}\n\n\t\t\t:root .btn:hover,\n\t\t\t:root .btn:focus {\n\t\t\t\tfilter: none\n\t\t\t}\n\n\t\t\t.btn:active {\n\t\t\t\tbackground-color: #161719;\n\t\t\t\tborder-color: #8b949e;\n\t\t\t\tbox-shadow: inset 0 .15em .3em rgba(1, 4, 9, .15);\n\t\t\t\tbackground-image: none;\n\t\t\t\tfilter: none\n\t\t\t}\n\n\t\t\t.social-count {\n\t\t\t\tcolor: #c9d1d9;\n\t\t\t\tbackground-color: #0d1117;\n\t\t\t\tborder-color: #24282e;\n\t\t\t\tborder-color: rgba(240, 246, 252, .1)\n\t\t\t}\n\n\t\t\t.social-count:hover,\n\t\t\t.social-count:focus {\n\t\t\t\tcolor: #58a6ff\n\t\t\t}\n\n\t\t\t.octicon-heart {\n\t\t\t\tcolor: #db61a2\n\t\t\t}\n\t\t}\n\t}\n}\n\n#share-link-copied {\n\tcolor: hsl(60, 100%, 30%);\n\tdisplay: none;\n}\n\n#share-link-instructions {\n\tdisplay: none;\n}\n\n// Hide instructions in kiosk mode (higher specificity than the show rule)\nbody.kiosk #loading .instructions {\n\tdisplay: none !important;\n}\n\n.kiosk {\n\n\t// In kiosk mode, hide everything except the main weather display\n\t>*:not(#divTwc) {\n\t\tdisplay: none !important;\n\t}\n}\n\n#divInfo {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 1fr;\n\tmax-width: 250px;\n}","@use 'colors'as c;\n\n@mixin text-shadow($offset: 3px, $outline: 1.5px) {\n\t/* eventually, when chrome supports paint-order for html elements */\n\t/* -webkit-text-stroke: 2px black; */\n\t/* paint-order: stroke fill; */\n\ttext-shadow:\n\t\t$offset $offset 0 c.$text-shadow,\n\t\t(-$outline) (-$outline) 0 c.$text-shadow,\n\t\t0 (-$outline) 0 c.$text-shadow,\n\t\t$outline (-$outline) 0 c.$text-shadow,\n\t\t$outline 0 0 c.$text-shadow,\n\t\t$outline $outline 0 c.$text-shadow,\n\t\t0 $outline 0 c.$text-shadow,\n\t\t(-$outline) $outline 0 c.$text-shadow,\n\t\t(-$outline) 0 0 c.$text-shadow;\n}\n\n@mixin status-colors() {\n\n\t.loading,\n\t.retrying {\n\t\tcolor: #ffff00;\n\t}\n\n\t.press-here {\n\t\tcolor: #00ff00;\n\t\tcursor: pointer;\n\t}\n\n\t.failed {\n\t\tcolor: #ff0000;\n\t}\n\n\t.no-data {\n\t\tcolor: #C0C0C0;\n\t}\n\n\t.disabled {\n\t\tcolor: #C0C0C0;\n\t}\n}","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n.weather-display {\n\twidth: 640px;\n\theight: 480px;\n\toverflow: hidden;\n\tposition: relative;\n\tbackground-image: url(../images/backgrounds/1.png);\n\n\t/* this method is required to hide blocks so they can be measured while off screen */\n\theight: 0px;\n\n\t&.show {\n\t\theight: 480px;\n\t}\n\n\t.template {\n\t\tdisplay: none;\n\t}\n\n\t.header {\n\t\twidth: 640px;\n\t\theight: 60px;\n\t\tpadding-top: 30px;\n\t\tposition: relative;\n\t\tz-index: 20;\n\n\t\t.title {\n\t\t\tcolor: c.$title-color;\n\t\t\t@include u.text-shadow(3px, 1.5px);\n\t\t\tfont-family: 'Star4000';\n\t\t\tfont-size: 24pt;\n\t\t\tposition: absolute;\n\t\t\twidth: 250px;\n\n\t\t\t&.single {\n\t\t\t\tleft: 170px;\n\t\t\t\ttop: 25px;\n\t\t\t}\n\n\t\t\t&.dual {\n\t\t\t\tleft: 170px;\n\n\t\t\t\t&>div {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t}\n\n\t\t\t\t.top {\n\t\t\t\t\ttop: -3px;\n\t\t\t\t}\n\n\t\t\t\t.bottom {\n\t\t\t\t\ttop: 26px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\t.logo {\n\t\t\ttop: 30px;\n\t\t\tleft: 50px;\n\t\t\tposition: absolute;\n\t\t\tz-index: 10;\n\t\t}\n\n\t\t.noaa-logo {\n\t\t\tposition: absolute;\n\t\t\ttop: 39px;\n\t\t\tleft: 356px;\n\t\t}\n\n\t\t.title.single {\n\t\t\ttop: 40px;\n\t\t}\n\n\t\t.date-time {\n\t\t\twhite-space: pre;\n\t\t\tcolor: c.$date-time;\n\t\t\tfont-family: 'Star4000 Small';\n\t\t\tfont-size: 24pt;\n\t\t\t@include u.text-shadow(3px, 1.5px);\n\t\t\tleft: 415px;\n\t\t\twidth: 170px;\n\t\t\ttext-align: right;\n\t\t\tposition: absolute;\n\n\t\t\t&.date {\n\t\t\t\tpadding-top: 22px;\n\t\t\t}\n\t\t}\n\t}\n\n\t.main {\n\t\tposition: relative;\n\n\t\t&.has-scroll {\n\t\t\twidth: 640px;\n\t\t\tmargin-top: 0;\n\t\t\theight: 310px;\n\t\t\toverflow: hidden;\n\n\t\t\t&.no-header {\n\t\t\t\theight: 400px;\n\t\t\t\tmargin-top: 0; // Reset for no-header case since the gap issue is header-related\n\t\t\t}\n\t\t}\n\n\t\t&.has-box {\n\t\t\tmargin-left: 64px;\n\t\t\tmargin-right: 64px;\n\t\t\twidth: calc(100% - 128px);\n\t\t}\n\n\t}\n\n}\n\n#container>.scroll {\n\tdisplay: none;\n\t@include u.text-shadow(3px, 1.5px);\n\twidth: 640px;\n\theight: 77px;\n\toverflow: hidden;\n\tmargin-top: 3px;\n\tposition: absolute;\n\tbottom: 0px;\n\tz-index: 1;\n\n\t&.hazard {\n\t\tbackground-color: rgb(112, 35, 35);\n\t}\n\n\t.scroll-container {\n\t\twidth: 640px;\n\n\t\t.fixed,\n\t\t.scroll-header {\n\t\t\tmargin-left: 55px;\n\t\t\tmargin-right: 55px;\n\t\t\toverflow: hidden;\n\t\t\twhite-space: nowrap;\n\t\t}\n\n\t\t.scroll-header {\n\t\t\theight: 26px;\n\t\t\tfont-family: \"Star4000 Small\";\n\t\t\tfont-size: 20pt;\n\t\t\tmargin-top: -10px;\n\t\t}\n\n\t\t.fixed {\n\t\t\tfont-family: 'Star4000';\n\t\t\tfont-size: 24pt;\n\n\t\t\t.scroll-area {\n\t\t\t\ttext-wrap: nowrap;\n\t\t\t\tposition: relative;\n\t\t\t\t// the following added by js code as it is dependent on the content of the element\n\t\t\t\t// transition: left (x)s;\n\t\t\t\t// left: calc((elem width) - 640px);\n\t\t\t}\n\t\t}\n\t}\n\n\n\n}\n\n.wide #container>.scroll {\n\twidth: 854px;\n\tmargin-left: -107px;\n\n\t.scroll-container {\n\t\tmargin-left: 107px;\n\t}\n}\n","$title-color: yellow;\n$date-time: white;\n$text-shadow: black;\n$column-header-text: yellow;\n$column-header: rgb(32, 0, 87);\n\n$gradient-main-background-1: #102080;\n$gradient-main-background-2: #001040;\n\n$gradient-loading-1: #09246f;\n$gradient-loading-2: #364ac0;\n$gradient-loading-3: #4f99f9;\n$gradient-loading-4: #8ffdfa;\n\n$extended-low: #8080FF;\n\n$blue-box: #26235a;","@use 'shared/_colors' as c;\n@use 'shared/_utils' as u;\n\n.weather-display .main.current-weather {\n\t&.main {\n\n\t\t.col {\n\t\t\theight: 50px;\n\t\t\twidth: 255px;\n\t\t\tdisplay: inline-block;\n\t\t\tmargin-top: 10px;\n\t\t\tpadding-top: 10px;\n\t\t\tposition: absolute;\n\n\t\t\t@include u.text-shadow();\n\n\t\t\t&.left {\n\t\t\t\tfont-family: 'Star4000 Extended';\n\t\t\t\tfont-size: 24pt;\n\n\t\t\t}\n\n\t\t\t&.right {\n\t\t\t\tright: 0px;\n\t\t\t\tfont-family: \"Star4000 Large\";\n\t\t\t\tfont-size: 20px;\n\t\t\t\tfont-weight: bold;\n\t\t\t\tline-height: 24px;\n\n\t\t\t\t.row {\n\t\t\t\t\tmargin-bottom: 12px;\n\n\t\t\t\t\t.label,\n\t\t\t\t\t.value {\n\t\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t\t}\n\n\t\t\t\t\t.label {\n\t\t\t\t\t\tmargin-left: 20px;\n\t\t\t\t\t}\n\n\t\t\t\t\t.value {\n\t\t\t\t\t\tfloat: right;\n\t\t\t\t\t\tmargin-right: 10px;\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\t.center {\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t.temp {\n\t\t\tfont-family: 'Star4000 Large';\n\t\t\tfont-size: 24pt;\n\t\t}\n\n\t\t.icon {\n\t\t\timg {\n\t\t\t\tmargin: 0 auto;\n\t\t\t\tdisplay: block;\n\t\t\t}\n\t\t}\n\n\t\t.wind-container {\n\t\t\tmargin-left: 10px;\n\t\t\tdisplay: flex;\n\n\t\t\t&>div {\n\t\t\t\twidth: 50%;\n\t\t\t}\n\n\t\t\t.wind {\n\t\t\t\ttext-align: right;\n\t\t\t}\n\t\t}\n\n\t\t.wind-gusts {\n\t\t\ttext-align: right;\n\t\t\tfont-size: 28px;\n\t\t}\n\n\t\t.location {\n\t\t\tcolor: c.$title-color;\n\t\t\tmax-height: 32px;\n\t\t\tmargin-bottom: 10px;\n\t\t\tpadding-top: 4px;\n\t\t\toverflow: hidden;\n\t\t\ttext-wrap: nowrap;\n\t\t}\n\t}\n}\n","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n#extended-forecast-html.weather-display {\n\tbackground-image: url('../images/backgrounds/2.png');\n}\n\n.weather-display .main.extended-forecast {\n\t.day-container {\n\t\tmargin-top: 16px;\n\t\tmargin-left: 27px;\n\t}\n\n\t.day {\n\t\t@include u.text-shadow();\n\t\tpadding: 5px;\n\t\theight: 285px;\n\t\twidth: 155px;\n\t\tdisplay: inline-block;\n\t\tmargin: 0px 15px;\n\t\tfont-family: 'Star4000';\n\t\tfont-size: 24pt;\n\n\t\t.date {\n\t\t\ttext-transform: uppercase;\n\t\t\ttext-align: center;\n\t\t\tcolor: c.$title-color;\n\t\t}\n\n\t\t.condition {\n\t\t\ttext-align: center;\n\t\t\theight: 74px;\n\t\t\tmargin-top: 5px;\n\t\t}\n\n\t\t.icon {\n\t\t\ttext-align: center;\n\t\t\theight: 75px;\n\n\t\t\timg {\n\t\t\t\tmax-height: 75px;\n\t\t\t}\n\t\t}\n\n\t\t.temperatures {\n\t\t\twidth: 100%;\n\n\t\t\t.temperature-block {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\twidth: 44%;\n\t\t\t\tvertical-align: top;\n\n\t\t\t\t>div {\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\n\t\t\t\t.value {\n\t\t\t\t\tfont-family: 'Star4000 Large';\n\t\t\t\t\tmargin-top: 4px;\n\t\t\t\t}\n\n\t\t\t\t&.lo .label {\n\t\t\t\t\tcolor: c.$extended-low;\n\t\t\t\t}\n\n\t\t\t\t&.hi .label {\n\t\t\t\t\tcolor: c.$title-color;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n.weather-display .main.hourly {\n\t&.main {\n\t\toverflow-y: hidden;\n\n\t\t.column-headers {\n\t\t\tbackground-color: c.$column-header;\n\t\t\theight: 20px;\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.column-headers {\n\t\t\tposition: sticky;\n\t\t\ttop: 0px;\n\t\t\tz-index: 5;\n\n\t\t\tdiv {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-family: 'Star4000 Small';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\tcolor: c.$column-header-text;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: -14px;\n\t\t\t\tz-index: 5;\n\t\t\t\t@include u.text-shadow();\n\t\t\t}\n\n\t\t\t.temp {\n\t\t\t\tleft: 355px;\n\t\t\t}\n\n\t\t\t.like {\n\t\t\t\tleft: 435px;\n\t\t\t}\n\n\t\t\t.wind {\n\t\t\t\tleft: 535px;\n\t\t\t}\n\t\t}\n\n\t\t.hourly-lines {\n\t\t\tmin-height: 338px;\n\t\t\tpadding-top: 10px;\n\n\t\t\tbackground: repeating-linear-gradient(0deg, c.$gradient-main-background-2 0px,\n\t\t\t\t\tc.$gradient-main-background-1 136px,\n\t\t\t\t\tc.$gradient-main-background-1 202px,\n\t\t\t\t\tc.$gradient-main-background-2 338px,\n\t\t\t\t);\n\n\t\t\t.hourly-row {\n\t\t\t\tfont-family: 'Star4000 Large';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\theight: 72px;\n\t\t\t\tcolor: c.$title-color;\n\t\t\t\t@include u.text-shadow();\n\t\t\t\tposition: relative;\n\n\t\t\t\t>div {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\twhite-space: pre;\n\t\t\t\t\ttop: 8px;\n\t\t\t\t}\n\n\t\t\t\t.hour {\n\t\t\t\t\tleft: 25px;\n\t\t\t\t}\n\n\t\t\t\t.icon {\n\t\t\t\t\tleft: 255px;\n\t\t\t\t\twidth: 70px;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t\ttop: unset;\n\t\t\t\t}\n\n\t\t\t\t.temp {\n\t\t\t\t\tleft: 355px;\n\t\t\t\t}\n\n\t\t\t\t.like {\n\t\t\t\t\tleft: 425px;\n\n\t\t\t\t\t&.heat-index {\n\t\t\t\t\t\tcolor: #e00;\n\t\t\t\t\t}\n\n\t\t\t\t\t&.wind-chill {\n\t\t\t\t\t\tcolor: c.$extended-low;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.wind {\n\t\t\t\t\tleft: 505px;\n\t\t\t\t\twidth: 100px;\n\t\t\t\t\ttext-align: right;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n#hourly-graph-html {\n\tbackground-image: url(../images/backgrounds/1-chart.png);\n\n\t.header {\n\t\t.right {\n\t\t\tposition: absolute;\n\t\t\ttop: 35px;\n\t\t\tright: 60px;\n\t\t\twidth: 360px;\n\t\t\tfont-family: 'Star4000 Small';\n\t\t\tfont-size: 28px;\n\t\t\t@include u.text-shadow();\n\t\t\ttext-align: right;\n\n\t\t\tdiv {\n\t\t\t\tmargin-top: -18px;\n\t\t\t}\n\n\t\t\t.temperature {\n\t\t\t\tcolor: red;\n\t\t\t}\n\n\t\t\t.dewpoint {\n\t\t\t\tcolor: green;\n\t\t\t}\n\n\t\t\t.cloud {\n\t\t\t\tcolor: lightgrey;\n\t\t\t}\n\n\t\t\t.rain {\n\t\t\t\tcolor: aqua;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.weather-display .main.hourly-graph {\n\n\t&.main {\n\t\t>div {\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t.label {\n\t\t\tfont-family: 'Star4000 Small';\n\t\t\tfont-size: 24pt;\n\t\t\tcolor: c.$column-header-text;\n\t\t\t@include u.text-shadow();\n\t\t\tmargin-top: -15px;\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t.x-axis {\n\t\t\tbottom: 0px;\n\t\t\tleft: 54px;\n\t\t\twidth: 532px;\n\t\t\theight: 20px;\n\n\t\t\t.label {\n\t\t\t\ttext-align: center;\n\t\t\t\ttransform: translateX(-50%);\n\t\t\t\twhite-space: nowrap;\n\n\t\t\t\t&.l-1 {\n\t\t\t\t\tleft: 0px;\n\t\t\t\t}\n\n\t\t\t\t&.l-2 {\n\t\t\t\t\tleft: calc(532px / 4 * 1);\n\t\t\t\t}\n\n\t\t\t\t&.l-3 {\n\t\t\t\t\tleft: calc(532px / 4 * 2);\n\t\t\t\t}\n\n\t\t\t\t&.l-4 {\n\t\t\t\t\tleft: calc(532px / 4 * 3);\n\t\t\t\t}\n\n\t\t\t\t&.l-5 {\n\t\t\t\t\tleft: calc(532px / 4 * 4);\n\t\t\t\t}\n\t\t\t}\n\n\n\n\t\t}\n\n\t\t.chart {\n\t\t\ttop: 0px;\n\t\t\tleft: 50px;\n\n\t\t\timg {\n\t\t\t\twidth: 532px;\n\t\t\t\theight: 285px;\n\t\t\t}\n\t\t}\n\n\t\t.y-axis {\n\t\t\ttop: 0px;\n\t\t\tleft: 0px;\n\t\t\twidth: 50px;\n\t\t\theight: 285px;\n\n\t\t\t.label {\n\t\t\t\ttext-align: right;\n\t\t\t\tright: 0px;\n\n\t\t\t\t&.l-1 {\n\t\t\t\t\ttop: 0px;\n\t\t\t\t}\n\n\t\t\t\t&.l-2 {\n\t\t\t\t\ttop: calc(280px / 3);\n\t\t\t\t}\n\n\t\t\t\t&.l-3 {\n\t\t\t\t\tbottom: calc(280px / 3 - 11px);\n\t\t\t\t}\n\n\t\t\t\t&.l-4 {\n\t\t\t\t\tbottom: 0px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.column-headers {\n\t\t\tbackground-color: c.$column-header;\n\t\t\theight: 20px;\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.column-headers {\n\t\t\tposition: sticky;\n\t\t\ttop: 0px;\n\t\t\tz-index: 5;\n\n\n\t\t\t.temp {\n\t\t\t\tleft: 355px;\n\t\t\t}\n\n\t\t\t.like {\n\t\t\t\tleft: 435px;\n\t\t\t}\n\n\t\t\t.wind {\n\t\t\t\tleft: 535px;\n\t\t\t}\n\t\t}\n\n\n\t}\n}","@use 'shared/_colors' as c;\n@use 'shared/_utils' as u;\n\n.weather-display .main.travel {\n\t&.main {\n\t\toverflow-y: hidden;\n\n\t\t.column-headers {\n\t\t\tbackground-color: c.$column-header;\n\t\t\theight: 20px;\n\t\t\tposition: sticky;\n\t\t\ttop: 0px;\n\t\t\twidth: 100%;\n\t\t\tz-index: 5;\n\t\t\toverflow: hidden; // prevent thin gaps between header and content\n\n\t\t\tdiv {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-family: 'Star4000 Small';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\tcolor: c.$column-header-text;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: -14px;\n\t\t\t\tz-index: 5;\n\t\t\t\t@include u.text-shadow();\n\t\t\t}\n\n\t\t\t.temp {\n\t\t\t\twidth: 50px;\n\t\t\t\ttext-align: center;\n\n\t\t\t\t&.low {\n\t\t\t\t\tleft: 455px;\n\n\t\t\t\t}\n\n\t\t\t\t&.high {\n\t\t\t\t\tleft: 510px;\n\t\t\t\t\twidth: 60px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.travel-lines {\n\t\t\tmin-height: 338px;\n\t\t\tpadding-top: 10px;\n\n\t\t\tbackground: repeating-linear-gradient(0deg, c.$gradient-main-background-2 0px,\n\t\t\t\t\tc.$gradient-main-background-1 136px,\n\t\t\t\t\tc.$gradient-main-background-1 202px,\n\t\t\t\t\tc.$gradient-main-background-2 338px,\n\t\t\t\t);\n\n\t\t\t.travel-row {\n\t\t\t\tfont-family: 'Star4000 Large';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\theight: 72px;\n\t\t\t\tcolor: c.$title-color;\n\t\t\t\t@include u.text-shadow();\n\t\t\t\tposition: relative;\n\n\t\t\t\t>div {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\twhite-space: pre;\n\t\t\t\t\ttop: 8px;\n\t\t\t\t}\n\n\t\t\t\t.city {\n\t\t\t\t\tleft: 80px;\n\t\t\t\t}\n\n\t\t\t\t.icon {\n\t\t\t\t\tleft: 330px;\n\t\t\t\t\twidth: 70px;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t\ttop: unset;\n\n\t\t\t\t\timg {\n\t\t\t\t\t\tmax-width: 47px;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t.temp {\n\t\t\t\t\twidth: 50px;\n\t\t\t\t\ttext-align: center;\n\n\t\t\t\t\t&.low {\n\t\t\t\t\t\tleft: 455px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&.high {\n\t\t\t\t\t\tleft: 510px;\n\t\t\t\t\t\twidth: 60px;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t}\n}\n","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n.weather-display .latest-observations {\n\n\t&.main {\n\t\toverflow-y: hidden;\n\n\t\t.column-headers {\n\t\t\theight: 20px;\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.column-headers {\n\t\t\ttop: 0px;\n\n\t\t\tdiv {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-family: 'Star4000 Small';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: -14px;\n\t\t\t\t@include u.text-shadow();\n\t\t\t}\n\n\t\t\t.temp {\n\t\t\t\t// hidden initially for english/metric switching\n\t\t\t\tdisplay: none;\n\n\t\t\t\t&.show {\n\t\t\t\t\tdisplay: inline-block;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.temp {\n\t\t\tleft: 230px;\n\t\t}\n\n\t\t.weather {\n\t\t\tleft: 280px;\n\t\t}\n\n\t\t.wind {\n\t\t\tleft: 430px;\n\t\t}\n\n\t\t.observation-lines {\n\t\t\tmin-height: 338px;\n\t\t\tpadding-top: 10px;\n\n\t\t\t.observation-row {\n\t\t\t\tfont-family: 'Star4000';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\t@include u.text-shadow();\n\t\t\t\tposition: relative;\n\t\t\t\theight: 40px;\n\n\t\t\t\t>div {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 8px;\n\t\t\t\t}\n\n\t\t\t\t.wind {\n\t\t\t\t\twhite-space: pre;\n\t\t\t\t\ttext-align: right;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n.weather-display .local-forecast {\n\t.container {\n\t\tposition: relative;\n\t\ttop: 15px;\n\t\tmargin: 0px 10px;\n\t\tbox-sizing: border-box;\n\t\theight: 280px;\n\t\toverflow: hidden;\n\t}\n\n\t.forecasts {\n\t\tposition: relative;\n\t}\n\n\t.forecast {\n\t\tfont-family: 'Star4000';\n\t\tfont-size: 24pt;\n\t\ttext-transform: uppercase;\n\t\t@include u.text-shadow();\n\t\tmin-height: 280px;\n\t\tline-height: 40px;\n\t}\n}","@use 'shared/_colors' as c;\n@use 'shared/_utils' as u;\n\n.weather-display .progress {\n\t@include u.text-shadow();\n\tfont-family: 'Star4000 Extended';\n\tfont-size: 18pt;\n\n\t.container {\n\t\tposition: relative;\n\t\ttop: 15px;\n\t\tmargin: 0px 10px;\n\t\tbox-sizing: border-box;\n\t\theight: 310px;\n\t\toverflow: hidden;\n\t\tline-height: 26px;\n\n\t\t.item {\n\t\t\tposition: relative;\n\n\t\t\t.name {\n\t\t\t\twhite-space: nowrap;\n\n\t\t\t\t&::after {\n\t\t\t\t\tcontent: '........................................................................';\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.links {\n\t\t\t\tposition: absolute;\n\t\t\t\ttext-align: right;\n\t\t\t\tright: 0px;\n\t\t\t\ttop: 0px;\n\n\t\t\t\t>div {\n\t\t\t\t\tbackground-color: c.$blue-box;\n\t\t\t\t\tdisplay: none;\n\t\t\t\t\tpadding-left: 4px;\n\t\t\t\t}\n\n\t\t\t\t@include u.status-colors();\n\n\t\t\t\t&.loading .loading,\n\t\t\t\t&.press-here .press-here,\n\t\t\t\t&.failed .failed,\n\t\t\t\t&.no-data .no-data,\n\t\t\t\t&.disabled .disabled,\n\t\t\t\t&.retrying .retrying {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t}\n\n\n}\n\n#progress-html.weather-display .scroll {\n\n\t@keyframes progress-scroll {\n\t\t0% {\n\t\t\tbackground-position: -40px 0;\n\t\t}\n\n\t\t100% {\n\t\t\tbackground-position: 40px 0;\n\t\t}\n\t}\n\n\t.progress-bar-container {\n\t\tborder: 2px solid black;\n\t\tbackground-color: white;\n\t\tmargin: 20px auto;\n\t\twidth: 524px;\n\t\tposition: relative;\n\t\tdisplay: none;\n\n\t\t&.show {\n\t\t\tdisplay: block;\n\t\t}\n\n\t\t.progress-bar {\n\t\t\theight: 20px;\n\t\t\tmargin: 2px;\n\t\t\twidth: 520px;\n\t\t\tbackground: repeating-linear-gradient(90deg,\n\t\t\t\t\tc.$gradient-loading-1 0px,\n\t\t\t\t\tc.$gradient-loading-1 5px,\n\t\t\t\t\tc.$gradient-loading-2 5px,\n\t\t\t\t\tc.$gradient-loading-2 10px,\n\t\t\t\t\tc.$gradient-loading-3 10px,\n\t\t\t\t\tc.$gradient-loading-3 15px,\n\t\t\t\t\tc.$gradient-loading-4 15px,\n\t\t\t\t\tc.$gradient-loading-4 20px,\n\t\t\t\t\tc.$gradient-loading-3 20px,\n\t\t\t\t\tc.$gradient-loading-3 25px,\n\t\t\t\t\tc.$gradient-loading-2 25px,\n\t\t\t\t\tc.$gradient-loading-2 30px,\n\t\t\t\t\tc.$gradient-loading-1 30px,\n\t\t\t\t\tc.$gradient-loading-1 40px,\n\t\t\t\t);\n\t\t\t// animation\n\t\t\tanimation-duration: 2s;\n\t\t\tanimation-fill-mode: forwards;\n\t\t\tanimation-iteration-count: infinite;\n\t\t\tanimation-name: progress-scroll;\n\t\t\tanimation-timing-function: steps(8, end);\n\t\t}\n\n\t\t.cover {\n\t\t\tposition: absolute;\n\t\t\ttop: 0px;\n\t\t\tright: 0px;\n\t\t\tbackground-color: white;\n\t\t\twidth: 100%;\n\t\t\theight: 24px;\n\t\t\ttransition: width 1s steps(6);\n\t\t}\n\t}\n}\n","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n#radar-html.weather-display {\n\tbackground-image: url('../images/backgrounds/4.png');\n\n\t.header {\n\t\theight: 83px;\n\n\t\t.title.dual {\n\t\t\tcolor: white;\n\t\t\tfont-family: 'Arial', sans-serif;\n\t\t\tfont-weight: bold;\n\t\t\tfont-size: 28pt;\n\t\t\tleft: 155px;\n\n\t\t\t.top {\n\t\t\t\ttop: -4px;\n\t\t\t}\n\n\t\t\t.bottom {\n\t\t\t\ttop: 31px;\n\t\t\t}\n\t\t}\n\n\t\t.right {\n\t\t\tposition: absolute;\n\t\t\tright: 0px;\n\t\t\twidth: 360px;\n\t\t\tmargin-top: 2px;\n\t\t\tfont-family: 'Star4000';\n\t\t\tfont-size: 18pt;\n\t\t\tfont-weight: bold;\n\t\t\t@include u.text-shadow();\n\t\t\ttext-align: center;\n\n\t\t\t.scale-table {\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\tgap: 7px;\n\n\t\t\t\t.item {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tflex-direction: column;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\twidth: 25px;\n\t\t\t\t\tgap: 2px;\n\t\t\t\t}\n\n\t\t\t\t.box {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t\tborder: 2px solid black;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\theight: 22px;\n\t\t\t\t\tpadding: 0;\n\t\t\t\t}\n\n\t\t\t\t.box-1 {\n\t\t\t\t\tbackground-color: rgb(73, 190, 246);\n\t\t\t\t}\n\n\t\t\t\t.box-2 {\n\t\t\t\t\tbackground-color: rgb(49, 210, 22);\n\t\t\t\t}\n\n\t\t\t\t.box-3 {\n\t\t\t\t\tbackground-color: rgb(241, 228, 88);\n\t\t\t\t}\n\n\t\t\t\t.box-4 {\n\t\t\t\t\tbackground-color: rgb(224, 142, 47);\n\t\t\t\t}\n\n\t\t\t\t.box-5 {\n\t\t\t\t\tbackground-color: rgb(196, 42, 42);\n\t\t\t\t}\n\n\t\t\t\t.box-6 {\n\t\t\t\t\tbackground-color: rgb(145, 59, 184);\n\t\t\t\t}\n\n\t\t\t\t.label {\n\t\t\t\t\tfont-family: 'Star4000 Small';\n\t\t\t\t\tfont-size: 10pt;\n\t\t\t\t\tline-height: 1;\n\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.scale {\n\t\t\t\tmargin-top: -2px;\n\t\t\t}\n\n\t\t\t.time {\n\t\t\t\tposition: relative;\n\t\t\t\tfont-weight: normal;\n\t\t\t\ttop: -20px;\n\t\t\t\tfont-family: 'Star4000 Small';\n\t\t\t\tfont-size: 18pt;\n\t\t\t\tleft: 130px;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.weather-display .main.radar {\n\toverflow: hidden;\n\theight: 367px;\n\n\t.container {\n\t\tposition: relative;\n\t\theight: 100%;\n\n\t\t.scroll-area {\n\t\t\tposition: relative;\n\t\t\theight: 100%;\n\t\t}\n\n\t\t.frame {\n\t\t\theight: 100%;\n\t\t}\n\n\t\t.map {\n\t\t\theight: 100%;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.leaflet-map {\n\t\t\theight: 100%;\n\t\t\twidth: 100%;\n\t\t\tbackground: #061f3e;\n\t\t}\n\n\t\t.leaflet-container {\n\t\t\tbackground: #061f3e;\n\t\t\tfont-family: inherit;\n\t\t}\n\n\t\t.radar-base-layer,\n\t\t.radar-base-layer .leaflet-tile {\n\t\t\tfilter: grayscale(0.35) brightness(0.58) contrast(1.1) saturate(0.2);\n\t\t}\n\n\t\t.radar-boundary-layer,\n\t\t.radar-boundary-layer .leaflet-tile {\n\t\t\tfilter: grayscale(0.8) brightness(0.7) contrast(1.3) saturate(0.1);\n\t\t}\n\n\t\t.leaflet-control-container,\n\t\t.leaflet-control-attribution,\n\t\t.leaflet-control-zoom {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\t.location-marker {\n\t\t\tbackground: #ff0;\n\t\t\tborder: 2px solid #000;\n\t\t\tborder-radius: 50%;\n\t\t}\n\n\t\t.nearby-weather-marker {\n\t\t\tbackground: transparent;\n\t\t\tborder: 0;\n\n\t\t\t.nearby-weather-marker-inner {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\talign-items: center;\n\t\t\t\tmin-width: 72px;\n\t\t\t\tpadding: 2px 4px;\n\t\t\t\tbackground: rgba(18, 34, 61, 0.88);\n\t\t\t\tborder: 1px solid #000;\n\t\t\t\tbox-shadow: 1px 1px 0 #000;\n\t\t\t\tcolor: #fff;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\n\t\t\t.city {\n\t\t\t\tfont-family: 'Star4000 Small';\n\t\t\t\tfont-size: 11pt;\n\t\t\t\tline-height: 1;\n\t\t\t\twhite-space: nowrap;\n\t\t\t\tmargin-bottom: 1px;\n\t\t\t\ttext-shadow: 1px 1px 0 #000;\n\t\t\t}\n\n\t\t\t.details {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tgap: 2px;\n\t\t\t}\n\n\t\t\t.temp {\n\t\t\t\tfont-family: 'Star4000';\n\t\t\t\tfont-size: 18pt;\n\t\t\t\tline-height: 1;\n\t\t\t\tcolor: #ff0;\n\t\t\t\ttext-shadow: 1px 1px 0 #000;\n\t\t\t}\n\n\t\t\timg {\n\t\t\t\twidth: auto;\n\t\t\t\theight: 20px;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.wide.radar #container {\n\tbackground: url(../images/backgrounds/4-wide.png);\n}\n","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n#regional-forecast-html.weather-display {\n\tbackground-image: url('../images/backgrounds/5.png');\n}\n\n.weather-display .main.regional-forecast {\n\tposition: relative;\n\toverflow: hidden;\n\tz-index: 0;\n\n\t.map {\n\t\tposition: absolute;\n\t\tinset: 0;\n\t}\n\n\t.leaflet-map {\n\t\theight: 100%;\n\t\twidth: 100%;\n\t\tbackground: #061f3e;\n\t}\n\n\t.leaflet-container {\n\t\tbackground: #061f3e;\n\t\tfont-family: inherit;\n\t}\n\n\t.radar-base-layer,\n\t.radar-base-layer .leaflet-tile {\n\t\tfilter: grayscale(0.35) brightness(0.58) contrast(1.1) saturate(0.2);\n\t}\n\n\t.radar-boundary-layer,\n\t.radar-boundary-layer .leaflet-tile {\n\t\tfilter: grayscale(0.8) brightness(0.7) contrast(1.3) saturate(0.1);\n\t}\n\n\t.leaflet-control-container,\n\t.leaflet-control-attribution,\n\t.leaflet-control-zoom {\n\t\tdisplay: none;\n\t}\n\n\t.location-marker {\n\t\tbackground: #ff0;\n\t\tborder: 2px solid #000;\n\t\tborder-radius: 50%;\n\t}\n\n\t.nearby-weather-marker {\n\t\tbackground: transparent;\n\t\tborder: 0;\n\n\t\t.nearby-weather-marker-inner {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\talign-items: center;\n\t\t\tmin-width: 72px;\n\t\t\tpadding: 2px 4px;\n\t\t\tbackground: rgba(18, 34, 61, 0.88);\n\t\t\tborder: 1px solid #000;\n\t\t\tbox-shadow: 1px 1px 0 #000;\n\t\t\tcolor: #fff;\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t.city {\n\t\t\tfont-family: 'Star4000 Small';\n\t\t\tfont-size: 11pt;\n\t\t\tline-height: 1;\n\t\t\twhite-space: nowrap;\n\t\t\tmargin-bottom: 1px;\n\t\t\t@include u.text-shadow();\n\t\t}\n\n\t\t.details {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: 2px;\n\t\t}\n\n\t\t.temp {\n\t\t\tfont-family: 'Star4000';\n\t\t\tfont-size: 18pt;\n\t\t\tline-height: 1;\n\t\t\tcolor: c.$title-color;\n\t\t\t@include u.text-shadow();\n\t\t}\n\n\t\timg {\n\t\t\twidth: auto;\n\t\t\theight: 20px;\n\t\t}\n\t}\n}\n","@use 'shared/_colors' as c;\n@use 'shared/_utils' as u;\n\n#almanac-html.weather-display {\n\tbackground-image: url('../images/backgrounds/3.png');\n}\n\n.weather-display .main.almanac {\n\tfont-family: 'Star4000';\n\tfont-size: 24pt;\n\t@include u.text-shadow();\n\n\t.sun {\n\t\t// Use CSS Grid for cross-browser consistency\n\t\t// Grid is populated in reading order (left-to-right, top-to-bottom):\n\t\tdisplay: grid;\n\t\tgrid-template-columns: auto auto auto;\n\t\tgrid-template-rows: auto auto auto;\n\t\tgap: 0px 90px;\n\t\tmargin: 3px auto 5px auto; // align the bottom of the div with the background\n\t\twidth: fit-content;\n\t\tline-height: 30px;\n\n\t\t.grid-item {\n\t\t\t// Reset inherited styles that interfere with grid layout\n\t\t\twidth: auto;\n\t\t\theight: auto;\n\t\t\tpadding: 0;\n\t\t\tmargin: 0;\n\t\t\tposition: relative;\n\n\t\t\t// Column headers (day names)\n\t\t\t&.header {\n\t\t\t\tcolor: c.$column-header-text;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\n\t\t\t// Row labels (Sunrise:, Sunset:)\n\t\t\t&.row-label {\n\t\t\t\t// color: c.$column-header-text; // screenshots show labels were white\n\t\t\t\ttext-align: right;\n\t\t\t}\n\n\t\t\t// Time values (sunrise/sunset)\n\t\t\t&.time {\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t}\n\t}\n\n\t.moon {\n\t\tposition: relative;\n\t\tpadding: 7px 50px;\n\t\tline-height: 36px;\n\n\t\t.title {\n\t\t\tcolor: c.$column-header-text;\n\t\t\tpadding-left: 13px;\n\t\t}\n\n\t\t.day {\n\t\t\tdisplay: inline-block;\n\t\t\ttext-align: center;\n\t\t\twidth: 132px;\n\n\t\t\t.icon {\n\t\t\t\t// shadow in image make it look off center\n\t\t\t\tpadding-left: 10px;\n\t\t\t}\n\n\t\t\t.date {\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -10px;\n\t\t\t}\n\t\t}\n\t}\n\n\n\n}\n","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n#hazards-html.weather-display {\n\tbackground-image: url('../images/backgrounds/7.png');\n}\n\n.weather-display .main.hazards {\n\t&.main {\n\t\toverflow-y: hidden;\n\t\theight: 480px;\n\t\tbackground-color: rgb(112, 35, 35);\n\n\n\t\t.hazard-lines {\n\t\t\tmin-height: 400px;\n\t\t\tpadding-top: 10px;\n\n\t\t\t.hazard {\n\t\t\t\tfont-family: 'Star4000';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\tcolor: white;\n\t\t\t\t@include u.text-shadow(0px);\n\t\t\t\tposition: relative;\n\t\t\t\ttext-transform: uppercase;\n\t\t\t\tmargin-top: 10px;\n\t\t\t\tmargin-left: 80px;\n\t\t\t\tmargin-right: 80px;\n\t\t\t\tpadding-bottom: 10px;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.wide.hazards #container {\n\tbackground: url(../images/backgrounds/7-wide.png);\n}",".media {\n\tdisplay: none;\n}\n\n#ToggleMediaContainer {\n\tdisplay: none;\n\tposition: relative;\n\n\t&.available {\n\t\tdisplay: inline-block;\n\n\t\timg.on {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\timg.off {\n\t\t\tdisplay: block;\n\t\t}\n\n\t\t// icon switch is handled by adding/removing the .playing class\n\t\t&.playing {\n\t\t\timg.on {\n\t\t\t\tdisplay: block;\n\t\t\t}\n\n\t\t\timg.off {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t}\n\n\n\t}\n\n\t.volume-slider {\n\t\tdisplay: none;\n\t\tposition: absolute;\n\t\ttop: 0px;\n\t\ttransform: translateY(-100%);\n\t\twidth: 100%;\n\t\tbackground-color: #000;\n\t\ttext-align: center;\n\t\tz-index: 100;\n\n\t\t@media (prefers-color-scheme: dark) {\n\t\t\tbackground-color: #303030;\n\t\t}\n\n\t\tinput[type=\"range\"] {\n\t\t\twriting-mode: vertical-lr;\n\t\t\tdirection: rtl;\n\t\t\tmargin-top: 20px;\n\t\t\tmargin-bottom: 20px;\n\t\t}\n\n\t\t&.show {\n\t\t\tdisplay: block;\n\t\t}\n\t}\n\n\n\n}","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n#spc-outlook-html.weather-display {\n\tbackground-image: url('../images/backgrounds/6.png');\n}\n\n.weather-display .spc-outlook {\n\n\t.container {\n\t\tposition: relative;\n\t\ttop: 0px;\n\t\tmargin: 0px 10px;\n\t\tbox-sizing: border-box;\n\t\theight: 300px;\n\t\toverflow: hidden;\n\t}\n\n\t.risk-levels {\n\t\tposition: absolute;\n\t\tleft: 206px;\n\t\tfont-family: 'Star4000 Small';\n\t\tfont-size: 32px;\n\t\t@include u.text-shadow();\n\n\n\t\t.risk-level {\n\t\t\tposition: relative;\n\t\t\ttop: -14px;\n\t\t\theight: 20px;\n\n\t\t\t&:nth-child(1) {\n\t\t\t\tleft: calc(20px * 5);\n\t\t\t}\n\n\t\t\t&:nth-child(2) {\n\t\t\t\tleft: calc(20px * 4);\n\t\t\t}\n\n\t\t\t&:nth-child(3) {\n\t\t\t\tleft: calc(20px * 3);\n\t\t\t}\n\n\t\t\t&:nth-child(4) {\n\t\t\t\tleft: calc(20px * 2);\n\t\t\t}\n\n\t\t\t&:nth-child(5) {\n\t\t\t\tleft: calc(20px * 1);\n\t\t\t}\n\n\t\t\t&:nth-child(6) {\n\t\t\t\tleft: calc(20px * 0);\n\t\t\t}\n\t\t}\n\t}\n\n\t.days {\n\t\tposition: absolute;\n\t\ttop: 120px;\n\n\t\t.day {\n\t\t\theight: 60px;\n\n\t\t\t.day-name {\n\t\t\t\tposition: absolute;\n\t\t\t\tfont-family: 'Star4000';\n\t\t\t\tfont-size: 24pt;\n\t\t\t\twidth: 200px;\n\t\t\t\ttext-align: right;\n\t\t\t\t@include u.text-shadow();\n\t\t\t\tpadding-top: 20px;\n\t\t\t}\n\n\t\t\t.risk-bar {\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: 150px;\n\t\t\t\theight: 40px;\n\t\t\t\tleft: 210px;\n\t\t\t\tmargin-top: 20px;\n\t\t\t\tborder: 3px outset hsl(0, 0%, 70%);\n\t\t\t\tbackground: linear-gradient(0deg, hsl(0, 0%, 40%) 0%, hsl(0, 0%, 60%) 50%, hsl(0, 0%, 40%) 100%);\n\t\t\t}\n\t\t}\n\t}\n}","@use 'shared/_colors'as c;\n@use 'shared/_utils'as u;\n\n#server-observations-html.weather-display {\n\t.header .title.single {\n\t\tfont-size: 20pt;\n\t}\n}\n\n.weather-display .server-observations {\n\t// Override the default has-scroll height to fit content properly\n\t&.main {\n\t\theight: auto !important;\n\t\tmin-height: 250px;\n\t}\n\n\t.container {\n\t\tposition: relative;\n\t\ttop: 15px;\n\t\tbox-sizing: border-box;\n\t\theight: 250px;\n\t\toverflow: hidden;\n\t}\n\n\t.server-output {\n\t\tposition: relative;\n\t\tfont-family: 'Star4000';\n\t\tfont-size: 20pt;\n\t\tline-height: 32px;\n\t\tcolor: #fff;\n\t\ttext-transform: uppercase;\n\t\ttext-align: center;\n\t\t@include u.text-shadow();\n\n\t\t.server-page {\n\t\t\theight: 250px;\n\t\t\tpadding: 0 8px;\n\t\t\tbox-sizing: border-box;\n\t\t}\n\n\t\t.server-line {\n\t\t\twhite-space: normal;\n\t\t\toverflow-wrap: anywhere;\n\t\t\tword-break: break-word;\n\t\t\tmargin-bottom: 6px;\n\t\t}\n\t}\n}\n","@use 'shared/_utils' as u;\n\n.weather-display .linux-news {\n\t&.main {\n\t\theight: auto !important;\n\t\tmin-height: 250px;\n\t}\n\n\t.container {\n\t\tposition: relative;\n\t\ttop: 15px;\n\t\tmargin: 0px 10px;\n\t\tbox-sizing: border-box;\n\t\theight: 250px;\n\t\toverflow: hidden;\n\t}\n\n\t.news-output {\n\t\tposition: relative;\n\n\t\t.news-page {\n\t\t\theight: 250px;\n\t\t\tbox-sizing: border-box;\n\t\t\tpadding: 0 8px;\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\tjustify-content: space-between;\n\t\t}\n\n\t\t.story {\n\t\t\theight: 116px;\n\t\t\toverflow: hidden;\n\t\t}\n\n\t\t.headline {\n\t\t\tfont-family: 'Star4000';\n\t\t\tfont-size: 17pt;\n\t\t\tline-height: 22px;\n\t\t\tcolor: #ff0;\n\t\t\ttext-transform: uppercase;\n\t\t\t@include u.text-shadow();\n\t\t\tmargin-bottom: 4px;\n\t\t}\n\n\t\t.blurb {\n\t\t\tfont-family: 'Star4000';\n\t\t\tfont-size: 14pt;\n\t\t\tline-height: 16px;\n\t\t\tcolor: #fff;\n\t\t\t@include u.text-shadow();\n\t\t\toverflow: hidden;\n\t\t}\n\t}\n}\n","/* =========================================================\n REGULAR SCANLINES SETTINGS\n ========================================================= */\n\n$scan-width: 1px;\n$scan-crt: false;\n$scan-fps: 20;\n$scan-color: rgba(#000, 0.30);\n$scan-z-index: 2147483648;\n\n$scan-moving-line: true;\n$scan-opacity: 0.75;\n\n/* =========================================================\n CRT / S-VIDEO EFFECT SETTINGS\n ========================================================= */\n\n// whole-screen softness\n$crt-soft-blur: 0.45px;\n\n// mild brightening / contrast to keep blur from looking muddy\n$crt-contrast: 1.04;\n$crt-saturation: 1.08;\n$crt-brightness: 0.98;\n\n// fake horizontal chroma bleed\n$crt-r-shift: -0.7px;\n$crt-b-shift: 0.7px;\n$crt-bleed-blur: 1.2px;\n$crt-rgb-opacity: 0.04;\n\n// subtle tube edge darkening\n$crt-vignette-opacity: 0.16;\n\n// optional tiny bloom\n$crt-glow-opacity: 0.05;\n\n/* =========================================================\n MIXINS\n ========================================================= */\n\n@mixin scan-crt($enabled) {\n @if $enabled == true {\n animation: scanlines 1s steps($scan-fps) infinite;\n } @else {\n animation: none;\n }\n}\n\n@mixin scan-moving($enabled) {\n @if $enabled == true {\n animation: scanline 6s linear infinite;\n } @else {\n animation: none;\n }\n}\n\n/* =========================================================\n APPLY TO THE REAL APP CONTAINER\n ========================================================= */\n\n/*\n You can add class=\"scanlines\" to #divTwcMain or #container.\n Example:\n
\n*/\n\n.scanlines {\n position: relative;\n overflow: hidden;\n isolation: isolate;\n\n /*\n This is the actual rendered weather area in your HTML.\n Applying the softness here affects the maps/text/icons themselves.\n */\n #container {\n position: relative;\n z-index: 1;\n transform: translateZ(0);\n will-change: filter;\n\n filter:\n blur($crt-soft-blur)\n saturate($crt-saturation)\n contrast($crt-contrast)\n brightness($crt-brightness);\n }\n\n /*\n Red fringe overlay\n */\n #container::before,\n #container::after {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 3;\n }\n\n #container::before {\n background:\n linear-gradient(\n to right,\n rgba(255, 0, 0, $crt-rgb-opacity) 0%,\n rgba(255, 0, 0, 0.01) 15%,\n rgba(255, 0, 0, 0.00) 50%,\n rgba(255, 0, 0, 0.01) 85%,\n rgba(255, 0, 0, $crt-rgb-opacity) 100%\n );\n transform: translateX($crt-r-shift);\n filter: blur($crt-bleed-blur);\n mix-blend-mode: screen;\n }\n\n /*\n Blue fringe overlay\n */\n #container::after {\n background:\n linear-gradient(\n to right,\n rgba(0, 140, 255, $crt-rgb-opacity) 0%,\n rgba(0, 140, 255, 0.01) 15%,\n rgba(0, 140, 255, 0.00) 50%,\n rgba(0, 140, 255, 0.01) 85%,\n rgba(0, 140, 255, $crt-rgb-opacity) 100%\n );\n transform: translateX($crt-b-shift);\n filter: blur($crt-bleed-blur);\n mix-blend-mode: screen;\n }\n\n /*\n Moving scanline\n */\n &:before,\n &:after {\n display: block;\n pointer-events: none;\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n }\n\n &:before {\n height: var(--scanline-thickness, $scan-width);\n z-index: $scan-z-index + 2;\n background: $scan-color;\n opacity: $scan-opacity;\n @include scan-moving($scan-moving-line);\n }\n\n /*\n Regular scanline mask\n */\n &:after {\n top: 0;\n bottom: 0;\n z-index: $scan-z-index;\n background: repeating-linear-gradient(\n to bottom,\n transparent 0,\n transparent var(--scanline-thickness, $scan-width),\n $scan-color var(--scanline-thickness, $scan-width),\n $scan-color calc(var(--scanline-thickness, $scan-width) * 2)\n );\n @include scan-crt($scan-crt);\n }\n\n /*\n Vignette layer\n Added as an inset shadow so you don't need extra HTML\n */\n box-shadow:\n inset 0 0 80px rgba(0, 0, 0, $crt-vignette-opacity),\n inset 0 0 18px rgba(255, 255, 255, $crt-glow-opacity);\n}\n\n/* =========================================================\n OPTIONAL: only affect active weather panels, not menus\n ========================================================= */\n\n/*\n If the controls / bottom nav get too blurry, move the blur\n from #container to the weather slides only:\n*/\n.scanlines.crt-panels-only {\n #container {\n filter: none;\n }\n\n .weather-display {\n filter:\n blur($crt-soft-blur)\n saturate($crt-saturation)\n contrast($crt-contrast)\n brightness($crt-brightness);\n\n transform: translateZ(0);\n }\n}\n\n/* =========================================================\n OPTIONAL: make text slightly glow like old TV phosphors\n ========================================================= */\n\n.scanlines {\n .header,\n .main,\n .scroll,\n .date-time,\n .city,\n .temp,\n .condition,\n .location,\n .label,\n .value,\n .title {\n text-shadow:\n 0 0 1px rgba(255, 255, 255, 0.18),\n 0 0 2px rgba(255, 255, 255, 0.06);\n }\n}\n\n/* =========================================================\n ANIMATIONS\n ========================================================= */\n\n@keyframes scanline {\n 0% {\n transform: translate3d(0, 200000%, 0);\n }\n}\n\n@keyframes scanlines {\n 0% {\n background-position: 0 50%;\n }\n}\n\n"],"file":"ws.min.css"}
\ No newline at end of file
diff --git a/views/partials/regional-forecast.ejs b/views/partials/regional-forecast.ejs
index bdcbb0c..c0ecfaa 100644
--- a/views/partials/regional-forecast.ejs
+++ b/views/partials/regional-forecast.ejs
@@ -1,13 +1,6 @@
<%- include('header.ejs', {titleDual:{ top: 'Regional' , bottom: 'Observations' }, hasTime: true }) %>
-
-
-
-
-
![]()
-
-
-
-
-
-
\ No newline at end of file
+
+