First prototype
diff --git a/js/views/l9n.js b/js/views/l9n.js
new file mode 100644
index 0000000..5951682
--- /dev/null
+++ b/js/views/l9n.js
@@ -0,0 +1,162 @@
+Number.prototype.mod = function(n) {
+  return ((this % n) + n) % n;
+}
+
+var scheduleController = {
+  maxSchedules: 6,
+  recommendedSchedulesPerLine: 2,
+  fetchInterval: 8*60*1000,
+  _times: [],
+  _queueTimes: [],
+  _lastTime: -1,
+  _lastDestinations: [],
+  _numRoutes: -1,
+  _stop: -1,
+  init: function() {
+    scheduleController.setStop();
+    scheduleController.removeDummy();
+    scheduleController.fetchTimes();
+    scheduleController.timer();
+  },
+  setStop: function() {
+    var params = new URLSearchParams(window.location.search);
+    scheduleController._stop = params.get("station");
+  },
+  isOldView: function() {
+    return document.body.classList.contains("view-old");
+  },
+  numSchedules: function() {
+    return Math.min(scheduleController.maxSchedules, 2 * scheduleController.recommendedSchedulesPerLine * scheduleController._numRoutes);
+  },
+  numSchedulesDetailed: function() {
+    return Math.ceil(scheduleController.numSchedules() / 2);
+  },
+  isDetailed: function(i) {
+    return (i < scheduleController.numSchedulesDetailed());
+  },
+  removeElement: function(el) {
+    el.parentNode.removeChild(el);
+  },
+  removeDummy: function() {
+    document.getElementById("trains").innerHTML = "";
+  },
+  timeSinceMidnight: function() {
+    var now = new Date();
+    var then = new Date(
+      now.getFullYear(),
+      now.getMonth(),
+      now.getDate(),
+      0,0,0);
+    return Math.floor((now.getTime() - then.getTime())/1000);
+  },
+  prettyTime: function(seconds, detailed = true) {
+    if (detailed) return (Math.floor(seconds/60)).toString().padStart(2, '0')+":"+(seconds % 60).toString().padStart(2, '0');
+    return Math.floor(seconds/60)+"min";
+  },
+  addCurrentTime: function(data) {
+    var trains = document.getElementById("trains");
+
+    var train = document.createElement("div");
+    train.setAttribute("class", "train");
+
+    var logo = document.createElement("div");
+    logo.setAttribute("class", "logo");
+    logo.style.backgroundColor = "#"+data.color;
+    logo.style.color = "#"+data.textColor;
+    logo.style.fontSize = ("calc("+((scheduleController.isOldView() ? 20 : 18)/data.route.length)+"*var(--unit-size))");
+    logo.textContent = data.route;
+    train.appendChild(logo);
+
+    var destination = document.createElement("div");
+    destination.setAttribute("class", "destination");
+    destination.textContent = data.destination;
+    train.appendChild(destination);
+
+    var time = document.createElement("div");
+    time.setAttribute("class", "time");
+    train.appendChild(time);
+
+    trains.appendChild(train);
+
+    data._element = time;
+    data._markedToRemove = false;
+    scheduleController._times.push(data);
+  },
+  addTime: function(data) {
+    if (data.departureTime > scheduleController._lastTime) {
+      scheduleController._lastTime = data.departureTime;
+      scheduleController._lastDestinations = [data.destination];
+    } else if (data.departureTime == scheduleController.lastTime) {
+      var flag = false;
+      scheduleController._lastDestinations.forEach(dest => {
+        if (data.destination == dest) flag = true;
+      });
+
+      if (flag) {
+        return;
+      }
+
+      scheduleController._lastDestinations.push(dest);
+    } else return;
+
+    if (scheduleController._times.length < scheduleController.numSchedules()) {
+      scheduleController.addCurrentTime(data);
+    } else {
+      scheduleController._queueTimes.push(data);
+    }
+  },
+  fetchTimes: function() {
+    fetch("ajax/api.php?action=getTimes&stop="+scheduleController._stop).then(res => {
+      return res.json();
+    }).then(json => {
+      if (json.status != "ok") throw Error("Not ok");
+
+      scheduleController._numRoutes = json.data.numRoutes;
+
+      json.data.schedules.forEach(data => {
+        scheduleController.addTime(data);
+      });
+    });
+
+    setTimeout(scheduleController.fetchTimes, scheduleController.fetchInterval);
+  },
+  timer: function() {
+    var timeSinceMidnight = scheduleController.timeSinceMidnight();
+
+    var removed = 0;
+    for (var i = 0;; ++i) {
+      if (scheduleController._times[i - removed] === undefined) break;
+
+      if (scheduleController._times[i - removed]._markedToRemove) {
+        scheduleController.removeElement(scheduleController._times[i - removed]._element.parentNode);
+        scheduleController._times.shift();
+        ++removed;
+      }
+
+      if (scheduleController._times[i - removed].departureTime - timeSinceMidnight == 0) {
+        scheduleController._times[i - removed]._markedToRemove = true;
+      }
+    }
+
+    for (var i = 0; i < removed; ++i) {
+      if (scheduleController._queueTimes.length > 0) {
+        scheduleController.addCurrentTime(scheduleController._queueTimes.shift());
+      }
+    }
+
+    var i = 0;
+    scheduleController._times.forEach(time => {
+      var diff = (time.departureTime - timeSinceMidnight).mod(24*60*60);
+
+      time._element.textContent = scheduleController.prettyTime(diff, scheduleController.isDetailed(i));
+
+      ++i;
+    });
+
+    setTimeout(scheduleController.timer, (1000 - (new Date().getTime()) % 1000));
+  }
+};
+
+window.addEventListener("load", _ => {
+  scheduleController.init();
+});