text: (function(laroux) {
      'use strict';
  
      // requires $l.helpers
      // requires $l.css
  
      // anim
      laroux.anim = {
          data: [],
  
          fx: {
              interpolate: function (source, target, shift) {
                  return (source + (target - source) * shift);
              },
  
              easing: function (pos) {
                  return (-Math.cos(pos * Math.PI) / 2) + 0.5;
              }
          },
  
          // {object, property, from, to, time, unit, reset}
          set: function(newanim) {
              newanim.startTime = null;
  
              if (newanim.unit === undefined || newanim.unit === null) {
                  newanim.unit = '';
              }
  
              if (newanim.from === undefined || newanim.from === null) {
                  if (newanim.object === document.body && newanim.property == 'scrollTop') {
                      newanim.from = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
                  } else {
                      newanim.from = newanim.object[newanim.property];
                  }
              }
  
              if (typeof newanim.from == 'string') {
                  newanim.from = Number(newanim.from);
              }
  
              if (newanim.reset === undefined || newanim.reset === null) {
                  newanim.reset = false;
              }
  
              // if (newanim.id === undefined) {
              //     newanim.id = laroux.helpers.getUniqueId();
              // }
  
              laroux.anim.data.push(newanim);
              if (laroux.anim.data.length === 1) {
                  requestAnimationFrame(laroux.anim.onframe);
              }
          },
  
          setCss: function(newanim) {
              if (newanim.from === undefined || newanim.from === null) {
                  newanim.from = laroux.css.getProperty(newanim.object, newanim.property);
              }
  
              newanim.object = newanim.object.style;
              newanim.property = laroux.helpers.camelCase(newanim.property);
  
              laroux.anim.set(newanim);
          },
  
          remove: function(id) {
              var targetKey = null;
  
              for (var item in laroux.anim.data) {
                  if (!laroux.anim.data.hasOwnProperty(item)) {
                      continue;
                  }
  
                  var currentItem = laroux.anim.data[item];
  
                  if (currentItem.id !== undefined && currentItem.id == id) {
                      targetKey = item;
                      break;
                  }
              }
  
              if (targetKey !== null) {
                  laroux.anim.data.splice(targetKey, 1);
                  return true;
              }
  
              return false;
          },
  
          onframe: function(timestamp) {
              var removeKeys = [];
              for (var item in laroux.anim.data) {
                  if (!laroux.anim.data.hasOwnProperty(item)) {
                      continue;
                  }
  
                  var currentItem = laroux.anim.data[item];
                  if (currentItem.startTime === null) {
                      currentItem.startTime = timestamp;
                  }
  
                  var result = laroux.anim.step(currentItem, timestamp);
  
                  if (result === false) {
                      removeKeys.unshift(item);
                  } else if (timestamp > currentItem.startTime + currentItem.time) {
                      if (currentItem.reset) {
                          currentItem.startTime = timestamp;
                          if (newanim.object === document.body && newanim.property == 'scrollTop') {
                              scrollTo(document.body, currentItem.from);
                          } else {
                              currentItem.object[currentItem.property] = currentItem.from;
                          }
                      } else {
                          removeKeys.unshift(item);
                      }
                  }
              }
  
              for (var item2 in removeKeys) {
                  if (!removeKeys.hasOwnProperty(item2)) {
                      continue;
                  }
  
                  laroux.anim.data.splice(removeKeys[item2], 1);
              }
  
              if (laroux.anim.data.length > 0) {
                  requestAnimationFrame(laroux.anim.onframe);
              }
          },
  
          step: function(newanim, timestamp) {
              var finishT = newanim.startTime + newanim.time,
                  shift = (timestamp > finishT) ? 1 : (timestamp - newanim.startTime) / newanim.time;
  
              var value = laroux.anim.fx.interpolate(
                  newanim.from,
                  newanim.to,
                  laroux.anim.fx.easing(shift)
              ) + newanim.unit;
  
              if (newanim.object === document.body && newanim.property == 'scrollTop') {
                  scrollTo(document.body, value);
              } else {
                  newanim.object[newanim.property] = value;
              }
          }
      };
  
  })(this.laroux);
  ;(function(laroux) {
      'use strict';
  
      // date
      laroux.date = {
          parseEpoch: function(timespan, limitWithWeeks) {
              if (timespan <= 3000) {
                  return 'now';
              }
  
              if (timespan < 60*1000) {
                  timespan = Math.ceil(timespan / 1000);
  
                  return timespan + ' seconds';
              }
  
              if (timespan < 60*60*1000) {
                  timespan = Math.ceil(timespan / (60*1000));
  
                  if (timespan == 1) {
                      return 'a minute';
                  }
  
                  return timespan + ' minutes';
              }
  
              if (timespan < 24*60*60*1000) {
                  timespan = Math.ceil(timespan / (60*60*1000));
  
                  if (timespan == 1) {
                      return 'an hour';
                  }
  
                  return timespan + ' hours';
              }
  
              if (timespan < 7*24*60*60*1000) {
                  timespan = Math.ceil(timespan / (24*60*60*1000));
  
                  if (timespan == 1) {
                      return 'a day';
                  }
  
                  return timespan + ' days';
              }
  
              if (timespan < 4*7*24*60*60*1000) {
                  timespan = Math.ceil(timespan / (7*24*60*60*1000));
  
                  if (timespan == 1) {
                      return 'a week';
                  }
  
                  return timespan + ' weeks';
              }
  
              if (limitWithWeeks === true) {
                  return null;
              }
  
              if (timespan < 30*7*24*60*60*1000) {
                  timespan = Math.ceil(timespan / (30*24*60*60*1000));
  
                  if (timespan == 1) {
                      return 'a month';
                  }
  
                  return timespan + ' months';
              }
  
              timespan = Math.ceil(timespan / (365*24*60*60*1000));
  
              if (timespan == 1) {
                  return 'a year';
              }
  
              return timespan + ' years';
          },
  
          monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
          getDateString: function(date, monthNames) {
              var now = Date.now();
  
              var leadingDate = ('0' + date.getDate()).substr(-2, 2);
              var leadingMonth = ('0' + (date.getMonth() + 1)).substr(-2, 2);
              var monthName = laroux.date.monthsShort[date.getMonth()];
              var fullYear = date.getFullYear();
  
              // timespan
              var timespan = now - date.getTime();
              var future;
              if (timespan < 0) {
                  future = true;
                  timespan = Math.abs(timespan);
              } else {
                  future = false;
              }
  
              var timespanstring = laroux.date.parseEpoch(timespan, true);
              if (timespanstring !== null) {
                  if (future) {
                      return timespanstring + ' later';
                  }
  
                  return timespanstring;
              }
  
              if (monthNames) {
                  return leadingDate + ' ' + monthName + ' ' + fullYear;
              }
  
              return leadingDate + '.' + leadingMonth + '.' + fullYear;
          },
  
          getLongDateString: function(date, monthNames, includeTime) {
              var leadingDate = ('0' + date.getDate()).substr(-2, 2);
              var leadingMonth = ('0' + (date.getMonth() + 1)).substr(-2, 2);
              var monthName = laroux.date.monthsShort[date.getMonth()];
              var fullYear = date.getFullYear();
  
              var result;
  
              if (monthNames) {
                  result = leadingDate + ' ' + monthName + ' ' + fullYear;
              } else {
                  result = leadingDate + '.' + leadingMonth + '.' + fullYear;
              }
  
              if (includeTime) {
                  var leadingHour = ('0' + date.getHours()).substr(-2, 2);
                  var leadingMinute = ('0' + date.getMinutes()).substr(-2, 2);
  
                  result += ' ' + leadingHour + ':' + leadingMinute;
              }
  
              return result;
          }
      };
  
  })(this.laroux);
  ;(function(laroux) {
      'use strict';
  
      // requires $l.dom
      // requires $l.helpers
  
      // mvc
      laroux.mvc = {
          appObjects: [],
  
          init: function() {
              var apps = laroux.dom.select('*[lr-app]');
  
              for (var app in apps) {
                  laroux.mvc.appObjects.push({
                      app: apps[app].getAttribute('lr-app'),
                      element: apps[app],
                      model: {},
                      cachedNodes: null
                  });
              }
          },
  
          scanElement: function(element, keys, nodes) {
              for (var i = 0, atts = element.attributes, m = atts.length; i < m; i++) {
                  for (var item1 in keys) {
                      var findStr1 = '{{' + keys[item1] + '}}';
  
                      if (atts[i].value.indexOf(findStr1) !== -1) {
                          nodes.push({node: atts[i], key: keys[item1], value: atts[i].value});
                      }
                  }
              }
  
              for (var j = 0, chldrn = element.childNodes, n = chldrn.length; j < n; j++) {
                  for (var item2 in keys) {
                      var findStr2 = '{{' + keys[item2] + '}}';
  
                      if (chldrn[j].nodeType === 3) {
                          if (chldrn[j].textContent.indexOf(findStr2) !== -1) {
                              nodes.push({node: chldrn[j], key: keys[item2], value: chldrn[j].textContent});
                          }
                          continue;
                      }
                  }
  
                  if (chldrn[j].nodeType === 1) {
                      laroux.mvc.scanElement(chldrn[j], keys, nodes);
                  }
              }
          },
  
          update: function() {
              for (var appObject in laroux.mvc.appObjects) {
                  var selectedappObject = laroux.mvc.appObjects[appObject];
                  laroux.mvc.updateApp(selectedappObject);
              }
          },
  
          updateApp: function(appObject, keys) {
              if (appObject.controller !== undefined) {
                  appObject.controller(appObject.model);
              }
  
              if (appObject.cachedNodes === null) {
                  appObject.cachedNodes = [];
                  var objectKeys = laroux.helpers.getKeysRecursive(appObject.model);
                  laroux.mvc.scanElement(appObject.element, objectKeys, appObject.cachedNodes);
              }
  
              for (var i1 in appObject.cachedNodes) {
                  var item1 = appObject.cachedNodes[i1];
  
                  if (keys !== undefined && keys.indexOf(item1.key) === -1) {
                      continue;
                  }
  
                  if (item1.node instanceof Attr) {
                      item1.node.value = item1.value;
                  } else {
                      item1.node.textContent = item1.value;
                  }
              }
  
              for (var i2 in appObject.cachedNodes) {
                  var item2 = appObject.cachedNodes[i2];
  
                  if (keys !== undefined && keys.indexOf(item2.key) === -1) {
                      continue;
                  }
  
                  var findStr = '{{' + item2.key + '}}';
                  var objectValue = laroux.helpers.getElement(appObject.model, item2.key);
  
                  if (item2.node instanceof Attr) {
                      item2.node.value = item2.node.value.replace(findStr, objectValue);
                  } else {
                      item2.node.textContent = item2.node.textContent.replace(findStr, objectValue);
                  }
              }
          },
  
          observer: function(changes) {
              var updates = {};
              for (var change in changes) {
                  if (changes[change].type == 'update') {
                      for (var appObject in laroux.mvc.appObjects) {
                          var selectedAppObject = laroux.mvc.appObjects[appObject];
  
                          if (selectedAppObject.model == changes[change].object) {
                              if (!(selectedAppObject.app in updates)) {
                                  updates[selectedAppObject.app] = {app: selectedAppObject, keys: [changes[change].name]};
                              } else {
                                  updates[selectedAppObject.app].keys.push(changes[change].name);
                              }
                          }
                      }
                  }
              }
  
              for (var update in updates) {
                  laroux.mvc.updateApp(updates[update].app, updates[update].keys);
              }
          },
  
          bind: function(app, model, controller) {
              if (controller === undefined) {
                  controller = window[app];
              }
  
              for (var appObject in laroux.mvc.appObjects) {
                  var selectedAppObject = laroux.mvc.appObjects[appObject];
  
                  if (selectedAppObject.app == app) {
                      selectedAppObject.model = model;
                      selectedAppObject.controller = controller;
  
                      laroux.mvc.updateApp(selectedAppObject);
                  }
              }
  
              Object.observe(model, laroux.mvc.observer);
          }
      };
  
  })(this.laroux);
  ;(function(laroux) {
      'use strict';
  
      // stack
      laroux.stack = function() {
          this.data = {};
  
          this.add = function(key, value) {
              this.data[key] = value;
          };
  
          this.addRange = function(values) {
              for (var valueKey in values) {
                  if (!values.hasOwnProperty(valueKey)) {
                      continue;
                  }
  
                  this.data[valueKey] = values[valueKey];
              }
          };
  
          this.get = function(key, defaultValue) {
              return this.data[key] || defaultValue || null;
          };
  
          this.getRange = function(keys) {
              var values = {};
  
              for (var item in keys) {
                  if (!keys.hasOwnProperty(item)) {
                      continue;
                  }
  
                  values[keys[item]] = this.data[keys[item]];
              }
  
              return values;
          };
  
          this.keys = function() {
              return Object.keys(this.data);
          };
  
          this.length = function() {
              return Object.keys(this.data).length;
          };
  
          this.exists = function(key) {
              return (key in this.data);
          };
  
          this.remove = function(key) {
              delete this.data[key];
          };
  
          this.clear = function() {
              this.data = {};
          };
      };
  
  })(this.laroux);
  ;(function(laroux) {
      'use strict';
  
      // requires $l.dom
  
      // templates
      laroux.templates = {
          engine: null,
  
          load: function(element, options) {
              var content = element.innerHTML;
  
              return laroux.templates.engine.compile(content, options);
          },
  
          apply: function(element, model, options) {
              var template = laroux.templates.load(element, options);
  
              return template.render(model);
          },
  
          insert: function(element, model, target, position, options) {
              var output = laroux.templates.apply(element, model, options);
  
              laroux.dom.insert(target, position || 'beforeend', output);
          }
      };
  
  })(this.laroux);
  ;(function(laroux) {
      'use strict';
  
      // requires $l
      // requires $l.dom
      // requires $l.helpers
      // requires $l.css
      // requires $l.timers
      // requires $l.date
  
      // ui
      laroux.ui = {
          floatContainer: null,
  
          popup: {
              defaultTimeout: 500,
  
              createBox: function(id, xclass, message) {
                  return laroux.dom.createElement('DIV', {id: id, 'class': xclass},
                      message
                  );
              },
  
              msgbox: function(timeout, message) {
                  var id = laroux.helpers.getUniqueId();
                  var obj = laroux.ui.popup.createBox(id, 'laroux_msgbox', message);
                  laroux.ui.floatContainer.appendChild(obj);
  
                  laroux.css.setProperty(obj, {opacity: 1});
  
                  laroux.timers.set({
                      timeout: timeout,
                      reset: false,
                      ontick: function(x) {
                          // laroux.css.setProperty(x, {opacity: 0});
                          laroux.dom.remove(x);
                      },
                      state: obj
                  });
              },
  
              init: function() {
                  laroux.popupFunc = function(message) {
                      laroux.ui.popup.msgbox(laroux.ui.popup.defaultTimeout, message);
                  };
              }
          },
  
          loading: {
              elementSelector: null,
              element: null,
              defaultDelay: 1500,
              timer: null,
  
              killTimer: function() {
                  clearTimeout(laroux.ui.loading.timer);
              },
  
              hide: function() {
                  laroux.ui.loading.killTimer();
  
                  laroux.css.setProperty(laroux.ui.loading.element, {display: 'none'});
                  localStorage.loadingIndicator = 'false';
              },
  
              show: function(delay) {
                  laroux.ui.loading.killTimer();
  
                  if (delay === undefined) {
                      delay = laroux.ui.loading.defaultDelay;
                  }
  
                  if (delay > 0) {
                      setTimeout(function() { laroux.ui.loading.show(0); }, delay);
                  } else {
                      laroux.css.setProperty(laroux.ui.loading.element, {display: 'block'});
                      localStorage.loadingIndicator = 'true';
                  }
              },
  
              init: function() {
                  if (laroux.ui.loading.element === null && laroux.ui.loading.elementSelector !== null) {
                      laroux.ui.loading.element = laroux.dom.selectSingle(laroux.ui.loading.elementSelector);
                  }
  
                  if (laroux.ui.loading.element !== null) {
                      laroux.dom.setEvent(window, 'load', laroux.ui.loading.hide);
                      laroux.dom.setEvent(window, 'beforeunload', laroux.ui.loading.show);
  
                      if (localStorage.loadingIndicator !== undefined && localStorage.loadingIndicator == 'true') {
                          laroux.ui.loading.show(0);
                      } else {
                          laroux.ui.loading.show();
                      }
                  }
              }
          },
  
          dynamicDates: {
              updateDatesElements: null,
  
              updateDates: function() {
                  if (laroux.ui.dynamicDates.updateDatesElements === null) {
                      laroux.ui.dynamicDates.updateDatesElements = laroux.dom.select('*[data-epoch]');
                  }
  
                  laroux.ui.dynamicDates.updateDatesElements.forEach(function(obj) {
                      // bitshifting (str >> 0) used instead of parseInt(str, 10)
                      var date = new Date((obj.getAttribute('data-epoch') >> 0) * 1000);
  
                      laroux.dom.replace(
                          obj,
                          laroux.date.getDateString(date)
                      );
  
                      obj.setAttribute('title', laroux.date.getLongDateString(date));
                  });
              },
  
              init: function() {
                  laroux.timers.set({
                      timeout: 500,
                      reset: true,
                      ontick: laroux.ui.dynamicDates.updateDates
                  });
              }
          },
  
          scrollView: {
              selectedElements: [],
  
              onhidden: function(elements) {
                  laroux.css.setProperty(elements, {opacity: 0});
                  laroux.css.setTransition(elements, ['opacity']);
              },
  
              onreveal: function(elements) {
                  laroux.css.setProperty(elements, {opacity: 1});
              },
  
              set: function(elements) {
                  for (var item in elements) {
                      if (!elements.hasOwnProperty(item)) {
                          continue;
                      }
  
                      if (!laroux.css.inViewport(elements[item])) {
                          laroux.ui.scrollView.selectedElements.push(elements[item]);
                      }
                  }
  
                  laroux.ui.scrollView.onhidden(laroux.ui.scrollView.selectedElements);
                  laroux.dom.setEvent(window, 'scroll', laroux.ui.scrollView.reveal);
              },
  
              reveal: function() {
                  var removeKeys = [],
                      elements = [];
  
                  laroux.each(
                      laroux.ui.scrollView.selectedElements,
                      function(i, element) {
                          if (laroux.css.inViewport(element)) {
                              removeKeys.unshift(i);
                              elements.push(element);
                          }
                      }
                  );
  
                  for (var item in removeKeys) {
                      if (!removeKeys.hasOwnProperty(item)) {
                          continue;
                      }
  
                      laroux.ui.scrollView.selectedElements.splice(removeKeys[item], 1);
                  }
  
                  if (laroux.ui.scrollView.selectedElements.length === 0) {
                      laroux.dom.unsetEvent(window, 'scroll');
                  }
  
                  if (elements.length > 0) {
                      laroux.ui.scrollView.onreveal(elements);
                  }
              }
          },
  
          createFloatContainer: function() {
              if (!laroux.ui.floatContainer) {
                  laroux.ui.floatContainer = laroux.dom.createElement('DIV', {id: 'laroux_floatdiv'});
                  document.body.insertBefore(laroux.ui.floatContainer, document.body.firstChild);
              }
          },
  
          init: function() {
              laroux.ui.createFloatContainer();
              laroux.ui.popup.init();
              laroux.ui.loading.init();
              laroux.ui.dynamicDates.init();
          }
      };
  
  })(this.laroux);
  