mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-16 00:29:34 -07:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc64e4bd7f | ||
|
|
776148fa6b | ||
|
|
69c050eb8f | ||
|
|
a3e142dade | ||
|
|
28917489bb | ||
|
|
2365a4c0f7 | ||
|
|
8afef77ea5 | ||
|
|
8f70ee87c5 | ||
|
|
4e7429bfba | ||
|
|
c5ffe1542a | ||
|
|
5364855c58 | ||
|
|
18efd810bd | ||
|
|
68a6bae3a7 | ||
|
|
5f0f0d9000 | ||
|
|
9d9cf4b0f3 | ||
|
|
9e500143c0 | ||
|
|
71da682660 | ||
|
|
1b9a1dcb22 | ||
|
|
095761ee81 | ||
|
|
21e528aaa3 | ||
|
|
a92c632937 | ||
|
|
6073fd1733 | ||
|
|
5da8185633 | ||
|
|
cf5c818ee3 | ||
|
|
97cec114f6 | ||
|
|
7efd2e8db7 | ||
|
|
8c28f41d54 | ||
|
|
e9d603fbfc | ||
|
|
32aa43c5b1 | ||
|
|
dbc56f014a | ||
|
|
3161a03797 |
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2024 Matt Walsh
|
||||
Copyright (c) 2020-2025 Matt Walsh
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
17
README.md
17
README.md
@@ -31,7 +31,7 @@ To run via Node locally:
|
||||
git clone https://github.com/netbymatt/ws4kp.git
|
||||
cd ws4kp
|
||||
npm i
|
||||
node index.js
|
||||
node index.mjs
|
||||
```
|
||||
|
||||
To run via Docker:
|
||||
@@ -87,6 +87,17 @@ I've made several changes to this Weather Star 4000 simulation compared to the o
|
||||
## Sharing a permalink (bookmarking)
|
||||
Selected displays, the forecast city and widescreen setting are sticky from one session to the next. However if you would like to share your exact configuration or bookmark it click the "Copy Permalink" (or get "Get Parmalink") near the bottom of the page. A URL will be copied to your clipboard with all of you selected displays and location (or copy it from the page if your browser doesn't support clipboard transfers directly). You can then share this link or add it to your bookmarks.
|
||||
|
||||
Your permalink will be very long. Here is an example for the Orlando Internation Airport:
|
||||
```
|
||||
https://weatherstar.netbymatt.com/?hazards-checkbox=false¤t-weather-checkbox=true&latest-observations-checkbox=true&hourly-checkbox=false&hourly-graph-checkbox=true&travel-checkbox=false®ional-forecast-checkbox=true&local-forecast-checkbox=true&extended-forecast-checkbox=true&almanac-checkbox=false&spc-outlook-checkbox=true&radar-checkbox=true&settings-wide-checkbox=false&settings-kiosk-checkbox=false&settings-scanLines-checkbox=false&settings-speed-select=1.00&settings-units-select=us&latLonQuery=Orlando+International+Airport%2C+Orlando%2C+FL%2C+USA&latLon=%7B%22lat%22%3A28.431%2C%22lon%22%3A-81.3076%7D
|
||||
```
|
||||
You can also build your own permalink. Any omitted settings will be filled with defaults. Here are a few examples:
|
||||
```
|
||||
https://weatherstar.netbymatt.com/?latLonQuery=Orlando+International+Airport
|
||||
https://weatherstar.netbymatt.com/?kiosk=true
|
||||
https://weatherstar.netbymatt.com/?settings-units-select=metric
|
||||
```
|
||||
|
||||
## Kiosk mode
|
||||
Kiosk mode can be activated by a checkbox on the page. Note that there is no way out of kiosk mode (except refresh or closing the browser), and the play/pause and other controls will not be available. This is deliberate as a browser's kiosk mode it intended not to be exited or significantly modified.
|
||||
|
||||
@@ -145,6 +156,10 @@ Note: not all units are converted to metric, if selected. Some text-based produc
|
||||
|
||||
Not retro enough? Try the [Weatherstar 3000+](https://github.com/netbymatt/ws3kp)
|
||||
|
||||
## Use
|
||||
|
||||
Linking directly to the live web site at https://weatherstar.netbymatt.com is encouraged. As is using the live site for digital signage, home dashboards, streaming and public display. Please note the disclaimer below.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This web site should NOT be used in life threatening weather situations, or be relied on to inform the public of such situations. The Internet is an unreliable network subject to server and network outages and by nature is not suitable for such mission critical use. If you require such access to NWS data, please consider one of their subscription services. The authors of this web site shall not be held liable in the event of injury, death or property damage that occur as a result of disregarding this warning.
|
||||
|
||||
@@ -99,8 +99,11 @@ const server = app.listen(port, () => {
|
||||
});
|
||||
|
||||
// graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
const gracefulShutdown = () => {
|
||||
server.close(() => {
|
||||
console.log('Server closed');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
process.on('SIGINT', gracefulShutdown);
|
||||
process.on('SIGTERM', gracefulShutdown);
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "5.21.2",
|
||||
"version": "5.21.13",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ws4kp",
|
||||
"version": "5.21.2",
|
||||
"version": "5.21.13",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.5.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ws4kp",
|
||||
"version": "5.21.2",
|
||||
"version": "5.21.13",
|
||||
"description": "Welcome to the WeatherStar 4000+ project page!",
|
||||
"main": "index.mjs",
|
||||
"type": "module",
|
||||
|
||||
BIN
server/images/icons/current-conditions/Smoke.gif
Normal file
BIN
server/images/icons/current-conditions/Smoke.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
server/images/nav/ic_scanlines_off_white_24dp_2x.png
Normal file
BIN
server/images/nav/ic_scanlines_off_white_24dp_2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 893 B |
BIN
server/images/nav/ic_scanlines_on_white_24dp_2x.png
Normal file
BIN
server/images/nav/ic_scanlines_on_white_24dp_2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 367 B |
@@ -38,6 +38,7 @@ const init = () => {
|
||||
document.querySelector('#NavigateNext').addEventListener('click', btnNavigateNextClick);
|
||||
document.querySelector('#NavigatePrevious').addEventListener('click', btnNavigatePreviousClick);
|
||||
document.querySelector('#NavigatePlay').addEventListener('click', btnNavigatePlayClick);
|
||||
document.querySelector('#ToggleScanlines').addEventListener('click', btnNavigateToggleScanlines);
|
||||
document.querySelector(TOGGLE_FULL_SCREEN_SELECTOR).addEventListener('click', btnFullScreenClick);
|
||||
const btnGetGps = document.querySelector(BNT_GET_GPS_SELECTOR);
|
||||
btnGetGps.addEventListener('click', btnGetGpsClick);
|
||||
@@ -294,6 +295,8 @@ const updateFullScreenNavigate = () => {
|
||||
};
|
||||
|
||||
const documentKeydown = (e) => {
|
||||
// don't trigger on ctrl/alt/shift modified key
|
||||
if (e.altKey || e.ctrlKey || e.shiftKey) return false;
|
||||
const { key } = e;
|
||||
|
||||
if (document.fullscreenElement || document.activeElement === document.body) {
|
||||
@@ -344,6 +347,11 @@ const btnNavigatePlayClick = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
const btnNavigateToggleScanlines = () => {
|
||||
settings.scanLines.value = !settings.scanLines.value;
|
||||
return false;
|
||||
};
|
||||
|
||||
// post a message to the iframe
|
||||
const postMessage = (type, myMessage = {}) => {
|
||||
navMessage({ type, message: myMessage });
|
||||
|
||||
@@ -50,6 +50,8 @@ class CurrentWeather extends WeatherDisplay {
|
||||
stillWaiting: () => this.stillWaiting(),
|
||||
});
|
||||
|
||||
if (observations.features.length === 0) throw new Error(`No features returned for station: ${station.properties.stationIdentifier}, trying next station`);
|
||||
|
||||
// test data quality
|
||||
if (observations.features[0].properties.temperature.value === null
|
||||
|| observations.features[0].properties.windSpeed.value === null
|
||||
@@ -59,10 +61,11 @@ class CurrentWeather extends WeatherDisplay {
|
||||
|| observations.features[0].properties.dewpoint.value === null
|
||||
|| observations.features[0].properties.barometricPressure.value === null) {
|
||||
observations = undefined;
|
||||
throw new Error(`Unable to get observations: ${station.properties.stationIdentifier}, trying next station`);
|
||||
throw new Error(`Incomplete data set for: ${station.properties.stationIdentifier}, trying next station`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
observations = undefined;
|
||||
}
|
||||
}
|
||||
// test for data received
|
||||
|
||||
@@ -126,6 +126,7 @@ const regexList = [
|
||||
[/chance /gi, ''],
|
||||
[/very /gi, ''],
|
||||
[/patchy /gi, ''],
|
||||
[/Areas Of /gi, ''],
|
||||
[/areas /gi, ''],
|
||||
[/dense /gi, ''],
|
||||
[/Thunderstorm/g, 'T\'Storm'],
|
||||
|
||||
@@ -65,6 +65,10 @@ const largeIcon = (link, _isNightTime) => {
|
||||
case 'sleet-n':
|
||||
return addPath('Sleet.gif');
|
||||
|
||||
case 'smoke':
|
||||
case 'smoke-n':
|
||||
return addPath('Smoke.gif');
|
||||
|
||||
case 'rain_showers':
|
||||
case 'rain_showers_high':
|
||||
case 'rain_showers-n':
|
||||
|
||||
@@ -44,6 +44,7 @@ const smallIcon = (link, _isNightTime) => {
|
||||
case 'sct-n':
|
||||
case 'nsct':
|
||||
case 'nsct-n':
|
||||
case 'haze-n':
|
||||
return addPath('Partly-Cloudy-Night.gif');
|
||||
|
||||
case 'ovc':
|
||||
|
||||
@@ -6,10 +6,27 @@ import WeatherDisplay from './weatherdisplay.mjs';
|
||||
import { registerDisplay, timeZone } from './navigation.mjs';
|
||||
import * as utils from './radar-utils.mjs';
|
||||
|
||||
// TEMPORARY fix to disable radar on ios safari. The same engine (webkit) is
|
||||
// used for all ios browers (chrome, brave, firefox, etc) so it's safe to skip
|
||||
// any subsequent narrowing of the user-agent.
|
||||
const isIos = /iP(ad|od|hone)/i.test(window.navigator.userAgent);
|
||||
// NOTE: iMessages/Messages preview is provided by an Apple scraper that uses a
|
||||
// user-agent similar to: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1)
|
||||
// AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4
|
||||
// facebookexternalhit/1.1 Facebot Twitterbot/1.0`. There is currently a bug in
|
||||
// Messages macos/ios where a constantly crashing website seems to cause an
|
||||
// entire Messages thread to permanently lockup until the individual website
|
||||
// preview is deleted! Messages ios will judder but allows the message to be
|
||||
// deleted eventually. Messages macos beachballs forever and prevents the
|
||||
// successful deletion. See
|
||||
// https://github.com/netbymatt/ws4kp/issues/74#issuecomment-2921154962 for more
|
||||
// context.
|
||||
const isBot = /twitterbot|Facebot/i.test(window.navigator.userAgent);
|
||||
|
||||
const RADAR_HOST = 'mesonet.agron.iastate.edu';
|
||||
class Radar extends WeatherDisplay {
|
||||
constructor(navId, elemId) {
|
||||
super(navId, elemId, 'Local Radar', true);
|
||||
super(navId, elemId, 'Local Radar', !isIos && !isBot);
|
||||
|
||||
this.okToDrawCurrentConditions = false;
|
||||
this.okToDrawCurrentDateTime = false;
|
||||
@@ -39,9 +56,6 @@ class Radar extends WeatherDisplay {
|
||||
{ time: 1, si: 4 },
|
||||
{ time: 12, si: 5 },
|
||||
];
|
||||
|
||||
// get some web workers started
|
||||
this.workers = (new Array(this.dopplerRadarImageMax)).fill(null).map(() => radarWorker());
|
||||
}
|
||||
|
||||
async getData(weatherParameters, refresh) {
|
||||
@@ -53,6 +67,12 @@ class Radar extends WeatherDisplay {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the workers started
|
||||
if (!this.workers) {
|
||||
// get some web workers started
|
||||
this.workers = (new Array(this.dopplerRadarImageMax)).fill(null).map(() => radarWorker());
|
||||
}
|
||||
|
||||
const baseUrl = `https://${RADAR_HOST}/archive/data/`;
|
||||
const baseUrlEnd = '/GIS/uscomp/?F=0&P=n0r*.png';
|
||||
const baseUrls = [];
|
||||
@@ -199,4 +219,7 @@ const radarWorker = () => {
|
||||
};
|
||||
|
||||
// register display
|
||||
registerDisplay(new Radar(11, 'radar'));
|
||||
// TEMPORARY: except on IOS and bots
|
||||
if (!isIos && !isBot) {
|
||||
registerDisplay(new Radar(11, 'radar'));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { json } 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 { DateTime, Interval } from '../vendor/auto/luxon.mjs';
|
||||
import WeatherDisplay from './weatherdisplay.mjs';
|
||||
import { registerDisplay } from './navigation.mjs';
|
||||
import * as utils from './regionalforecast-utils.mjs';
|
||||
@@ -77,6 +77,9 @@ class RegionalForecast extends WeatherDisplay {
|
||||
// get a unit converter
|
||||
const temperatureConverter = temperatureUnit();
|
||||
|
||||
// get now as DateTime for calculations below
|
||||
const now = DateTime.now();
|
||||
|
||||
// get regional forecasts and observations (the two are intertwined due to the design of api.weather.gov)
|
||||
const regionalDataAll = await Promise.all(regionalCities.map(async (city) => {
|
||||
try {
|
||||
@@ -110,14 +113,24 @@ class RegionalForecast extends WeatherDisplay {
|
||||
preloadImg(getSmallIcon(regionalObservation.icon, !regionalObservation.daytime));
|
||||
|
||||
// return a pared-down forecast
|
||||
// 0th object is the current conditions
|
||||
// first object is the next period i.e. if it's daytime then it's the "tonight" forecast
|
||||
// second object is the following period
|
||||
// always skip the first forecast index because it's what's going on right now
|
||||
// 0th object should contain the current conditions, but when WFOs go offline or otherwise don't post
|
||||
// an updated forecast it's possible that the 0th object is in the past.
|
||||
// so we go on a search for the current time in the start/end times provided in the forecast periods
|
||||
const { periods } = forecast.properties;
|
||||
const currentPeriod = periods.reduce((prev, period, index) => {
|
||||
const start = DateTime.fromISO(period.startTime);
|
||||
const end = DateTime.fromISO(period.endTime);
|
||||
const interval = Interval.fromDateTimes(start, end);
|
||||
if (interval.contains(now)) {
|
||||
return index;
|
||||
}
|
||||
return prev;
|
||||
}, 0);
|
||||
// group together the current observation and next two periods
|
||||
return [
|
||||
regionalObservation,
|
||||
utils.buildForecast(forecast.properties.periods[1], city, cityXY),
|
||||
utils.buildForecast(forecast.properties.periods[2], city, cityXY),
|
||||
utils.buildForecast(forecast.properties.periods[currentPeriod + 1], city, cityXY),
|
||||
utils.buildForecast(forecast.properties.periods[currentPeriod + 2], city, cityXY),
|
||||
];
|
||||
} catch (error) {
|
||||
console.log(`No regional forecast data for '${city.name ?? city.city}'`);
|
||||
|
||||
@@ -33,6 +33,12 @@ const init = () => {
|
||||
[1.5, 'Very Slow'],
|
||||
],
|
||||
});
|
||||
settings.scanLines = new Setting('scanLines', {
|
||||
name: 'Scan Lines',
|
||||
defaultValue: false,
|
||||
changeAction: scanLineChange,
|
||||
sticky: true,
|
||||
});
|
||||
settings.units = new Setting('units', {
|
||||
name: 'Units',
|
||||
type: 'select',
|
||||
@@ -85,6 +91,18 @@ const kioskChange = (value) => {
|
||||
}
|
||||
};
|
||||
|
||||
const scanLineChange = (value) => {
|
||||
const container = document.getElementById('container');
|
||||
const navIcons = document.getElementById('ToggleScanlines');
|
||||
if (value) {
|
||||
container.classList.add('scanlines');
|
||||
navIcons.classList.add('on');
|
||||
} else {
|
||||
container.classList.remove('scanlines');
|
||||
navIcons.classList.remove('on');
|
||||
}
|
||||
};
|
||||
|
||||
const unitChange = () => {
|
||||
// reload the data at the top level to refresh units
|
||||
// after the initial load
|
||||
|
||||
@@ -5,6 +5,11 @@ const text = (url, params) => fetchAsync(url, 'text', params);
|
||||
const blob = (url, params) => fetchAsync(url, 'blob', params);
|
||||
|
||||
const fetchAsync = async (_url, responseType, _params = {}) => {
|
||||
// add user agent header to json request at api.weather.gov
|
||||
const headers = {};
|
||||
if (_url.toString().match(/api\.weather\.gov/)) {
|
||||
headers['user-agent'] = 'Weatherstar 4000+; weatherstar@netbymatt.com';
|
||||
}
|
||||
// combine default and provided parameters
|
||||
const params = {
|
||||
method: 'GET',
|
||||
@@ -12,6 +17,7 @@ const fetchAsync = async (_url, responseType, _params = {}) => {
|
||||
type: 'GET',
|
||||
retryCount: 0,
|
||||
..._params,
|
||||
headers,
|
||||
};
|
||||
// store original number of retries
|
||||
params.originalRetries = params.retryCount;
|
||||
|
||||
@@ -189,7 +189,7 @@ class Setting {
|
||||
break;
|
||||
case 'checkbox':
|
||||
default:
|
||||
this.element.checked = newValue;
|
||||
this.element.querySelector('input').checked = newValue;
|
||||
}
|
||||
this.storeToLocalStorage(this.myValue);
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -23,6 +23,8 @@ body {
|
||||
|
||||
&.kiosk {
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +143,10 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.kiosk #divTwc {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
#divTwcLeft {
|
||||
display: none;
|
||||
text-align: right;
|
||||
@@ -322,10 +328,6 @@ body {
|
||||
transform-origin: unset;
|
||||
}
|
||||
|
||||
.kiosk #divTwc #container {
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
#loading {
|
||||
width: 640px;
|
||||
height: 480px;
|
||||
@@ -426,10 +428,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.kiosk #divTwc {
|
||||
justify-content: unset;
|
||||
}
|
||||
|
||||
#divTwc:fullscreen #display,
|
||||
.kiosk #divTwc #display {
|
||||
position: relative;
|
||||
@@ -458,6 +456,30 @@ body {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#ToggleScanlines {
|
||||
display: inline-block;
|
||||
|
||||
.on {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.off {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
&.on {
|
||||
.on {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.off {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.visible {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
@@ -738,7 +760,7 @@ body {
|
||||
}
|
||||
|
||||
#share-link-copied {
|
||||
color: c.$title-color;
|
||||
color: hsl(60, 100%, 30%);
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -750,6 +772,7 @@ body {
|
||||
|
||||
#divQuery,
|
||||
>.info,
|
||||
>.related-links,
|
||||
>.heading,
|
||||
#enabledDisplays,
|
||||
#settings,
|
||||
|
||||
@@ -13,4 +13,5 @@
|
||||
@use 'almanac';
|
||||
@use 'hazards';
|
||||
@use 'media';
|
||||
@use 'spc-outlook';
|
||||
@use 'spc-outlook';
|
||||
@use 'shared/scanlines';
|
||||
106
server/styles/scss/shared/_scanlines.scss
Normal file
106
server/styles/scss/shared/_scanlines.scss
Normal file
@@ -0,0 +1,106 @@
|
||||
/* REGULAR SCANLINES SETTINGS */
|
||||
|
||||
// width of 1 scanline (min.: 1px)
|
||||
$scan-width: 1px;
|
||||
|
||||
// emulates a damage-your-eyes bad pre-2000 CRT screen ♥ (true, false)
|
||||
$scan-crt: false;
|
||||
|
||||
// frames-per-second (should be > 1), only applies if $scan-crt: true;
|
||||
$scan-fps: 20;
|
||||
|
||||
// scanline-color (rgba)
|
||||
$scan-color: rgba(#000, .3);
|
||||
|
||||
// set z-index on 8, like in ♥ 8-bits ♥, or…
|
||||
// set z-index on 2147483648 or more to enable scanlines on Chrome fullscreen (doesn't work in Firefox or IE);
|
||||
$scan-z-index: 2147483648;
|
||||
|
||||
/* MOVING SCANLINE SETTINGS */
|
||||
|
||||
// moving scanline (true, false)
|
||||
$scan-moving-line: true;
|
||||
|
||||
// opacity of the moving scanline
|
||||
$scan-opacity: .75;
|
||||
|
||||
/* MIXINS */
|
||||
|
||||
// apply CRT animation: @include scan-crt($scan-crt);
|
||||
@mixin scan-crt($scan-crt) {
|
||||
@if $scan-crt==true {
|
||||
animation: scanlines 1s steps($scan-fps) infinite;
|
||||
}
|
||||
|
||||
@else {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
// apply CRT animation: @include scan-crt($scan-crt);
|
||||
@mixin scan-moving($scan-moving-line) {
|
||||
@if $scan-moving-line==true {
|
||||
animation: scanline 6s linear infinite;
|
||||
}
|
||||
|
||||
@else {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* CSS .scanlines CLASS */
|
||||
|
||||
.scanlines {
|
||||
position: relative;
|
||||
overflow: hidden; // only to animate the unique scanline
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
// unique scanline travelling on the screen
|
||||
&:before {
|
||||
// position: absolute;
|
||||
// bottom: 100%;
|
||||
width: 100%;
|
||||
height: $scan-width * 1;
|
||||
z-index: $scan-z-index + 1;
|
||||
background: $scan-color;
|
||||
opacity: $scan-opacity;
|
||||
// animation: scanline 6s linear infinite;
|
||||
@include scan-moving($scan-moving-line);
|
||||
}
|
||||
|
||||
// the scanlines, so!
|
||||
&:after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: $scan-z-index;
|
||||
background: linear-gradient(to bottom,
|
||||
transparent 50%,
|
||||
$scan-color 51%);
|
||||
background-size: 100% $scan-width*2;
|
||||
@include scan-crt($scan-crt);
|
||||
}
|
||||
}
|
||||
|
||||
/* ANIMATE UNIQUE SCANLINE */
|
||||
@keyframes scanline {
|
||||
0% {
|
||||
transform: translate3d(0, 200000%, 0);
|
||||
// bottom: 0%; // to have a continuous scanline move, use this line (here in 0% step) instead of transform and write, in &:before, { position: absolute; bottom: 100%; }
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scanlines {
|
||||
0% {
|
||||
background-position: 0 50%;
|
||||
// bottom: 0%; // to have a continuous scanline move, use this line (here in 0% step) instead of transform and write, in &:before, { position: absolute; bottom: 100%; }
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,10 @@
|
||||
<img class="navButton off" src="images/nav/ic_volume_off_white_24dp_2x.png" title="Unmute" />
|
||||
<img class="navButton on" src="images/nav/ic_volume_on_white_24dp_2x.png" title="Mute" />
|
||||
</div>
|
||||
<div id="ToggleScanlines">
|
||||
<img class="navButton off" src="images/nav/ic_scanlines_off_white_24dp_2x.png" title="Scan lines on" />
|
||||
<img class="navButton on" src="images/nav/ic_scanlines_on_white_24dp_2x.png" title="Scan lines off" />
|
||||
</div>
|
||||
<img id="ToggleFullScreen" class="navButton" src="images/nav/ic_fullscreen_white_24dp_2x.png" title="Enter Fullscreen" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user