From 6af5c5259e420707cb1e43d3a67c7e7cd8bd5163 Mon Sep 17 00:00:00 2001
From: 2deep4real <denis.peters@udo.edu>
Date: Fri, 21 Jul 2017 00:09:56 +0200
Subject: [PATCH] departures und clock angepasst

---
 js/Clock.js                          | 418 +++++++++++++++++++++++++++
 js/worker/AJAX.js                    | 138 +++++++++
 js/worker/AbstractWorker.js          |  75 +++++
 js/worker/DepartureWorker.js         | 164 +++++++++++
 js/worker/Worker.js                  |  60 ++++
 js/worker/WorkerDeparture.js         | 138 +++++++++
 js/worker/check.js                   |  64 ++++
 panels/clock/script.js               | 348 +---------------------
 panels/clock/style.less              |  11 +-
 panels/clock/template.html           |   6 +-
 panels/departure/cache/.gitignore    |   1 -
 panels/departure/config/default.json |   2 +-
 panels/departure/config/oh14.json    |  10 +-
 panels/departure/departures.php      | 204 -------------
 panels/departure/script.js           | 190 ++++++------
 15 files changed, 1184 insertions(+), 645 deletions(-)
 create mode 100644 js/Clock.js
 create mode 100644 js/worker/AJAX.js
 create mode 100644 js/worker/AbstractWorker.js
 create mode 100644 js/worker/DepartureWorker.js
 create mode 100644 js/worker/Worker.js
 create mode 100644 js/worker/WorkerDeparture.js
 create mode 100644 js/worker/check.js
 delete mode 100755 panels/departure/cache/.gitignore
 delete mode 100755 panels/departure/departures.php

diff --git a/js/Clock.js b/js/Clock.js
new file mode 100644
index 0000000..a0ad43c
--- /dev/null
+++ b/js/Clock.js
@@ -0,0 +1,418 @@
+function Clock(panel, config) {
+	this.canvas = panel[0].querySelector("canvas");
+	this.pane = this.canvas.getContext("2d");
+	this.bgCanvas = document.createElement("canvas");
+	this.bgPane = this.canvas.getContext("2d");
+
+	this.monthNames		= ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"];
+	this.dayNames		= ["so","mo","di","mi","do","fr","sa"];
+
+	this.data = {
+		panel: {
+			widht: 0,
+			height: 0
+		},
+		bounds: {
+			outer: {
+				x: 0,
+				y: 0,
+				widht: 0,
+				height: 0
+			},
+			inner: {
+				x: 0,
+				y: 0,
+				widht: 0,
+				height: 0
+			}
+		},
+		colors: {
+			background: "#000",
+			foreground: "#fff",
+			shaddow: "#333"
+		},
+		fonts: {
+			fat: "time-fat",
+			medium: "time-medium"
+		},
+		aClock: {
+			bgImage: "background.svg",
+			radius: 0,
+			center: {
+				x: 0,
+				y: 0
+			},
+			bounds: {
+				outer: {
+					x: 0,
+					y: 0,
+					widht: 0,
+					height: 0
+				},
+				inner: {
+					x: 0,
+					y: 0,
+					widht: 0,
+					height: 0
+				}
+			}
+		},
+		dClock: {
+			fonts: {
+				fat: "",
+				big: "",
+				small: ""
+			},
+			bounds: {
+				outer: {
+					x: 0,
+					y: 0,
+					widht: 0,
+					height: 0
+				},
+				inner: {
+					x: 0,
+					y: 0,
+					widht: 0,
+					height: 0
+				}
+			},
+			positions: {
+				hour : {
+					x: 0,
+					y: 0,
+				},
+				pulse : {
+					x: 0,
+					y: 0,
+				},
+				minute : {
+					x: 0,
+					y: 0,
+				},
+				second : {
+					x: 0,
+					y: 0,
+				}
+			}
+		},
+		dDate: {
+			font: "",
+			bounds: {
+				outer: {
+					x: 0,
+					y: 0,
+					widht: 0,
+					height: 0
+				},
+				inner: {
+					x: 0,
+					y: 0,
+					widht: 0,
+					height: 0
+				}
+			},
+			position: {
+				x: 0,
+				y: 0
+			}
+		}
+	};
+	
+    this.resize = function(width, height) {
+        this.data.panel.width = width;
+        this.data.panel.height = height;
+        // canvas
+        this.canvas.width = this.data.panel.width;
+        this.canvas.height = this.data.panel.height;
+        this.pane = this.canvas.getContext("2d");
+        // bgCanvas
+        this.bgCanvas.width = this.data.panel.width;
+        this.bgCanvas.height = this.data.panel.height;
+        this.bgPane = this.bgCanvas.getContext("2d");
+
+        // outer bounds
+        this.data.bounds.outer.x = 10;
+        this.data.bounds.outer.y = 10;
+        this.data.bounds.outer.width = width - 20;
+        this.data.bounds.outer.height = height - 20;
+        var ratio = this.data.bounds.outer.width / this.data.bounds.outer.height;
+
+        if (ratio > 1.5) {
+            // widget
+            if (this.data.bounds.outer.height * 3 < this.data.bounds.outer.width) {
+                this.data.bounds.inner.height = this.data.bounds.outer.height;
+                this.data.bounds.inner.width = this.data.bounds.outer.height * 3;
+            } else {
+                this.data.bounds.inner.height = this.data.bounds.outer.width / 3;
+                this.data.bounds.inner.width = this.data.bounds.outer.width;
+            }
+            this.data.bounds.inner.x = this.data.bounds.outer.x + (this.data.bounds.outer.width - this.data.bounds.inner.width) / 2;
+            this.data.bounds.inner.y = this.data.bounds.outer.y + (this.data.bounds.outer.height - this.data.bounds.inner.height) / 2;
+            // dClock
+            this.data.dClock.bounds.outer.x = this.data.bounds.inner.x + this.data.bounds.inner.width * 0.4;
+            this.data.dClock.bounds.outer.y = this.data.bounds.inner.y;
+            this.data.dClock.bounds.outer.width = this.data.bounds.inner.width * 0.6;
+            this.data.dClock.bounds.outer.height = this.data.bounds.inner.height * 0.6;
+            // dDate
+            this.data.dDate.bounds.outer.x = this.data.bounds.inner.x + this.data.bounds.inner.width * 0.4;
+            this.data.dDate.bounds.outer.y = this.data.bounds.inner.y + this.data.bounds.inner.height * 0.6;
+            this.data.dDate.bounds.outer.width = this.data.bounds.inner.width * 0.6;
+            this.data.dDate.bounds.outer.height = this.data.bounds.inner.height * 0.4;
+            // aClock
+            this.data.aClock.bounds.outer.x = this.data.bounds.inner.x;
+            this.data.aClock.bounds.outer.y = this.data.bounds.inner.y;
+            this.data.aClock.bounds.outer.width = this.data.bounds.inner.width * 0.4;
+            this.data.aClock.bounds.outer.height = this.data.bounds.inner.height;
+        } else if (ratio < 1) {
+            // widget
+            if (this.data.bounds.outer.height * 0.7 < this.data.bounds.outer.width) {
+                this.data.bounds.inner.height = this.data.bounds.outer.height;
+                this.data.bounds.inner.width = this.data.bounds.outer.height * 0.7;
+            } else {
+                this.data.bounds.inner.height = this.data.bounds.outer.width / 0.7;
+                this.data.bounds.inner.width = this.data.bounds.outer.width;
+            }
+            this.data.bounds.inner.x = this.data.bounds.outer.x + (this.data.bounds.outer.width - this.data.bounds.inner.width) / 2;
+            this.data.bounds.inner.y = this.data.bounds.outer.y + (this.data.bounds.outer.height - this.data.bounds.inner.height) / 2;
+            // dClock
+            this.data.dClock.bounds.outer.x = this.data.bounds.inner.x;
+            this.data.dClock.bounds.outer.y = this.data.bounds.inner.y + this.data.bounds.inner.height * 0.7;
+            this.data.dClock.bounds.outer.width = this.data.bounds.inner.width;
+            this.data.dClock.bounds.outer.height = this.data.bounds.inner.height * 0.2;
+            // dDate
+            this.data.dDate.bounds.outer.x = this.data.bounds.inner.x;
+            this.data.dDate.bounds.outer.y = this.data.bounds.inner.y + this.data.bounds.inner.height * 0.9;
+            this.data.dDate.bounds.outer.width = this.data.bounds.inner.width;
+            this.data.dDate.bounds.outer.height = this.data.bounds.inner.height * 0.1;
+            // aClock
+            this.data.aClock.bounds.outer.x = this.data.bounds.inner.x;
+            this.data.aClock.bounds.outer.y = this.data.bounds.inner.y;
+            this.data.aClock.bounds.outer.width = this.data.bounds.inner.width;
+            this.data.aClock.bounds.outer.height = this.data.bounds.inner.height * 0.7;
+        } else {
+            // widget
+            if (this.data.bounds.outer.height * 2 < this.data.bounds.outer.width) {
+                this.data.bounds.inner.height = this.data.bounds.outer.height;
+                this.data.bounds.inner.width = this.data.bounds.outer.height * 2;
+            } else {
+                this.data.bounds.inner.height = this.data.bounds.outer.width / 2;
+                this.data.bounds.inner.width = this.data.bounds.outer.width;
+            }
+            this.data.bounds.inner.x = this.data.bounds.outer.x + (this.data.bounds.outer.width - this.data.bounds.inner.width) / 2;
+            this.data.bounds.inner.y = this.data.bounds.outer.y + (this.data.bounds.outer.height - this.data.bounds.inner.height) / 2;
+            // dClock
+            this.data.dClock.bounds.outer.x = this.data.bounds.inner.x + this.data.bounds.inner.width / 3;
+            this.data.dClock.bounds.outer.y = this.data.bounds.inner.y;
+            this.data.dClock.bounds.outer.width = this.data.bounds.inner.width - this.data.bounds.inner.width / 3;
+            this.data.dClock.bounds.outer.height = this.data.bounds.inner.height - this.data.bounds.inner.height / 3;
+            // dDate
+            this.data.dDate.bounds.outer.x = this.data.bounds.inner.x;
+            this.data.dDate.bounds.outer.y = this.data.bounds.inner.y + this.data.bounds.inner.height / 3 * 2;
+            this.data.dDate.bounds.outer.width = this.data.bounds.inner.width;
+            this.data.dDate.bounds.outer.height = this.data.bounds.inner.height / 3;
+            // aClock
+            this.data.aClock.bounds.outer.x = this.data.bounds.inner.x;
+            this.data.aClock.bounds.outer.y = this.data.bounds.inner.y;
+            this.data.aClock.bounds.outer.width = this.data.bounds.inner.width / 3;
+            this.data.aClock.bounds.outer.height = this.data.bounds.inner.height -  + this.data.bounds.inner.height / 3;
+        }
+        resizeDClock();
+        resizeDDate();
+        resizeAClock();
+        renderBG();
+	}
+
+	this.render = function() {
+		var
+			date = new Date(),
+			n = date.getMilliseconds(),
+			h = date.getHours(),
+			i = date.getMinutes(),
+			s = date.getSeconds(),
+			t = date.getDay(),
+			d = date.getDate(),
+			m = date.getMonth(),
+			y = date.getFullYear();
+		if (d.toString().length < 2)
+			d = "0" + d;
+		if (h.toString().length < 2)
+			h = "0" + h;
+		if (i.toString().length < 2)
+			i = "0" + i;
+		if (s.toString().length < 2)
+			s = "0" + s;
+        // background
+        this.pane.fillStyle = this.data.colors.background;
+		this.pane.fillRect(0, 0, this.data.panel.width, this.data.panel.height);
+        this.pane.drawImage(this.bgCanvas, 0, 0);
+        // foreground dClock
+        this.pane.fillStyle = this.data.colors.foreground;
+        this.pane.font = this.data.dClock.fonts.fat;
+        if (n < 500) this.pane.fillText(":", this.data.dClock.positions.pulse.x, this.data.dClock.positions.pulse.y);
+        this.pane.font = this.data.dClock.fonts.big;
+        this.pane.fillText(h, this.data.dClock.positions.hour.x, this.data.dClock.positions.hour.y);
+        this.pane.fillText(i, this.data.dClock.positions.minute.x, this.data.dClock.positions.minute.y);
+        this.pane.font = this.data.dClock.fonts.small;
+        this.pane.fillText(s, this.data.dClock.positions.second.x, this.data.dClock.positions.second.y);
+        // foreground dDate
+        this.pane.font = this.data.dDate.font;
+        this.pane.fillText(this.dayNames[t] + " " + d + " " + this.monthNames[m] + " " + y, this.data.dDate.position.x, this.data.dDate.position.y);
+        // foreground aClock
+        this.pane.strokeStyle = this.data.colors.foreground;
+        //hour
+        var hour = ((h % 12) * Math.PI / 6) + (i * Math.PI / (6 * 60)) + (s * Math.PI / (360 * 60));
+        renderHand(hour, this.data.aClock.radius * 0.5, this.data.aClock.radius * 0.07);
+        //minute
+        var minute = (i * Math.PI / 30) + (s * Math.PI / (30 * 60));
+        renderHand(minute, this.data.aClock.radius * 0.8, this.data.aClock.radius * 0.07);
+        // second
+        var second = (s * Math.PI / 30);
+        renderHand(second, this.data.aClock.radius * 0.9, this.data.aClock.radius * 0.02);
+        // debug
+        //renderDebug();
+	}
+	
+    var renderHand = function(pos, length, width) {
+        this.pane.beginPath();
+        this.pane.lineWidth = width;
+        this.pane.lineCap = "round";
+		this.pane.translate(this.data.aClock.center.x, this.data.aClock.center.y);
+        this.pane.moveTo(0, 0);
+        this.pane.rotate(pos);
+        this.pane.lineTo(0, -length);
+        this.pane.stroke();
+        this.pane.rotate(-pos);
+		this.pane.translate(-this.data.aClock.center.x, -this.data.aClock.center.y);
+        this.pane.lineWidth = 1;
+    }.bind(this);
+
+    var renderBG = function() {
+        // background dClock
+        this.bgPane.fillStyle = this.data.colors.shaddow;
+        this.bgPane.font = this.data.dClock.fonts.fat;
+        this.bgPane.fillText(":", this.data.dClock.positions.pulse.x, this.data.dClock.positions.pulse.y);
+        this.bgPane.font = this.data.dClock.fonts.big;
+        this.bgPane.fillText("88", this.data.dClock.positions.hour.x, this.data.dClock.positions.hour.y);
+        this.bgPane.fillText("88", this.data.dClock.positions.minute.x, this.data.dClock.positions.minute.y);
+        this.bgPane.font = this.data.dClock.fonts.small;
+        this.bgPane.fillText("88", this.data.dClock.positions.second.x, this.data.dClock.positions.second.y);
+        // background dDate
+        this.bgPane.font = this.data.dDate.font;
+        this.bgPane.fillText("00 00 000 0000", this.data.dDate.position.x, this.data.dDate.position.y);
+        this.bgPane.fillText("** ** *** ****", this.data.dDate.position.x, this.data.dDate.position.y);
+
+        if (typeof this.data.aClock.bgImage == "string") {
+            var img = new Image();
+            img.onload = function(e) {
+                this.data.aClock.bgImage = e.target;
+		        this.bgPane.drawImage(
+                    this.data.aClock.bgImage,
+                    this.data.aClock.bounds.inner.x,
+                    this.data.aClock.bounds.inner.y,
+                    this.data.aClock.bounds.inner.width,
+                    this.data.aClock.bounds.inner.height
+                );
+            }.bind(this);
+            img.src = "panels/clock/"+this.data.aClock.bgImage;
+        } else {
+            this.bgPane.drawImage(
+                this.data.aClock.bgImage,
+                this.data.aClock.bounds.inner.x,
+                this.data.aClock.bounds.inner.y,
+                this.data.aClock.bounds.inner.width,
+                this.data.aClock.bounds.inner.height
+            );
+        }
+    }.bind(this);
+
+    var resizeDClock = function() {
+        var aspect = 6 / 16;
+        var ratio = this.data.dClock.bounds.outer.width * aspect;
+        if (ratio > this.data.dClock.bounds.outer.height) {
+            this.data.dClock.bounds.inner.width	= this.data.dClock.bounds.outer.height / aspect;
+            this.data.dClock.bounds.inner.height	= this.data.dClock.bounds.outer.height;
+        } else if (ratio < this.data.dClock.bounds.outer.height) {
+            this.data.dClock.bounds.inner.width	= this.data.dClock.bounds.outer.width;
+            this.data.dClock.bounds.inner.height	= this.data.dClock.bounds.outer.width * aspect;
+        } else {
+            this.data.dClock.bounds.inner.width	= this.data.dClock.bounds.outer.width;
+            this.data.dClock.bounds.inner.height	= this.data.dClock.bounds.outer.height;
+        }
+        this.data.dClock.bounds.inner.x = this.data.dClock.bounds.outer.x + (this.data.dClock.bounds.outer.width - this.data.dClock.bounds.inner.width) / 2;
+        this.data.dClock.bounds.inner.y = this.data.dClock.bounds.outer.y + (this.data.dClock.bounds.outer.height - this.data.dClock.bounds.inner.height) / 2;
+        this.data.dClock.positions.pulse.x = this.data.dClock.bounds.inner.x + this.data.dClock.bounds.inner.width * 0.315;
+        this.data.dClock.positions.pulse.y = this.data.dClock.bounds.inner.y + this.data.dClock.bounds.inner.height * 0.72;
+        this.data.dClock.positions.hour.x = this.data.dClock.bounds.inner.x + this.data.dClock.bounds.inner.width * 0.02;
+        this.data.dClock.positions.hour.y = this.data.dClock.bounds.inner.y + this.data.dClock.bounds.inner.height * 0.88;
+        this.data.dClock.positions.minute.x = this.data.dClock.bounds.inner.x + this.data.dClock.bounds.inner.width * 0.43;
+        this.data.dClock.positions.minute.y = this.data.dClock.bounds.inner.y + this.data.dClock.bounds.inner.height * 0.88;
+        this.data.dClock.positions.second.x = this.data.dClock.bounds.inner.x + this.data.dClock.bounds.inner.width * 0.79;
+        this.data.dClock.positions.second.y = this.data.dClock.bounds.inner.y + this.data.dClock.bounds.inner.height * 0.85;
+        var
+            bigFontSize = this.data.dClock.bounds.inner.height * 1.3,
+            smlFontSize = this.data.dClock.bounds.inner.height * 0.7;
+        this.data.dClock.fonts.fat   = bigFontSize + "px " + this.data.fonts.fat;
+        this.data.dClock.fonts.big   = bigFontSize + "px " + this.data.fonts.medium;
+        this.data.dClock.fonts.small = smlFontSize + "px " + this.data.fonts.medium;
+    }.bind(this);
+
+    var resizeDDate = function() {
+        var aspect = 2.3 / 16;
+        var ratio = this.data.dDate.bounds.outer.width * aspect;
+        if (ratio > this.data.dDate.bounds.outer.height) {
+            this.data.dDate.bounds.inner.width	= this.data.dDate.bounds.outer.height / aspect;
+            this.data.dDate.bounds.inner.height	= this.data.dDate.bounds.outer.height;
+        } else if (ratio < this.data.dDate.bounds.outer.height) {
+            this.data.dDate.bounds.inner.width	= this.data.dDate.bounds.outer.width;
+            this.data.dDate.bounds.inner.height	= this.data.dDate.bounds.outer.width * aspect;
+        } else {
+            this.data.dDate.bounds.inner.width	= this.data.dDate.bounds.outer.width;
+            this.data.dDate.bounds.inner.height	= this.data.dDate.bounds.outer.height;
+        }
+        this.data.dDate.bounds.inner.x = this.data.dDate.bounds.outer.x + (this.data.dDate.bounds.outer.width - this.data.dDate.bounds.inner.width) / 2;
+        this.data.dDate.bounds.inner.y = this.data.dDate.bounds.outer.y + (this.data.dDate.bounds.outer.height - this.data.dDate.bounds.inner.height) / 2;
+        this.data.dDate.position.x = this.data.dDate.bounds.inner.x + this.data.dDate.bounds.inner.width * 0.02;
+        this.data.dDate.position.y = this.data.dDate.bounds.inner.y + this.data.dDate.bounds.inner.height * 0.88;
+        var fontSize = this.data.dDate.bounds.inner.height * 1.3;
+        this.data.dDate.font = fontSize + "px " + this.data.fonts.medium;
+    }.bind(this);
+
+    var resizeAClock = function() {
+        if (this.data.aClock.bounds.outer.width > this.data.aClock.bounds.outer.height) {
+            this.data.aClock.bounds.inner.width  = this.data.aClock.bounds.outer.height;
+            this.data.aClock.bounds.inner.height = this.data.aClock.bounds.outer.height;
+        } else if (this.data.aClock.bounds.outer.width < this.data.aClock.bounds.outer.height) {
+            this.data.aClock.bounds.inner.width  = this.data.aClock.bounds.outer.width;
+            this.data.aClock.bounds.inner.height = this.data.aClock.bounds.outer.width;
+        } else {
+            this.data.aClock.bounds.inner.width  = this.data.aClock.bounds.outer.width;
+            this.data.aClock.bounds.inner.height = this.data.aClock.bounds.outer.height;
+        }
+        this.data.aClock.bounds.inner.x = this.data.aClock.bounds.outer.x + (this.data.aClock.bounds.outer.width - this.data.aClock.bounds.inner.width) / 2;
+        this.data.aClock.bounds.inner.y = this.data.aClock.bounds.outer.y + (this.data.aClock.bounds.outer.height - this.data.aClock.bounds.inner.height) / 2;
+        this.data.aClock.center.x = this.data.aClock.bounds.inner.x + this.data.aClock.bounds.inner.width / 2;
+        this.data.aClock.center.y = this.data.aClock.bounds.inner.y + this.data.aClock.bounds.inner.height / 2;
+        this.data.aClock.radius = this.data.aClock.bounds.inner.height * 0.45;
+    }.bind(this);
+
+    var renderDebug = function() {
+        // panel bounds
+        this.pane.strokeStyle = "#ff0";
+        this.pane.strokeRect(this.data.bounds.inner.x, this.data.bounds.inner.y, this.data.bounds.inner.width, this.data.bounds.inner.height);
+        this.pane.strokeStyle = "#0ff";
+        this.pane.strokeRect(this.data.bounds.outer.x, this.data.bounds.outer.y, this.data.bounds.outer.width, this.data.bounds.outer.height);
+        // inner bounds
+        this.pane.strokeStyle = "#0f0";
+        this.pane.strokeRect(this.data.dClock.bounds.inner.x, this.data.dClock.bounds.inner.y, this.data.dClock.bounds.inner.width, this.data.dClock.bounds.inner.height);
+        this.pane.strokeRect(this.data.dDate.bounds.inner.x, this.data.dDate.bounds.inner.y, this.data.dDate.bounds.inner.width, this.data.dDate.bounds.inner.height);
+        this.pane.strokeRect(this.data.aClock.bounds.inner.x, this.data.aClock.bounds.inner.y, this.data.aClock.bounds.inner.width, this.data.aClock.bounds.inner.height);
+        // outer bounds
+        this.pane.strokeStyle = "#f0f";
+        this.pane.strokeRect(this.data.dClock.bounds.outer.x, this.data.dClock.bounds.outer.y, this.data.dClock.bounds.outer.width, this.data.dClock.bounds.outer.height);
+        this.pane.strokeRect(this.data.dDate.bounds.outer.x, this.data.dDate.bounds.outer.y, this.data.dDate.bounds.outer.width, this.data.dDate.bounds.outer.height);
+        this.pane.strokeRect(this.data.aClock.bounds.outer.x, this.data.aClock.bounds.outer.y, this.data.aClock.bounds.outer.width, this.data.aClock.bounds.outer.height);
+        this.pane.strokeStyle = "";
+    }.bind(this);
+	
+}
\ No newline at end of file
diff --git a/js/worker/AJAX.js b/js/worker/AJAX.js
new file mode 100644
index 0000000..80c62b4
--- /dev/null
+++ b/js/worker/AJAX.js
@@ -0,0 +1,138 @@
+AJAX = new (function AJAX() {
+		
+	function newXHR() {
+		return !!self.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
+	}
+
+    function serialize(obj, prefix) {
+        var str = [];
+        for (var p in obj) {
+          if (obj.hasOwnProperty(p)) {
+            var k = prefix ? prefix + "[" + p + "]" : p,
+                  v = obj[p];
+              str.push(typeof v == "object" ? serialize(v, k) : encodeURIComponent(k) + "=" + encodeURIComponent(v));
+          }
+        }
+        return str.join("&");
+    }
+	
+	function decodeResponse(type, response) {
+		try {			
+			switch (type) {
+				case "application/json":
+					return JSON.parse(response);
+				default:
+					return response;
+			}
+		} catch(e) {
+			
+		}
+		return response;
+	}
+	
+	function doReject(strict, result){
+		return !!strict ? Promise.reject(result) : Promise.resolve(result);
+	}
+
+    function doRequest(url, method, params, strict) {
+        return new Promise(function(resolve, reject) {
+			try {
+				if (typeof url != 'string') {
+					return doReject(strict, {
+						success: false,
+						response: 'url parameter must be string',
+						status: 0
+					}).then(resolve, reject);
+				}
+				if (typeof method != 'string') {
+					return doReject(strict, {
+						success: false,
+						response: 'method parameter must be string',
+						status: 0
+					}).then(resolve, reject);
+				}
+				method = method.toUpperCase();
+				var xhr = newXHR();
+				xhr.onload = function(e) {
+					if (e.target.status == 200) {
+						var response = decodeResponse(e.target.getResponseHeader("Content-Type"), e.target.response);
+						resolve({
+							success: true,
+							response: response,
+							status: e.target.status
+						});	
+					} else {
+						var statusText = e.target.statusText;
+						if (!statusText.length) statusText = "unexpected error";
+						doReject(strict, {
+							success: false,
+							response: statusText, 
+							status: e.target.status
+						}).then(resolve, reject);
+					}
+				}
+				xhr.onerror = function(e) {
+					var statusText = e.target.statusText;
+					if (!statusText.length) statusText = "unexpected error";
+					doReject(strict, {
+						success: false,
+						response: statusText,
+						status: e.target.status
+					}).then(resolve, reject);
+				}
+				xhr.onabort = function(e) {
+					doReject(strict, {
+						success: false,
+						response: 'download aborted',
+						status: 0
+					}).then(resolve, reject);
+				}
+				params = typeof params == 'object' ? serialize(params) : '';
+				if (method === 'GET') {
+					if (!!params.length) params = "?" + params;
+					xhr.open(method, url+params);
+					xhr.send();
+				} else {
+					xhr.open(method, url);
+					xhr.send(!!params.length ? params : undefined);
+				}
+			} catch(e) {
+					doReject(strict, {
+						success: false,
+						response: e.message,
+						status: 0
+					}).then(resolve, reject);
+			}
+        });
+    }
+	
+	this.request = function(requests, strict) {
+		if (typeof strict != 'boolean') strict = true;
+		try {
+			if (typeof requests == 'string') {
+				return doRequest(requests, 'GET', [], strict);
+			} else {
+				if (typeof requests == 'object') {
+					if (Array.isArray(requests)) {
+						var proms = [];
+						for (var i = 0; i < requests.length; ++i)
+							proms.push(this.request(requests[i], strict));
+						return Promise.all(proms);
+					} else {
+						return doRequest(requests.url, requests.method, requests.params, strict);
+					}
+				}
+				throw new Error('wrong type (string/array/dict expected, '+(typeof requests)+' detected)');
+			}
+		} catch(e) {
+			return doReject(strict, {
+				success: false,
+				response: e.message,
+				status: 0
+			});
+		}
+	}
+
+	return Object.freeze(this);
+	
+});
\ No newline at end of file
diff --git a/js/worker/AbstractWorker.js b/js/worker/AbstractWorker.js
new file mode 100644
index 0000000..32db1f5
--- /dev/null
+++ b/js/worker/AbstractWorker.js
@@ -0,0 +1,75 @@
+class AbstractWorker {
+	
+	constructor() {
+		this.config = {};
+		
+		var
+			ival = 1000,
+			timer,
+			cmds = [],
+			execRun = false;
+		
+		var update = function() {
+			if (!!timer) self.clearTimeout(timer);
+			this.tick(function(result) {
+				self.postMessage(result);
+			});
+			if (ival > 0) timer = self.setTimeout(update, ival);
+		}.bind(this);
+	
+		var exec = function() {
+			if (!!cmds.length) {
+				execRun = true;
+				var cmd = cmds.shift();
+				if (typeof this[cmd.cmd] == "function") {
+					this[cmd.cmd].apply(this,cmd.params);
+				} else {
+					exec();
+				}
+			} else {
+				execRun = false;
+			}
+		}.bind(this);
+	
+		this.start = function() {
+			update();
+			exec();
+		}
+		
+		this.stop = function() {
+			if (!!timer) self.clearTimeout(timer);
+			exec();
+		}
+		
+		this.loadConfig = function(name) {
+			AJAX.request("../../panels/departure/config/"+name+".json").then(function(data) {
+				this.config = this.processConfig(data.response);
+				exec();
+			}.bind(this),function() {
+				exec();
+			});
+		}
+	
+		self.onmessage = function(e) {
+			cmds.push(e.data);
+			if (!execRun) exec();
+		}.bind(this);
+		
+		this.setSpeed = function(val) {
+			if (isNaN(val = parseInt(val))) {
+				val = 0;
+			}
+			ival = val;
+		}
+		
+	}
+	
+	tick(callback) {
+		callback({});
+	}
+	
+	processConfig(config) {
+		return config;
+	}
+		
+}
\ No newline at end of file
diff --git a/js/worker/DepartureWorker.js b/js/worker/DepartureWorker.js
new file mode 100644
index 0000000..b325b36
--- /dev/null
+++ b/js/worker/DepartureWorker.js
@@ -0,0 +1,164 @@
+importScripts('AJAX.js');
+importScripts('check.js');
+importScripts('AbstractWorker.js');
+
+class DepartureWorker extends AbstractWorker {
+	
+	constructor() {
+		super();
+		this.setSpeed(360000);
+	}
+	
+	processConfig(config) {
+		if (isObject(config) && isArrayNotEmpty(config['stops'])) {
+			config['stops_converted'] = [];
+			for (var i = 0; i < config['stops'].length; ++i) {
+				config['stops_converted'].push({url:'http://vrrf.finalrewind.org/'+config['stops'][i].split(':').join('/')+'.json',method:'GET',params:{frontend:'json'}});
+			}	
+		}
+		return config;
+	}
+	
+	tick(callback) {
+		var config = this.config;
+		if (isObject(config) && isArrayNotEmpty(config['stops_converted'])) {
+			AJAX.request(config['stops_converted']).then(function(results) {
+				if (results.length == 0) return;
+				var
+					reftime = new Date(),
+					result = {
+						vrrf_version: {
+							actual: '0.0.0.0',
+							expected: '0.0.0.0'
+						},
+						errors: {},
+						info: {},
+						lines: {},
+						raw: {}
+					},
+					rawLines = {};
+				reftime = calcDateValue(reftime.getFullYear(), reftime.getMonth()+1, reftime.getDate(), reftime.getHours(), reftime.getMinutes());
+				for (var i = 0; i < results.length; ++i) {
+					if (!results[i].success) continue;
+					var data = results[i].response;
+					if (!isObject(data)) data = JSON.parse(data);
+					var stopName = config['stops'][i].split('/').join(' - ');
+					result['vrrf_version']['actual'] = data['version'];
+					result['vrrf_version']['expected'] = '0.0.0.0';
+					result['errors'][stopName] = data['error'];
+					result['info'][stopName] = '';
+					result['raw'][stopName] = data['raw'];
+					for (var j = 0; j < data['raw'].length; ++j) {
+						var entry = data['raw'][j];
+						// filter
+						//if (isObjectNotEmpty(config['filter']) && !passFilter(config['filter'], entry)) continue;
+						// interprete
+						var
+							schedDate = entry['sched_date'].split('.'),
+							schedTime = entry['sched_time'].split(':'),
+							deptime = calcDateValue(schedDate[2], schedDate[1], schedDate[0], schedTime[0], schedTime[1]);
+						var delay = parseInt(entry['delay']);
+						if (isNaN(delay)) delay = 0;
+						if (((deptime + delay) - reftime) > 0) {
+							var ident = /*entry['lineref']['operator']+'|'+entry['lineref']['type']+'|'+*/entry['line']+'|'+entry['lineref']['identifier']+'|'+entry['key'];
+							if (!isObjectNotEmpty(result['lines'][ident])) {
+								result['lines'][ident] = {
+									timeValue: deptime,
+									line: entry['line'],
+									destination: entry['destination'],
+									type: entry['type'],
+									stops: [{
+										timeValue: deptime,
+										time: entry['sched_time'],
+										delay: entry['delay'],
+										cancel: entry['is_cancelled'],
+										name: stopName,
+										info: entry['info']
+									}]
+								};
+							} else {
+								result['lines'][ident]['stops'].push({
+										timeValue: deptime,
+										time: entry['sched_time'],
+										delay: entry['delay'],
+										cancel: entry['is_cancelled'],
+										name: stopName,
+										info: entry['info']
+									});
+							}
+						}
+					}
+				}
+				result['lines'] = sortData(Object.values(result['lines']));
+				if (isNumber(config.max)) {
+					result['lines'] = result['lines'].slice(0, config.max);
+				}
+				callback(result);
+			});	
+		}
+	}
+	
+}
+
+function passFilter(filter, entry) {
+	if (isObjectNotEmpty(filter['bl'])) {
+		if (isArrayNotEmpty(filter['bl']['line'])
+		&&  arrayContains(filter['bl']['line'], entry['line'])) {
+			return false;
+		}
+		if (isArrayNotEmpty(filter['bl']['type'])
+		&&  arrayContains(filter['bl']['type'], entry['type'])) {
+			return false;
+		}
+		if (isArrayNotEmpty(filter['bl']['platform'])
+		&&  arrayContains(filter['bl']['platform'], entry['platform'])) {
+			return false;
+		}
+		if (isArrayNotEmpty(filter['bl']['destination'])
+		&&  arrayContains(filter['bl']['destination'], entry['destination'])) {
+			return false;
+		}
+	}
+	if (isObjectNotEmpty(filter['wl'])) {
+		if (isArrayNotEmpty(filter['wl']['line'])
+		&&  !arrayContains(filter['wl']['line'], entry['line'])) {
+				return false;
+		}
+		if (isArrayNotEmpty(filter['wl']['type'])
+		&&  !arrayContains(filter['wl']['type'], entry['type'])) {
+				return false;
+		}
+		if (isArrayNotEmpty(filter['wl']['platform'])
+		&&  !arrayContains(filter['wl']['platform'], entry['platform'])) {
+				return false;
+		}
+		if (isArrayNotEmpty(filter['wl']['destination'])
+		&&  !arrayContains(filter['wl']['destination'], entry['destination'])) {
+				return false;
+		}
+	}
+	return true;
+}
+
+function sortData(data) {
+	for (var i = 0; i < data.length; ++i) {
+		data[i]['stops'] = data[i]['stops'].sort(sortFn);
+		data[i]['timeValue'] = data[i]['stops'][0]['timeValue'];
+	}
+	return data.sort(sortFn);
+}
+
+function sortFn(a,b) {
+	return a['timeValue'] - b['timeValue'];
+}
+
+function calcDateValue(year, month, day, hour, minute) {
+	year = parseInt(year) * 12 * 31 * 24 * 60;
+	month = parseInt(month) * 31 * 24 * 60;
+	day = parseInt(day) * 24 * 60;
+	hour = parseInt(hour) * 60;
+	minute = parseInt(minute);
+	return year+month+day+hour+minute;
+}
+
+new DepartureWorker;
\ No newline at end of file
diff --git a/js/worker/Worker.js b/js/worker/Worker.js
new file mode 100644
index 0000000..2512e11
--- /dev/null
+++ b/js/worker/Worker.js
@@ -0,0 +1,60 @@
+self.interval = self.interval || 1000;
+self.tick = self.tick || function() {};
+self.config = self.config || {};
+
+(new function() {
+	var
+		me = this,
+		timer,
+		cmds = [],
+		execRun = false;
+	
+	function update() {
+		if (!!timer) self.clearTimeout(timer);
+		self.tick(function(result) {
+			self.postMessage(result);
+		});
+		timer = self.setTimeout(update, self.interval);
+	}
+	
+	function exec() {
+		if (!!cmds.length) {
+			execRun = true;
+			var cmd = cmds.shift();
+			me[cmd.cmd].apply(this,cmd.params);
+		} else {
+			execRun = false;
+		}
+	}
+	
+	this.start = function() {
+		update();
+		exec();
+	}
+	
+	this.stop = function() {
+		if (!!timer) self.clearTimeout(timer);
+		exec();
+	}
+	
+	this.loadConfig = function(name) {
+		AJAX.request("../../panels/departure/config/"+name+".json").then(function(data) {
+			config = data.response;
+			if (isObject(config) && isArrayNotEmpty(config['stops'])) {
+				config['stops_converted'] = [];
+				for (var i = 0; i < config['stops'].length; ++i) {
+					config['stops_converted'].push({url:'http://vrrf.finalrewind.org/'+config['stops'][i].split(':').join('/')+'.json',method:'GET',params:{frontend:'json'}});
+				}	
+			}
+			exec();
+		},function() {
+			exec();
+		});
+	}
+	
+	self.onmessage = function(e) {
+		cmds.push(e.data);
+		if (!execRun) exec();
+	}.bind(this);
+	
+}());
\ No newline at end of file
diff --git a/js/worker/WorkerDeparture.js b/js/worker/WorkerDeparture.js
new file mode 100644
index 0000000..d58c7a0
--- /dev/null
+++ b/js/worker/WorkerDeparture.js
@@ -0,0 +1,138 @@
+importScripts('AJAX.js');
+importScripts('check.js');
+
+self.interval = 360000;
+
+self.tick = function(callback) {
+	var config = self.config;
+	if (isObject(config) && isArrayNotEmpty(config['stops_converted'])) {
+		AJAX.request(config['stops_converted']).then(function(results) {
+			if (results.length == 0) return;
+			var
+				result = {
+					vrrf_version: {
+						actual: '0.0.0.0',
+						expected: '0.0.0.0'
+					},
+					errors: {},
+					info: {},
+					lines: {},
+					raw: {}
+				},
+				rawLines = {};
+			for (var i = 0; i < results.length; ++i) {
+				if (!results[i].success) continue;
+				var data = JSON.parse(results[i].response);
+				var stopName = config['stops'][i].split(':').join(' - ');
+				result['vrrf_version']['actual'] = data['version'];
+				result['vrrf_version']['expected'] = '0.0.0.0';
+				result['errors'][stopName] = data['error'];
+				result['info'][stopName] = '';
+				result['raw'][stopName] = data['raw'];
+				for (var j = 0; j < data['raw'].length; ++j) {
+					var entry = data['raw'][j];
+					// filter
+					if (isObjectNotEmpty(config['filter']) && !passFilter(config['filter'], entry)) continue;
+					// interprete
+					var
+						reftime = new Date(),
+						schedDate = entry['sched_date'].split('.'),
+						schedTime = entry['sched_time'].split(':'),
+						deptime = calcDateValue(schedDate[0], schedTime[0], schedTime[1]);
+					reftime = calcDateValue(reftime.getDate(), reftime.getHours(), reftime.getMinutes());
+					if (deptime + parseInt(entry['delay']) - reftime) {
+						var ident = entry['lineref']['operator']+'|'+entry['lineref']['type']+'|'+entry['line']+'|'+entry['lineref']['identifier']+'|'+entry['key'];
+						if (!isObjectNotEmpty(result['lines'][ident])) {
+							result['lines'][ident] = {
+								timeValue: deptime,
+								line: entry['line'],
+								destination: entry['destination'],
+								type: entry['type'],
+								stops: [{
+									timeValue: deptime,
+									time: entry['sched_time'],
+									delay: entry['delay'],
+									cancel: entry['is_cancelled'],
+									name: stopName,
+									info: entry['info']
+								}]
+							};
+						} else {
+							result['lines'][ident]['stops'].push({
+									timeValue: deptime,
+									time: entry['sched_time'],
+									delay: entry['delay'],
+									cancel: entry['is_cancelled'],
+									name: stopName,
+									info: entry['info']
+								});
+						}
+					}
+				}
+			}
+			result['lines'] = sortData(Object.values(result['lines']));
+			if (isNumber(config.max)) {
+				result['lines'] = result['lines'].slice(0, config.max);
+			}
+			callback(result);
+		});	
+	}
+}
+
+function passFilter(filter, entry) {
+	if (isObjectNotEmpty(filter['bl'])) {
+		if (isArrayNotEmpty(filter['bl']['line'])
+		&&  arrayContains(filter['bl']['line'], entry['line'])) {
+			return false;
+		}
+		if (isArrayNotEmpty(filter['bl']['type'])
+		&&  arrayContains(filter['bl']['type'], entry['type'])) {
+			return false;
+		}
+		if (isArrayNotEmpty(filter['bl']['platform'])
+		&&  arrayContains(filter['bl']['platform'], entry['platform'])) {
+			return false;
+		}
+		if (isArrayNotEmpty(filter['bl']['destination'])
+		&&  arrayContains(filter['bl']['destination'], entry['destination'])) {
+			return false;
+		}
+	}
+	if (isObjectNotEmpty(filter['wl'])) {
+		if (isArrayNotEmpty(filter['wl']['line'])
+		&&  !arrayContains(filter['wl']['line'], entry['line'])) {
+				return false;
+		}
+		if (isArrayNotEmpty(filter['wl']['type'])
+		&&  !arrayContains(filter['wl']['type'], entry['type'])) {
+				return false;
+		}
+		if (isArrayNotEmpty(filter['wl']['platform'])
+		&&  !arrayContains(filter['wl']['platform'], entry['platform'])) {
+				return false;
+		}
+		if (isArrayNotEmpty(filter['wl']['destination'])
+		&&  !arrayContains(filter['wl']['destination'], entry['destination'])) {
+				return false;
+		}
+	}
+	return true;
+}
+
+function sortData(data) {
+	for (var i = 0; i < data.length; ++i) {
+		data[i]['stops'] = data[i]['stops'].sort(sortFn);
+		data[i]['timeValue'] = data[i]['stops'][0]['timeValue'];
+	}
+	return data.sort(sortFn);
+}
+
+function sortFn(a,b) {
+	return a['timeValue'] > b['timeValue'];
+}
+
+function calcDateValue(day, hour, minute) {
+	return parseInt(day) * 24 * 60 + parseInt(hour) * 60 + parseInt(minute);
+}
+
+importScripts('Worker.js');
\ No newline at end of file
diff --git a/js/worker/check.js b/js/worker/check.js
new file mode 100644
index 0000000..cce4070
--- /dev/null
+++ b/js/worker/check.js
@@ -0,0 +1,64 @@
+function isEmpty(value) {
+	switch (typeof value) {
+		case 'undefined':
+			return true;
+			break;
+		case 'object':
+			if (Array.isArray(value)) return value.length <= 0;
+			return value === null || Object.getOwnPropertyNames(value).length <= 0;
+			break;
+		case 'string':
+			return value == "";
+			break;
+		case 'number':
+			return isNaN(value);
+			break;
+		default:
+			return false;
+			break;
+	}
+}
+
+function isFunction(value) {
+	return typeof(value) == 'function';
+}
+
+function isObject(value) {
+	return typeof(value) == 'object' && !Array.isArray(value);
+}
+
+function isObjectNotEmpty(value) {
+	return typeof(value) == 'object' && !Array.isArray(value) && Object.getOwnPropertyNames(value).length > 0;
+}
+
+function isArray(value) {
+	return Array.isArray(value);
+}
+
+function isArrayNotEmpty(value) {
+	return Array.isArray(value) && value.length > 0;
+}
+
+function isString(value) {
+	return typeof(value) == 'string';
+}
+
+function isStringNotEmpty(value) {
+	return typeof(value) == 'string' && value != "";
+}
+
+function isNumber(value) {
+	return typeof(value) == 'number';
+}
+
+function isInteger(value) {
+	return Number.isSafeInteger(value);
+}
+
+function isBoolean(value) {
+	return typeof(value) == 'boolean';
+}
+
+function arrayContains(array, value) {
+	return isArray(array) && !!(array.indexOf(value)+1);
+}
\ No newline at end of file
diff --git a/panels/clock/script.js b/panels/clock/script.js
index c3fd232..ff61b99 100755
--- a/panels/clock/script.js
+++ b/panels/clock/script.js
@@ -1,339 +1,19 @@
-var DCC;
-
 this.loaded = function(panel, config) {
-
-	var DigitalCanvasClock = function() {
-		var
-			me				= this,
-			analBGFile		= "panels/clock/background.png",
-			analBGImg		= new Image(),
-			analogFactor	= 0.9,
-			useBackground	= true,
-			usePulse		= true,
-			digiClock		= panel.find("canvas[data-digital-clock]")[0],
-			analClock		= panel.find("canvas[data-analog-clock]")[0],
-			digiBackground	= null,
-			analBackground	= null,
-			dfgc			= digiClock ? digiClock.getContext("2d") : null,
-			dbgc			= null,
-			afgc			= analClock ? analClock.getContext("2d") : null,
-			abgc			= null,
-			bgColor			= null, //panel.css("background-color"),
-			sdColor			= "#111",
-			icColor			= "#FFF",
-			date			= new Date(),
-			monthNames		= ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"],
-			dayNames		= ["so","mo","di","mi","do","fr","sa"],
-			lastSecond		= -1,
-			seperatorOn		= false,
-			// sizes digital (16:9)
-			digitalWidth	= 0,
-			digitalHeight	= 0,
-			clockWidth		= 0,
-			clockHeight		= 0,
-			clockX			= 0,
-			clockY			= 0,
-			// clock
-			clockHourX		= 0,					// width * 4.375%
-			clockSeperatorX	= 0,					// height * 60.185%
-			clockMinuteX	= 0,					// width * 47.083%
-			clockSecondX	= 0,					// height * 145.185%
-			dateX			= 0,					// width * 1.875%
-			clockTimeY		= 0,					// height * 58.148%
-			clockSeperatorY	= 0,					// height * 47.037%
-			dateY			= 0,					// width / 2
-			// sizes analog (1:1)
-			analogWidth		= 0,
-			analogHeight	= 0,
-			analogX			= 0,
-			analogY			= 0,
-			analogRad		= 0,
-			// font
-			fatFont			= "",					// width * 43.75%
-			bigFont			= "",					// width * 43.75%
-			smlFont			= "",					// width * 18.75%
-			fatFontPost		= "time-fat",			// width * 43.75%
-			bigFontPost		= "time-medium",		// width * 43.75%
-			smlFontPost		= "time-medium";		// width * 18.75%
-		
-		inita = function() {
-			if (window.fontsReady) {
-				digiBackground = document.createElement('canvas');
-				analBackground = document.createElement('canvas');
-				me.resize($(panel).width(), $(panel).height());
-
-				loop();
-			} else {
-				setTimeout(inita,0);
-			}
-		}
-			
-		loop = function(delta) {
-			me.render();
-			window.requestAnimationFrame(loop);
-		}
-		
-		this.resize = function(width, height) {
-			if (height > width) {
-				var size = height / 2;
-				if (!!analClock) {
-					$(analClock).attr('width', width).attr('height', size);
-				}
-				if (!!digiClock) {
-					$(digiClock).attr('width', width).attr('height', size);
-				}
-			} else {
-				var size = height > width / 2 ? width / 2 : height;
-				if (!!analClock) {
-					$(analClock).attr('width', size).attr('height', height);
-				}
-				if (!!digiClock) {
-					$(digiClock).attr('width', width - size - 6).attr('height', height);
-				}
-			}
-			calc();
-		}
-		
-		this.indicatorColor = function(color) {
-			icColor = color || icColor;
-		}
-		
-		this.shaddowColor = function(color) {
-			sdColor = color || sdColor;
-			renderBackground();
-		}
-		
-		this.backgroundColor = function(color) {
-			bgColor = color || bgColor;
-			renderBackground();
-		}
-		
-		this.enableBackground = function(value) {
-			if (typeof value === 'boolean')
-				useBackground = value;
-		}
-		
-		this.enablePulse = function(value) {
-			if (typeof value === 'boolean')
-				usePulse = value;
-		}
-		
-		calc = function() {
-		
-			if (!!digiClock) calcDigital();
-			if (!!analClock) calcAnalog();
-
-		}
-			
-		calcDigital = function() {
-			// canvas size
-			digitalWidth	= $(digiClock).width();
-			digitalHeight	= $(digiClock).height();
-			// clock size
-			var ratio = digitalWidth * 9 / 16;
-			if (ratio > digitalHeight) {
-				clockWidth	= digitalHeight * 16 / 9;
-				clockHeight	= digitalHeight;
-			} else if (ratio < digitalHeight) {
-				clockWidth	= digitalWidth;
-				clockHeight	= digitalWidth * 9 / 16;
-			} else {
-				clockWidth	= digitalWidth;
-				clockHeight	= digitalHeight;
-			}
-			clockX	= digitalWidth / 2 - clockWidth / 2;
-			clockY	= digitalHeight / 2 - clockHeight / 2;
-			// fonts
-			var
-				bigFontSize = clockWidth * 43.75 / 100,
-				smlFontSize = clockWidth * 18.75 / 100;
-			fatFont = bigFontSize + "px " + fatFontPost;
-			bigFont = bigFontSize + "px " + bigFontPost;
-			smlFont = smlFontSize + "px " + smlFontPost;
-			// positions
-			clockHourX		= clockX + clockWidth * 4.375 / 100;
-			clockMinuteX	= clockX + clockWidth * 47.083 / 100;
-			clockSecondX	= clockX + clockHeight * 145.185 / 100;
-			clockTimeY		= clockY + clockHeight * 58.148 / 100;
-			clockSeperatorX	= clockX + clockHeight * 60.185 / 100;
-			clockSeperatorY	= clockY + clockHeight * 47.037 / 100;
-			dateX			= clockX + clockWidth * 1.875 / 100;
-			dateY			= clockY + clockWidth / 2;
-			// background
-			digiBackground.width	= digitalWidth;
-			digiBackground.height	= digitalHeight;
-			dbgc					= digiBackground.getContext("2d");
-			renderDigiBackground();
-		}
-		
-		calcAnalog = function() {
-
-			analogWidth		= $(analClock).width();
-			analogHeight	= $(analClock).height();
-			// clock size
-			var size;
-			if (analogWidth > analogHeight) {
-				size	= analogHeight;
-			} else {
-				size	= analogWidth;
-			}
-			analogRad	= size * (analogFactor / 2);
-			analogX		= analogWidth / 2;
-			analogY		= analogHeight / 2;
-			afgc.translate(analogX, analogY);
-			// background
-			analBackground.width	= analogWidth;
-			analBackground.height	= analogHeight;
-			abgc 					= analBackground.getContext("2d");
-			abgc.translate(analogX, analogY);
-
-			renderAnalBackground();
-		}
-			
-		this.render = function() {
-			date = new Date();
-			var ms = date.getMilliseconds();
-			if ((ms > 500 && seperatorOn) || (ms < 500 && !seperatorOn)) {
-				var
-					h = date.getHours(),
-					i = date.getMinutes(),
-					s = date.getSeconds(),
-					t = date.getDay(),
-					d = date.getDate(),
-					m = date.getMonth(),
-					y = date.getFullYear();
-				if (d.toString().length < 2)
-					d = "0" + d;
-				if (h.toString().length < 2)
-					h = "0" + h;
-				if (i.toString().length < 2)
-					i = "0" + i;
-				if (s.toString().length < 2)
-					s = "0" + s;
-				if (!!dfgc) renderDigital(h, i, s, t, d, m, y);
-				if (!!afgc && s != lastSecond) renderAnalog(h, i, s);
-				lastSecond = s;
-			}
-		}
-		
-		renderDigital = function(h, i, s, t, d, m, y) {
-			if (bgColor == null) {
-				dfgc.clearRect(0, 0, digitalWidth, digitalHeight);
-			}
-
-			dfgc.drawImage(digiBackground, 0, 0);
-			dfgc.fillStyle = icColor;
-			if (!seperatorOn) {
-				dfgc.font = fatFont;
-				dfgc.fillText(":", clockSeperatorX, clockSeperatorY);
-				seperatorOn = true;
-			} else {
-				seperatorOn = false;
-			}
-			dfgc.font = bigFont;
-			dfgc.fillText(h, clockHourX, clockTimeY);
-			dfgc.fillText(i, clockMinuteX, clockTimeY);
-			dfgc.font = smlFont;
-			dfgc.fillText(s, clockSecondX, clockTimeY);
-			dfgc.fillText(dayNames[t] + " " + d + " " + monthNames[m] + " " + y, dateX, dateY);
-			dfgc.fillStyle = "black";
-		}
-		
-		renderAnalog = function(h, i, s) {
-			if (bgColor == null) {
-				afgc.clearRect(-analogX, -analogY, analogWidth, analogHeight);
-			}
-			//debugger;
-			afgc.drawImage(analBackground, -analogX, -analogY);
-			afgc.strokeStyle = icColor;
-			//drawNumbers();
-			//hour
-			var hour=((h % 12) * Math.PI / 6) + (i * Math.PI / (6 * 60)) + (s * Math.PI / (360 * 60));
-			renderHand(hour, analogRad * 0.5, analogRad * 0.07);
-			//minute
-			var minute = (i * Math.PI / 30) + (s * Math.PI / (30 * 60));
-			renderHand(minute, analogRad * 0.8, analogRad * 0.07);
-			// second
-			var second = (s * Math.PI / 30);
-			renderHand(second, analogRad * 0.9, analogRad * 0.02);
-			afgc.strokeStyle = "black";
-		}
-		
-		renderHand = function(pos, length, width) {
-			afgc.beginPath();
-			afgc.lineWidth = width;
-			afgc.lineCap = "round";
-			afgc.moveTo(0,0);
-			afgc.rotate(pos);
-			afgc.lineTo(0, -length);
-			afgc.stroke();
-			afgc.rotate(-pos);
-		}
-		
-		renderDigiBackground = function() {
-			if (bgColor == null) {
-				dbgc.clearRect(0, 0, digitalWidth, digitalHeight);
-			} else {
-				dbgc.fillStyle = bgColor;
-				dbgc.fillRect(0, 0, digitalWidth, digitalHeight);
-			}			
-			dbgc.fillStyle = sdColor;
-			dbgc.font = fatFont;
-			dbgc.fillText(":", clockSeperatorX, clockSeperatorY);
-			dbgc.font = bigFont;
-			dbgc.fillText("88", clockHourX, clockTimeY);
-			dbgc.fillText("88", clockMinuteX, clockTimeY);
-			dbgc.font = smlFont;
-			dbgc.fillText("88", clockSecondX, clockTimeY);
-			dbgc.fillText("00 00 000 0000", dateX, dateY);
-			dbgc.fillText("** ** *** ****", dateX, dateY);
-			dbgc.fillStyle = "black";
-		}
-		
-		renderAnalBackground = function() {
-			abgc.fillStyle = bgColor;
-			var
-				bc = document.createElement('canvas'),
-				steps = Math.ceil(Math.log(analBGImg.width / (analogRad * 2)) / Math.log(2)) - 1,
-				bctx;
-			bc.width  = analBGImg.width;
-			bc.height = analBGImg.height;
-			bctx = bc.getContext('2d');
-			bctx.drawImage(analBGImg, 0, 0, bc.width, bc.height);
-			for (var i = 0; i < steps; ++i) {
-				var oc = document.createElement('canvas');
-				oc.width  = bc.width  * 0.5;
-				oc.height = bc.height * 0.5;
-				bctx = oc.getContext('2d');
-				bctx.drawImage(bc, 0, 0, oc.width, oc.height);
-				bc = oc;
-			}
-
-			if (bgColor == null) {
-				abgc.clearRect(-analogX, -analogY, analogWidth, analogHeight);
-			} else {
-				abgc.fillRect(-analogX, -analogY, analogWidth, analogHeight);
-			}		
-			abgc.fillStyle = "black";
-			//abgc.drawImage(buffer, -analogRad, -analogRad, analogRad * 2, analogRad * 2);
-			abgc.drawImage(bc, -analogRad, -analogRad, analogRad * 2,   analogRad * 2);
-
-		}
-		analBGImg.onload = function() {
-		inita();
-		}
-		analBGImg.src = analBGFile;
-	}
 	
-	DCC = new DigitalCanvasClock();
+	var clock = new Clock(panel, config);
+	
+	window.addEventListener("resize", function() {
+		clock.resize(panel[0].clientWidth, panel[0].clientHeight);
+	}.bind(this));
 	
-	r = function() {
-		resize(panel);
+	function tick() {
+		clock.render();
+		window.requestAnimationFrame(function() {
+			tick();
+		}, 0);
 	}
 	
-	$(window).resize(r);
-}
-
-var resize = function(panel) {
-	DCC.resize($(panel).width(), $(panel).height());
-}
+	clock.resize(panel[0].clientWidth, panel[0].clientHeight);
+	tick();
+	
+};
\ No newline at end of file
diff --git a/panels/clock/style.less b/panels/clock/style.less
index 1414f31..ae1be74 100755
--- a/panels/clock/style.less
+++ b/panels/clock/style.less
@@ -1,11 +1,8 @@
-[data-analog-clock] {
+canvas {
 	position: relative;
 	display: inline;
 	left: 0px;
-}
-
-[data-digital-clock] {
-	position: relative;
-	display: inline;
-	right: 0px;
+	top: 0px;
+	width: 100%;
+	height: 100%;
 }
\ No newline at end of file
diff --git a/panels/clock/template.html b/panels/clock/template.html
index 6c4da64..bbef1f2 100755
--- a/panels/clock/template.html
+++ b/panels/clock/template.html
@@ -1,4 +1,2 @@
-<canvas data-analog-clock>
-</canvas>
-<canvas data-digital-clock>
-</canvas>
+<canvas>
+</canvas>
\ No newline at end of file
diff --git a/panels/departure/cache/.gitignore b/panels/departure/cache/.gitignore
deleted file mode 100755
index 72e8ffc..0000000
--- a/panels/departure/cache/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*
diff --git a/panels/departure/config/default.json b/panels/departure/config/default.json
index 60e85c1..c147784 100755
--- a/panels/departure/config/default.json
+++ b/panels/departure/config/default.json
@@ -1,7 +1,7 @@
 {
 	"max":"20",
     "stops": [
-        "Dortmund:Hbf"
+        "Dortmund/Hbf"
     ],
     "filter": {
         "bl": {
diff --git a/panels/departure/config/oh14.json b/panels/departure/config/oh14.json
index 437e4b1..5a2773d 100755
--- a/panels/departure/config/oh14.json
+++ b/panels/departure/config/oh14.json
@@ -1,9 +1,9 @@
 {
-	"max":"20",
+	"max":20,
     "stops": [
-        "Dortmund:Universität S",
-        "Dortmund:Joseph-von-Fraunhofer Straße",
-        "Dortmund:Meitnerweg"
+        "Dortmund/Universität S",
+        "Dortmund/Joseph-von-Fraunhofer Straße",
+        "Dortmund/Meitnerweg"
     ],
     "filter": {
         "bl": {
@@ -11,7 +11,7 @@
                 "H-Bahn"
             ],
 	    	"destination": [
-			"Dortmund Otto-Hahn-Straße"
+				"Dortmund Otto-Hahn-Straße"
 			]
         }
     }
diff --git a/panels/departure/departures.php b/panels/departure/departures.php
deleted file mode 100755
index 97868a5..0000000
--- a/panels/departure/departures.php
+++ /dev/null
@@ -1,204 +0,0 @@
-<?php
-	error_reporting (-1);    
-	ini_set ('display_errors', 1);
-	
-	// initialize frontend
-	header('Content-type: text/json');
-	
-	// version-info
-	$version = "0.05";
-	$vrrf_version = "0.07";
-	
-	// options
-	$options = array();
-	
-	// max entries
-	$max = 1000000;
-	
-	// configuration
-	if (isset($_GET['config'])) {
-		$file = "config/" . $_GET['config'] . ".json";
-		if (!file_exists($file)) {
-			$result['error'] = 'config file "'.$file.'" not found';
-			$result['config']['name'] = $file;
-			$result['options'] = $options;
-			echo json_encode($result);
-			exit;
-		}
-		$config = file_get_contents($file);
-		$buffer = json_decode($config, true);
-		$options['stops'] = $buffer['stops'];
-		$options['filter'] = $buffer['filter'];
-		if (is_numeric($buffer['max'])) {
-			$max = $buffer['max'];
-		}
-		if (count($options['stops']) == 0) {
-			$result['error'] = 'no stops defined';
-			$result['config']['name'] = $config_name;
-			$result['config']['type'] = 'panel/departure';
-			$result['options'] = $options;
-			echo json_encode($result);
-			exit;
-		}
-	} else {
-		$result['error'] = 'no config defined';
-		$result['config']['name'] = $config_name;
-		$result['config']['type'] = 'panel/departure';
-		$result['options'] = $options;
-		echo json_encode($result);
-		exit;
-	}
-	
-	
-	usort($options['stops'], function($a, $b) {
-		return strcmp($a, $b);
-	});
-	
-	// cache
-	$name = json_encode($options);
-	$cachefile = "cache/vrr_".md5($name).".json";
-	$cachetime = 60;
-	$cache_ablauf = 0;
-	$cacheused = false;
-	$rawdata = array();
-	$alldata = array();
-	
-	if (is_file($cachefile))
-		$cache_ablauf=filemtime($cachefile)+$cachetime;
-	if (!isset($_GET["nocache"]) && is_file($cachefile) && (time() < filemtime($cachefile)+$cachetime)) {
-		$plain = file_get_contents($cachefile);
-		$alldata = json_decode($plain, true);
-		$cacheused = true;
-	} else {
-		foreach ($options['stops'] as $n => $stop) {
-			$stopsplit = explode(":",$stop);
-
-			// errors?
-			if ($stopsplit == $stop)
-				echo "<div error><h1>NAME ERROR!</h1>$stop<br/>Not a propper stop name. Usage: city:stop[;city:stop]*</div>";
-			
-			// get departures
-			ob_start();
-			$plain = file_get_contents("http://vrrf.finalrewind.org/$stopsplit[0]/$stopsplit[1].json?frontend=json");
-			$data = json_decode($plain, true);
-			// version
-			$alldata["version"] = $version;
-			$alldata["vrrf_version"] = $data['version'];
-			$alldata["used_vrrf_version"] = $vrrf_version;
-			// api_errors
-			$alldata["errors"]["$stopsplit[0] - $stopsplit[1]"] = $data['error'];
-			// info
-			$alldata["info"] = ""; // TODO generate informations
-			// data
-			$rawdata["$stopsplit[0] - $stopsplit[1]"] = $data['raw'];
-		}
-		
-		$reftime = date("dHi");
-		$alldata['lines'] = array();
-		foreach ($rawdata as $stop => $data) {
-			// process
-			foreach ($data as $i => $entry) {
-		
-				// filter
-				if (isset($options['filter']['bl'])) {
-					if (isset($options['filter']['bl']['line']) ) {
-						if (in_array($entry['line'], $options['filter']['bl']['line'])) {
-							continue;
-						}
-					}
-					if (isset($options['filter']['bl']['type']) ) {
-						if (in_array($entry['type'], $options['filter']['bl']['type'])) {
-							continue;
-						}
-					}
-					if (isset($options['filter']['bl']['platform']) ) {
-						if (in_array($entry['platform'], $options['filter']['bl']['platform'])) {
-							continue;
-						}
-					}
-					if (isset($options['filter']['bl']['destination']) ) {
-						if (in_array($entry['destination'], $options['filter']['bl']['destination'])) {
-							continue;
-						}
-					}
-				}
-				if (isset($options['filter']['wl'])) {
-					if (isset($options['filter']['wl']['line']) ) {
-						if (!in_array($entry['line'], $options['filter']['wl']['line'])) {
-							continue;
-						}
-					}
-					if (isset($options['filter']['wl']['type']) ) {
-						if (!in_array($entry['type'], $options['filter']['wl']['type'])) {
-							continue;
-						}
-					}
-					if (isset($options['filter']['wl']['platform']) ) {
-						if (!in_array($entry['platform'], $options['filter']['wl']['platform'])) {
-							continue;
-						}
-					}
-					if (isset($options['filter']['wl']['destination']) ) {
-						if (!in_array($entry['destination'], $options['filter']['wl']['destination'])) {
-							continue;
-						}
-					}
-				}
-				// !filter
-				
-				$scheddate = explode(".",$entry['sched_date']);
-				$schedtime = explode(":",$entry['sched_time']);
-				$deptime = $scheddate[0].$schedtime[0].$schedtime[1];
-				//echo intval($deptime)." + ".intval($entry['delay'])." - ".intval($reftime)." = ".(intval($deptime) + intval($entry['delay']) - intval($reftime))."<br/>";
-				if (intval($deptime) + intval($entry['delay']) - intval($reftime) > 0) {
-					$buf = array('date' => $entry['sched_date'], 'time' => $entry['sched_time'], 'delay' => $entry['delay'], 'cancel' => $entry['is_cancelled'], 'name' => $stop, 'info' => $entry['info']);
-					foreach ($alldata['lines'] as $key => $value) {
-						if (($entry['key'] == $value['key']) && ($entry['lineref']['identifier'] == $value['identifier']) && ($entry['line'] == $value['line'])) {
-							$ibuf = sizeof($alldata['lines'][$key]['stops']);
-							while ($buf != null) {
-								if ($ibuf < 1 || strcmp($buf['time'], $alldata['lines'][$key]['stops'][$ibuf - 1]['time']) > 0) {
-									$alldata['lines'][$key]['stops'][$ibuf] = $buf;
-									$buf = null;
-								} else {
-									$alldata['lines'][$key]['stops'][$ibuf] = $alldata['lines'][$key]['stops'][$ibuf - 1];
-									$ibuf--;
-								}
-							}
-							break;
-						}
-					}
-					if ($buf != null) {
-						$dbuf = array('line' => $entry['line'], 'destination' => $entry['destination'], 'type' => $entry['type'], 'key' => $entry['key'], 'identifier' => $entry['lineref']['identifier'], 'stops' => array($buf));
-						$alldata['lines'][] = $dbuf;
-					}
-				}
-			}
-		
-		}
-			
-		// sort
-		usort($alldata['lines'], function($a, $b) {
-			// atime
-			$scheddate = explode(".",$a['stops'][0]['date']);
-			$schedtime = explode(":",$a['stops'][0]['time']);
-			$atime = $scheddate[2].$scheddate[1].$scheddate[0].$schedtime[0].$schedtime[1];
-			// btime
-			$scheddate = explode(".",$b['stops'][0]['date']);
-			$schedtime = explode(":",$b['stops'][0]['time']);
-			$btime = $scheddate[2].$scheddate[1].$scheddate[0].$schedtime[0].$schedtime[1];
-			// compare
-			return intval($atime) - intval($btime);
-		});
-		
-		// clean
-		foreach ($alldata['lines'] as $id => $line) {
-			unset($alldata['lines'][$id]['key']);
-			unset($alldata['lines'][$id]['identifier']);
-		}
-		
-		// save cache
-		file_put_contents($cachefile,json_encode($alldata));
-	}
-	
-	echo json_encode($alldata);
-?>
diff --git a/panels/departure/script.js b/panels/departure/script.js
index 6717c73..166f8ea 100755
--- a/panels/departure/script.js
+++ b/panels/departure/script.js
@@ -34,7 +34,7 @@
 */
 
 this.loaded = function(panel, config) {
-	console.log("Departure: register with config [" + config + "]");
+	console.log("departures: register with config [" + config + "]");
 	// TODO load config
 	var
 		tplDeparture	= $(panel).find('template[data-departure]').html(),
@@ -46,113 +46,125 @@ this.loaded = function(panel, config) {
 	
 	$(panel).empty().append(fields.cont);
 
-	var u = function() {
-		update(config, fields);
-	};
-	setInterval(u, 60000);
+	//var u = function() {
+	//	console.log("departures: get data [" + config + "]");
+	//	$.get("panels/departure/departures.php?config=" + config , function(decodedData) {
+	//		update(decodedData, fields);
+	//	});
+	//};
+	//setInterval(u, 60000);
 
 	var r = function() {
 		resize(panel, config, fields.cont);
 	};
 	$(window).resize(r);
+	
+	//var worker = new Worker('js/worker/WorkerDeparture.js');
+	var worker = new Worker('js/worker/DepartureWorker.js');
+	
+	worker.addEventListener('message', function(e) {
+		var data = e.data;
+		update(data, fields, config);
+	}, false);
+	
+	//worker.postMessage([{cmd:"loadConfig", params:[config]},{cmd:"start"}]);
+	worker.postMessage({cmd:"loadConfig", params:[config]});
+	worker.postMessage({cmd:"start"});
 
-	u();
+	//u();
 	r();
 }
 
-var update = function(config, fields) {
-	console.log("Departure: get data [" + config + "]");
-	$.get("panels/departure/departures.php?config=" + config , function(decodedData) {
+var update = function(decodedData, fields, config) {
 		
-		console.log("Departure: check vrrf errors [" + config + "]");
-		if (!!decodedData.errors) {
-			var msg = "";
-			for (var i in decodedData.errors) {
-				if (decodedData.errors[i]) {
-					msg += (msg.lenth > 0 ? '\r\n' : '') + i + ': ' + decodedData.errors[i]
-				}
+	console.log("departures: check vrrf errors [" + config + "]");
+	if (!!decodedData.errors) {
+		var msg = "";
+		for (var i in decodedData.errors) {
+			if (decodedData.errors[i]) {
+				msg += (msg.lenth > 0 ? '\r\n' : '') + i + ': ' + decodedData.errors[i]
 			}
-			$(fields.vrrf.ttle).html('VRRF - Error');
-			$(fields.vrrf.msge).html(msg);
-		} else {
-			$(fields.vrrf.ttle).html('');
-			$(fields.vrrf.msge).html('');
-			$(fields.vrrf.msge).css("display", "none");
-		}
-		
-		console.log("Departure: check error [" + config + "]");
-		if (!!decodedData.error) {
-			$(fields.erro.ttle).html('InfoBoard - Error');
-			$(fields.erro.msge).html(decodedData.error);
-		} else {
-			$(fields.erro.ttle).html('');
-			$(fields.erro.msge).html('');
-			$(fields.erro.msge).css("display", "none");
-		}
-		
-		console.log("Departure: check information [" + config + "]");
-		if (!!decodedData.info) {
-			$(fields.info.ttle).html('Information');
-			$(fields.info.msge).html(decodedData.info);
-			//$(fields.info.msge).css("display", "");
-		} else {
-			$(fields.info.ttle).html('');
-			$(fields.info.msge).html('');
-			//$(fields.info.msge).css("display", "none");
 		}
-		
-		console.log("Departure: updating elements [" + config + "]");
-		if (!!decodedData.lines) {
-			for (var i = 0; i < 20; ++i) {
-				var 
-					actDep	= fields.dprt[i],
-					strDep	= decodedData.lines[i];
-				if (!strDep) {
-					$(actDep.self).css("display", "none");
-					$(actDep.line).html('');
-					$(actDep.name).html('');
-					for (var j = 0; j < 3; ++j) {
-						var actStn = actDep.sttn[j];
+		$(fields.vrrf.ttle).html('VRRF - Error');
+		$(fields.vrrf.msge).html(msg);
+	} else {
+		$(fields.vrrf.ttle).html('');
+		$(fields.vrrf.msge).html('');
+		$(fields.vrrf.msge).css("display", "none");
+	}
+	
+	console.log("departures: check error [" + config + "]");
+	if (!!decodedData.error) {
+		$(fields.erro.ttle).html('InfoBoard - Error');
+		$(fields.erro.msge).html(decodedData.error);
+	} else {
+		$(fields.erro.ttle).html('');
+		$(fields.erro.msge).html('');
+		$(fields.erro.msge).css("display", "none");
+	}
+	
+	console.log("departures: check information [" + config + "]");
+	if (!!decodedData.info) {
+		$(fields.info.ttle).html('Information');
+		$(fields.info.msge).html(decodedData.info);
+		//$(fields.info.msge).css("display", "");
+	} else {
+		$(fields.info.ttle).html('');
+		$(fields.info.msge).html('');
+		//$(fields.info.msge).css("display", "none");
+	}
+	
+	console.log("departures: updating elements [" + config + "]");
+	if (!!decodedData.lines) {
+		for (var i = 0; i < 20; ++i) {
+			var 
+				actDep	= fields.dprt[i],
+				strDep	= decodedData.lines[i];
+			if (!strDep) {
+				$(actDep.self).css("display", "none");
+				$(actDep.line).html('');
+				$(actDep.name).html('');
+				for (var j = 0; j < 3; ++j) {
+					var actStn = actDep.sttn[j];
+					$(actStn.self).css("display", "none");
+					$(actStn.time).html('');
+					$(actStn.name).html('');
+					//$(actStn.info.self).css("display", "none");
+					//$(actStn.info.msge).html('');
+				}
+			} else {
+				$(actDep.self).css("display", "");
+				$(actDep.line).html(strDep.line.toUpperCase());
+				$(actDep.name).html(strDep.destination.replace("Dortmund ", ""));
+				for (var j = 0; j < 3; ++j) {
+					var 
+						actStn = actDep.sttn[j],
+						strStn = strDep.stops[j];
+					if (!strStn) {
 						$(actStn.self).css("display", "none");
 						$(actStn.time).html('');
+						$(actStn.dlay).html('');
 						$(actStn.name).html('');
-						$(actStn.info.self).css("display", "none");
-						$(actStn.info.msge).html('');
-					}
-				} else {
-					$(actDep.self).css("display", "");
-					$(actDep.line).html(strDep.line.toUpperCase());
-					$(actDep.name).html(strDep.destination.replace("Dortmund ", ""));
-					for (var j = 0; j < 3; ++j) {
-						var 
-							actStn = actDep.sttn[j],
-							strStn = strDep.stops[j];
-						if (!strStn) {
-							$(actStn.self).css("display", "none");
-							$(actStn.time).html('');
-							$(actStn.dlay).html('');
-							$(actStn.name).html('');
+					} else {
+						$(actStn.self).css("display", "");
+						$(actStn.time).html(strStn.time);
+						$(actStn.name).html(strStn.name.replace("Dortmund - ", ""));
+						if (strStn.cancel == 1) {
+							$(actStn.name).css("text-decoration", "outline");
 						} else {
-							$(actStn.self).css("display", "");
-							$(actStn.time).html(strStn.time);
-							$(actStn.name).html(strStn.name.replace("Dortmund - ", ""));
-							if (strStn.cancel == 1) {
-								$(actStn.name).css("text-decoration", "outline");
-							} else {
-								$(actStn.name).css("text-decoration", "");
-							}
-								
-							if (strStn.delay > 0) {
-								$(actStn.dlay).html('+' + strStn.delay);
-							} else {
-								$(actStn.dlay).html('');
-							}
+							$(actStn.name).css("text-decoration", "");
+						}
+							
+						if (strStn.delay > 0) {
+							$(actStn.dlay).html('+' + strStn.delay);
+						} else {
+							$(actStn.dlay).html('');
 						}
 					}
 				}
 			}
 		}
-	});
+	}
 }
 
 var resize = function(panel, config, cont) {
@@ -211,7 +223,7 @@ var getStringWidth = function(fontSize, string) {
 }
 
 var generate = function(config, stations, departs, tplDeparture, tplStop, tplMsg) {
-	console.log("Departure: generating structure [" + config + "]");
+	console.log("departures: generating structure [" + config + "]");
 	var
 		buffer	= null,
 		fields	= {
@@ -279,7 +291,7 @@ var generate = function(config, stations, departs, tplDeparture, tplStop, tplMsg
 }
 
 var buildDOM = function(config, fields) {
-	console.log("Departure: building dom [" + config + "]");
+	console.log("departures: building dom [" + config + "]");
 	var elements = [];	
 	elements.push(fields.info.self);
 	elements.push(fields.erro.self);
-- 
GitLab