jscancer

Javascript crap (relatively small)
git clone git://git.codemadness.org/jscancer
Log | Files | Refs | README | LICENSE

commit ba166782d82d92a2dcf4ee22b96da2285874a4b3
parent 9ba8da523384de2bdf895531b8145f93441ceec9
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Thu, 15 Feb 2024 19:18:22 +0100

add script

Diffstat:
Anarrowcasting/README | 27+++++++++++++++++++++++++++
Anarrowcasting/index.html | 48++++++++++++++++++++++++++++++++++++++++++++++++
Anarrowcasting/script.js | 619+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anarrowcasting/style.css | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 931 insertions(+), 0 deletions(-)

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. 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> 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); 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; +}