add script - jscancer - Javascript crap (relatively small)
(HTM) git clone git://git.codemadness.org/jscancer
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
(DIR) commit ba166782d82d92a2dcf4ee22b96da2285874a4b3
(DIR) parent 9ba8da523384de2bdf895531b8145f93441ceec9
(HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Thu, 15 Feb 2024 19:18:22 +0100
add script
Diffstat:
A narrowcasting/README | 27 +++++++++++++++++++++++++++
A narrowcasting/index.html | 48 +++++++++++++++++++++++++++++++
A narrowcasting/script.js | 619 +++++++++++++++++++++++++++++++
A narrowcasting/style.css | 237 +++++++++++++++++++++++++++++++
4 files changed, 931 insertions(+), 0 deletions(-)
---
(DIR) diff --git a/narrowcasting/README b/narrowcasting/README
@@ -0,0 +1,27 @@
+Narrowcasting
+-------------
+
+This is a simple script for some "narrowcasting" screen.
+It allows to defined HTML elements with data attributes for their configuration.
+The script will then handle the widgets and slides logic.
+
+
+Some features:
+
+* Support embedding of data in <iframe>'s and refresh using some timer.
+* Support embedding of data in <embed> and update using some timer.
+* Support updating parts of the screen data using XMLHttpRequest (AJAX) using
+ some timer.
+* Date / clock widget, format it with a strftime()-like syntax.
+ Supports any locale forthe weekday and month names.
+* Support embedded videos, automatically pausing and continueing them when it
+ skips to the next slide or looping them.
+* A mechanism to poll some file for changes using some timer. When this data
+ changes the page is reloaded (forcing also a cache flush). This is useful for
+ remotely updating the layouts or scripts.
+* Show a newsticker. This uses the JSON Feed format.
+ sfeed_json could be used to convert from RSS/Atom to this format. This data
+ could be updated using a cronjob.
+* Progressbar to show the slides remaining duration.
+* Hotkeys to skip slides, useful for debugging also.
+* Uses vanilla JS with no dependencies on frameworks, etc.
(DIR) diff --git a/narrowcasting/index.html b/narrowcasting/index.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title>Narrowcasting</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+<link rel="icon" type="image/png" href="favicon.png" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<meta http-equiv="X-UA-Compatible" content="IE=edge" />
+</head>
+<body>
+
+<div class="screen check-updates" data-update="120000" data-url="data/check_updated">
+ <div class="logo"></div>
+ <div class="topbar-right">
+ <span class="datetime time" data-format="%H:%M" data-update="60000"></span>
+ <span class="datetime date" data-format="<span>%A <b>%e %B</b>" data-update="60000"></span>
+ </div>
+
+ <div class="topbar"></div>
+
+ <div class="topbar-info-dashboard"></div>
+
+ <div class="slides">
+ <div class="slide slide-1" id="slide-1" data-displaynext="30000">
+ <!-- widgets slide 1 -->
+ <div class="widget xhr-content" data-url="data/1" data-timeout="60000" data-update="900000" data-animate-class="visible"></div>
+ <div class="widget xhr-content" data-url="data/2" data-update="900000" data-animate-class="visible"></div>
+ <div class="widget xhr-content" data-url="data/3" data-update="900000" data-animate-class="visible"></div>
+
+ <div class="widget widget-lichess embed-content" data-update="3600000">
+ <iframe src="https://lichess.org/training/frame?theme=green&bg=light" style="width: 400px; height: 444px;" allowtransparency="true" frameborder="0"></iframe>
+ </div>
+
+ <div class="news-ticker ticker1" data-url="data/news1.json" data-update="900000" data-fadein="1000" data-displaynext="20000"></div>
+ <div class="news-ticker ticker2" data-url="data/news2.json" data-update="900000" data-fadein="1000" data-displaynext="20000"></div>
+ </div>
+
+ <div class="slide slide-2" id="slide-2" data-displaynext="30000" style="background-image: url('http://local/image.png')">
+ </div>
+
+ <div class="progressbar"></div>
+ </div>
+</div>
+<script type="text/javascript" src="script.js">
+</script>
+</body>
+</html>
(DIR) diff --git a/narrowcasting/script.js b/narrowcasting/script.js
@@ -0,0 +1,619 @@
+// run atleast once, then every n milliseconds.
+// if n is 0 just run once.
+function updateevery(n, fn) {
+ // schedule in parallel now.
+ setTimeout(function() {
+ fn();
+ }, 0);
+ if (!n)
+ return null;
+ return setInterval(fn, n);
+}
+
+function pad0(n) {
+ return (parseInt(n) < 10 ? "0" : "") + n.toString();
+}
+
+function padspace(n) {
+ return (parseInt(n) < 10 ? " " : "") + n.toString();
+}
+
+// dutch weekdays and months.
+//var strftime_weekdays = [ "Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag" ];
+//var strftime_months = [ "Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December" ];
+
+// subset of strftime()
+function strftime(fmt, d) {
+ //var locale = locale || navigator.language || "en";
+ var locale = "nl-NL"; // dutch
+ d = d || new Date();
+
+ var mon = d.getMonth(); // starts at 0
+ var day = d.getDay(); // weekday, starts at 0 (sunday).
+
+ var s = fmt;
+ s = s.replace(/%Y/g, d.getFullYear().toString());
+ s = s.replace(/%m/g, pad0(mon + 1));
+ s = s.replace(/%d/g, pad0(d.getDate()));
+ s = s.replace(/%e/g, padspace(d.getDate()));
+ s = s.replace(/%H/g, pad0(d.getHours()));
+ s = s.replace(/%M/g, pad0(d.getMinutes()));
+ s = s.replace(/%S/g, pad0(d.getSeconds()));
+
+ s = s.replace(/%A/g, (new Intl.DateTimeFormat(locale, { weekday: "long" })).format(d)); // full weekday name
+ s = s.replace(/%a/g, (new Intl.DateTimeFormat(locale, { weekday: "short" })).format(d)); // abbreviated weekday name
+ s = s.replace(/%B/g, (new Intl.DateTimeFormat(locale, { month: "long" })).format(d)); // full month name
+ s = s.replace(/%b/g, (new Intl.DateTimeFormat(locale, { month: "short" })).format(d)); // abbreviated month name
+
+ //s = s.replace(/%A/g, strftime_weekdays[day]); // full weekday name
+ //s = s.replace(/%a/g, strftime_weekdays[day].substring(0, 3)); // abbreviated weekday name
+ //s = s.replace(/%B/g, strftime_months[mon]); // full month name
+ //s = s.replace(/%b/g, strftime_months[mon].substring(0, 3)); // abbreviated month name
+
+ return s;
+}
+
+// XMLHttpRequest helper function.
+function xhr(url, fn, timeout) {
+ var x = new(XMLHttpRequest);
+ x.open("get", url, true); // async
+ x.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ x.timeout = parseInt(timeout || 10000); // default: 10 seconds
+ x.onreadystatechange = function () {
+ if (x.readyState != 4)
+ return;
+ fn(x);
+ };
+ var data = "";
+ x.send(data);
+}
+
+// find direct descendent child element with a certain classname.
+function getdirectchilds(parent, classname) {
+ var els = [];
+
+ for (var i = 0; i < parent.children.length; i++) {
+ var el = parent.children[i];
+ if (el.classList.contains(classname))
+ els.push(el);
+ }
+
+ return els;
+}
+
+// news tickers.
+var tickerels = document.getElementsByClassName("news-ticker");
+var tickers = [];
+for (var i = 0; i < tickerels.length; i++) {
+ var url = tickerels[i].getAttribute("data-url") || "";
+ if (url == "")
+ continue;
+
+ // bind element to context
+ var t = (function(tickerel, delay) {
+ var displayfn = function(ticker) {
+ var parent = ticker.el;
+ var items = ticker.items || [];
+ var counter = ticker.counter || 0;
+
+ var node = document.createElement("div");
+ node.innerText = items[counter] || "";
+ parent.appendChild(node); // add new.
+ counter = (counter + 1) % items.length;
+
+ ticker.counter = counter; // update counter
+
+ if (parent.children.length) {
+ // schedule to be removed.
+ setTimeout(function() {
+ if (parent.children.length)
+ parent.children[0].className = "out";
+ setTimeout(function() {
+ if (parent.children.length > 1)
+ parent.removeChild(parent.children[0]);
+ }, ticker.fadein);
+ }, ticker.display);
+ }
+ };
+
+ var processfn = function(x) {
+ var feed = JSON.parse(x.responseText || "");
+ var items = [];
+ var feeditems = feed.items || [];
+
+ for (var j = 0; j < feeditems.length; j++) {
+ var title = feeditems[j].title;
+ var ts = feeditems[j].date_published || "";
+ // if there is a timestamp prefix it in the title.
+ if (ts.length) {
+ var d = new Date(Date.parse(ts));
+ title = strftime("%H:%M", d) + " " + title;
+ }
+ items.push(title);
+ }
+ ticker.items = items;
+ };
+
+ // ticker config / context.
+ var ticker = {
+ el: tickerel,
+ url: (tickerel.getAttribute("data-url") || ""),
+ display: parseInt(tickerel.getAttribute("data-displaynext")) || 10000, // default: 10 seconds
+ fadein: parseInt(tickerel.getAttribute("data-fadein")) || 1000, // default: 1 second
+ update: parseInt(tickerel.getAttribute("data-update")) || (15 * 60 * 1000) // default: 15 minutes
+ };
+
+ ticker.processfn = processfn;
+ ticker.displayfn = displayfn;
+
+ // reload RSS/Atom data.
+ var updater = function() {
+ xhr(ticker.url, function(x) {
+ ticker.processfn(x);
+
+ // reset and stop previous timer for cycling items.
+ if (ticker.displaytimer)
+ clearInterval(ticker.displaytimer);
+
+ ticker.counter = 0; // reset item counter
+ ticker.el.innerHTML = ""; // clear contents
+ ticker.displayfn(ticker); // immediately update the first time
+
+ // display / cycle existing items.
+ ticker.displaytimer = setInterval(function() { ticker.displayfn(ticker); }, ticker.display);
+ });
+ };
+
+ ticker.updater = updater;
+ ticker.updatetimer = setInterval(updater, ticker.update); // update every 15 minutes.
+ setTimeout(updater, delay); // small delay so the newstickers don't align up.
+
+ return ticker;
+ })(tickerels[i], 1000 * i);
+
+ tickers.push(t);
+}
+
+// elements showing simple HTML content in a placeholder, refresh using XMLHTTPRequest (AJAX).
+var xhrcontentels = document.getElementsByClassName("xhr-content");
+for (var i = 0; i < xhrcontentels.length; i++) {
+ var xhrcontentel = xhrcontentels[i];
+ var url = xhrcontentel.getAttribute("data-url") || "";
+ if (url == "")
+ continue;
+
+ // bind element to context.
+ (function(bindel) {
+ var updatefn = function(config) {
+ var parent = config.el;
+ var curcontent = parent.childNodes.length ? parent.childNodes[0].innerHTML : "";
+ var newcontent = config.data || "";
+ // content changed.
+ if (curcontent === newcontent)
+ return;
+
+ // remove previous nodes.
+ while (parent.childNodes.length > 0)
+ parent.removeChild(parent.childNodes[0]);
+
+ // add nodes to force a transition.
+ var node = document.createElement("div");
+ node.innerHTML = newcontent;
+ if (config.animate !== "") {
+ node.classList.add("animate-once");
+ node.classList.add(config.animateclass);
+ }
+ parent.appendChild(node); // add new.
+ };
+
+ var processfn = function(x) {
+ var data = "";
+ if (x.status >= 200 && x.status < 400)
+ data = x.responseText || "";
+ else
+ data = ""; // "ERROR";
+ config.data = data;
+ };
+
+ // config / context.
+ var config = {
+ animateclass: bindel.getAttribute("data-animate-class"),
+ el: bindel,
+ processfn: processfn,
+ timeout: parseInt(bindel.getAttribute("data-timeout")) || 10000, // default: 10 seconds
+ update: parseInt(bindel.getAttribute("data-update")) || (15 * 60 * 1000), // default: 15 minutes
+ updatefn: updatefn,
+ url: (bindel.getAttribute("data-url") || "")
+ };
+
+ // reload data.
+ var updater = function() {
+ xhr(config.url, function(x) {
+ // process data.
+ config.processfn(x);
+ // update and display data.
+ config.updatefn(config);
+ }, config.timeout);
+ };
+
+ setInterval(updater, config.update);
+ // run almost immediately once the first time.
+ setTimeout(updater, 1);
+ })(xhrcontentel);
+}
+
+// object or iframe elements that simply show some HTML and refresh in an interval.
+var embedcontentels = document.getElementsByClassName("embed-content");
+for (var i = 0; i < embedcontentels.length; i++) {
+ var embedcontentel = embedcontentels[i];
+
+ // must have one child node of type "object" or "iframe".
+ if (embedcontentel.children.length <= 0)
+ continue;
+
+ var child = embedcontentel.children[0];
+
+ // bind element to context.
+ (function(bindel, child) {
+ var url = "";
+ var type = "";
+ var tagname = child.tagName;
+ if (tagname === "OBJECT") {
+ url = child.data || "";
+ type = child.getAttribute("data-type") || "text/html";
+ } else if (tagname === "IFRAME") {
+ url = child.getAttribute("src") || "";
+ }
+
+ // config / context.
+ var config = {
+ el: bindel,
+ update: parseInt(bindel.getAttribute("data-update")) || (15 * 60 * 1000), // default: 15 minutes
+ url: url,
+ type: type
+ };
+
+ var updater;
+ if (tagname === "OBJECT") {
+ // reload data.
+ updater = function() {
+ var objects = Array.from(bindel.childNodes); // copy nodes.
+
+ var object = document.createElement("object");
+ object.data = config.url; // reload
+ object.type = config.type;
+ object.style.visibility = "hidden";
+ object.onload = function() {
+ // quickly swap out the old object(s) and show the new object.
+ // this prevents some flickering.
+ for (var j = 0; j < objects.length; j++)
+ bindel.removeChild(objects[j]);
+ this.style.visibility = "visible";
+ };
+ bindel.appendChild(object);
+ };
+ } else if (tagname === "IFRAME") {
+ // reload data.
+ updater = function() {
+ child.contentWindow.location.reload();
+ };
+ }
+
+ setInterval(updater, config.update);
+ // run almost immediately once the first time.
+ setTimeout(updater, 1);
+ })(embedcontentel, child);
+}
+
+// pause videos inside element.
+function pausevideos(el) {
+ var videos = el.getElementsByTagName("video");
+ for (var i = 0; i < videos.length; i++) {
+ videos[i].pause();
+ if ((videos[i].getAttribute("data-reset-on-pause") || "") === "1")
+ videos[i].currentTime = 0; // reset time / seek to start.
+ }
+}
+
+// play / resume videos inside element.
+function playvideos(el) {
+ var videos = el.getElementsByTagName("video");
+ for (var i = 0; i < videos.length; i++) {
+ videos[i].muted = true; // mute audio by default.
+ if ((videos[i].getAttribute("data-reset-on-play") || "") === "1")
+ videos[i].currentTime = 0; // reset time / seek to start.
+ videos[i].play(); // NOTE: auto-play must be enabled by the client.
+ }
+}
+
+// date / clocks.
+var dates = document.getElementsByClassName("datetime");
+for (var i = 0; i < dates.length; i++) {
+ var format = dates[i].getAttribute("data-format") || "";
+ var freq = dates[i].getAttribute("data-update") || 1000; // default: 1 second
+
+ var fn = (function(el, freq, format) {
+ return function() {
+ el.innerHTML = strftime(format); // allow HTML in format.
+ };
+ })(dates[i], freq, format);
+ updateevery(freq, fn);
+}
+
+// init slides and timings.
+function slides_init(rootel) {
+ rootel = rootel || document;
+ var slides = {
+ current: 0,
+ prev: -1,
+ prevprev: -1,
+ slides: []
+ };
+
+ // find direct descendent element with class "slide".
+ var slideels = getdirectchilds(rootel, "slide");
+ for (var i = 0; i < slideels.length; i++) {
+ var attrtiming = slideels[i].getAttribute("data-displaynext") || "";
+ var timing = parseInt(attrtiming);
+ if (timing <= 0)
+ timing = 5000;
+
+ var slide = {
+ timing: timing,
+ el: slideels[i]
+ };
+ slides.slides.push(slide);
+ }
+ return slides;
+}
+
+function slides_showcurrent(slides) {
+ var el = slides.slides[slides.current].el;
+ if (el === null)
+ return;
+ el.classList.add("visible");
+ playvideos(el);
+}
+
+function slides_change(slides) {
+ if (typeof(slides.prev) !== "undefined")
+ slides.slides[slides.prev].elapsed = 0;
+ slides.slides[slides.current].elapsed = 0;
+
+ slides_showcurrent(slides);
+ if (slides.onchange)
+ slides.onchange(slides.prev, slides.prevprev); // current, prev
+
+ // completely hidden.
+ if (slides.prevprev !== -1) {
+ var el = slides.slides[slides.prevprev].el;
+ el.classList.remove("pause");
+ }
+
+ // pause / fade out.
+ if (slides.prev !== -1) {
+ var el = slides.slides[slides.prev].el;
+ el.classList.remove("visible");
+ el.classList.add("pause");
+ pausevideos(el);
+ }
+}
+
+function slides_go(slides, n) {
+ // out-of-bounds or the same: do nothing.
+ if (n < 0 || n >= slides.slides.length || n == slides.current)
+ return;
+ slides.prevprev = slides.prev;
+ slides.prev = slides.current;
+ slides.current = n;
+ slides_change(slides);
+}
+
+function slides_prev(slides) {
+ var n = slides.current - 1;
+ if (n < 0 && slides.slides.length)
+ n = slides.slides.length - 1;
+ slides_go(slides, n);
+ slides_change(slides);
+}
+
+function slides_next(slides) {
+ var n = slides.current + 1;
+ if (n >= slides.slides.length)
+ n = 0;
+ slides_go(slides, n);
+ slides_change(slides);
+}
+
+function slides_pause(slides) {
+ slides.paused = true;
+ clearTimeout(slides.timernextslide);
+ clearTimeout(slides.timercurrentslide);
+ pausevideos(slides.slides[slides.current].el);
+}
+
+function slides_play(slides) {
+ slides_continue(slides);
+}
+
+function slides_continue(slides) {
+ slides.paused = false;
+ slides_updater(slides);
+ slides_showcurrent(slides);
+}
+
+function slides_updater(slides) {
+ // need more than 1 slide to change it.
+ if (slides.slides.length <= 1)
+ return;
+ var fn = function() {
+ slides_next(slides);
+ var currentslide = slides.slides[slides.current];
+ var timing = currentslide.timing || 5000; // default: 5 seconds
+ timing -= (currentslide.elapsed || 0); // minus played time for this slide.
+ slides.timernextslide = setTimeout(fn, timing);
+ };
+ var currentslide = slides.slides[slides.current];
+ var timing = currentslide.timing || 5000; // default: 5 seconds
+ timing -= (currentslide.elapsed || 0); // minus played time for this slide.
+ slides.timercurrentslide = setTimeout(fn, timing);
+}
+
+function progressbar_init(slides, progressbar) {
+ if (slides.slides.length <= 1)
+ return;
+
+ // config: show total progress or progress per slide.
+ var usetotalprogress = parseInt(progressbar.getAttribute("data-total-progress")) || 0;
+
+ function progressbar_update(slides) {
+ var currentslide = slides.slides[slides.current];
+ var total = currentslide.timing || 5000; // default: 5 seconds
+
+ if (usetotalprogress) {
+ total = 0;
+ for (var i = 0; i < slides.slides.length; i++) {
+ total += parseInt(slides.slides[i].timing) || 0;
+ }
+ }
+
+ currentslide.elapsed = currentslide.elapsed || 0;
+
+ var perfnow = window.performance.now()
+ slides.prevprogress = slides.prevprogress || 0;
+
+ if (!slides.paused) {
+ currentslide.elapsed += perfnow - slides.prevprogress;
+ }
+ slides.prevprogress = perfnow;
+
+ var elapsed = currentslide.elapsed;
+ if (usetotalprogress) {
+ // current slide progress + all finished previous ones.
+ for (var i = 0; i < slides.current; i++)
+ elapsed += parseInt(slides.slides[i].timing) || 0;
+ }
+
+ // generate linear gradient with ticks for the total progress.
+ if (usetotalprogress) {
+ var total = 0;
+ for (var i = 0; i < slides.slides.length; i++) {
+ total += parseInt(slides.slides[i].timing) || 0;
+ }
+
+ var pattern = [];
+ var timing = 0;
+ for (var i = 0; i < slides.slides.length; i++) {
+ timing += parseInt(slides.slides[i].timing) || 0;
+ var percent = (timing / total) * 100.0;
+
+ pattern.push("rgba(255, 255, 255, 0.0) " + (Math.floor((percent - 0.2) * 100) / 100.0) + "%");
+
+ // don't show tick for last.
+ if (i !== slides.slides.length - 1) {
+ // tick color
+ pattern.push("rgba(255,255,255, 1.0) " + (Math.floor((percent) * 100) / 100.0) + "%");
+ pattern.push("rgba(255,255,255, 0.0) " + (Math.floor((percent + 0.2) * 100) / 100.0) + "%");
+ }
+ }
+
+ // ticks gradient;
+ var gradient = "linear-gradient(90deg, " + pattern.join(",") + ")";
+ // progress gradient.
+ var percent = (elapsed / total) * 100.0;
+ gradient += ", linear-gradient(90deg, rgba(255, 255, 255, 0.5) " + percent +
+ "%, rgba(255, 255, 255, 0) " + (percent + 0.1) +
+ "%, rgba(255, 255, 255, 0) 100%)" // actual progress
+ progressbar.style.background = gradient;
+ progressbar.style.width = "100%";
+ } else {
+ var percent = (elapsed / total) * 100.0;
+ progressbar.style.width = percent + "%";
+ }
+ }
+
+ function progressbar_updater() {
+ var fn = function() {
+ progressbar_update(slides);
+ };
+ setInterval(fn, 16); // 16ms, update at ~60fps
+ }
+
+ progressbar_updater(slides);
+}
+
+// initialize slides, can be nested.
+var rootels = document.getElementsByClassName("slides") || [];
+for (var i = 0; i < rootels.length; i++) {
+ var rootel = rootels[i];
+
+ var slides = slides_init(rootel);
+ slides_play(slides);
+
+ /* progressbar: shows current slide progress */
+ var progressbars = getdirectchilds(rootel, "progressbar");
+ if (progressbars.length) // use first progressbar.
+ progressbar_init(slides, progressbars[0]);
+}
+
+/* mechanism to poll if screen data is updated and needs a full refresh
+ ignoring the cache. */
+var lasthash = null;
+var els = document.getElementsByClassName("check-updates");
+for (var i = 0; i < els.length; i++) {
+ var el = els[i];
+ var interval = parseInt(el.getAttribute("data-update"));
+ if (interval <= 0)
+ continue;
+ var url = el.getAttribute("data-url") || "";
+ if (url === "")
+ continue;
+
+ var fn = function(x) {
+ if (x.status >= 200 && x.status < 400) {
+ var newhash = x.responseText || "";
+ if (lasthash === null) {
+ lasthash = newhash;
+ } else if (newhash !== lasthash) {
+ // reload, ignore cache: works in Chrome and Firefox.
+ window.location.reload(true);
+ }
+ }
+ };
+
+ setInterval(function() {
+ xhr(url, fn, 10000);
+ }, interval);
+ xhr(url, fn, 10000);
+
+ break;
+}
+
+window.addEventListener("keyup", function(e) {
+ switch (e.keyCode) {
+ case 32: // space: toggle pause/play.
+ if (slides.paused)
+ slides_continue(slides);
+ else
+ slides_pause(slides);
+ break;
+ case 37: // left arrow: previous slide.
+ slides_prev(slides);
+ break;
+ case 13: // return or right arrow next slide.
+ case 39:
+ slides_next(slides);
+ break;
+ case 49: // '1': go to slide 1
+ case 50: // '2': go to slide 2
+ case 51: // '3': go to slide 3
+ case 52: // '4': go to slide 4
+ case 53: // '5': go to slide 5
+ case 54: // '6': go to slide 6
+ case 55: // '7': go to slide 7
+ case 56: // '8': go to slide 8
+ case 57: // '9': go to slide 9
+ slides_go(slides, e.keyCode - 49);
+ break;
+ }
+}, false);
(DIR) diff --git a/narrowcasting/style.css b/narrowcasting/style.css
@@ -0,0 +1,237 @@
+@keyframes fade-in {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+@keyframes fade-out {
+ 0% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+@keyframes news-slide-in {
+ 0% {
+ margin-left: 100%;
+ opacity: 0;
+ }
+ 100% {
+ margin-left: 0;
+ opacity: 1;
+ }
+}
+@keyframes news-slide-out {
+ 0% {
+ margin-left: 0;
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ margin-left: -100%;
+ }
+}
+body {
+ font-family: sans-serif;
+}
+html, body {
+ overflow: hidden;
+}
+h1 {
+ font-size: 120%;
+ font-weight: bold;
+ margin: 5px 0;
+ padding: 0;
+}
+ul {
+ margin: 0;
+ padding: 0;
+}
+iframe {
+ border: 0;
+}
+.news-ticker {
+ position: fixed;
+ background-color: #eee;
+ line-height: 90px;
+ height: 90px;
+ overflow: hidden;
+}
+.news-ticker div {
+ position: absolute;
+ padding: 0 0 0 5px;
+ line-height: 90px;
+ height: 90px;
+ animation: ease-out news-slide-in 1s;
+}
+
+.news-ticker div.out {
+ animation: ease-out news-slide-out 2s;
+}
+.ticker1 {
+ z-index: 9999;
+ bottom: 90px;
+ left: 0;
+ right: 0;
+ font-size: 40px;
+ background-color: #333;
+ color: #fff;
+ font-weight: bold;
+}
+.ticker2 {
+ z-index: 9999;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ font-size: 40px;
+ background-color: #555;
+ color: #fff;
+ font-weight: bold;
+}
+.logo {
+ width: 854px;
+ background-image: url('logo.png');
+ background-repeat: no-repeat;
+ background-position: center left;
+ background-size: auto 160px;
+ height: 180px;
+ position: absolute;
+ top: 0;
+ left: 25px;
+ z-index: 999;
+}
+.screen {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ background-repeat: no-repeat;
+}
+.topbar {
+ z-index: 997;
+
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 115px;
+ right: 0;
+ background-color: #333;
+}
+.topbar-right {
+ z-index: 999;
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 115px;
+ background-color: #333;
+ background-repeat: no-repeat;
+ text-align: left;
+}
+
+.topbar-info-dashboard {
+ z-index: 999;
+ position: absolute;
+ top: 0;
+ width: 343px;
+ left: 100px;
+ height: 158px;
+ background-image: url('img/image.png');
+ background-repeat: no-repeat;
+ text-align: left;
+}
+
+.date {
+ float: right;
+ color: #fff;
+ font-size: 60px;
+ line-height: 115px;
+ padding-right: 70px;
+ white-space: nowrap;
+}
+.time {
+ margin-left: 50px;
+ padding-right: 50px;
+ float: right;
+ color: #fff;
+ font-weight: bold;
+ font-size: 90px;
+ line-height: 115px;
+ height: 115px;
+}
+.slide {
+ display: none;
+ background-repeat: no-repeat;
+ /* default background color, must be set for overlapping slides for transitions */
+ /*background-color: #fff;*/
+}
+
+body > .screen > .slides > .slide {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+}
+body > .screen > .slides > .slide-2 {
+ background-color: #333;
+}
+
+.visible {
+ z-index: 995; /* visible has more preference over paused */
+ display: block;
+
+ animation: ease-in fade-in 1s;
+ animation-play-state: running;
+}
+
+.pause {
+ z-index: 990;
+ display: block;
+ opacity: 0;
+
+ animation: ease-out fade-out 1s;
+ animation-play-state: running;
+}
+
+.animate-once {
+ animation-iteration-count: 1;
+}
+.progressbar {
+ z-index: 9999;
+ position: absolute;
+ bottom: 0;
+ height: 3px;
+ left: 0;
+ width: 0%;
+ background-color: #fff;
+ opacity: 1.0 !important;
+}
+
+.widget {
+ padding: 0;
+ margin: 0;
+ color: #333;
+ font-size: 14pt;
+}
+.widget-header {
+ font-size: 30px;
+ font-weight: bold;
+ background-repeat: no-repeat;
+ color: #fff;
+ line-height: 60px;
+ padding: 0 10px;
+ text-transform: uppercase;
+}
+.widget-body {
+ padding: 5px 10px;
+}
+.widget-body ul {
+ padding: 0 20px;
+}