Repository: brainelectronics/Micropython-ESP-WiFi-Manager (language: python)

Issue:
Improve html pages UI
* Better UI
* cleaner CSS
* Increased documentation


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9d20512
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,149 @@
+*.o
+
+.vagrant/
+
+# project specific files
+simulation/wifi-secure.json
+.idea
+simulation/reports/*
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+# micropython libs are stored in lib/
+# lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+*.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5da940e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,193 @@
+# ESP WiFi Manager
+
+Simple Flask style Micropython Server running on an ESP32
+
+
+-----------------------
+
+
+## Setup
+
+The [esptool][ref-esptool] is required to flash the micropython firmware onto
+the device.
+
+For interaction with the filesystem of the device the
+[Remote MicroPython shell][ref-remote-upy-shell] can be used.
+
+### Installation
+
+Install both python packages with the following command in a virtual
+environment to avoid any conflicts with other packages installed on your local
+system.
+
+```bash
+python3 -m venv .venv
+source .venv/bin/activate
+
+pip install esptool
+pip install rshell
+```
+
+Test both tools by showing their man/help info description.
+
+```bash
+esptool.py --help
+rshell --help
+```
+
+### Flash firmware
+
+To flash the [micropython firmware][ref-upy-firmware-download] as described on
+the micropython firmware download page, use the `esptool.py` to erase the
+flash before flashing the firmware.
+
+```bash
+esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART erase_flash
+esptool.py --chip esp32 --port /dev/tty.SLAB_USBtoUART --baud 460800 write_flash -z 0x1000 esp32-20210623-v1.16.bin
+```
+
+If the Micropython board is equipped with an external PSRAM chip, the
+`esp32spiram-20210623-v1.16.bin` can also be used for ESP32 devices. If there
+is no external PRSAM only the non SPIRAM version is working.
+
+### Upload files to board
+
+#### rshell
+
+Open the remote shell with the following command. Additionally use `-b 115200`
+in case no CP210x is used but a CH34x.
+
+```bash
+rshell -p /dev/tty.SLAB_USBtoUART --editor nano
+```
+
+##### Setup check
+
+Check the board config with this simple `boards` call inside the rshell. The
+result will look similar to this after the connection
+
+```bash
+Using buffer-size of 32
+Connecting to /dev/tty.SLAB_USBtoUART (buffer-size 32)...
+Trying to connect to REPL  connected
+Retrieving sysname ... esp32
+Testing if ubinascii.unhexlify exists ... Y
+Retrieving root directories ... /boot.py/ /helpers/ /lib/ /main.py/ /templates/ /wifi-secure.json/ /winbond.py/
+Setting time ... Oct 11, 2021 13:15:24
+Evaluating board_name ... pyboard
+Retrieving time epoch ... Jan 01, 2000
+Welcome to rshell. Use Control-D (or the exit command) to exit rshell.
+/Users/Jones/Downloads/MicroPython/ESP-Webserver-Picoweb> boards
+pyboard @ /dev/tty.SLAB_USBtoUART connected Epoch: 2000 Dirs: /boot.py /static /templates /wifi_manager.py /pyboard/boot.py /pyboard/static /pyboard/templates /pyboard/wifi_manager.py
+```
+
+##### Download files (with script)
+
+Files can be copied to the device with the following command
+
+```bash
+cp SOURCE_FILE_NAME /pyboard
+
+# optional copy it as another file name
+cp SOURCE_FILE_NAME /pyboard/NEW_FILE_NAME
+```
+
+```bash
+/Users/Jones/Downloads/MicroPython/ESP-WiFi-Manager/> cp wifi_manager.py /pyboard
+Copying '/Users/Jones/Downloads/MicroPython/ESP-WiFi-Manager/wifi_manager.py' to '/pyboard/wifi_manager.py' ...
+```
+
+Create compressed CSS and JS files as described in the
+[simulation static files README](simulation/static) to save disk space on the
+device and increase the performance (webpages are loading faster)
+
+```bash
+mkdir /pyboard/static/
+cp simulation/static/css/*.gz /pyboard/static/
+# around 24kB compared to uncompressed 120kB
+
+# optional, not used so far
+# mkdir /pyboard/static/
+# cp simulation/static/js/*.gz /pyboard/static/
+# around 12kB compared to uncompressed 40kB
+
+mkdir /pyboard/templates
+cp templates/* /pyboard/templates
+# around 20kB
+
+mkdir /pyboard/helpers
+cp helpers/*.py /pyboard/helpers
+# around 64kB
+
+mkdir /pyboard/primitives
+cp primitives/*.py /pyboard/primitives
+# around 8kB
+
+mkdir /pyboard/lib
+cp -r lib/* /pyboard/lib
+# around 72kB
+
+cp wifi_manager.py /pyboard
+cp main.py /pyboard
+cp boot.py /pyboard
+# around 40kB
+```
+
+##### Open REPL in rshell
+
+Call `repl` in the rshell. Use CTRL+X to leave the repl or CTRL+D for a soft
+reboot of the device
+
+### Install Micropython Packages
+
+Restart ESP device and open the printed IP address in your browser
+
+Close all connection, and start REPL of uPyCraft or other serial connection.
+
+```python
+import upip
+upip.install('picoweb')
+upip.install('utemplate')
+upip.install('micropython-ulogging')
+```
+
+<!--
+## Templates
+
+If a template `.tpl` is updated on the device, the old and outdated `_tpl.py`
+file shall be deleted.
+
+Its content might be outdated with respect to the input parameters.
+
+```python
+# Autogenerated file
+def render(req, mytime):
+    yield """
+<html>
+Request path: '"""
+    yield str(req.path)
+    yield """'<br>
+<table border=\"1\">
+"""
+    for i in range(5):
+        yield """
+<tr><td> """
+        yield str(i)
+        yield """ </td><td> """
+        yield str("%2d" % i ** 2)
+        yield """ </td></tr>
+"""
+    yield """
+</table>
+Time: '"""
+    yield str(mytime)
+    yield """'<br>
+</html>
+"""
+```
+-->
+
+<!-- Links -->
+[ref-esptool]: https://github.com/espressif/esptool
+[ref-remote-upy-shell]: https://github.com/dhylands/rshell
+[ref-upy-firmware-download]: https://micropython.org/download/
diff --git a/boot.py b/boot.py
new file mode 100644
index 0000000..3173eb2
--- /dev/null
+++ b/boot.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+boot script, do initial stuff here, similar to the setup() function on Arduino
+"""
+
+import esp
+import gc
+import network
+import time
+
+# custom packages
+from helpers.led_helper import Led
+
+
+# set clock speed to 240MHz instead of default 160MHz
+# machine.freq(240000000)
+
+# disable ESP os debug output
+esp.osdebug(None)
+
+led = Led()
+led.flash(amount=3, delay_ms=50)
+led.turn_on()
+
+station = network.WLAN(network.STA_IF)
+if station.active() and station.isconnected():
+    station.disconnect()
+station.active(False)
+time.sleep_ms(1000)
+station.active(True)
+
+led.turn_off()
+
+# run garbage collector at the end to clean up
+gc.collect()
+
+print('Finished booting')
diff --git a/changelog.md b/changelog.md
new file mode 100644
index 0000000..7ad4ca9
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,66 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+<!--
+## [x.y.z] - yyyy-mm-dd
+### Added
+### Changed
+### Removed
+### Fixed
+-->
+
+## [Unreleased]
+
+## Released
+## [0.1.0] - 2022-02-19
+### Added
+- This changelog file
+- Empty [`LICENSE.md`](LICENSE.md) file
+- [Micropython helpers](helpers) submodule
+- [`.gitignore`](.gitignore) file
+- [Micropython primitives](primitives)
+- [Static CSS](static) and [HTML template](templates) files
+- Initial [`WiFi Manager`](wifi_manager.py) implementation
+- Micropython [`boot`](boot.py) and [`main`](main.py) files
+- [`README`](README.md) and [`requirements.txt`](requirements.txt) files
+- Compressed version of
+  [`bootstrap.min.css`](simulation/static/css/bootstrap.min.css) and
+  [`bootstrap.min.js`](simulation/static/js/bootstrap.min.js)
+- Scan only for available networks if `start_config` is called
+
+#### Simulation
+- [`Simulation README`](simulation/README.md) file
+- [`.coverage`](simulation/.coveragerc) and [`.flake8`](simulation/.flake8) file
+- [`requirements.txt`](simulation/requirements.txt) file
+- Empty [`LICENSE.txt`](simulation/LICENSE.txt) file
+- Setup [cfg](simulation/setup.cfg) and [py](simulation/setup.py) files
+- [Static CSS](simulation/static/CSS), [JavaScript](simulation/static/js) and
+  [HTML template](templates) files
+- [`GenericHelper`](simulation/src/generic_helper/generic_helper.py),
+  [`Neopixel`](simulation/src/led_helper/led_helper.py),
+  [`PathHelper`](simulation/src/path_helper/path_helper.py),
+  [`WifiHelper`](simulation/src/wifi_helper/wifi_helper.py) and
+  [`WiFiManager`](simulation/src/wifi_manager/wifi_manager.py) files
+- main [simulation file](simulation/src/run_simulation.py)
+- [Simple bash script](simulation/run.sh) to start simulation
+- Fake implementation of Micropython's [machine](simulation/src/machine)
+  including [`RTC`](simulation/src/machine/rtc.py) and
+  [`Pin`](simulation/src/machine/pin.py)
+- [`TimeHelper`](simulation/src/time_helper/time_helper.py) class
+- Fake implementation of Micropython's
+  [neopixel](simulation/src/led_helper/neopixel.py)
+- Fake implementation of Micropython's
+  [network station and client](simulation/src/wifi_helper/network.py)
+- Bash script to prepare all folders for a unittest coverage report
+- Unittests for all modules and fakes
+- Render list of selectable networks in Python and provide result via API
+- `sendfile` function implemented in same way as on Micropythons PicoWeb
+
+<!-- Links -->
+[Unreleased]: https://github.com/brainelectronics/Micropython-ESP-WiFi-Manager/compare/0.1.0...develop
+
+[0.1.0]: https://github.com/brainelectronics/Micropython-ESP-WiFi-Manager//tree/0.1.0
diff --git a/lib/@PaxHeader b/lib/@PaxHeader
new file mode 100644
index 0000000..4e88e8c
--- /dev/null
+++ b/lib/@PaxHeader
@@ -0,0 +1,1 @@
+22 mtime=1624788837.0
diff --git a/lib/picoweb/__init__.py b/lib/picoweb/__init__.py
new file mode 100644
index 0000000..b771121
--- /dev/null
+++ b/lib/picoweb/__init__.py
@@ -0,0 +1,323 @@
+# Picoweb web pico-framework for Pycopy, https://github.com/pfalcon/pycopy
+# Copyright (c) 2014-2020 Paul Sokolovsky
+# SPDX-License-Identifier: MIT
+import sys
+import gc
+import micropython
+import utime
+import uio
+import ure as re
+import uerrno
+import uasyncio as asyncio
+import pkg_resources
+
+from .utils import parse_qs
+
+SEND_BUFSZ = 128
+
+
+def get_mime_type(fname):
+    # Provide minimal detection of important file
+    # types to keep browsers happy
+    if fname.endswith(".html"):
+        return "text/html"
+    if fname.endswith(".css"):
+        return "text/css"
+    if fname.endswith(".png") or fname.endswith(".jpg"):
+        return "image"
+    return "text/plain"
+
+def sendstream(writer, f):
+    buf = bytearray(SEND_BUFSZ)
+    while True:
+        l = f.readinto(buf)
+        if not l:
+            break
+        yield from writer.awrite(buf, 0, l)
+
+
+def jsonify(writer, dict):
+    import ujson
+    yield from start_response(writer, "application/json")
+    yield from writer.awrite(ujson.dumps(dict))
+
+def start_response(writer, content_type="text/html; charset=utf-8", status="200", headers=None):
+    yield from writer.awrite("HTTP/1.0 %s NA\r\n" % status)
+    yield from writer.awrite("Content-Type: ")
+    yield from writer.awrite(content_type)
+    if not headers:
+        yield from writer.awrite("\r\n\r\n")
+        return
+    yield from writer.awrite("\r\n")
+    if isinstance(headers, bytes) or isinstance(headers, str):
+        yield from writer.awrite(headers)
+    else:
+        for k, v in headers.items():
+            yield from writer.awrite(k)
+            yield from writer.awrite(": ")
+            yield from writer.awrite(v)
+            yield from writer.awrite("\r\n")
+    yield from writer.awrite("\r\n")
+
+def http_error(writer, status):
+    yield from start_response(writer, status=status)
+    yield from writer.awrite(status)
+
+
+class HTTPRequest:
+
+    def __init__(self):
+        pass
+
+    def read_form_data(self):
+        size = int(self.headers[b"Content-Length"])
+        data = yield from self.reader.readexactly(size)
+        form = parse_qs(data.decode())
+        self.form = form
+
+    def parse_qs(self):
+        form = parse_qs(self.qs)
+        self.form = form
+
+
+class WebApp:
+
+    def __init__(self, pkg, routes=None, serve_static=True):
+        if routes:
+            self.url_map = routes
+        else:
+            self.url_map = []
+        if pkg and pkg != "__main__":
+            self.pkg = pkg.split(".", 1)[0]
+        else:
+            self.pkg = None
+        if serve_static:
+            self.url_map.append((re.compile("^/(static/.+)"), self.handle_static))
+        self.mounts = []
+        self.inited = False
+        # Instantiated lazily
+        self.template_loader = None
+        self.headers_mode = "parse"
+
+    def parse_headers(self, reader):
+        headers = {}
+        while True:
+            l = yield from reader.readline()
+            if l == b"\r\n":
+                break
+            k, v = l.split(b":", 1)
+            headers[k] = v.strip()
+        return headers
+
+    def _handle(self, reader, writer):
+        if self.debug > 1:
+            micropython.mem_info()
+
+        close = True
+        req = None
+        try:
+            request_line = yield from reader.readline()
+            if request_line == b"":
+                if self.debug >= 0:
+                    self.log.error("%s: EOF on request start" % reader)
+                yield from writer.aclose()
+                return
+            req = HTTPRequest()
+            # TODO: bytes vs str
+            request_line = request_line.decode()
+            method, path, proto = request_line.split()
+            if self.debug >= 0:
+                self.log.info('%.3f %s %s "%s %s"' % (utime.time(), req, writer, method, path))
+            path = path.split("?", 1)
+            qs = ""
+            if len(path) > 1:
+                qs = path[1]
+            path = path[0]
+
+            #print("================")
+            #print(req, writer)
+            #print(req, (method, path, qs, proto), req.headers)
+
+            # Find which mounted subapp (if any) should handle this request
+            app = self
+            while True:
+                found = False
+                for subapp in app.mounts:
+                    root = subapp.url
+                    #print(path, "vs", root)
+                    if path[:len(root)] == root:
+                        app = subapp
+                        found = True
+                        path = path[len(root):]
+                        if not path.startswith("/"):
+                            path = "/" + path
+                        break
+                if not found:
+                    break
+
+            # We initialize apps on demand, when they really get requests
+            if not app.inited:
+                app.init()
+
+            # Find handler to serve this request in app's url_map
+            found = False
+            for e in app.url_map:
+                pattern = e[0]
+                handler = e[1]
+                extra = {}
+                if len(e) > 2:
+                    extra = e[2]
+
+                if path == pattern:
+                    found = True
+                    break
+                elif not isinstance(pattern, str):
+                    # Anything which is non-string assumed to be a ducktype
+                    # pattern matcher, whose .match() method is called. (Note:
+                    # Django uses .search() instead, but .match() is more
+                    # efficient and we're not exactly compatible with Django
+                    # URL matching anyway.)
+                    m = pattern.match(path)
+                    if m:
+                        req.url_match = m
+                        found = True
+                        break
+
+            if not found:
+                headers_mode = "skip"
+            else:
+                headers_mode = extra.get("headers", self.headers_mode)
+
+            if headers_mode == "skip":
+                while True:
+                    l = yield from reader.readline()
+                    if l == b"\r\n":
+                        break
+            elif headers_mode == "parse":
+                req.headers = yield from self.parse_headers(reader)
+            else:
+                assert headers_mode == "leave"
+
+            if found:
+                req.method = method
+                req.path = path
+                req.qs = qs
+                req.reader = reader
+                close = yield from handler(req, writer)
+            else:
+                yield from start_response(writer, status="404")
+                yield from writer.awrite("404\r\n")
+            #print(req, "After response write")
+        except Exception as e:
+            if self.debug >= 0:
+                self.log.exc(e, "%.3f %s %s %r" % (utime.time(), req, writer, e))
+            yield from self.handle_exc(req, writer, e)
+
+        if close is not False:
+            yield from writer.aclose()
+        if __debug__ and self.debug > 1:
+            self.log.debug("%.3f %s Finished processing request", utime.time(), req)
+
+    def handle_exc(self, req, resp, e):
+        # Can be overriden by subclasses. req may be not (fully) initialized.
+        # resp may already have (partial) content written.
+        # NOTE: It's your responsibility to not throw exceptions out of
+        # handle_exc(). If exception is thrown, it will be propagated, and
+        # your webapp will terminate.
+        # This method is a coroutine.
+        return
+        yield
+
+    def mount(self, url, app):
+        "Mount a sub-app at the url of current app."
+        # Inspired by Bottle. It might seem that dispatching to
+        # subapps would rather be handled by normal routes, but
+        # arguably, that's less efficient. Taking into account
+        # that paradigmatically there's difference between handing
+        # an action and delegating responisibilities to another
+        # app, Bottle's way was followed.
+        app.url = url
+        self.mounts.append(app)
+        # TODO: Consider instead to do better subapp prefix matching
+        # in _handle() above.
+        self.mounts.sort(key=lambda app: len(app.url), reverse=True)
+
+    def route(self, url, **kwargs):
+        def _route(f):
+            self.url_map.append((url, f, kwargs))
+            return f
+        return _route
+
+    def add_url_rule(self, url, func, **kwargs):
+        # Note: this method skips Flask's "endpoint" argument,
+        # because it's alleged bloat.
+        self.url_map.append((url, func, kwargs))
+
+    def _load_template(self, tmpl_name):
+        if self.template_loader is None:
+            import utemplate.source
+            self.template_loader = utemplate.source.Loader(self.pkg, "templates")
+        return self.template_loader.load(tmpl_name)
+
+    def render_template(self, writer, tmpl_name, args=()):
+        tmpl = self._load_template(tmpl_name)
+        for s in tmpl(*args):
+            yield from writer.awritestr(s)
+
+    def render_str(self, tmpl_name, args=()):
+        #TODO: bloat
+        tmpl = self._load_template(tmpl_name)
+        return ''.join(tmpl(*args))
+
+    def sendfile(self, writer, fname, content_type=None, headers=None):
+        if not content_type:
+            content_type = get_mime_type(fname)
+        try:
+            with pkg_resources.resource_stream(self.pkg, fname) as f:
+                yield from start_response(writer, content_type, "200", headers)
+                yield from sendstream(writer, f)
+        except OSError as e:
+            if e.args[0] == uerrno.ENOENT:
+                yield from http_error(writer, "404")
+            else:
+                raise
+
+    def handle_static(self, req, resp):
+        path = req.url_match.group(1)
+        print(path)
+        if ".." in path:
+            yield from http_error(resp, "403")
+            return
+        yield from self.sendfile(resp, path)
+
+    def init(self):
+        """Initialize a web application. This is for overriding by subclasses.
+        This is good place to connect to/initialize a database, for example."""
+        self.inited = True
+
+    def serve(self, loop, host, port):
+        # Actually serve client connections. Subclasses may override this
+        # to e.g. catch and handle exceptions when dealing with server socket
+        # (which are otherwise unhandled and will terminate a Picoweb app).
+        # Note: name and signature of this method may change.
+        loop.create_task(asyncio.start_server(self._handle, host, port))
+        loop.run_forever()
+
+    def run(self, host="127.0.0.1", port=8081, debug=False, lazy_init=False, log=None):
+        if log is None and debug >= 0:
+            import ulogging
+            log = ulogging.getLogger("picoweb")
+            if debug > 0:
+                log.setLevel(ulogging.DEBUG)
+        self.log = log
+        gc.collect()
+        self.debug = int(debug)
+        self.init()
+        if not lazy_init:
+            for app in self.mounts:
+                app.init()
+        loop = asyncio.get_event_loop()
+        if debug > 0:
+            print("* Running on http://%s:%s/" % (host, port))
+        self.serve(loop, host, port)
+        loop.close()
diff --git a/lib/picoweb/utils.py b/lib/picoweb/utils.py
new file mode 100644
index 0000000..8943ed8
--- /dev/null
+++ b/lib/picoweb/utils.py
@@ -0,0 +1,28 @@
+def unquote_plus(s):
+    # TODO: optimize
+    s = s.replace("+", " ")
+    arr = s.split("%")
+    arr2 = [chr(int(x[:2], 16)) + x[2:] for x in arr[1:]]
+    return arr[0] + "".join(arr2)
+
+def parse_qs(s):
+    res = {}
+    if s:
+        pairs = s.split("&")
+        for p in pairs:
+            vals = [unquote_plus(x) for x in p.split("=", 1)]
+            if len(vals) == 1:
+                vals.append(True)
+            old = res.get(vals[0])
+            if old is not None:
+                if not isinstance(old, list):
+                    old = [old]
+                    res[vals[0]] = old
+                old.append(vals[1])
+            else:
+                res[vals[0]] = vals[1]
+    return res
+
+#print(parse_qs("foo"))
+#print(parse_qs("fo%41o+bar=+++1"))
+#print(parse_qs("foo=1&foo=2"))
diff --git a/lib/pkg_resources.py b/lib/pkg_resources.py
new file mode 100644
index 0000000..9ab28e9
--- /dev/null
+++ b/lib/pkg_resources.py
@@ -0,0 +1,27 @@
+import uio
+
+c = {}
+
+def resource_stream(package, resource):
+    if package not in c:
+        try:
+            if package:
+                p = __import__(package + ".R", None, None, True)
+            else:
+                p = __import__("R")
+            c[package] = p.R
+        except ImportError:
+            if package:
+                p = __import__(package)
+                d = p.__path__
+            else:
+                d = "."
+#            if d[0] != "/":
+#                import uos
+#                d = uos.getcwd() + "/" + d
+            c[package] = d + "/"
+
+    p = c[package]
+    if isinstance(p, dict):
+        return uio.BytesIO(p[resource])
+    return open(p + resource, "rb")
diff --git a/lib/typing.py b/lib/typing.py
new file mode 100644
index 0000000..212aec7
--- /dev/null
+++ b/lib/typing.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Fake classes of typing module.
+
+https://github.com/micropython/micropython-lib/issues/190
+
+https://github.com/micropython/micropython-lib/blob/3d779b8ceab5b65b9f70accbcbb15ab3509eceb7/typing/typing.py
+"""
+
+
+class Any:
+    pass
+
+
+class NoReturn:
+    pass
+
+
+class ClassVar:
+    pass
+
+
+class Union:
+    pass
+
+
+class Optional:
+    pass
+
+
+class Generic:
+    pass
+
+
+class NamedTuple:
+    pass
+
+
+class Hashable:
+    pass
+
+
+class Awaitable:
+    pass
+
+
+class Coroutine:
+    pass
+
+
+class AsyncIterable:
+    pass
+
+
+class AsyncIterator:
+    pass
+
+
+class Iterable:
+    pass
+
+
+class Iterator:
+    pass
+
+
+class Reversible:
+    pass
+
+
+class Sized:
+    pass
+
+
+class Container:
+    pass
+
+
+class Collection:
+    pass
+
+
+class Callable:
+    pass
+
+
+class AbstractSet:
+    pass
+
+
+class MutableSet:
+    pass
+
+
+class Mapping:
+    pass
+
+
+class MutableMapping:
+    pass
+
+
+class Sequence:
+    pass
+
+
+class MutableSequence:
+    pass
+
+
+class ByteString:
+    pass
+
+
+class Tuple:
+    pass
+
+
+class List:
+    pass
+
+
+class Deque:
+    pass
+
+
+class Set:
+    pass
+
+
+class FrozenSet:
+    pass
+
+
+class MappingView:
+    pass
+
+
+class KeysView:
+    pass
+
+
+class ItemsView:
+    pass
+
+
+class ValuesView:
+    pass
+
+
+class ContextManager:
+    pass
+
+
+class AsyncContextManager:
+    pass
+
+
+class Dict:
+    pass
+
+
+class DefaultDict:
+    pass
+
+
+class Counter:
+    pass
+
+
+class ChainMap:
+    pass
+
+
+class Generator:
+    pass
+
+
+class AsyncGenerator:
+    pass
+
+
+class Type:
+    pass
+
+
+def cast(typ, val):
+    return val
+
+
+def _overload_dummy(*args, **kwds):
+    """Helper for @overload to raise when called."""
+    raise NotImplementedError(
+        "You should not call an overloaded function. "
+        "A series of @overload-decorated functions "
+        "outside a stub module should always be followed "
+        "by an implementation that is not @overload-ed."
+    )
+
+
+def overload():
+    return _overload_dummy
diff --git a/lib/uasyncio/__init__.py b/lib/uasyncio/__init__.py
new file mode 100644
index 0000000..0e43d46
--- /dev/null
+++ b/lib/uasyncio/__init__.py
@@ -0,0 +1,272 @@
+# (c) 2014-2020 Paul Sokolovsky. MIT license.
+from micropython import const
+import uerrno
+import uselect as select
+import usocket as _socket
+import uio
+from uasyncio.core import *
+
+
+DEBUG = 0
+log = None
+
+def set_debug(val):
+    global DEBUG, log
+    DEBUG = val
+    if val:
+        import ulogging
+        log = ulogging.getLogger("uasyncio")
+
+
+class PollEventLoop(EventLoop):
+
+    def __init__(self, runq_len=16, waitq_len=16):
+        EventLoop.__init__(self, runq_len, waitq_len)
+        self.poller = select.poll()
+
+    def add_reader(self, sock, cb, *args):
+        if DEBUG and __debug__:
+            log.debug("add_reader%s", (sock, cb, args))
+        if args:
+            self.poller.register(sock, select.POLLIN, (cb, args))
+        else:
+            self.poller.register(sock, select.POLLIN, cb)
+
+    def remove_reader(self, sock):
+        if DEBUG and __debug__:
+            log.debug("remove_reader(%s)", sock)
+        self.poller.unregister(sock, False)
+
+    def add_writer(self, sock, cb, *args):
+        if DEBUG and __debug__:
+            log.debug("add_writer%s", (sock, cb, args))
+        if args:
+            self.poller.register(sock, select.POLLOUT, (cb, args))
+        else:
+            self.poller.register(sock, select.POLLOUT, cb)
+
+    def remove_writer(self, sock):
+        if DEBUG and __debug__:
+            log.debug("remove_writer(%s)", sock)
+        # StreamWriter.awrite() first tries to write to a socket,
+        # and if that succeeds, yield IOWrite may never be called
+        # for that socket, and it will never be added to poller. So,
+        # ignore such error.
+        self.poller.unregister(sock, False)
+
+    def cancel_io(self, sock):
+        if DEBUG and __debug__:
+            log.debug("cancel_io(%s)", sock)
+        # Cancel both reader and writer
+        # Don't remove, in the hope that it will be used again, though
+        # this call is usually used for timeouts, and timeouts are
+        # usually handled as fatal errors (but then underlying stream
+        # should be closed by user).
+        # Use modify() deliberately, to catch a case when we cancel
+        # a socket which was never pended, what shouldn't happen.
+        self.poller.modify(sock, 0)
+
+    def wait(self, delay):
+        if DEBUG and __debug__:
+            log.debug("poll.wait(%d)", delay)
+        # We need one-shot behavior (second arg of 1 to .poll())
+        res = self.poller.ipoll(delay, 1)
+        #log.debug("poll result: %s", res)
+        for sock, ev, cb in res:
+            if ev & (select.POLLHUP | select.POLLERR):
+                # These events are returned even if not requested, and
+                # are sticky, i.e. will be returned again and again.
+                # If the caller doesn't do proper error handling and
+                # unregistering this sock, we'll busy-loop on it, so we
+                # as well can unregister it now "just in case".
+                self.remove_reader(sock)
+            if DEBUG and __debug__:
+                log.debug("Calling IO callback: %r", cb)
+            if isinstance(cb, tuple):
+                cb[0](*cb[1])
+            else:
+                cb.pend_throw(None)
+                self.call_soon(cb)
+
+
+class Stream:
+
+    def __init__(self, polls, ios=None, extra=None):
+        if ios is None:
+            ios = polls
+        self.polls = polls
+        self.ios = ios
+        self.extra = extra
+
+    def read(self, n=-1):
+        while True:
+            res = self.ios.read(n)
+            if res is None:
+                yield IORead(self.polls)
+            elif res is uio.WANT_WRITE:
+                yield IOWrite(self.polls)
+            else:
+                break
+        if not res:
+            yield IOReadDone(self.polls)
+        return res
+
+    def readexactly(self, n):
+        buf = b""
+        while n:
+            res = self.ios.read(n)
+            if res is None:
+                yield IORead(self.polls)
+            elif res is uio.WANT_WRITE:
+                yield IOWrite(self.polls)
+            elif not res:
+                yield IOReadDone(self.polls)
+                break
+            else:
+                buf += res
+                n -= len(res)
+                # Give other tasks a chance to run
+                yield
+        return buf
+
+    def readline(self):
+        if DEBUG and __debug__:
+            log.debug("StreamReader.readline()")
+        buf = b""
+        while True:
+            res = self.ios.readline()
+            if res is None:
+                yield IORead(self.polls)
+            elif res is uio.WANT_WRITE:
+                yield IOWrite(self.polls)
+            elif not res:
+                yield IOReadDone(self.polls)
+                break
+            else:
+                buf += res
+                if buf[-1] == 0x0a:
+                    break
+                # Give other tasks a chance to run
+                yield
+        if DEBUG and __debug__:
+            log.debug("StreamReader.readline(): %s", buf)
+        return buf
+
+    def awrite(self, buf, off=0, sz=-1):
+        # This method is called awrite (async write) to not proliferate
+        # incompatibility with original asyncio. Unlike original asyncio
+        # whose .write() method is both not a coroutine and guaranteed
+        # to return immediately (which means it has to buffer all the
+        # data), this method is a coroutine.
+        if sz == -1:
+            sz = len(buf) - off
+        if DEBUG and __debug__:
+            log.debug("StreamWriter.awrite(): spooling %d bytes", sz)
+        while sz:
+            res = self.ios.write(buf, off, sz)
+            # If we spooled everything, fast return
+            if res == sz:
+                if DEBUG and __debug__:
+                    log.debug("StreamWriter.awrite(): completed spooling %d bytes", res)
+                return
+            elif res is None:
+                yield IOWrite(self.polls)
+            elif res is uio.WANT_READ:
+                yield IORead(self.polls)
+            else:
+                if DEBUG and __debug__:
+                    log.debug("StreamWriter.awrite(): spooled partial %d bytes", res)
+                assert res != 0 and res < sz
+                off += res
+                sz -= res
+                # Give other tasks a chance to run
+                yield
+
+    # This function is tentative, subject to change
+    def awritestr(self, s):
+        yield from self.awrite(s.encode())
+
+    # Write piecewise content from iterable (usually, a generator)
+    def awriteiter(self, iterable):
+        for buf in iterable:
+            yield from self.awrite(buf)
+
+    def aclose(self):
+        yield IOWriteDone(self.polls)
+        self.ios.close()
+        self.polls.close()
+
+    def get_extra_info(self, name, default=None):
+        return self.extra.get(name, default)
+
+    def __repr__(self):
+        return "<Stream %r %r>" % (self.polls, self.ios)
+
+
+StreamReader = StreamWriter = const(Stream)
+
+
+def open_connection(host, port, ssl=False, server_hostname=None):
+    if DEBUG and __debug__:
+        log.debug("open_connection(%s, %s)", host, port)
+    ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
+    ai = ai[0]
+    s = _socket.socket(ai[0], ai[1], ai[2])
+    s.setblocking(False)
+    try:
+        s.connect(ai[-1])
+    except OSError as e:
+        if e.args[0] != uerrno.EINPROGRESS:
+            s.close()
+            raise
+    if DEBUG and __debug__:
+        log.debug("open_connection: After connect")
+    yield IOWrite(s)
+    if DEBUG and __debug__:
+        log.debug("open_connection: After iowait: %s", s)
+    s2 = s
+    if ssl:
+        if ssl is True:
+            import ussl
+            ssl = ussl.SSLContext()
+        s2 = ssl.wrap_socket(s, server_hostname=server_hostname, do_handshake=False)
+        s2.setblocking(False)
+    return StreamReader(s, s2), StreamWriter(s, s2)
+
+
+def start_server(client_coro, host, port, backlog=10, ssl=None):
+    if DEBUG and __debug__:
+        log.debug("start_server(%s, %s)", host, port)
+    ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
+    ai = ai[0]
+    s = _socket.socket(ai[0], ai[1], ai[2])
+    s2 = None
+    try:
+        s.setblocking(False)
+        s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
+        s.bind(ai[-1])
+        s.listen(backlog)
+        while True:
+            if DEBUG and __debug__:
+                log.debug("start_server: Before accept")
+            yield IORead(s)
+            if DEBUG and __debug__:
+                log.debug("start_server: After iowait")
+            s2, client_addr = s.accept()
+            s3 = s2
+            if ssl:
+                s3 = ssl.wrap_socket(s2, server_side=True, do_handshake=False)
+            s3.setblocking(False)
+            if DEBUG and __debug__:
+                log.debug("start_server: After accept: %s", s2)
+            extra = {"peername": client_addr}
+            yield client_coro(StreamReader(s2, s3), StreamWriter(s2, s3, extra))
+            s2 = s3 = None
+    finally:
+        if s2:
+            s2.close()
+        s.close()
+
+
+import uasyncio.core
+uasyncio.core._event_loop_class = PollEventLoop
diff --git a/lib/uasyncio/core.py b/lib/uasyncio/core.py
new file mode 100644
index 0000000..1fb1c6f
--- /dev/null
+++ b/lib/uasyncio/core.py
@@ -0,0 +1,338 @@
+# (c) 2014-2018 Paul Sokolovsky. MIT license.
+import utime as time
+import utimeq
+import ucollections
+
+
+type_gen = type((lambda: (yield))())
+
+DEBUG = 0
+log = None
+
+def set_debug(val):
+    global DEBUG, log
+    DEBUG = val
+    if val:
+        import ulogging
+        log = ulogging.getLogger("uasyncio.core")
+
+
+class CancelledError(Exception):
+    pass
+
+
+class TimeoutError(CancelledError):
+    pass
+
+
+class EventLoop:
+
+    def __init__(self, runq_len=16, waitq_len=16):
+        self.runq = ucollections.deque((), runq_len, True)
+        self.waitq = utimeq.utimeq(waitq_len)
+        # Current task being run. Task is a top-level coroutine scheduled
+        # in the event loop (sub-coroutines executed transparently by
+        # yield from/await, event loop "doesn't see" them).
+        self.cur_task = None
+
+    def time(self):
+        return time.ticks_ms()
+
+    def create_task(self, coro):
+        # CPython 3.4.2
+        self.call_later_ms(0, coro)
+        # CPython asyncio incompatibility: we don't return Task object
+
+    def call_soon(self, callback, *args):
+        if __debug__ and DEBUG:
+            log.debug("Scheduling in runq: %s", (callback, args))
+        self.runq.append(callback)
+        if not isinstance(callback, type_gen):
+            self.runq.append(args)
+
+    def call_later(self, delay, callback, *args):
+        self.call_at_(time.ticks_add(self.time(), int(delay * 1000)), callback, args)
+
+    def call_later_ms(self, delay, callback, *args):
+        if not delay:
+            return self.call_soon(callback, *args)
+        self.call_at_(time.ticks_add(self.time(), delay), callback, args)
+
+    def call_at_(self, time, callback, args=()):
+        if __debug__ and DEBUG:
+            log.debug("Scheduling in waitq: %s", (time, callback, args))
+        id = self.waitq.push(time, callback, args)
+        if isinstance(callback, type_gen):
+            prev = callback.pend_throw(id)
+
+    def wait(self, delay):
+        # Default wait implementation, to be overriden in subclasses
+        # with IO scheduling
+        if __debug__ and DEBUG:
+            log.debug("Sleeping for: %s", delay)
+        time.sleep_ms(delay)
+
+    def run_forever(self):
+        cur_task = [0, 0, 0]
+        while True:
+            # Expire entries in waitq and move them to runq
+            tnow = self.time()
+            while self.waitq:
+                t = self.waitq.peektime()
+                delay = time.ticks_diff(t, tnow)
+                if delay > 0:
+                    break
+                self.waitq.pop(cur_task)
+
+                if isinstance(cur_task[1], type_gen):
+                    prev = cur_task[1].pend_throw(None)
+
+                if __debug__ and DEBUG:
+                    log.debug("Moving from waitq to runq: %s", cur_task[1])
+                self.call_soon(cur_task[1], *cur_task[2])
+
+            # Process runq
+            l = len(self.runq)
+            if __debug__ and DEBUG:
+                log.debug("Entries in runq: %d", l)
+            while l:
+                cb = self.runq.popleft()
+                l -= 1
+                args = ()
+                if not isinstance(cb, type_gen):
+                    args = self.runq.popleft()
+                    l -= 1
+                    if __debug__ and DEBUG:
+                        log.info("Next callback to run: %s", (cb, args))
+                    cb(*args)
+                    continue
+
+                if __debug__ and DEBUG:
+                    log.info("Next coroutine to run: %s", (cb, args))
+                self.cur_task = cb
+                delay = 0
+                try:
+                    if args is ():
+                        ret = next(cb)
+                    else:
+                        ret = cb.send(*args)
+                    if __debug__ and DEBUG:
+                        log.info("Coroutine %s yield result: %s", cb, ret)
+                    if isinstance(ret, SysCall1):
+                        arg = ret.arg
+                        if isinstance(ret, SleepMs):
+                            delay = arg
+                        elif isinstance(ret, IORead):
+                            # Store stream object so we can call cancel_io()
+                            # for it in case of cancellation.
+                            cb.pend_throw(arg)
+                            self.add_reader(arg, cb)
+                            continue
+                        elif isinstance(ret, IOWrite):
+                            cb.pend_throw(arg)
+                            self.add_writer(arg, cb)
+                            continue
+                        elif isinstance(ret, IOReadDone):
+                            self.remove_reader(arg)
+                        elif isinstance(ret, IOWriteDone):
+                            self.remove_writer(arg)
+                        elif isinstance(ret, StopLoop):
+                            return arg
+                        else:
+                            assert False, "Unknown syscall yielded: %r (of type %r)" % (ret, type(ret))
+                    elif isinstance(ret, type_gen):
+                        self.call_soon(ret)
+                    elif isinstance(ret, int):
+                        # Delay
+                        delay = ret
+                    elif ret is None:
+                        # Just reschedule
+                        pass
+                    elif ret is False:
+                        # Don't reschedule
+                        continue
+                    else:
+                        assert False, "Unsupported coroutine yield value: %r (of type %r)" % (ret, type(ret))
+                except StopIteration as e:
+                    if __debug__ and DEBUG:
+                        log.debug("Coroutine finished: %s", cb)
+                    continue
+                except CancelledError as e:
+                    if __debug__ and DEBUG:
+                        log.debug("Coroutine cancelled: %s", cb)
+                    continue
+                # Currently all syscalls don't return anything, so we don't
+                # need to feed anything to the next invocation of coroutine.
+                # If that changes, need to pass that value below.
+                if delay:
+                    self.call_later_ms(delay, cb)
+                else:
+                    self.call_soon(cb)
+
+            # Wait until next waitq task or I/O availability
+            delay = 0
+            if not self.runq:
+                delay = -1
+                if self.waitq:
+                    tnow = self.time()
+                    t = self.waitq.peektime()
+                    delay = time.ticks_diff(t, tnow)
+                    if delay < 0:
+                        delay = 0
+            self.wait(delay)
+
+    def run_until_complete(self, coro):
+        def _run_and_stop():
+            yield from coro
+            yield StopLoop(0)
+        self.call_soon(_run_and_stop())
+        self.run_forever()
+
+    def stop(self):
+        self.call_soon((lambda: (yield StopLoop(0)))())
+
+    def close(self):
+        pass
+
+
+class SysCall:
+
+    def __init__(self, *args):
+        self.args = args
+
+    def handle(self):
+        raise NotImplementedError
+
+# Optimized syscall with 1 arg
+class SysCall1(SysCall):
+
+    def __init__(self, arg):
+        self.arg = arg
+
+class StopLoop(SysCall1):
+    pass
+
+class IORead(SysCall1):
+    pass
+
+class IOWrite(SysCall1):
+    pass
+
+class IOReadDone(SysCall1):
+    pass
+
+class IOWriteDone(SysCall1):
+    pass
+
+
+_event_loop = None
+_event_loop_class = EventLoop
+def get_event_loop(runq_len=16, waitq_len=16):
+    global _event_loop
+    if _event_loop is None:
+        _event_loop = _event_loop_class(runq_len, waitq_len)
+    return _event_loop
+
+def sleep(secs):
+    yield int(secs * 1000)
+
+# Implementation of sleep_ms awaitable with zero heap memory usage
+class SleepMs(SysCall1):
+
+    def __init__(self):
+        self.v = None
+        self.arg = None
+
+    def __call__(self, arg):
+        self.v = arg
+        #print("__call__")
+        return self
+
+    def __iter__(self):
+        #print("__iter__")
+        return self
+
+    def __next__(self):
+        if self.v is not None:
+            #print("__next__ syscall enter")
+            self.arg = self.v
+            self.v = None
+            return self
+        #print("__next__ syscall exit")
+        _stop_iter.__traceback__ = None
+        raise _stop_iter
+
+_stop_iter = StopIteration()
+sleep_ms = SleepMs()
+
+
+def cancel(coro):
+    prev = coro.pend_throw(CancelledError())
+    if prev is None:
+        pass
+    elif isinstance(prev, int):
+        # utimeq id
+        _event_loop.waitq.remove(prev)
+        _event_loop.call_soon(coro)
+    else:
+        # stream obj
+        _event_loop.cancel_io(prev)
+        _event_loop.call_soon(coro)
+
+
+class TimeoutObj:
+    def __init__(self, coro):
+        self.coro = coro
+
+
+def wait_for_ms(coro, timeout):
+
+    def waiter(coro, timeout_obj):
+        res = yield from coro
+        if __debug__ and DEBUG:
+            log.debug("waiter: cancelling %s", timeout_obj)
+        timeout_obj.coro = None
+        return res
+
+    def timeout_func(timeout_obj):
+        if timeout_obj.coro:
+            if __debug__ and DEBUG:
+                log.debug("timeout_func: cancelling %s", timeout_obj.coro)
+            prev = timeout_obj.coro.pend_throw(TimeoutError())
+            #print("prev pend", prev)
+            if prev is None:
+                pass
+            elif isinstance(prev, int):
+                _event_loop.waitq.remove(prev)
+                _event_loop.call_soon(timeout_obj.coro)
+            else:
+                _event_loop.cancel_io(prev)
+                _event_loop.call_soon(timeout_obj.coro)
+
+    timeout_obj = TimeoutObj(_event_loop.cur_task)
+    _event_loop.call_later_ms(timeout, timeout_func, timeout_obj)
+    return (yield from waiter(coro, timeout_obj))
+
+
+def wait_for(coro, timeout):
+    return wait_for_ms(coro, int(timeout * 1000))
+
+
+def coroutine(f):
+    return f
+
+#
+# The functions below are deprecated in uasyncio, and provided only
+# for compatibility with CPython asyncio
+#
+
+def ensure_future(coro, loop=_event_loop):
+    _event_loop.call_soon(coro)
+    # CPython asyncio incompatibility: we don't return Task object
+    return coro
+
+
+# CPython asyncio incompatibility: Task is a function, not a class (for efficiency)
+def Task(coro, loop=_event_loop):
+    # Same as async()
+    _event_loop.call_soon(coro)
diff --git a/lib/ulogging.py b/lib/ulogging.py
new file mode 100644
index 0000000..cea2de0
--- /dev/null
+++ b/lib/ulogging.py
@@ -0,0 +1,94 @@
+import sys
+
+CRITICAL = 50
+ERROR    = 40
+WARNING  = 30
+INFO     = 20
+DEBUG    = 10
+NOTSET   = 0
+
+_level_dict = {
+    CRITICAL: "CRIT",
+    ERROR: "ERROR",
+    WARNING: "WARN",
+    INFO: "INFO",
+    DEBUG: "DEBUG",
+}
+
+_stream = sys.stderr
+
+class Logger:
+
+    level = NOTSET
+
+    def __init__(self, name):
+        self.name = name
+
+    def _level_str(self, level):
+        l = _level_dict.get(level)
+        if l is not None:
+            return l
+        return "LVL%s" % level
+
+    def setLevel(self, level):
+        self.level = level
+
+    def isEnabledFor(self, level):
+        return level >= (self.level or _level)
+
+    def log(self, level, msg, *args):
+        if level >= (self.level or _level):
+            _stream.write("%s:%s:" % (self._level_str(level), self.name))
+            if not args:
+                print(msg, file=_stream)
+            else:
+                print(msg % args, file=_stream)
+
+    def debug(self, msg, *args):
+        self.log(DEBUG, msg, *args)
+
+    def info(self, msg, *args):
+        self.log(INFO, msg, *args)
+
+    def warning(self, msg, *args):
+        self.log(WARNING, msg, *args)
+
+    def error(self, msg, *args):
+        self.log(ERROR, msg, *args)
+
+    def critical(self, msg, *args):
+        self.log(CRITICAL, msg, *args)
+
+    def exc(self, e, msg, *args):
+        self.log(ERROR, msg, *args)
+        sys.print_exception(e, _stream)
+
+    def exception(self, msg, *args):
+        self.exc(sys.exc_info()[1], msg, *args)
+
+
+_level = INFO
+_loggers = {}
+
+def getLogger(name):
+    if name in _loggers:
+        return _loggers[name]
+    l = Logger(name)
+    _loggers[name] = l
+    return l
+
+def info(msg, *args):
+    getLogger(None).info(msg, *args)
+
+def debug(msg, *args):
+    getLogger(None).debug(msg, *args)
+
+def basicConfig(level=INFO, filename=None, stream=None, format=None):
+    global _level, _stream
+    _level = level
+    if stream:
+        _stream = stream
+    if filename is not None:
+        print("logging.basicConfig: filename arg is not supported")
+    if format is not None:
+        print("logging.basicConfig: format arg is not supported")
diff --git a/lib/utemplate/compiled.py b/lib/utemplate/compiled.py
new file mode 100644
index 0000000..82237a4
--- /dev/null
+++ b/lib/utemplate/compiled.py
@@ -0,0 +1,14 @@
+class Loader:
+
+    def __init__(self, pkg, dir):
+        if dir == ".":
+            dir = ""
+        else:
+            dir = dir.replace("/", ".") + "."
+        if pkg and pkg != "__main__":
+            dir = pkg + "." + dir
+        self.p = dir
+
+    def load(self, name):
+        name = name.replace(".", "_")
+        return __import__(self.p + name, None, None, (name,)).render
diff --git a/lib/utemplate/recompile.py b/lib/utemplate/recompile.py
new file mode 100644
index 0000000..c728c7c
--- /dev/null
+++ b/lib/utemplate/recompile.py
@@ -0,0 +1,21 @@
+# (c) 2014-2020 Paul Sokolovsky. MIT license.
+try:
+    from uos import stat, remove
+except:
+    from os import stat, remove
+from . import source
+
+
+class Loader(source.Loader):
+
+    def load(self, name):
+        o_path = self.pkg_path + self.compiled_path(name)
+        i_path = self.pkg_path + self.dir + "/" + name
+        try:
+            o_stat = stat(o_path)
+            i_stat = stat(i_path)
+            if i_stat[8] > o_stat[8]:
+                # input file is newer, remove output to force recompile
+                remove(o_path)
+        finally:
+            return super().load(name)
diff --git a/lib/utemplate/source.py b/lib/utemplate/source.py
new file mode 100644
index 0000000..429f589
--- /dev/null
+++ b/lib/utemplate/source.py
@@ -0,0 +1,188 @@
+# (c) 2014-2019 Paul Sokolovsky. MIT license.
+from . import compiled
+
+
+class Compiler:
+
+    START_CHAR = "{"
+    STMNT = "%"
+    STMNT_END = "%}"
+    EXPR = "{"
+    EXPR_END = "}}"
+
+    def __init__(self, file_in, file_out, indent=0, seq=0, loader=None):
+        self.file_in = file_in
+        self.file_out = file_out
+        self.loader = loader
+        self.seq = seq
+        self._indent = indent
+        self.stack = []
+        self.in_literal = False
+        self.flushed_header = False
+        self.args = "*a, **d"
+
+    def indent(self, adjust=0):
+        if not self.flushed_header:
+            self.flushed_header = True
+            self.indent()
+            self.file_out.write("def render%s(%s):\n" % (str(self.seq) if self.seq else "", self.args))
+            self.stack.append("def")
+        self.file_out.write("    " * (len(self.stack) + self._indent + adjust))
+
+    def literal(self, s):
+        if not s:
+            return
+        if not self.in_literal:
+            self.indent()
+            self.file_out.write('yield """')
+            self.in_literal = True
+        self.file_out.write(s.replace('"', '\\"'))
+
+    def close_literal(self):
+        if self.in_literal:
+            self.file_out.write('"""\n')
+        self.in_literal = False
+
+    def render_expr(self, e):
+        self.indent()
+        self.file_out.write('yield str(' + e + ')\n')
+
+    def parse_statement(self, stmt):
+        tokens = stmt.split(None, 1)
+        if tokens[0] == "args":
+            if len(tokens) > 1:
+                self.args = tokens[1]
+            else:
+                self.args = ""
+        elif tokens[0] == "set":
+            self.indent()
+            self.file_out.write(stmt[3:].strip() + "\n")
+        elif tokens[0] == "include":
+            if not self.flushed_header:
+                # If there was no other output, we still need a header now
+                self.indent()
+            tokens = tokens[1].split(None, 1)
+            args = ""
+            if len(tokens) > 1:
+                args = tokens[1]
+            if tokens[0][0] == "{":
+                self.indent()
+                # "1" as fromlist param is uPy hack
+                self.file_out.write('_ = __import__(%s.replace(".", "_"), None, None, 1)\n' % tokens[0][2:-2])
+                self.indent()
+                self.file_out.write("yield from _.render(%s)\n" % args)
+                return
+
+            with self.loader.input_open(tokens[0][1:-1]) as inc:
+                self.seq += 1
+                c = Compiler(inc, self.file_out, len(self.stack) + self._indent, self.seq)
+                inc_id = self.seq
+                self.seq = c.compile()
+            self.indent()
+            self.file_out.write("yield from render%d(%s)\n" % (inc_id, args))
+        elif len(tokens) > 1:
+            if tokens[0] == "elif":
+                assert self.stack[-1] == "if"
+                self.indent(-1)
+                self.file_out.write(stmt + ":\n")
+            else:
+                self.indent()
+                self.file_out.write(stmt + ":\n")
+                self.stack.append(tokens[0])
+        else:
+            if stmt.startswith("end"):
+                assert self.stack[-1] == stmt[3:]
+                self.stack.pop(-1)
+            elif stmt == "else":
+                assert self.stack[-1] == "if"
+                self.indent(-1)
+                self.file_out.write("else:\n")
+            else:
+                assert False
+
+    def parse_line(self, l):
+        while l:
+            start = l.find(self.START_CHAR)
+            if start == -1:
+                self.literal(l)
+                return
+            self.literal(l[:start])
+            self.close_literal()
+            sel = l[start + 1]
+            #print("*%s=%s=" % (sel, EXPR))
+            if sel == self.STMNT:
+                end = l.find(self.STMNT_END)
+                assert end > 0
+                stmt = l[start + len(self.START_CHAR + self.STMNT):end].strip()
+                self.parse_statement(stmt)
+                end += len(self.STMNT_END)
+                l = l[end:]
+                if not self.in_literal and l == "\n":
+                    break
+            elif sel == self.EXPR:
+    #            print("EXPR")
+                end = l.find(self.EXPR_END)
+                assert end > 0
+                expr = l[start + len(self.START_CHAR + self.EXPR):end].strip()
+                self.render_expr(expr)
+                end += len(self.EXPR_END)
+                l = l[end:]
+            else:
+                self.literal(l[start])
+                l = l[start + 1:]
+
+    def header(self):
+        self.file_out.write("# Autogenerated file\n")
+
+    def compile(self):
+        self.header()
+        for l in self.file_in:
+            self.parse_line(l)
+        self.close_literal()
+        return self.seq
+
+
+class Loader(compiled.Loader):
+
+    def __init__(self, pkg, dir):
+        super().__init__(pkg, dir)
+        self.dir = dir
+        if pkg == "__main__":
+            # if pkg isn't really a package, don't bother to use it
+            # it means we're running from "filesystem directory", not
+            # from a package.
+            pkg = None
+
+        self.pkg_path = ""
+        if pkg:
+            p = __import__(pkg)
+            if isinstance(p.__path__, str):
+                # uPy
+                self.pkg_path = p.__path__
+            else:
+                # CPy
+                self.pkg_path = p.__path__[0]
+            self.pkg_path += "/"
+
+    def input_open(self, template):
+        path = self.pkg_path + self.dir + "/" + template
+        return open(path)
+
+    def compiled_path(self, template):
+        return self.dir + "/" + template.replace(".", "_") + ".py"
+
+    def load(self, name):
+        try:
+            return super().load(name)
+        except (OSError, ImportError):
+            pass
+
+        compiled_path = self.pkg_path + self.compiled_path(name)
+
+        f_in = self.input_open(name)
+        f_out = open(compiled_path, "w")
+        c = Compiler(f_in, f_out, loader=self)
+        c.compile()
+        f_in.close()
+        f_out.close()
+        return super().load(name)
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..e276bce
--- /dev/null
+++ b/main.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+main script, do your stuff here, similar to the loop() function on Arduino
+"""
+
+from wifi_manager import WiFiManager
+
+if __name__ == '__main__':
+    wm = WiFiManager()
+    result = wm.load_and_connect()
+    wm.start_config()
+
+    # if result is False:
+    #     wm.start_config()
+    # else:
+    #     print('Successfully connected to a network :)')
diff --git a/primitives/__init__.py b/primitives/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/primitives/message.py b/primitives/message.py
new file mode 100644
index 0000000..61a34ae
--- /dev/null
+++ b/primitives/message.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+# message.py
+# https://github.com/peterhinch/micropython-async/blob/a87bda1b716090da27fd288cc8b19b20525ea20c/v3/primitives/message.py
+# Now uses ThreadSafeFlag for efficiency
+
+# Copyright (c) 2018-2021 Peter Hinch
+# Released under the MIT License (MIT) - see LICENSE file
+
+# Usage:
+# from primitives.message import Message
+
+try:
+    import uasyncio as asyncio
+except ImportError:
+    import asyncio
+
+# A coro waiting on a message issues await message
+# A coro or hard/soft ISR raising the message issues.set(payload)
+# .clear() should be issued by at least one waiting task and before
+# next event.
+
+
+class Message(asyncio.ThreadSafeFlag):
+    def __init__(self, _=0):
+        # Arg: poll interval. Compatibility with old code.
+        self._evt = asyncio.Event()
+        self._data = None     # Message
+        self._state = False   # Ensure only one task waits on ThreadSafeFlag
+        self._is_set = False  # For .is_set()
+        super().__init__()
+
+    def clear(self):
+        # At least one task must call clear when scheduled
+        self._state = False
+        self._is_set = False
+
+    def __iter__(self):
+        yield from self.wait()
+        return self._data
+
+    async def wait(self):
+        if self._state:
+            # A task waits on ThreadSafeFlag
+            await self._evt.wait()  # Wait on event
+        else:
+            # First task to wait
+            self._state = True
+            # Ensure other tasks see updated ._state before they wait
+            await asyncio.sleep_ms(0)
+            await super().wait()  # Wait on ThreadSafeFlag
+            self._evt.set()
+            self._evt.clear()
+
+        return self._data
+
+    def set(self, data=None):
+        # Can be called from a hard ISR
+        self._data = data
+        self._is_set = True
+        super().set()
+
+    def is_set(self):
+        return self._is_set
+
+    def value(self):
+        return self._data
diff --git a/primitives/queue.py b/primitives/queue.py
new file mode 100644
index 0000000..a8ed406
--- /dev/null
+++ b/primitives/queue.py
@@ -0,0 +1,73 @@
+# queue.py: adapted from uasyncio V2
+
+# Copyright (c) 2018-2020 Peter Hinch
+# Released under the MIT License (MIT) - see LICENSE file
+
+# Code is based on Paul Sokolovsky's work.
+# This is a temporary solution until uasyncio V3 gets an efficient official version
+
+import uasyncio as asyncio
+
+
+# Exception raised by get_nowait().
+class QueueEmpty(Exception):
+    pass
+
+
+# Exception raised by put_nowait().
+class QueueFull(Exception):
+    pass
+
+
+class Queue:
+    def __init__(self, maxsize: int = 0):
+        self.maxsize = maxsize
+        self._queue = []
+        self._evput = asyncio.Event()  # Triggered by put, tested by get
+        self._evget = asyncio.Event()  # Triggered by get, tested by put
+
+    def _get(self):
+        self._evget.set()  # Schedule all tasks waiting on get
+        self._evget.clear()
+        return self._queue.pop(0)
+
+    async def get(self):  #  Usage: item = await queue.get()
+        while self.empty():  # May be multiple tasks waiting on get()
+            # Queue is empty, suspend task until a put occurs
+            # 1st of N tasks gets, the rest loop again
+            await self._evput.wait()
+        return self._get()
+
+    def get_nowait(self):  # Remove and return an item from the queue.
+        # Return an item if one is immediately available, else raise QueueEmpty.
+        if self.empty():
+            raise QueueEmpty()
+        return self._get()
+
+    def _put(self, val):
+        self._evput.set()  # Schedule tasks waiting on put
+        self._evput.clear()
+        self._queue.append(val)
+
+    async def put(self, val):  # Usage: await queue.put(item)
+        while self.full():
+            # Queue full
+            await self._evget.wait()
+            # Task(s) waiting to get from queue, schedule first Task
+        self._put(val)
+
+    def put_nowait(self, val):  # Put an item into the queue without blocking.
+        if self.full():
+            raise QueueFull()
+        self._put(val)
+
+    def qsize(self):  # Number of items in the queue.
+        return len(self._queue)
+
+    def empty(self):  # Return True if the queue is empty, False otherwise.
+        return len(self._queue) == 0
+
+    def full(self):  # Return True if there are maxsize items in the queue.
+        # Note: if the Queue was initialized with maxsize=0 (the default) or
+        # any negative number, then full() is never True.
+        return self.maxsize > 0 and self.qsize() >= self.maxsize
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..3e6a60d
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+esptool
+rshell>=0.0.30,<1.0.0
\ No newline at end of file
diff --git a/simulation/.coveragerc b/simulation/.coveragerc
new file mode 100644
index 0000000..94935d6
--- /dev/null
+++ b/simulation/.coveragerc
@@ -0,0 +1,31 @@
+# Configuration for python coverage package
+[run]
+branch = True
+omit =
+    */tests/*,
+    .venv/*,
+    .idea/*,
+    setup*,
+    .eggs/*
+    .tox/*,
+    build/*,
+    dist/*,
+    version.py
+
+[report]
+include = src/*
+# Regexes for lines to exclude from consideration
+
+ignore_errors = True
+
+[html]
+directory = reports/coverage/html
+skip_empty = True
+
+[xml]
+output = reports/coverage/coverage.xml
+
+[json]
+output  = reports/coverage/coverage.json
+pretty_print = True
+show_contexts = True
\ No newline at end of file
diff --git a/simulation/.flake8 b/simulation/.flake8
new file mode 100644
index 0000000..02bb191
--- /dev/null
+++ b/simulation/.flake8
@@ -0,0 +1,102 @@
+# Configuration for flake8 analysis
+[flake8]
+# Set the maximum length that any line (with some exceptions) may be.
+max-line-length = 78
+
+# Set the maximum length that a comment or docstring line may be.
+max-doc-length = 78
+
+# Set the maximum allowed McCabe complexity value for a block of code.
+# max-complexity = 15
+
+# Specify a list of codes to ignore.
+# D107: Missing docstring in __init__
+# D400: First line should end with a period
+# W504: line break after binary operator -> Cannot break line with a long pathlib Path
+# D204: 1 blank line required after class docstring
+ignore = D107, D400, W504, D204
+
+# Specify a list of mappings of files and the codes that should be ignored for the entirety of the file.
+per-file-ignores =
+    tests/*:D101,D102,D104
+
+# Provide a comma-separated list of glob patterns to exclude from checks.
+exclude =
+    # No need to traverse our git directory
+    .git,
+    # Python virtual environments
+    .venv,
+    # There's no value in checking cache directories
+    __pycache__,
+    # The conf file is mostly autogenerated, ignore it
+    docs/source/conf.py,
+    # This contains our built documentation
+    build,
+    # This contains builds that we don't want to check
+    dist,
+    # We don't use __init__.py for scripts
+    __init__.py
+
+# Provide a comma-separated list of glob patterns to add to the list of excluded ones.
+# extend-exclude =
+#     legacy/,
+#     vendor/
+
+# Provide a comma-separate list of glob patterns to include for checks.
+# filename =
+#     example.py,
+#     another-example*.py
+
+# Enable PyFlakes syntax checking of doctests in docstrings.
+doctests = False
+
+# Specify which files are checked by PyFlakes for doctest syntax.
+# include-in-doctest =
+#     dir/subdir/file.py,
+#     dir/other/file.py
+
+# Specify which files are not to be checked by PyFlakes for doctest syntax.
+# exclude-from-doctest =
+#     tests/*
+
+# Enable off-by-default extensions.
+# enable-extensions =
+#     H111,
+#     G123
+
+# If True, report all errors, even if it is on the same line as a # NOQA comment.
+disable-noqa = False
+
+# Specify the number of subprocesses that Flake8 will use to run checks in parallel.
+jobs = auto
+
+# Also print output to stdout if output-file has been configured.
+tee = True
+
+# Count the number of occurrences of each error/warning code and print a report.
+statistics = True
+
+# Print the total number of errors.
+count = True
+
+# Print the source code generating the error/warning in question.
+show-source = False
+
+# Decrease the verbosity of Flake8’s output. Each time you specify it, it will print less and less information.
+quiet = 0
+
+# Select the formatter used to display errors to the user.
+format = pylint
+
+[pydocstyle]
+# choose the basic list of checked errors by specifying an existing convention. Possible conventions: pep257, numpy, google.
+convention = pep257
+
+# check only files that exactly match <pattern> regular expression
+# match = (?!test_).*\.py
+
+# search only dirs that exactly match <pattern> regular expression
+# match_dir = [^\.].*
+
+# ignore any functions or methods that are decorated by a function with a name fitting the <decorators> regular expression.
+# ignore_decorators =
\ No newline at end of file
diff --git a/simulation/LICENSE.txt b/simulation/LICENSE.txt
new file mode 100644
index 0000000..f721577
--- /dev/null
+++ b/simulation/LICENSE.txt
@@ -0,0 +1,1 @@
+Empty
\ No newline at end of file
diff --git a/simulation/README.md b/simulation/README.md
new file mode 100644
index 0000000..6ee66d6
--- /dev/null
+++ b/simulation/README.md
@@ -0,0 +1,204 @@
+# ESP WiFi Manager Simulation
+
+Simple Flask Server to create ESP WiFi Manager webpages
+
+
+-----------------------
+
+
+## Setup
+
+Install all required packages with the following command in a virtual
+environment to avoid any conflicts with other packages installed on your local
+system.
+
+```bash
+python3 -m venv .venv
+source .venv/bin/activate
+
+pip install -r requirements.txt
+```
+
+### Windows
+
+On Windows the package `pycryptodome>=3.14.0,<4` shall be used instead of
+`pycrypto>=2.6.1,<3`
+
+## Bootstrap
+
+Simulation webpages use [bootstrap 3.4][ref-bootstrap-34].
+
+## Usage
+
+Run the simulation of the ESP WiFi Manager **after** activating the virtual
+environment of the [Setup section](#setup)
+
+```bash
+sh run.sh
+```
+
+Open [`http://127.0.0.1:5000/`](http://127.0.0.1:5000/){:target="_blank"} in a browser
+
+## Unittests
+
+### General
+
+The unittests are covering a wide range of the simulation interface and use
+the `nose2` package to do so.
+
+### Usage
+
+Run unittests for all or a single class or even for a single function as shown
+onwards
+
+### Prepare
+
+To create the necessary coverage report directories call the
+[`prepare_test.sh`](prepare_test.sh) script
+
+```bash
+cd ./
+
+# prepare coverage directories
+sh prepare_test.sh
+
+# run all unittests
+nose2 --config tests/unittest.cfg -v tests
+
+# run all unittests of the class WiFiManager
+nose2 --config tests/unittest.cfg -v tests.test_wifi_manager
+
+# run the unittest of the "load_and_connect" function of WiFiManager class
+nose2 --config tests/unittest.cfg -v tests.test_wifi_manager.TestWiFiManager.test_load_and_connect
+```
+
+### Available tests
+
+#### Absolute truth
+
+Test absolute truth of the unittesting framework itself.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_absolute_truth.TestAbsoluteTruth
+```
+
+#### Generic Helper
+
+Test [`generic helper`][ref-generic-helper-test] implementation
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_generic_helper.TestGenericHelper
+```
+
+#### LED Helper
+
+Test [`led helper`][ref-led-helper-test] implementation. Currently only
+the used Neopixel part of it.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_led_helper.TestNeopixel
+```
+
+#### Fakes
+
+Unittests of all Micropython fake modules.
+
+##### Machine
+
+Test [`fake machine`][ref-machine-test] implementations.
+
+###### Machine
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_machine.TestMachine
+```
+
+###### Pin
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_pin.TestPin
+```
+
+###### RTC
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_rtc.TestRTC
+```
+
+##### Neopixel
+
+Test [`fake neopixel`][ref-neopixel-test] implementations.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_neopixel.TestNeoPixel
+```
+
+##### Network
+
+Test [`fake network`][ref-network-test] implementations.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_network.TestNetworkHelper
+```
+
+##### Primitives
+
+Test [`message`][ref-message-test] implementations.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_message.TestMessage
+```
+
+#### Path Helper
+
+Test [`path helper`][ref-path-helper-test] implementation.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_path_helper.TestPathHelper
+```
+
+#### Time Helper
+
+Test [`time helper`][ref-time-helper-test] implementation.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_time_helper.TestTimeHelper
+```
+
+#### WiFi Helper
+
+Test [`wifi helper`][ref-wifi-helper-test] implementation.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_wifi_helper.TestWifiHelper
+```
+
+#### WiFi Manager
+
+Test [`wifi manager`][ref-wifi-manager-test] implementation.
+
+```bash
+nose2 --config tests/unittest.cfg -v tests.test_wifi_manager.TestWiFiManager
+```
+
+<!-- Links -->
+<!-- Generic -->
+[ref-bootstrap-34]: https://getbootstrap.com/docs/3.4/getting-started/#download
+
+<!-- Unittests -->
+[ref-generic-helper-test]: src/generic_helper/generic_helper.py
+[ref-led-helper-test]: src/led_helper/led_helper.py
+
+<!-- Unittests Fakes -->
+[ref-machine-test]: src/machine
+[ref-neopixel-test]: src/led_helper/neopixel.py
+[ref-network-test]: src/wifi_helper/network.py
+
+<!-- Unittest primitives -->
+[ref-message-test]: src/generic_helper/message.py
+
+<!-- Unittests custom modules -->
+[ref-path-helper-test]: src/path_helper/path_helper.py
+[ref-time-helper-test]: src/time_helper/time_helper.py
+[ref-wifi-helper-test]: src/wifi_helper/wifi_helper.py
+[ref-wifi-manager-test]: src/wifi_manager/wifi_manager.py
diff --git a/simulation/requirements.txt b/simulation/requirements.txt
new file mode 100644
index 0000000..71b02b0
--- /dev/null
+++ b/simulation/requirements.txt
@@ -0,0 +1,6 @@
+flask>=2.0.2,<3
+jinja2>=3.0.2,<4
+PyYAML>=5.4.1,<6
+nose2>=0.10.0,<1
+netifaces>=0.11.0,<1
+pycrypto>=2.6.1,<3
diff --git a/simulation/run.sh b/simulation/run.sh
new file mode 100644
index 0000000..4a82db5
--- /dev/null
+++ b/simulation/run.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+# title         :run.sh
+# description   :Start Flask server
+# author        :brainelectronics
+# date          :20220202
+# version       :0.1.0
+# usage         :sh run.sh
+# notes         :None
+# bash_version  :3.2.53(1)-release
+#=============================================================================
+
+cd src
+python run_simulation.py
diff --git a/simulation/setup.cfg b/simulation/setup.cfg
new file mode 100644
index 0000000..1c3cb9f
--- /dev/null
+++ b/simulation/setup.cfg
@@ -0,0 +1,204 @@
+[metadata]
+# https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html
+
+# This includes the license file(s) in the wheel.
+# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file
+license_files = LICENSE.txt
+
+
+# This is the name of your project. The first time you publish this
+# package, this name will be registered for you. It will determine how
+# users can install this project, e.g.:
+#
+# $ pip install PACKAGE-NAME
+#
+# There are some restrictions on what makes a valid project name
+# specification here:
+# https://packaging.python.org/specifications/core-metadata/#name
+name = wifi-mananger-simulation
+
+# For a discussion on single-sourcing the version across setup.py and
+# the project code, see
+# https://packaging.python.org/en/latest/single_source_version.html
+# Version will be automatically calculated by setuptools_scm
+# version =
+
+# This is a one-line description or tagline of what your project does.
+# This corresponds to the "Summary" metadata field:
+# https://packaging.python.org/specifications/core-metadata/#summary
+description = WiFi Manager Simulation
+
+# This is an optional longer description of your project that represents
+# the body of text which users will see when they visit PyPI.
+#
+# Often, this is the same as your README, so you can just read it in
+# from that file directly (as we have already done above)
+#
+# This field corresponds to the "Description" metadata field:
+# https://packaging.python.org/specifications/core-metadata/#description-optional
+long_description = file: README.md
+
+# Denotes that our long_description is in Markdown; valid values are
+# text/plain, text/x-rst, and text/markdown
+#
+# Optional if long_description is written in reStructuredText (rst) but
+# required for plain-text or Markdown; if unspecified, "applications
+# should attempt to render [the long_description] as
+# text/x-rst; charset=UTF-8 and fall back to text/plain if it is not
+# valid rst" (see link below)
+#
+# This field corresponds to the "Description-Content-Type" metadata field:
+# https://packaging.python.org/specifications/core-metadata/#description-content-type-optional
+long_description_content_type = text/markdown
+
+# This should be a valid link to your project's main homepage.
+#
+# This field corresponds to the "Home-Page" metadata field:
+# https://packaging.python.org/specifications/core-metadata/#home-page-optional
+url = https://github.com/brainelectronics
+
+# List additional URLs that are relevant to your project as a dict.
+#
+# This field corresponds to the "Project-URL" metadata fields:
+# https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use
+#
+# Examples listed include a pattern for specifying where the package
+# tracks issues, where the source is hosted, where to say thanks to the
+# package maintainers, and where to support the project financially.
+# The key is what's used to render the link text on PyPI.
+project_urls =
+    API Documentation = https://github.com/brainelectronics
+
+# This should be your name or the name of the organization which owns
+# the project.
+author = Jonas Scharpf (brainelectronics)
+maintainer = Jonas Scharpf (brainelectronics)
+
+# This should be a valid email address corresponding to the author
+# listed above.
+author_email = 'info@brainelectronics.de'
+maintainer_email = 'info@brainelectronics.de'
+
+# Classifiers help users find your project by categorizing it.
+#
+# For a list of valid classifiers, see https://pypi.org/classifiers/
+classifiers =
+    # How mature is this project? Common values are
+    #   3 - Alpha
+    #   4 - Beta
+    #   5 - Production/Stable
+    Development Status :: 3 - Alpha
+
+    # Indicate who your project is intended for
+    Intended Audience :: Developers
+    Topic :: Software Development :: Build Tools
+
+    # Pick your license as you wish
+    # License :: OSI Approved :: MIT License,
+
+    Operating System :: OS Independent
+
+    # Specify the Python versions you support here. In particular, ensure
+    # that you indicate you support Python 3. These classifiers are *not*
+    # checked by 'pip install'. See instead 'python_requires' below.
+    'Programming Language :: Python :: 3',
+    'Programming Language :: Python :: 3.8',
+    'Programming Language :: Python :: 3.9',
+    'Programming Language :: Python :: 3 :: Only',
+
+# This field adds keywords for your project which will appear on the
+# project page. What does your project relate to?
+#
+# Note that this is a list of additional keywords, separated
+# by commas, to be used to assist searching for the distribution in a
+# larger catalog.
+keywords = 'wifi, manager, simulation, esp32'
+
+[options]
+# When your source code is in a subdirectory under the project root, e.g.
+# `src/`, it is necessary to specify the `package_dir` argument.
+package_dir =
+    = src
+
+# You can just specify package directories manually here if your project is
+# simple. Or you can use find_packages().
+#
+# Alternatively, if you just want to distribute a single Python file, use
+# the `py_modules` argument instead as follows, which will expect a file
+# called `my_module.py` to exist:
+#
+#   py_modules=["my_module"],
+#
+packages = find:
+
+# Specify which Python versions you support. In contrast to the
+# 'Programming Language' classifiers above, 'pip install' will check this
+# and refuse to install the project if the version does not match. See
+# https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
+python_requires = >=3.8, <4
+
+setup_requires = setuptools_scm
+
+
+# This field lists other packages that your project depends on to run.
+# Any package you put here will be installed by pip when your project is
+# installed, so they must be valid existing projects.
+#
+# For an analysis of "install_requires" vs pip's requirements files see:
+# https://packaging.python.org/en/latest/requirements.html
+# install_requires =
+#     GitPython >= 3.1.14,<4
+
+# List additional groups of dependencies here (e.g. development
+# dependencies). Users will be able to install these using the "extras"
+# syntax, for example:
+#
+#   $ pip install sampleproject[dev]
+#
+# Similar to `install_requires` above, these must be valid existing
+# projects.
+[options.extras_require]
+dev =
+    flake8>=3.9.0,<4
+    flake8-docstrings>=1.6.0,<2
+    nose2>=0.9.2,<1
+    tox>=3.23.0,<4
+doc =
+    sphinx >= 3.5.2,<4
+    sphinx_rtd_theme
+    m2rr >= 0.2.3,<1
+    setuptools_scm
+
+[options.packages.find]
+where = src
+
+# [options.package_data]
+# If there are data files included in your packages that need to be
+# installed, specify them here.
+# * = *.txt, *.rst
+# hello = *.msg
+
+[options.data_files]
+# Although 'package_data' is the preferred approach, in some case you may
+# need to place data files outside of your packages. See:
+# http://docs.python.org/distutils/setupscript.html#installing-additional-files
+#
+# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
+# /etc/my_package =
+#     site.d/00_default.conf
+#     host.d/00_default.conf
+# data = data/img/logo.png, data/svg/icon.svg
+
+[options.entry_points]
+# To provide executable scripts, use entry points in preference to the
+# "scripts" keyword. Entry points provide cross-platform support and allow
+# `pip` to create the appropriate form of executable for the target
+# platform.
+#
+# For example, the following would provide a command called `sample` which
+# executes the function `main` from this package when invoked:
+# console_scripts =
+    # command = folder-inside-options-packages-find.file_name:function
+    # test by installing and calling a valid command, e.g. --help
+    # $ python setup.py develop
+    # $ command --help
diff --git a/simulation/setup.py b/simulation/setup.py
new file mode 100644
index 0000000..7787600
--- /dev/null
+++ b/simulation/setup.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+
+"""
+A setuptools based setup module.
+
+See:
+https://packaging.python.org/guides/distributing-packages-using-setuptools/
+https://github.com/pypa/sampleproject
+"""
+
+# Always prefer setuptools over distutils
+from setuptools import setup
+import pathlib
+import shutil
+
+# Arguments marked as "Required" below must be included for upload to PyPI.
+# Fields marked as "Optional" may be commented out.
+
+# Clean up previous builds
+if pathlib.Path('build').is_dir():
+    shutil.rmtree('build', ignore_errors=True)
+if pathlib.Path('dist').is_dir():
+    shutil.rmtree('dist', ignore_errors=True)
+
+setup(
+    # Versions should comply with PEP 440:
+    # https://www.python.org/dev/peps/pep-0440/
+    #
+    # For a discussion on single-sourcing the version across setup.py and the
+    # project code, see
+    # https://packaging.python.org/en/latest/single_source_version.html
+    # version='2.0.0',  # Required
+    use_scm_version=True,
+)
diff --git a/simulation/src/__init__.py b/simulation/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/simulation/src/generic_helper/__init__.py b/simulation/src/generic_helper/__init__.py
new file mode 100644
index 0000000..162acd3
--- /dev/null
+++ b/simulation/src/generic_helper/__init__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+from .generic_helper import GenericHelper
+from .message import Message
diff --git a/simulation/src/generic_helper/generic_helper.py b/simulation/src/generic_helper/generic_helper.py
new file mode 100644
index 0000000..0be1e6b
--- /dev/null
+++ b/simulation/src/generic_helper/generic_helper.py
@@ -0,0 +1,293 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Generic helper
+
+Collection of helper functions used in other modules
+"""
+
+import gc
+import json
+import logging
+import os
+import random
+import sys
+
+from typing import Optional, Union
+
+
+class GenericHelper(object):
+    """docstring for GenericHelper"""
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def create_logger(logger_name: Optional[str] = None) -> logging.Logger:
+        """
+        Create a logger.
+
+        :param      logger_name:  The logger name
+        :type       logger_name:  str, optional
+
+        :returns:   Configured logger
+        :rtype:     logging.Logger
+        """
+        custom_format = '[%(asctime)s] [%(levelname)-8s] [%(filename)-15s @'\
+                        ' %(funcName)-15s:%(lineno)4s] %(message)s'
+
+        # configure logging
+        logging.basicConfig(level=logging.INFO,
+                            format=custom_format,
+                            stream=sys.stdout)
+
+        if logger_name and (isinstance(logger_name, str)):
+            logger = logging.getLogger(logger_name)
+        else:
+            logger = logging.getLogger(__name__)
+
+        # set the logger level to DEBUG if specified differently
+        logger.setLevel(logging.DEBUG)
+
+        return logger
+
+    @staticmethod
+    def set_level(logger: logging.Logger, level: str) -> None:
+        """
+        Set the level of a logger.
+
+        :param      logger:  The logger to set the level
+        :type       logger:  logging.Logger
+        :param      level:   The new level
+        :type       level:   str
+        """
+        if level.lower() == 'debug':
+            logger.setLevel(level=logging.DEBUG)
+        elif level.lower() == 'info':
+            logger.setLevel(level=logging.DEBUG)
+        elif level.lower() == 'warning':
+            logger.setLevel(level=logging.WARNING)
+        elif level.lower() == 'error':
+            logger.setLevel(level=logging.ERROR)
+        elif level.lower() == 'critical':
+            logger.setLevel(level=logging.CRITICAL)
+
+    @staticmethod
+    def set_logger_verbose_level(logger: logging.Logger,
+                                 verbose_level: int,
+                                 debug_output: bool):
+        """
+        Set the logger verbose level and debug output
+
+        :param      logger:         The logger to apply the settings to
+        :type       logger:         logging.Logger
+        :param      verbose_level:  The verbose level
+        :type       verbose_level:  int
+        :param      debug_output:   The debug mode
+        :type       debug_output:   bool
+        """
+        LOG_LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
+        LOG_LEVELS = LOG_LEVELS[::-1]
+
+        if verbose_level is None:
+            if not debug_output:
+                # disable the logger
+                logger.disabled = True
+        else:
+            log_level = min(len(LOG_LEVELS) - 1, max(verbose_level, 0))
+            log_level_name = LOG_LEVELS[log_level]
+
+            # set the level of the logger
+            logger.setLevel(log_level_name)
+
+    @staticmethod
+    def get_random_value(lower: int = 0, upper: int = 255) -> int:
+        """
+        Get a random value within the given range
+
+        :param      lower:  The lower boundary
+        :type       lower:  int, optional
+        :param      upper:  The upper boundary, inclusive, default 255
+        :type       upper:  int, optional
+
+        :returns:   The random value.
+        :rtype:     int
+        """
+        return random.randint(lower, upper)
+
+    @staticmethod
+    def df(path: str = '//', unit: Optional[str] = None) -> Union[int, str]:
+        """
+        Get free disk space
+
+        :param      path:    Path to obtain informations
+        :type       path:    str, optional
+        :param      unit:    Unit of returned size [None, 'byte', 'kB', 'MB']
+        :type       unit:    str, optional
+
+        :returns:   Available disk space in byte or as string in kB or MB
+        :rtype:     int or str
+        """
+        info = os.statvfs(path)
+
+        result = -1
+
+        if unit is None:
+            result = info[0] * info[3]
+        elif unit.lower() == 'byte':
+            result = ('{} byte'.format((info[0] * info[3])))
+        elif unit.lower() == 'kb':
+            result = ('{0:.3f} kB'.format((info[0] * info[3]) / 1024))
+        elif unit.lower() == 'mb':
+            result = ('{0:.3f} MB'.format((info[0] * info[3]) / 1048576))
+
+        return result
+
+    @staticmethod
+    def get_free_memory() -> dict:
+        """
+        Get free memory (RAM)
+
+        :param      update:  Flag to collect latest informations
+        :type       update:  bool, optional
+
+        :returns:   Informations about system RAM
+        :rtype:     dict
+        """
+        gc.collect()
+        free = gc.mem_free()
+        allocated = gc.mem_alloc()
+        total = free + allocated
+
+        if total:
+            percentage = '{0:.2f}%'.format((free / total) * 100)
+        else:
+            percentage = '100.00%'
+
+        memory_stats = {'free': free,
+                        'total': total,
+                        'percentage': percentage}
+
+        return memory_stats
+
+    @staticmethod
+    def free(full: bool = False) -> Union[int, str]:
+        """
+        Get detailed informations about free RAM
+
+        :param      full:    Flag to return str with total, free, percentage
+        :type       full:    bool, optional
+
+        :returns:   Informations, percentage by default
+        :rtype:     int or str
+        """
+        memory_stats = GenericHelper.get_free_memory()
+
+        if full is False:
+            return memory_stats['percentage']
+        else:
+            return ('Total: {0:.1f} kB, Free: {1:.2f} kB ({2})'.
+                    format(memory_stats['total'] / 1024,
+                           memory_stats['free'] / 1024,
+                           memory_stats['percentage']))
+
+    @staticmethod
+    def str_to_dict(data: str) -> dict:
+        """
+        Convert string to dictionary
+
+        :param      data:  The data
+        :type       data:  str
+
+        :returns:   Dictionary of string
+        :rtype:     dict
+        """
+        return json.loads(data.replace("'", "\""))
+
+    @staticmethod
+    def save_json(data: dict, path: str, mode: str = 'w') -> None:
+        """
+        Save data as JSON file.
+
+        :param      data:  The data
+        :type       data:  dict
+        :param      path:  The path to the JSON file
+        :type       path:  str
+        :param      mode:  The mode of file operation
+        :type       mode:  str, optional
+        """
+        with open(path, mode) as file:
+            json.dump(data, file)
+
+    @staticmethod
+    def load_json(path: str, mode: str = 'r') -> dict:
+        """
+        Load data from JSON file.
+
+        :param      path:  The path to the JSON file
+        :type       path:  str
+        :param      mode:  The mode of file operation
+        :type       mode:  str, optional
+
+        :returns:   Loaded data
+        :rtype:     dict
+        """
+        read_data = dict()
+        with open(path, mode) as file:
+            read_data = json.load(file)
+
+        return read_data
+
+    @staticmethod
+    def save_file(data: str, path: str, mode: str = 'wb') -> None:
+        """
+        Save data to a file.
+
+        :param      data:  The data
+        :type       data:  str
+        :param      path:  The path to the file
+        :type       path:  str
+        :param      mode:  The mode of file operation
+        :type       mode:  str, optional
+        """
+        # save to file as binary by default
+        with open(path, mode) as file:
+            file.write(data)
+
+    @staticmethod
+    def load_file(path: str, mode: str = 'rb') -> str:
+        """
+        Wrapper for read_file.
+
+        :param      path:  The path to the file to read
+        :type       path:  str
+
+        :returns:   The raw file content.
+        :rtype:     str
+        :param      mode:  The mode of file operation
+        :type       mode:  str, optional
+
+        :returns:   Content of file
+        :rtype:     str
+        """
+        return GenericHelper.read_file(path=path, mode=mode)
+
+    @staticmethod
+    def read_file(path: str, mode: str = 'rb') -> str:
+        """
+        Read file content.
+
+        :param      path:  The path to the file to read
+        :type       path:  str
+        :param      mode:  The mode of file operation
+        :type       mode:  str, optional
+
+        :returns:   The raw file content.
+        :rtype:     str
+        """
+        read_data = ""
+
+        with open(path, mode) as file:
+            read_data = file.read()
+
+        return read_data
diff --git a/simulation/src/generic_helper/message.py b/simulation/src/generic_helper/message.py
new file mode 100755
index 0000000..6faa7d4
--- /dev/null
+++ b/simulation/src/generic_helper/message.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+# message.py
+# https://github.com/peterhinch/micropython-async/blob/a87bda1b716090da27fd288cc8b19b20525ea20c/v3/primitives/message.py
+# Now uses ThreadSafeFlag for efficiency
+
+# Copyright (c) 2018-2021 Peter Hinch
+# Released under the MIT License (MIT) - see LICENSE file
+
+# Usage:
+# from primitives.message import Message
+
+try:
+    import uasyncio as asyncio
+except ImportError:
+    import asyncio
+
+# A coro waiting on a message issues await message
+# A coro or hard/soft ISR raising the message issues.set(payload)
+# .clear() should be issued by at least one waiting task and before
+# next event.
+
+
+# class Message(asyncio.ThreadSafeFlag):
+class Message(object):
+    def __init__(self, _=0):
+        # Arg: poll interval. Compatibility with old code.
+        self._evt = asyncio.Event()
+        self._data = None     # Message
+        self._state = False   # Ensure only one task waits on ThreadSafeFlag
+        self._is_set = False  # For .is_set()
+        # super().__init__()
+
+    def clear(self):
+        # At least one task must call clear when scheduled
+        self._state = False
+        self._is_set = False
+
+    def __iter__(self):
+        yield from self.wait()
+        return self._data
+
+    async def wait(self):
+        if self._state:
+            # A task waits on ThreadSafeFlag
+            await self._evt.wait()  # Wait on event
+        else:
+            # First task to wait
+            self._state = True
+            # Ensure other tasks see updated ._state before they wait
+            await asyncio.sleep_ms(0)
+            await super().wait()  # Wait on ThreadSafeFlag
+            self._evt.set()
+            self._evt.clear()
+
+        return self._data
+
+    def set(self, data=None):
+        # Can be called from a hard ISR
+        self._data = data
+        self._is_set = True
+        # super().set()
+
+    def is_set(self):
+        return self._is_set
+
+    def value(self):
+        return self._data
diff --git a/simulation/src/led_helper/__init__.py b/simulation/src/led_helper/__init__.py
new file mode 100755
index 0000000..f14827e
--- /dev/null
+++ b/simulation/src/led_helper/__init__.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+from .led_helper import Neopixel
+from .neopixel import NeoPixel
diff --git a/simulation/src/led_helper/led_helper.py b/simulation/src/led_helper/led_helper.py
new file mode 100755
index 0000000..336eb8e
--- /dev/null
+++ b/simulation/src/led_helper/led_helper.py
@@ -0,0 +1,429 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Mock LED operations of a Micropython board"""
+
+from . import neopixel
+from machine import Pin
+import _thread
+import time
+
+from typing import Union
+
+
+class Neopixel(object):
+    """docstring for Neopixel"""
+    def __init__(self, neopixel_pin: int = 27, neopixels: int = 1) -> None:
+        """
+        Initialize Neopixel.
+
+        Default Neopixel color is red with intensity of 30/255
+
+        :param      neopixel_pin:   Pin of Neopixel LED
+        :type       neopixel_pin:   int, optional
+        :param      neopixels:      Number of Neopixel LEDs
+        :type       neopixels:      int, optional
+        """
+        neopixel_pin = Pin(neopixel_pin, Pin.OUT)
+        self._neopixel_amount = neopixels
+        self.pixel = neopixel.NeoPixel(pin=neopixel_pin, n=neopixels)
+
+        # 30/255 as default intensity to aviod getting blinded by the lights
+        self._colors = {
+            'red': [30, 0, 0],
+            'green': [0, 30, 0],
+            'blue': [0, 0, 30],
+            # other colors may need adjustment as they are just technically
+            # correct, but maybe not colorwise
+            'yellow': [30, 30, 0],
+            'cyan': [0, 30, 30],
+            'magenta': [30, 0, 30],
+            'white': [30, 30, 30],
+            'maroon': [30 // 2, 0, 0],
+            'darkgreen': [0, 30 // 2, 0],
+            'darkblue': [0, 0, 30 // 2],
+            'olive': [30 // 2, 30 // 2, 0],
+            'teal': [0, 30 // 2, 30 // 2],
+            'purple': [30 // 2, 0, 30 // 2],
+        }
+        self._pwmtable = [0, 1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 8, 10, 11, 13, 16, 19, 23, 27, 32, 38, 45, 54, 64, 76, 91, 108, 128, 152, 181, 215, 255]
+        self._color = self._colors['red']
+        self._intensity = max(self._color)
+        self._last_intensity = self._intensity
+        self._active = False
+
+        # fade specific defines
+        self._fade_lock = _thread.allocate_lock()
+        self._fade_delay = 50
+        self._fading = False
+        self._fade_pixel_amount = -1    # fade all by default
+
+    def clear(self, number: int = -1) -> None:
+        """
+        Turn Neopixel off by setting the RGB color to [0, 0, 0]
+
+        :param      number:     Neopixel number to clear, all by default
+        :type       number:     int, optional
+        """
+        self.set(rgb=[0, 0, 0], number=number)
+
+    def set(self,
+            red: int = 0,
+            green: int = 0,
+            blue: int = 0,
+            rgb: list = None,
+            number: Union[int, list] = -1) -> None:
+        """
+        Set the neopixel color.
+
+        A RGB value can be specified by a list or by setting the individual color.
+
+        :param      red:    The red brightness
+        :type       red:    int, optional
+        :param      green:  The green brightness
+        :type       green:  int, optional
+        :param      blue:   The blue brightness
+        :type       blue:   int, optional
+        :param      rgb:    The RGB value
+        :type       rgb:    list, optional
+        :param      number: Number/list of Neopixels to change, -1 changes all
+        :type       number: Union[int, list], optional
+        """
+        if rgb is None:
+            color = (red, green, blue)
+        else:
+            color = tuple(rgb)
+
+        if number == -1:
+            # update all Neopixels
+            for p in range(0, self._neopixel_amount):
+                self.pixel[p] = color
+        else:
+            # update only specific Neopixels
+            if isinstance(number, list):
+                for p in number:
+                    self.pixel[number] = color
+            else:
+                self.pixel[number] = color
+
+        self.pixel.write()
+
+        # update neopixel properties
+        if color != (0, 0, 0):
+            self.active = True
+            if not self.fading:
+                # only update if not called by fading
+                # intensity would finally be 1 or zero
+                self.color = list(color)
+                self.intensity = max(color)
+        else:
+            # only update if not called by fading
+            # neopixel would be cleared after every cycle
+            if not self.fading:
+                self.active = False
+                # do not clear color or intensity property
+
+    def red(self, intensity: int = 30, number: Union[int, list] = -1) -> None:
+        """
+        Set the Neopixel to red.
+
+        :param      intensity:  The intensity
+        :type       intensity:  int, optional
+        :param      number:     Neopixel number/list to change, all by default
+        :type       number:     Union[int, list], optional
+        """
+        self.set(red=intensity, number=number)
+
+    def green(self, intensity: int = 30, number: Union[int, list] = -1) -> None:
+        """
+        Set the Neopixel to green.
+
+        :param      intensity:  The intensity
+        :type       intensity:  int, optional
+        :param      number:     Neopixel number/list to change, all by default
+        :type       number:     Union[int, list], optional
+        """
+        self.set(green=intensity, number=number)
+
+    def blue(self, intensity: int = 30, number: Union[int, list] = -1) -> None:
+        """
+        Set the Neopixel to blue.
+
+        :param      intensity:  The intensity
+        :type       intensity:  int, optional
+        :param      number:     Neopixel number/list to change, all by default
+        :type       number:     Union[int, list], optional
+        """
+        self.set(blue=intensity, number=number)
+
+    @property
+    def pixels(self) -> int:
+        """
+        Get number of defined Neopixels
+
+        :returns:   Amount of Neopixels
+        :rtype:     int
+        """
+        return self._neopixel_amount
+
+    @property
+    def color(self) -> list:
+        """
+        Get the currently set color of the Neopixel, might not be active
+
+        :returns:   Neopixel color if active
+        :rtype:     list
+        """
+        return self._color
+
+    @color.setter
+    def color(self, color: Union[list, str]) -> None:
+        """
+        Set a Neopixel color. Neopixel will be activated.
+
+        :param      color:      The color
+        :type       color:      Union[list, str]
+        """
+        if isinstance(color, str):
+            if color in self.colors:
+                color = self.colors[color]
+            else:
+                print('Color "{}" unknown, may add it with "colors" function'.
+                      format(color))
+                return
+
+        if color != self.color:
+            self._color = color
+
+            if color != [0, 0, 0]:
+                self.set(rgb=color, number=-1)
+            else:
+                self.clear()
+
+    @property
+    def intensity(self) -> int:
+        """
+        Get current Neopixel intensity.
+
+        :returns:   Neopixel intensity, maximum 255
+        :rtype:     int
+        """
+        return self._intensity
+
+    @intensity.setter
+    def intensity(self, value: int) -> None:
+        """
+        Set new intensity for Neopixel.
+
+        If Neopixel is active and is showing a color, the new intensity will
+        be applied.
+
+        :param      value:  The intensity, value will be
+        :type       value:  int
+        """
+        do_update = False
+        self._last_intensity = value
+
+        # update neopixel if new intensity is different from current one and
+        # the Neopixel is currently active
+        if ((self.intensity != self._last_intensity) and self.active):
+            # apply new intensity only if a valid color is set
+            if self.color != [0, 0, 0]:
+                do_update = True
+
+        if not value:
+            self.clear(number=-1)   # disable all Neopixel by default
+            do_update = False
+
+        value = max(min(255, value), 0)
+        self._intensity = value
+
+        if do_update:
+            # intensity = 40
+            # color = [60, 10, 7]
+            # maximum_brightness = max(color) # 60
+            # ratio = maximum_brightness / intensity  # 1.5
+            # new_color = [round(ele / ratio) for ele in color]
+            #  -> [40, 7, 5]
+            ratio = max(self.color) / value
+            new_color = [round(ele / ratio) for ele in self.color]
+            self.set(rgb=new_color, number=-1)  # update all Neopixel
+
+    @property
+    def active(self) -> bool:
+        """
+        Get current status of Neopixel
+
+        :returns:   Flag whether Neopixel is active or not
+        :rtype:     bool
+        """
+        return self._active
+
+    @active.setter
+    def active(self, value: bool) -> None:
+        """
+        Turn all Neopixel on or off
+
+        :param      value:  The state
+        :type       value:  bool
+        """
+        if value != self.active:
+            self._active = value
+
+            if value:
+                self.set(rgb=self.color, number=-1)     # activate all Neopixel
+            else:
+                self.clear(number=-1)   # disable all Neopixel by default
+
+    @property
+    def colors(self) -> dict:
+        """
+        Get available colors of Neopixel.
+
+        :returns:   Neopixel colors and their RGB value
+        :rtype:     dict
+        """
+        return self._colors
+
+    @colors.setter
+    def colors(self, value: dict) -> None:
+        """
+        Add new colors or change RGB value of existing color
+
+        :param      value:  Color name as key and RGB intensity list as value
+        :type       value:  dict
+        """
+        self._colors.update(value)
+
+    def fade(self, delay_ms: int = 50, pixel_amount: int = -1) -> None:
+        """
+        Fade Neopixel color. Wrapper around property usage.
+
+        All defined Neopixels will be fading with the same color
+        A fade delay below 30ms is not recommened due to high CPU load.
+        REPL might get slow.
+
+        :param      delay_ms:       The delay between intensity changes in milliseconds
+        :type       delay_ms:       int
+        :param      pixel_amount:   Which or how many Neopixel to fade, default is all
+        :type       pixel_amount:   int, optional
+        """
+        self.fade_delay = delay_ms
+        self.fade_pixel_amount = pixel_amount
+        self.fading = True
+
+    def _fade(self, delay_ms: int, pixel_amount: int, lock: int) -> None:
+        """
+        Internal Neopixel fading thread content.
+
+        :param      delay_ms:       The delay between intensity changes in milliseconds
+        :type       delay_ms:       int
+        :param      pixel_amount:   Which or how many Neopixel to fade, default is all
+        :type       pixel_amount:   int, optional
+        :param      lock:           The lock object
+        :type       lock:           lock
+        """
+        # find smallest value which is not zero in latest color list
+        maximum_intensity = min([val for val in self.color if val != 0])
+
+        # find closest match of maximum_intensity in _pwmtable
+        # set this as maximum_intensity
+        closest_match = min(self._pwmtable, key=lambda x: abs(x - maximum_intensity))
+        closest_match_index = self._pwmtable.index(closest_match)
+
+        while lock.locked():
+            try:
+                for val in self._pwmtable[:closest_match_index]:
+                    color = [val if ele != 0 else 0 for ele in self.color]
+                    self.set(rgb=color, number=pixel_amount)
+                    # time.sleep_ms(delay_ms)
+                    time.sleep(delay_ms / 1000.0)
+
+                for val in self._pwmtable[:closest_match_index][::-1]:
+                    color = [val if ele != 0 else 0 for ele in self.color]
+                    self.set(rgb=color, number=pixel_amount)
+                    # time.sleep_ms(delay_ms)
+                    time.sleep(delay_ms / 1000.0)
+            except KeyboardInterrupt:
+                break
+
+        # turn LED finally off
+        self.active = False
+        self._fading = False
+
+    @property
+    def fade_delay(self) -> int:
+        """
+        Get the fade delay in milliseconds.
+
+        :returns:   Delay between intensity changes in milliseconds
+        :rtype:     int
+        """
+        return self._fade_delay
+
+    @fade_delay.setter
+    def fade_delay(self, delay_ms: int) -> None:
+        """
+        Set the Neopixel fade delay in milliseconds.
+
+        :param      delay_ms:  The delay between intensity changes in milliseconds
+        :type       delay_ms:  int
+        """
+        if delay_ms < 1:
+            delay_ms = 1
+        self._fade_delay = delay_ms
+
+    @property
+    def fade_pixel_amount(self) -> int:
+        """
+        Get amount of fading Neopixels
+
+        :returns:   Number of fading pixels
+        :rtype:     int
+        """
+        return self._fade_pixel_amount
+
+    @fade_pixel_amount.setter
+    def fade_pixel_amount(self, value: int) -> None:
+        """
+        Set amount of fading Neopixels
+
+        :param      value:  The amount of fading Neopixels
+        :type       value:  int
+        """
+        if value > 0:
+            value -= 1
+        self._fade_pixel_amount = value
+
+    @property
+    def fading(self) -> bool:
+        """
+        Get the fading status.
+
+        :returns:   Flag whether neopixel is fading or not
+        :rtype:     bool
+        """
+        # returning self._fade_lock.locked() is not sufficient, as it will be
+        # False after "fading = False" is called and the remaining
+        # "set" calls would change the color and intensity property
+        # values until the for loop of "_fade" is finished
+        return self._fading
+
+    @fading.setter
+    def fading(self, value: bool) -> None:
+        """
+        Start or stop fading of the Neopixel.
+
+        :param      value:  The value
+        :type       value:  bool
+        """
+        if value and (not self._fade_lock.locked()):
+            # start blinking if not already blinking
+            self._fade_lock.acquire()
+            self.active = True
+            self._fading = True
+            params = (self.fade_delay, self.fade_pixel_amount, self._fade_lock)
+            _thread.start_new_thread(self._fade, params)
+        elif (value is False) and self._fade_lock.locked():
+            # stop fading if not already stopped
+            self._fade_lock.release()
diff --git a/simulation/src/led_helper/neopixel.py b/simulation/src/led_helper/neopixel.py
new file mode 100755
index 0000000..fa8e2b8
--- /dev/null
+++ b/simulation/src/led_helper/neopixel.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Fake Micropython NeoPixel
+
+See https://docs.micropython.org/en/latest/library/neopixel.html
+"""
+
+from machine import Pin
+
+
+class NeoPixel(object):
+    """docstring for NeoPixel"""
+    def __init__(self, pin: Pin, n: int, bpp: int = 3, timing: int = 1):
+        """
+        Initialise NeoPixel
+
+        :param      pin:    Pin of Neopixel LED
+        :type       pin:    Pin
+        :param      n:      Number of Neopixel LEDs
+        :type       n:      int
+        :param      bpp:    3 for RGB LEDs, 4 for RGBW LEDs
+        :type       bpp:    int, optional
+        :param      timing: 0 for 400KHz, and 1 for 800kHz LEDs
+        :type       timing: int, optional
+        """
+        self._pin = pin
+        self._amount = n
+
+    def fill(self, pixel) -> None:
+        for _ in range(0, self.__len__()):
+            pass
+
+    def __len__(self) -> int:
+        return self._amount
+
+    def __setitem__(self, index, value) -> None:
+        setattr(self, str(index), value)
+
+    def __getitem__(self, index):
+        return getattr(self, index)
+
+    def write(self) -> None:
+        pass
diff --git a/simulation/src/machine/__init__.py b/simulation/src/machine/__init__.py
new file mode 100755
index 0000000..4f7e730
--- /dev/null
+++ b/simulation/src/machine/__init__.py
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+from .machine import machine
+from .pin import Pin
+from .rtc import RTC
diff --git a/simulation/src/machine/machine.py b/simulation/src/machine/machine.py
new file mode 100644
index 0000000..d19917c
--- /dev/null
+++ b/simulation/src/machine/machine.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Fake Micropython Machine class
+
+See https://docs.micropython.org/en/latest/library/machine.html
+"""
+
+
+class machine(object):
+    """docstring for machine"""
+    UNKNOWN_RESET = 0
+    PWRON_RESET = 1
+    HARD_RESET = 2
+    WDT_RESET = 3
+    DEEPSLEEP_RESET = 4
+    SOFT_RESET = 5
+
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def reset_cause() -> int:
+        return machine.SOFT_RESET
+
+    @staticmethod
+    def unique_id() -> bytes:
+        return b'DEADBEEF'
diff --git a/simulation/src/machine/pin.py b/simulation/src/machine/pin.py
new file mode 100644
index 0000000..243bfeb
--- /dev/null
+++ b/simulation/src/machine/pin.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Fake Micropython Pin class
+
+See https://docs.micropython.org/en/latest/library/machine.Pin.html
+"""
+
+
+class Pin(object):
+    """docstring for Pin"""
+    IN = 1
+    OUT = 2
+
+    def __init__(self, pin: int, mode: int):
+        self._pin = pin
+        self._mode = mode
diff --git a/simulation/src/machine/rtc.py b/simulation/src/machine/rtc.py
new file mode 100644
index 0000000..9a90d61
--- /dev/null
+++ b/simulation/src/machine/rtc.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Fake Micropython RTC class
+
+See https://docs.micropython.org/en/latest/library/machine.RTC.html
+"""
+
+
+class RTC(object):
+    """docstring for RTC"""
+    def __init__(self, id: int = 0):
+        pass
diff --git a/simulation/src/path_helper/__init__.py b/simulation/src/path_helper/__init__.py
new file mode 100755
index 0000000..94a8b78
--- /dev/null
+++ b/simulation/src/path_helper/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+from .path_helper import PathHelper
diff --git a/simulation/src/path_helper/path_helper.py b/simulation/src/path_helper/path_helper.py
new file mode 100755
index 0000000..5094c9f
--- /dev/null
+++ b/simulation/src/path_helper/path_helper.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Path Helper
+
+Provide unavailable path functions for Micropython boards
+"""
+
+# import os
+from pathlib import Path
+
+
+class PathHelper(object):
+    """docstring for PathHelper"""
+    def __init__(self):
+        pass
+
+    # There's currently no os.path.exists() support in MicroPython
+    @staticmethod
+    def exists(path: str) -> bool:
+        """
+        Check existance of file at given path.
+
+        :param      path:   The path to the file
+        :type       path:   str
+
+        :returns:   Existance of file
+        :rtype:     bool
+        """
+        result = Path(path).exists()
+        return result
+
+        """
+        result = False
+
+        path_to_file_list = path.split('/')
+        # ['path', 'to', 'some', 'file.txt']
+
+        root_path = ''
+        # if sys.platform == 'esp32':
+        # if sys.platform not in ['aix', 'linux', 'win32', 'cygwin', 'darwin']:
+        #     root_path = ''
+        # else:
+        #     root_path = os.path.dirname(os.path.abspath(__file__))
+        # print('The root path: {}'.format(root_path))
+
+        this_path = root_path
+        for ele in path_to_file_list[:-1]:
+            files_in_dir = os.listdir(this_path)
+            # print('Files in {}: {}'.format(this_path, files_in_dir))
+
+            if ele in files_in_dir:
+                # print('"{}" found in "{}"'.format(ele, files_in_dir))
+
+                if this_path == '':
+                    this_path += '{}'.format(ele)
+                else:
+                    this_path += '/{}'.format(ele)
+
+                # print('Next folder to be checked is: {}'.format(this_path))
+            else:
+                return result
+
+        files_in_dir = os.listdir(this_path)
+        if path_to_file_list[-1] in files_in_dir:
+            # print('File "{}" found in "{}"'.
+            #       format(path_to_file_list[-1], this_path))
+            return True
+        else:
+            return False
+        """
diff --git a/simulation/src/run_simulation.py b/simulation/src/run_simulation.py
new file mode 100644
index 0000000..163013b
--- /dev/null
+++ b/simulation/src/run_simulation.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Run WiFi Manager simulation"""
+
+from wifi_manager import WiFiManager
+
+
+def main():
+    wm = WiFiManager(logger=None, quiet=False)
+    result = wm.load_and_connect()
+    print('Result of load_and_connect: {}'.format(result))
+
+    print('Starting configuration anyway in simulation')
+    result = False
+
+    if result is False:
+        wm.start_config()
+    else:
+        print('Successfully connected to a network :)')
+
+
+if __name__ == "__main__":
+    main()
diff --git a/simulation/src/time_helper/__init__.py b/simulation/src/time_helper/__init__.py
new file mode 100755
index 0000000..7b06768
--- /dev/null
+++ b/simulation/src/time_helper/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+from .time_helper import TimeHelper
diff --git a/simulation/src/time_helper/time_helper.py b/simulation/src/time_helper/time_helper.py
new file mode 100644
index 0000000..47a2645
--- /dev/null
+++ b/simulation/src/time_helper/time_helper.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Time Helper
+
+Sync and set internal clock (RTC) with NTP server time
+"""
+
+from machine import RTC
+# import ntptime
+import time
+
+
+class TimeHelper(object):
+    """docstring for TimeHelper"""
+    def __init__(self, tz: int = 1):
+        """
+        Initialize TimeHelper
+
+        :param      tz: Timezone offset
+        :type       tz: int, optional
+        """
+        self.rtc = RTC()
+        self._timezone = tz
+
+    def sync_time(self, timezone: int = None) -> None:
+        """
+        Sync the RTC with data from NTP server.
+
+        No network check is performed.
+        Existing RTC value will not be changed if NTP server is not reached.
+        No daylight saving is implemented.
+
+        :param      timezone:  The timezone shift
+        :type       timezone:  int, optional
+        """
+        pass
diff --git a/simulation/src/wifi_helper/__init__.py b/simulation/src/wifi_helper/__init__.py
new file mode 100644
index 0000000..838334e
--- /dev/null
+++ b/simulation/src/wifi_helper/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+from .wifi_helper import WifiHelper
diff --git a/simulation/src/wifi_helper/network.py b/simulation/src/wifi_helper/network.py
new file mode 100644
index 0000000..7128eeb
--- /dev/null
+++ b/simulation/src/wifi_helper/network.py
@@ -0,0 +1,668 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+Fake Micropython network class
+
+See https://docs.micropython.org/en/latest/library/network.html
+"""
+
+import netifaces
+import random
+import re
+import socket
+import subprocess
+import sys
+from typing import Any, List, Optional, Tuple, Union
+
+STA_IF = 1
+AP_IF = 2
+
+AUTH_OPEN = 1
+AUTH_WEP = 2
+AUTH_WPA_PSK = 3
+AUTH_WPA2_PSK = 4
+AUTH_WPA_WPA2_PSK = 5
+
+
+class NetworkHelper(object):
+    """docstring for NetworkHelper"""
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def _internal_scan() -> List[Tuple[Union[str, int]]]:
+        """
+        Wrapper around internal functions depending on the platform type
+
+        :returns:   List of found networks as tuple
+        :rtype:     List[Tuple[Union[str, int]]]
+        """
+        scan_result = list(dict())
+
+        if sys.platform == "linux" or sys.platform == "linux2":
+            scan_result = NetworkHelper._scan_linux()
+        elif sys.platform == "darwin":
+            scan_result = NetworkHelper._scan_mac()
+        elif sys.platform == "win32":
+            scan_result = NetworkHelper._scan_windows()
+        else:
+            scan_result = NetworkHelper._dummy_data()
+
+        networks = NetworkHelper._convert_scan_to_upy_format(data=scan_result)
+
+        return networks
+
+    @staticmethod
+    def _scan_linux() -> List[dict]:
+        """
+        Perform WiFi scan on Linux systems.
+
+        This is currently not yet implemented, dummy data will be returned
+
+        :returns:   List of found networks as dict
+        :rtype:     List[dict]
+        """
+        return NetworkHelper._dummy_data()
+
+    @staticmethod
+    def _scan_mac() -> List[dict]:
+        """
+        Perform WiFi scan on Mac systems.
+
+        Use command line interface of airport
+
+        :returns:   List of found networks as dict
+        :rtype:     List[dict]
+        """
+        scan_result = subprocess.check_output(['/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport', '--scan'])
+
+        if len(scan_result) == 0:
+            return NetworkHelper._dummy_data()
+
+        scan_result = [ele.decode('ascii') for ele in scan_result.splitlines()]
+        # [
+        #     b'                            SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)',
+        #     b'                  TP-LINK_FBFC3C a0:f3:c1:fb:fc:3c -57  1,+1    Y  -- WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP) ',
+        #     b'                  FRITZ!Box 7490 38:10:d5:17:eb:39 -56  1,+1    Y  DE WPA2(PSK/AES/AES) '
+        # ]
+
+        description = re.sub(' +', ' ', scan_result[0]).lstrip().split(' ')
+        # [
+        #   'SSID', 'BSSID', 'RSSI', 'CHANNEL', 'HT', 'CC', 'SECURITY',
+        #   '(auth/unicast/group)'
+        # ]
+
+        indexes = dict()
+        for kw in description[0:-1]:
+            indexes[kw.lower()] = scan_result[0].index(kw)
+        # {
+        #     'ssid': 28,
+        #     'bssid': 33,
+        #     'rssi': 51,
+        #     'channel': 56,
+        #     'ht': 64,
+        #     'cc': 67,
+        #     'security': 70
+        # }
+
+        nets = list()
+        for net in scan_result[1:]:
+            info = dict()
+
+            ssid_info = net[0:indexes['bssid']]
+            info['ssid'] = ssid_info.rstrip().lstrip()
+
+            bssid_info = net[indexes['bssid']:indexes['rssi']]
+            info['bssid'] = bssid_info.rstrip().lstrip()
+
+            rssi_info = net[indexes['rssi']:indexes['channel']]
+            info['RSSI'] = int(rssi_info.rstrip().lstrip())
+
+            channel_info = net[indexes['channel']:indexes['ht']]
+            info['channel'] = int(channel_info.rstrip().lstrip().split(',')[0])
+
+            ht_info = net[indexes['ht']:indexes['cc']]
+            info['ht'] = ht_info.rstrip().lstrip()
+
+            cc_info = net[indexes['cc']:indexes['security']]
+            info['cc'] = cc_info.rstrip().lstrip()
+
+            authmode_info = net[indexes['security']:]
+            info['authmode'] = authmode_info.rstrip().lstrip()
+
+            info['hidden'] = True if info['ht'] == "Y" else False
+
+            nets.append(info)
+            # {
+            #     'ssid': 'FRITZ!Box 7490',
+            #     'bssid': '38:10:d5:17:eb:39',
+            #     'RSSI': -57,
+            #     'channel': 1,
+            #     'ht': 'Y',
+            #     'cc': 'DE',
+            #     'authmode': 'WPA2(PSK/AES/AES)',
+            #     'hidden': True
+            # }
+
+        # [
+        #     {
+        #         'ssid': 'FRITZ!Box 7490',
+        #         'bssid': '38:10:d5:17:eb:39',
+        #         'RSSI': -57,
+        #         'channel': 1,
+        #         'ht': 'Y',
+        #         'cc': 'DE',
+        #         'authmode': 'WPA2(PSK/AES/AES)',
+        #         'hidden': True,
+        #         # 'quality': 86
+        #     },
+        #     {
+        #         'ssid': 'TP-LINK_FBFC3C',
+        #         'bssid': 'a0:f3:c1:fb:fc:3c',
+        #         'RSSI': -57,
+        #         'channel': 1,
+        #         'ht': 'Y',
+        #         'cc': '--',
+        #         'authmode': 'WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)',
+        #         'hidden': True
+        #     }
+        # ]
+
+        return nets
+
+    @staticmethod
+    def _scan_windows() -> List[dict]:
+        """
+        Perform WiFi scan on Windows systems.
+
+        Use command line interface of netsh.
+
+        This function might fail on non english or german language systems
+
+        :returns:   List of found networks as dict
+        :rtype:     List[dict]
+        """
+        try:
+            scan_result = subprocess.check_output(['netsh', 'wlan', 'show', 'networks', 'mode=bssid'])
+        except Exception as e:
+            # print('Failed to scan due to this error: {}'.format(e))
+            return NetworkHelper._dummy_data()
+
+        # netsh call returns report in local language, try to decode it
+        try:
+            scan_result = [ele.decode('cp850').lstrip() for ele in scan_result.splitlines()]
+        except Exception as e:
+            # print('Failed to decode scan data due to this error: {}'.format(e))
+            return NetworkHelper._dummy_data()
+
+        # [
+        #     '',
+        #     'Schnittstellenname : WLAN ', 'Momentan sind 2 Netzwerke sichtbar.',
+        #     '',
+        #     'SSID 1 : FRITZ!Box 7490',
+        #     'Netzwerktyp             : Infrastruktur',
+        #     'Authentifizierung       : WPA2-Personal',
+        #     'Verschl▒sselung         : CCMP',
+        #     'BSSIDD 1                 : 38:10:d5:17:eb:39',
+        #     'Signal             : 83%  ',
+        #     'Funktyp         : 802.11n',
+        #     'Kanal           : 11 ',
+        #     'Basisraten (MBit/s)   : 1 2 5.5 6 11 12 24',
+        #     'Andere Raten (MBit/s) : 9 18 36 48 54', '',
+        #     'SSID 2 : TP-LINK_FBFC3C',
+        #     'Netzwerktyp             : Infrastruktur',
+        #     'Authentifizierung       : WPA2-Personal',
+        #     'Verschl▒sselung         : CCMP',
+        #     'BSSIDD 1                 : a0:f3:c1:fb:fc:3c',
+        #     'Signal             : 83%  ',
+        #     'Funktyp         : 802.11n',
+        #     'Kanal           : 1 ',
+        #     'Basisraten (MBit/s)   : 1 2 5.5 11',
+        #     'Andere Raten (MBit/s) : 6 9 12 18 24 36 48 54',
+        #     ''
+        # ]
+
+        nets_tmp = list()
+        this_net = list()
+        active_net = False
+        for idx, line in enumerate(scan_result):
+            if line.startswith('SSID'):
+                active_net = True
+
+            if line == '':
+                active_net = False
+                if len(this_net):
+                    nets_tmp.append(this_net.copy())
+                this_net = list()
+
+            if active_net:
+                this_net.append(line)
+
+        nets_parsed = list()
+        for net in nets_tmp:
+            info = dict()
+
+            for ele in net:
+                this_info = ele.split(':')
+                key = this_info[0].lstrip().rstrip().lower()
+                if len(this_info) > 2:
+                    # bssid keyword
+                    val = ':'.join(this_info[1:]).lstrip().rstrip()
+                else:
+                    val = this_info[1].lstrip().rstrip()
+                info[key] = val
+
+            nets_parsed.append(info)
+
+        nets = list()
+        for net in nets_parsed:
+            info = dict()
+
+            ssid_key = [ssid for ssid in net.keys() if ssid.startswith('ssid ')][0]
+            info['ssid'] = net[ssid_key]
+
+            bssid_key = [bssid for bssid in net.keys() if bssid.startswith('bssidd ')][0]
+            info['bssid'] = net[bssid_key]
+
+            quality = int(net['signal'][:-1])
+            info['RSSI'] = NetworkHelper._quality_to_dbm(quality=quality)
+
+            if 'channel' in net:
+                # english system language
+                info['channel'] = int(net['channel'])
+            elif 'kanal' in net:
+                # german system language
+                info['channel'] = int(net['kanal'])
+            else:
+                # other system language
+                info['channel'] = 1
+
+            auth_key = [auth for auth in net.keys() if auth.startswith('auth')][0]
+            if len(auth_key):
+                # german or english system language
+                info['authmode'] = net[auth_key]
+            else:
+                # other system language
+                info['authmode'] = 'WPA2-Dummy'
+
+            info['hidden'] = False
+
+            nets.append(info)
+            # {
+            #     'ssid': 'FRITZ!Box 7490',
+            #     'bssid': '38:10:d5:17:eb:39',
+            #     'RSSI': '87%',
+            #     'channel': '11',
+            #     'authmode': 'WPA2-Personal',
+            #     'hidden': False
+            # }
+
+        # [
+        #     {
+        #         'ssid': 'FRITZ!Box 7490',
+        #         'bssid': '38:10:d5:17:eb:39',
+        #         'RSSI': '85%',
+        #         'channel': '11',
+        #         'authmode': 'WPA2-Personal',
+        #         'hidden': False
+        #     },
+        #     {
+        #         'ssid': 'TP-LINK_FBFC3C',
+        #         'bssid': 'a0:f3:c1:fb:fc:3c',
+        #         'RSSI': '83%',
+        #         'channel': '1',
+        #         'authmode': 'WPA2-Personal',
+        #         'hidden': False
+        #     }
+        # ]
+
+        return nets
+
+    @staticmethod
+    def _quality_to_dbm(quality: int) -> int:
+        """
+        Convert quality to dBm
+
+        :param      quality:  The quality [0, 100]
+        :type       quality:  int
+
+        :returns:   dBm in range [-100, -50]
+        :rtype:     int
+        """
+        # https://stackoverflow.com/questions/15797920/how-to-convert-wifi-signal-strength-from-quality-percent-to-rssi-dbm/30585711
+        return min(max((quality / 2) - 100, -100), -50)
+
+    @staticmethod
+    def _dummy_data() -> List[dict]:
+        """
+        Get dummy WiFi scan data.
+
+        RSSI value is fluctuating between 0 and 100
+
+        :returns:   List of found networks as dict
+        :rtype:     List[dict]
+        """
+        nets = [
+            {
+                'ssid': 'TP-LINK_FBFC3C',
+                'RSSI': -random.randint(0, 100),
+                'bssid': 'a0f3c1fbfc3c',
+                'authmode': 'WPA/WPA2-PSK',
+                'channel': 1,
+                'hidden': False
+            },
+            {
+                'ssid': 'FRITZ!Box 7490',
+                'RSSI': -random.randint(0, 100),
+                'bssid': '3810d517eb39',
+                'authmode': 'WPA2-PSK',
+                'channel': 11,
+                'hidden': False
+            }
+        ]
+        # print('Returning dummy data')
+
+        return nets
+
+    @staticmethod
+    def _dummy_data_upy() -> List[list]:
+        """
+        Get dummy WiFi scan data in same style as Mircopython would return it
+
+        :returns:   List of lists with scan data as reported on Micropython
+        :rtype:     List[Union[str, int]]
+        """
+        dummy_data = NetworkHelper._dummy_data()
+        nets_raw = NetworkHelper._convert_scan_to_upy_format(data=dummy_data)
+
+        return nets_raw
+
+    @staticmethod
+    def _convert_scan_to_upy_format(data: List[dict]) -> List[Tuple[Union[str, int]]]:
+        """
+        Convert dummy data to raw data as Mircopython scan would return
+
+        :param      data:  List of complete WiFi scan data
+        :type       data:  List[dict]
+
+        :returns:   List of tuples with scan data as reported on Micropython
+        :rtype:     List[Tuple[Union[str, int]]]
+        """
+        raw_nets = list()
+
+        for net in data:
+            this_raw = (
+                net['ssid'],
+                net['bssid'],
+                net['channel'],
+                net['RSSI'],
+                net['authmode'],
+                net['hidden']
+            )
+            # (
+            #     'FRITZ!Box 7490',
+            #     '3810d517eb39',
+            #     11,
+            #     -random.randint(0, 100),
+            #     'WPA2-PSK',
+            #     False
+            # )
+
+            raw_nets.append(this_raw)
+
+        return raw_nets
+
+    @staticmethod
+    def _gather_ifconfig_data() -> Tuple[str, str, str, str]:
+        """
+        Gather ifconfig data
+
+        :returns:   fifconfig data in same style as Micropython would return it
+        :rtype:     Tuple[str, str, str, str]
+        """
+        ip = '192.168.0.1'
+        subnet = '255.255.255.0'
+        gateway = '192.168.0.1'
+        dns = '8.8.8.8'
+
+        # get IP address
+        try:
+            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            s.connect(("8.8.8.8", 80))
+            ip = s.getsockname()[0]
+            s.close()
+        except Exception:
+            pass
+
+        # get subnet
+        try:
+            addrs = netifaces.ifaddresses('en0')
+            subnet = addrs[netifaces.AF_INET][0]['netmask']
+        except Exception:
+            pass
+
+        # get gateway
+        try:
+            gws = netifaces.gateways()
+            gateway = gws['default'][netifaces.AF_INET][0]
+        except Exception:
+            pass
+
+        return (ip, subnet, gateway, dns)
+
+
+class Station(NetworkHelper):
+    """docstring for Station"""
+    def __init__(self):
+        self._connected = False
+        self._active = False
+        self._connected_network = None
+
+    def active(self, is_active: Optional[bool] = None) -> Union[None, bool]:
+        """
+        Get/set WiFi status
+
+        :param      is_active:  Flag to change state of WiFi
+        :type       is_active:  Optional[bool]
+
+        :returns:   State of WiFi interface
+        :rtype:     Union[None, bool]
+        """
+        if is_active is not None:
+            self._active = is_active
+        else:
+            return self._active
+
+    def connect(self,
+                service_id: str,
+                key: Optional[str] = None,
+                **kwargs) -> None:
+        """
+        Connect to a network
+
+        :param      service_id:  The service identifier (BSSID/MAC address)
+        :type       service_id:  str
+        :param      key:         The key (password)
+        :type       key:         Optional[str]
+        :param      kwargs:      The keywords arguments
+        :type       kwargs:      dictionary
+        """
+        # remember connected BSSID
+        self._connected_network = service_id
+        self.connected = True
+
+    def disconnect(self) -> None:
+        """Disconnect from network."""
+        self.connected = False
+
+    def isconnected(self) -> bool:
+        """
+        Get connection status
+
+        :returns:   Flag whether device is connected to network or not
+        :rtype:     bool
+        """
+        return self.connected
+
+    def scan(self) -> List[Tuple[Union[str, int]]]:
+        """
+        Return WiFi scan data
+
+        :returns:   List of tuples with discovered service parameters
+        :rtype:     List[Tuple[Union[str, int]]]
+        """
+        return self._internal_scan()
+
+    def status(self, param: Optional[str] = None) -> Union[str, int, tuple]:
+        """
+        Query dynamic status information of the interface
+
+        :param      param:  The status parameter to retrieve
+        :type       param:  Optional[str]
+
+        :returns:   Status information
+        :rtype:     Union[str, int, tuple]
+        """
+        if param is not None:
+            return self.isconnected()
+        else:
+            pass
+
+    def ifconfig(self,
+                 value: Optional[Tuple[str, str, str, str]] = None) -> Union[None, Tuple[str, str, str, str]]:
+        """
+        Get/set IP-level network interface parameters
+
+        :param      value:  The value to set
+        :type       value:  Optional[Tuple[str, str, str, str]]
+
+        :returns:   A 4-tuple with information about IO, subnet, gateway, DNS
+        :rtype:     Union[None, Tuple[str, str, str, str]]
+        """
+        if value is not None:
+            print('Change of ifconfig not supported')
+        else:
+            return self._gather_ifconfig_data()
+
+    def config(self, param: str) -> Union[str, int]:
+        """
+        Get/set general network interface parameters
+
+        :param      param:  The status parameter to set
+        :type       param:  Optional[str]
+
+        :returns:   Status information
+        :rtype:     Union[str, int, tuple]
+        """
+        if param == 'essid':
+            return self._connected_network
+        else:
+            pass
+
+    @property
+    def connected(self) -> bool:
+        """
+        Get connection status
+
+        :returns:   Flag whether device is connected to network or not
+        :rtype:     bool
+        """
+        return self._connected
+
+    @connected.setter
+    def connected(self, value: bool) -> None:
+        """
+        Set connection status
+
+        :param      value:  The value
+        :type       value:  bool
+        """
+        self._connected = value
+
+
+class Client(NetworkHelper):
+    """docstring for Client"""
+    valid_kwargs = ['_essid', '_authmode', '_password', '_channel', '_active']
+
+    def __init__(self):
+        # for key in Client.valid_kwargs:
+        #     setattr(self, key, None)
+
+        self._essid = None
+        self._authmode = None
+        self._password = None
+        self._channel = None
+        self._active = None
+        self._active = False
+
+    def active(self, is_active: Optional[bool] = None) -> Union[None, bool]:
+        """
+        Get/set WiFi status
+
+        :param      is_active:  Flag to change state of WiFi
+        :type       is_active:  Optional[bool]
+
+        :returns:   State of WiFi interface
+        :rtype:     Union[None, bool]
+        """
+        if is_active is not None:
+            self._active = is_active
+        else:
+            return self._active
+
+    def ifconfig(self,
+                 value: Optional[Tuple[str, str, str, str]] = None) -> Union[None, Tuple[str, str, str, str]]:
+        """
+        Get/set IP-level network interface parameters
+
+        :param      value:  The value to set
+        :type       value:  Optional[Tuple[str, str, str, str]]
+
+        :returns:   A 4-tuple with information about IO, subnet, gateway, DNS
+        :rtype:     Union[None, Tuple[str, str, str, str]]
+        """
+        if value is not None:
+            print('Change of ifconfig not supported')
+        else:
+            return self._gather_ifconfig_data()
+
+    def config(self, **kwargs) -> None:
+        print('Config Accesspoint with: {}'.format(kwargs))
+        for key, val in kwargs.items():
+            key_name = '_{}'.format(key)
+            if key_name not in Client.valid_kwargs:
+                raise TypeError("Invalid keyword argument {}".format(key))
+            print('Set {} to {}'.format(key_name, val))
+            setattr(self, key_name, val)
+
+
+class WLAN(object):
+    """
+    WLAN would inherit from Station and Client.
+
+    Due to the diamond problem this hack fakes an inheritance of another object
+    by trying to find the attribute in its child classes in case it has not
+    been found in the parent class. The lookup is done in that class, which has
+    been set as "true" child during the init.
+
+    See https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem
+    """
+    def __init__(self, val: int) -> None:
+        self._type = None
+        self._type_name = None
+
+        if val == STA_IF:
+            self._type = Station()
+            self._type_name = STA_IF
+        elif val == AP_IF:
+            self._type = Client()
+            self._type_name = AP_IF
+
+    def __getattr__(self, item: Any) -> Any:
+        if self._type_name == STA_IF:
+            return super(Station, self._type).__getattribute__(item)
+        elif self._type_name == AP_IF:
+            return super(Client, self._type).__getattribute__(item)
+        else:
+            raise AttributeError("WLAN has no attribute '{}'".format(item))
diff --git a/simulation/src/wifi_helper/wifi_helper.py b/simulation/src/wifi_helper/wifi_helper.py
new file mode 100644
index 0000000..0a33aa7
--- /dev/null
+++ b/simulation/src/wifi_helper/wifi_helper.py
@@ -0,0 +1,482 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+WiFi Helper
+
+Connect to specified network(s) or create an accesspoint
+"""
+
+# import ubinascii
+import json
+from machine import machine
+from . import network
+import time
+from collections import namedtuple
+
+from time_helper import TimeHelper
+
+# not natively supported on micropython, see lib/typing.py
+from typing import (List, NamedTuple, Union)
+
+
+class WifiHelper(object):
+    """docstring for WifiHelper"""
+    def __init__(self):
+        self._scan_info = ['ssid', 'bssid', 'channel', 'RSSI', 'authmode', 'hidden']
+        self._auth_modes = {
+            0: "open",
+            1: "WEP",
+            2: "WPA-PSK",
+            3: "WPA2-PSK",
+            4: "WPA/WPA2-PSK"
+        }
+        self._network_list = list()
+        self._station = network.WLAN(network.STA_IF)
+
+    @staticmethod
+    def _do_connect(station: network.WLAN,
+                    ssid: str,
+                    password: str,
+                    timeout: int) -> bool:
+        """
+        Establish the network connection.
+
+        :param      station:   The network object
+        :type       station:   network.WLAN
+        :param      ssid:      The SSID of the network to connect to
+        :type       ssid:      str
+        :param      password:  The password of the network
+        :type       password:  str
+        :param      timeout:   Seconds to establish a connection to the network
+        :type       timeout:   int, optional
+
+        :returns:   Result of connection
+        :rtype:     bool
+        """
+        is_successfull = False
+
+        print('Connect to network "{}" with password "{}"'.
+              format(ssid, '*' * 8))
+
+        # WiFi connection remains after a soft reset
+        if machine.reset_cause() == machine.SOFT_RESET:
+            print('Soft reset, checking existing WiFi connection')
+            if station.isconnected():
+                print('System already connected')
+                is_successfull = True
+                return is_successfull
+
+        try:
+            station.disconnect()
+            time.sleep(250 / 1000)
+            station.connect(ssid, password)
+        except Exception as e:
+            print('Failed to connect due to: {}'.format(e))
+            return is_successfull
+
+        # get current system timestamp in seconds since 01/01/2000
+        now = time.time()
+
+        # wait for connection no longer than the specified timeout
+        while (time.time() < (now + timeout)):
+            if station.isconnected():
+                is_successfull = True
+                return is_successfull
+            else:
+                pass
+
+            time.sleep(100 / 1000)
+
+        return is_successfull
+
+    @staticmethod
+    def connect(ssid: Union[None, List[str], str] = None,
+                password: Union[None, List[str], str] = None,
+                networks: Union[dict, None] = None,
+                timeout: int = 5,
+                reconnect: bool = False) -> bool:
+        """
+        Connect to the configured network
+
+        :param      ssid:      The SSID of the network to connect to
+        :type       ssid:      list or str
+        :param      password:  The password of the network
+        :type       password:  list or str
+        :param      networks:  Networks and passwords
+        :type       networks:  dict, optional
+        :param      timeout:   Seconds to establish a connection to the network
+        :type       timeout:   int, optional
+        :param      reconnect: Reconnect/disconnect from active connection
+        :type       reconnect: bool, optional
+
+        :returns:   Result of connection
+        :rtype:     bool
+        """
+        is_connected = False
+
+        # configure the WiFi as station mode (client)
+        station = network.WLAN(network.STA_IF)
+
+        # activate WiFi if not yet enabled
+        if not station.active():
+            station.active(True)
+
+        if station.isconnected():
+            current_network = station.config('essid')
+            print('Already connected to "{}"'.format(current_network))
+            if reconnect:
+                station.disconnect()
+                print('Disconnected from "{}"'.format(current_network))
+            else:
+                is_connected = True
+
+                th = TimeHelper()
+                th.sync_time()
+                print(station.ifconfig())
+
+                return is_connected
+
+        # get current system timestamp in seconds since 01/01/2000
+        now = time.time()
+
+        if ((type(ssid) is str) and (type(password) is str)):
+            # user provided string of single network to connect to
+            print('Connect by single network and password')
+
+            is_connected = WifiHelper._do_connect(station=station,
+                                                  ssid=ssid,
+                                                  password=password,
+                                                  timeout=timeout)
+            print('Connected to {}: {}'.format(ssid, is_connected))
+        elif ((type(ssid) is list) and
+              (type(password) is list)):
+            # user provided list of networks to connect to
+            print('Connect by list of networks and passwords')
+
+            for idx, s in enumerate(ssid):
+                is_connected = WifiHelper._do_connect(station=station,
+                                                      ssid=s,
+                                                      password=password[idx],
+                                                      timeout=timeout)
+                print('Connected to {}: {}'.format(s, is_connected))
+                if is_connected:
+                    break
+        elif ((networks is not None) and
+              (type(networks) is dict)):
+            # user provided dict of networks and passwords
+            print('Connect by dict of networks and passwords')
+
+            for ssid, password in networks.items():
+                is_connected = WifiHelper._do_connect(station=station,
+                                                      ssid=ssid,
+                                                      password=password,
+                                                      timeout=timeout)
+                print('Connected to {}: {}'.format(ssid, is_connected))
+                if is_connected:
+                    break
+        else:
+            print('SSID and/or password neither list nor string')
+
+        print('Stopped trying to connect to network after {} seconds'.
+              format(time.time() - now))
+
+        if is_connected:
+            print('Connection successful')
+            th = TimeHelper()
+            th.sync_time()
+        else:
+            print('Connection timeout of failed to connect')
+            print('Please check configured SSID and password')
+
+        print(station.ifconfig())
+
+        # return True if connection has been established
+        return is_connected
+
+    @property
+    def isconnected(self) -> bool:
+        """
+        Return whether device is connected as client to a network
+
+        :returns:   Result of connection status
+        :rtype:     bool
+        """
+        # configure the WiFi as station mode (client)
+        station = self.station
+        return station.isconnected()
+
+    @property
+    def station(self):
+        """
+        Return WiFi network station aka client interface object
+
+        :returns:   WiFi network station aka client interface object
+        :rtype:     WLAN
+        """
+        return self._station
+
+    @staticmethod
+    def create_ap(ssid: str,
+                  password: str = '',
+                  channel: int = 11,
+                  timeout: int = 5) -> bool:
+        """
+        Create an Accesspoint
+
+        :param      ssid:      The SSID of the network to create
+        :type       ssid:      str
+        :param      password:  The password of the accesspoint
+        :type       password:  str, optional
+        :param      channel:   The channel of the accesspoint
+        :type       channel:   int, optional
+        :param      timeout:   Seconds to create an accesspoint
+        :type       timeout:   int, optional
+
+        :returns:   Result of connection
+        :rtype:     bool
+        """
+        is_successfull = True
+
+        # configure the WiFi as accesspoint mode (server)
+        accesspoint = network.WLAN(network.AP_IF)
+
+        # activate accesspoint if not yet enabled
+        if not accesspoint.active():
+            accesspoint.active(True)
+
+        # check for open AccessPoint configuration
+        if len(password) > 7:
+            # WPA and WPA2 passwords can range from 8 to 63 characters
+            _authmode = network.AUTH_WPA_WPA2_PSK
+        else:
+            # in case a to short/long password has been given, set it to empty
+            _authmode = network.AUTH_OPEN
+            if len(password):
+                print('Invalid WPA/WPA2 password')
+                password = ''
+
+        print('Create AccessPoint "{}" with password "{}"'.
+              format(ssid, password))
+
+        accesspoint.config(essid=ssid,
+                           authmode=_authmode,
+                           password=password,
+                           channel=channel)
+
+        # get current system timestamp in seconds since 01/01/2000
+        now = time.time()
+
+        # wait for success no longer than the specified timeout
+        while (time.time() < (now + timeout)):
+            if accesspoint.active():
+                is_successfull = True
+                break
+            else:
+                pass
+
+        print('Stopped trying to setup AccessPoint after {} seconds'.
+              format(time.time() - now))
+
+        if is_successfull:
+            print('AccessPoint setup successful')
+        else:
+            print('Connection timeout, failed to setup AccessPoint')
+
+        print(accesspoint.ifconfig())
+
+        return is_successfull
+
+    @property
+    def scan_info(self) -> list:
+        """
+        Get WiFi scan informations.
+
+        :returns:   WiFi scan properties
+        :rtype:     list
+        """
+        return self._scan_info
+
+    @property
+    def auth_modes(self) -> dict:
+        """
+        Get authentication modes.
+
+        :returns:   Supported authentication modes
+        :rtype:     dict
+        """
+        return self._auth_modes
+
+    def scan_networks(self) -> None:
+        """
+        Scan for available WiFi networks and return found networks.
+
+        :param      sort_key:  The sort key
+        :type       sort_key:  str, optional
+
+        :returns:   The WiFi networks
+        :rtype:     List[dict]
+        """
+        station = self.station
+
+        # activate WiFi if not yet enabled
+        if not station.active():
+            station.active(True)
+
+        # scan for available networks
+        found_networks = list()
+        try:
+            found_networks = station.scan()
+            if not len(found_networks):
+                station.active(False)
+        except RuntimeError:
+            print('RuntimeError during scan')
+            # no access points were found.
+            # RuntimeError: Wifi Unknown Error 0x0102
+        except Exception as e:
+            print('Unknown exception: {}'.format(e))
+
+        # create dict based on scan_info and found networks
+        self._network_list = [dict(zip(self.scan_info, x)) for x in found_networks]
+
+        for net in self._network_list:
+            net['quality'] = self.dbm_to_quality(dBm=net['RSSI'])
+            if 'authmode' in net:
+                try:
+                    net['authmode'] = self.auth_modes[net['authmode']]
+                except KeyError:
+                    # print('{} is unknown authmode'.format(net['authmode']))
+                    pass
+                except Exception:
+                    pass
+            if 'bssid' in net:
+                try:
+                    net['bssid'] = net['bssid']
+                except Exception:
+                    pass
+
+    @property
+    def networks(self) -> List[NamedTuple]:
+        """
+        Get list of available networks
+
+        :returns:   List of NamedTuple networks
+        :rtype:     List[NamedTuple]
+        """
+        return [namedtuple('net', list(net.keys()))(*net.values()) for net in self._network_list]
+
+    def get_wifi_networks_sorted(self,
+                                 rescan: bool = False,
+                                 scan_if_empty: bool = False,
+                                 as_json: bool = False,
+                                 sort_key: str = 'RSSI') -> Union[List[dict], str]:
+        """
+        Get the available WiFi networks in sorted order.
+
+        :param      rescan:         Flag to scan before returning result
+        :type       rescan:         bool
+        :param      scan_if_empty:  Flag to scan if network list is empty
+        :type       scan_if_empty:  bool
+        :param      as_json:        Flag to return data as json string
+        :type       as_json:        bool
+        :param      sort_key:       The sort key
+        :type       sort_key:       str
+
+        :returns:   The available WiFi networks in sorted order.
+        :rtype:     Union[List[dict], str]
+        """
+        if (scan_if_empty and not len(self.networks)) or rescan:
+            self.scan_networks()
+
+        if not (sort_key in self.scan_info):
+            sort_key = 'RSSI'
+
+        # sort list by specified sort_key
+        sorted(self._network_list, key=lambda k: k[sort_key])
+
+        if as_json:
+            return json.dumps(self._network_list)
+        else:
+            return self._network_list
+
+    @staticmethod
+    def dbm_to_quality(dBm: int) -> int:
+        """
+        Convert dBm to quality
+
+        :param      dBm:  The dBm [-100, -50]
+        :type       dBm:  int
+
+        :returns:   Quality index in percent [0, 100]
+        :rtype:     int
+        """
+        # https://stackoverflow.com/questions/15797920/how-to-convert-wifi-signal-strength-from-quality-percent-to-rssi-dbm/30585711
+        """
+        quality = 0
+        if dBm <= -100:
+            # very bad signal
+            quality = 0
+        elif dBm >= -50:
+            quality = 100
+        else:
+            quality = 2 * (dBm + 100)
+
+        return quality
+        """
+        return min(max(2 * (dBm + 100), 0), 100)
+
+    @staticmethod
+    def quality_to_dbm(quality: int) -> int:
+        """
+        Convert quality to dBm
+
+        :param      quality:  The quality [0, 100]
+        :type       quality:  int
+
+        :returns:   dBm in range [-100, -50]
+        :rtype:     int
+        """
+        # https://stackoverflow.com/questions/15797920/how-to-convert-wifi-signal-strength-from-quality-percent-to-rssi-dbm/30585711
+        """
+        dBm = 0
+        if quality <= 0:
+            # very bad signal
+            dBm = -100
+        elif quality >= 100:
+            dBm = -50
+        else:
+            dBm = (quality / 2) - 100
+
+        return dBm
+        """
+        return min(max((quality / 2) - 100, -100), -50)
+
+    @property
+    def ifconfig_client(self):
+        """
+        Get current network interface parameters of the client
+
+        :returns:   WiFi or general network informations
+        :rtype:     NamedTuple
+        """
+        empty_config = ('0.0.0.0', '0.0.0.0', '0.0.0.0', '0.0.0.0')
+        _ifconfig = namedtuple('ifconfig', ('ip', 'subnet', 'gateway', 'dns'))
+        station = self.station
+
+        if station.active() and station.isconnected():
+            return _ifconfig(*station.ifconfig())
+        else:
+            return _ifconfig(*empty_config)
+
+    @property
+    def ifconfig_ap(self):
+        """
+        Get current network interface parameters of the accesspoint
+
+        :returns:   WiFi or general network informations
+        :rtype:     NamedTuple
+        """
+        _ifconfig = namedtuple('ifconfig', ('ip', 'subnet', 'gateway', 'dns'))
+        ap = network.WLAN(network.AP_IF)
+
+        return _ifconfig(*ap.ifconfig())
diff --git a/simulation/src/wifi_manager/__init__.py b/simulation/src/wifi_manager/__init__.py
new file mode 100644
index 0000000..a697265
--- /dev/null
+++ b/simulation/src/wifi_manager/__init__.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+from .wifi_manager import WiFiManager
diff --git a/simulation/src/wifi_manager/wifi_manager.py b/simulation/src/wifi_manager/wifi_manager.py
new file mode 100644
index 0000000..a950fa9
--- /dev/null
+++ b/simulation/src/wifi_manager/wifi_manager.py
@@ -0,0 +1,767 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+main script, do your stuff here, similar to the loop() function on Arduino
+"""
+
+# system packages
+from Crypto.Cipher import AES
+import gc
+import json
+from machine import machine
+# import os
+from pathlib import Path
+import _thread
+import time
+# import ubinascii
+# import ucryptolib
+from typing import List, Union
+
+from generic_helper import Message
+
+# pip installed packages
+from flask import Flask, render_template, url_for, redirect, request, jsonify, make_response
+
+# custom packages
+from generic_helper import GenericHelper
+from led_helper import Neopixel     # Led
+from path_helper import PathHelper
+from wifi_helper import WifiHelper
+
+
+class WiFiManager(object):
+    """docstring for WiFiManager"""
+    def __init__(self, logger=None, quiet=False, name=__name__):
+        # setup and configure logger if none is provided
+        if logger is None:
+            logger = GenericHelper.create_logger(logger_name=self.__class__.__name__)
+            GenericHelper.set_level(logger, 'debug')
+        self.logger = logger
+        self.logger.disabled = quiet
+        self._config_file = (Path(__file__).parent / '..' / '..').resolve() / 'wifi-secure.json'
+        print('Config file at: {}'.format(self._config_file))
+
+        flask_root_folder = (Path(__file__).parent / '..' / '..').resolve()
+        template_folder = flask_root_folder / 'templates'
+        static_folder = flask_root_folder / 'static'
+        self.app = Flask(name,
+                         template_folder=template_folder,
+                         static_folder=static_folder)
+        self.wh = WifiHelper()
+        self.pixel = Neopixel()
+        self.pixel.color = 'yellow'
+        self.pixel.intensity = 20
+
+        self.event_sinks = set()
+
+        self._add_app_routes()
+
+        # other setup and init here
+        # AES key shall be 16 or 32 bytes
+        required_len = 16
+        uuid = machine.unique_id()
+        amount = required_len // len(uuid) + (required_len % len(uuid) > 0)
+        self._enc_key = (uuid * amount).decode('ascii')[:required_len]
+
+        self._configured_networks = list()
+        self._selected_network_bssid = ''
+
+        # WiFi scan specific defines
+        self._scan_lock = _thread.allocate_lock()
+        self._scan_interval = 5000  # milliseconds
+        # Queue also works, but in this case there is no need for a history
+        self._scan_net_msg = Message()
+        self._scan_net_msg.set([])  # empty list, required by save_wifi_config
+        self._latest_scan = None
+
+        # finally start the WiFi scanning thread
+        self.scanning = True
+
+    def load_and_connect(self) -> bool:
+        """
+        Load configured network credentials and try to connect to those
+
+        :returns:   Result of connection
+        :rtype:     bool
+        """
+        result = False
+
+        # check wifi config file existance
+        if PathHelper.exists(path=self._config_file):
+            self.logger.debug('Encrypted wifi config file exists')
+            loaded_cfg = self._load_wifi_config_data(path=self._config_file,
+                                                     encrypted=True)
+
+            private_cfg = None
+            ssids = list()
+            passwords = list()
+
+            if isinstance(loaded_cfg, list):
+                # deepcopy would be necessary but not builtin in micropython
+                private_cfg = json.loads(json.dumps(loaded_cfg))
+                for net in private_cfg:
+                    if 'ssid' in net:
+                        ssids.append(net['ssid'])
+                    if 'password' in net:
+                        passwords.append(net['password'])
+                        net['password'] = '*' * 8
+                    else:
+                        passwords.append('')
+                self._configured_networks = list(ssids).copy()
+            elif isinstance(loaded_cfg, dict):
+                private_cfg = loaded_cfg.copy()
+                if 'ssid' in loaded_cfg:
+                    ssids = loaded_cfg['ssid']
+                if 'password' in private_cfg:
+                    passwords = loaded_cfg['password']
+                    private_cfg['password'] = '*' * 8
+                self._configured_networks = ssids
+
+            # self._configured_networks = list(ssids).copy()
+            # self.logger.debug('All SSIDs: {}'.format(ssids))
+            self.logger.debug('Config content: {}'.format(loaded_cfg))
+            self.logger.debug('Private config content: {}'.
+                              format(private_cfg))
+            self.logger.debug('Configured networks: {}'.
+                              format(self._configured_networks))
+
+            self.logger.info('Connecting to loaded network config...')
+            result = WifiHelper.connect(ssid=ssids,
+                                        password=passwords,
+                                        timeout=5,
+                                        reconnect=False)
+            self.logger.debug('Result of connection: {}'.format(result))
+        else:
+            self.logger.debug('WiFi config file does not (yet) exist')
+
+        return result
+
+    def start_config(self) -> None:
+        """Start WiFi manager accesspoint and webserver."""
+        self.logger.info('Starting Manager with AccessPoint')
+        result = self.wh.create_ap(ssid='WiFiManager',
+                                   password='',
+                                   channel=11,
+                                   timeout=5)
+        self.logger.debug('Created AP: {}'.format(result))
+        ifconfig = self.wh.ifconfig_ap
+        self.logger.debug(ifconfig)
+
+        # finally
+        self.run(port=80, debug=True)
+
+    def _add_app_routes(self) -> None:
+        """Add all application routes to the webserver."""
+        self.app.add_url_rule("/", view_func=self.landing_page)
+        self.app.add_url_rule("/select", view_func=self.wifi_selection)
+        self.app.add_url_rule("/render_network_inputs",
+                              view_func=self.render_network_inputs)
+        self.app.add_url_rule("/configure", view_func=self.wifi_configs)
+        self.app.add_url_rule("/save_wifi_config",
+                              view_func=self.save_wifi_config,
+                              methods=['POST', 'GET'])
+        self.app.add_url_rule("/remove_wifi_config",
+                              view_func=self.remove_wifi_config,
+                              methods=['POST', 'GET'])
+        self.app.add_url_rule("/scan_result", view_func=self.scan_result)
+
+        self.app.add_url_rule(
+            "/static/css/bootstrap.min.css",
+            view_func=self.styles
+        )
+        # regex not supported in Flask
+        # self.app.add_url_rule(re.compile('^\/(.+\.css)$'),
+        #                       view_func=self.styles)
+
+    def _encrypt_data(self, data: Union[str, list, dict]) -> bytes:
+        """
+        Encrypt data with encryption key
+
+        :param      data:  The data
+        :type       data:  Union[str, list, dict]
+
+        :returns:   Encrypted data
+        :rtype:     bytes
+        """
+        # create bytes array of the data and encrypt it
+        if not isinstance(data, str):
+            data = str(data)
+
+        data_bytes = data.encode()
+        enc = AES.new(self._enc_key, AES.MODE_ECB)
+
+        # add '\x00' to fill up the data string to reach a multiple of 16
+        encrypted_data = enc.encrypt(data_bytes + b'\x00' * ((16 - (len(data_bytes) % 16)) % 16))
+
+        return encrypted_data
+
+    def _decrypt_data(self, data: bytes) -> str:
+        """
+        Decrypt data with decryption key
+
+        :param      data:  The data
+        :type       data:  bytes
+
+        :returns:   Decrypted data
+        :rtype:     str
+        """
+        # decrypt bytes array
+        dec = AES.new(self._enc_key, AES.MODE_ECB)
+        decrypted_data = dec.decrypt(data)
+
+        # remove added '\x00' stuff after decoding it to ascii
+        decrypted_data_str = decrypted_data.decode('ascii').rstrip('\x00')
+
+        return decrypted_data_str
+
+    def extend_wifi_config_data(self,
+                                data: Union[dict, List[dict]],
+                                path: str,
+                                encrypted: bool = False) -> None:
+        """
+        Extend WiFi configuration data of file.
+
+        :param      data:       The data
+        :type       data:       Union[dict, List[dict]]
+        :param      path:       The full path to the file
+        :type       path:       str
+        :param      encrypted:  Flag to save data encrypted
+        :type       encrypted:  bool, optional
+        """
+        # in case the file already exists, extend its data content
+        if PathHelper.exists(path=path):
+            existing_data = self._load_wifi_config_data(path=path,
+                                                        encrypted=encrypted)
+            self.logger.debug('Existing WiFi config data: {}'.
+                              format(existing_data))
+            if isinstance(existing_data, dict):
+                if isinstance(data, dict):
+                    data = [existing_data, data]
+                elif isinstance(data, list):
+                    data = [existing_data] + data
+            elif isinstance(existing_data, list):
+                if isinstance(data, dict):
+                    existing_data.append(data)
+                    data = existing_data
+                elif isinstance(data, list):
+                    data = existing_data + data
+            else:
+                # unknown content, overwrite it
+                pass
+
+        self.logger.debug('Updated data: {}'.format(data))
+
+        ssids = list()
+        if isinstance(data, list):
+            for net in data:
+                if 'ssid' in net:
+                    ssids.append(net['ssid'])
+        elif isinstance(data, dict):
+            if 'ssid' in data:
+                ssids = [data['ssid']]
+
+        self._configured_networks = ssids.copy()
+
+        if encrypted:
+            # create bytes array of the dict and encrypt it
+            encrypted_data = self._encrypt_data(data=data)
+
+            # save data to file as binary as it contains encrypted data
+            GenericHelper.save_file(data=encrypted_data,
+                                    path=path,
+                                    mode='wb')
+            self.logger.debug('Saved encrypted data as json: {}'.
+                              format(encrypted_data))
+        else:
+            # save data to file, no need for binary mode
+            GenericHelper.save_json(data=data,
+                                    path=path,
+                                    mode='w')
+            self.logger.debug('Saved data as json: {}'.format(data))
+
+    def _load_wifi_config_data(self,
+                               path: str,
+                               encrypted: bool = False) -> Union[dict,
+                                                                 List[dict]]:
+        """
+        Load WiFi configuration data from file.
+
+        :param      path:       The full path to the file
+        :type       path:       str
+        :param      encrypted:  Flag to decrypt data
+        :type       encrypted:  bool, optional
+
+        :returns:   The loaded data
+        :rtype:     Union[dict, List[dict]]
+        """
+        data = dict()
+
+        if encrypted:
+            # read file in binary as it contains encrypted data
+            encrypted_read_data = GenericHelper.load_file(path=path,
+                                                          mode='rb')
+            self.logger.debug('Read encrypted data: {}'.
+                              format(encrypted_read_data))
+
+            # decrypt read data
+            decrypted_data_str = self._decrypt_data(data=encrypted_read_data)
+            self.logger.debug('Decrypted data str: {}'.
+                              format(decrypted_data_str))
+
+            # convert string to dict
+            data = GenericHelper.str_to_dict(data=decrypted_data_str)
+            self.logger.debug('Decrypted data dict: {}'.format(data))
+        else:
+            data = GenericHelper.load_json(path=path, mode='r')
+            self.logger.debug('Read non encrypted data: {}'.
+                              format(data))
+
+        return data
+
+    @property
+    def configured_networks(self) -> List[str]:
+        """
+        Get SSIDs of all configured networks
+
+        :returns:   SSIDs of configured networks
+        :rtype:     List[str]
+        """
+        return self._configured_networks
+
+    def _scan(self,
+              pixel: Neopixel,
+              wh: WifiHelper,
+              msg: Message,
+              scan_interval: int,
+              lock: int) -> None:
+        """
+        Scan for available networks.
+
+        :param      pixel:          Neopixel helper object
+        :type       pixel:          Neopixel
+        :param      wh:             Wifi helper object
+        :type       wh:             WifiHelper
+        :param      msg:            The shared message from this thread
+        :type       msg:            Message
+        :param      scan_interval:  The scan interval in milliseconds
+        :type       scan_interval:  int
+        :param      lock:           The lock object
+        :type       lock:           _thread.lock
+        """
+        pixel.fading = True
+
+        while lock.locked():
+            try:
+                # rescan for available networks
+                found_nets = wh.get_wifi_networks_sorted(rescan=True,
+                                                         scan_if_empty=True)
+
+                # wait for specified time
+                time.sleep(scan_interval / 1000.0)
+
+                msg.set(found_nets)
+            except KeyboardInterrupt:
+                break
+
+        pixel.fading = False
+        print('Finished scanning')
+
+    @property
+    def scan_interval(self) -> int:
+        """
+        Get the WiFi scan interval in milliseconds.
+
+        :returns:   Interval of WiFi scans in milliseconds
+        :rtype:     int
+        """
+        return self._scan_interval
+
+    @scan_interval.setter
+    def scan_interval(self, value: int) -> None:
+        """
+        Set the WiFi scan interval in milliseconds.
+
+        Values below 1000 ms are set to 1000 ms.
+        One scan takes around 3 sec, which leads to maximum 15 scans per min
+
+        :param      value:  Interval of WiFi scans in milliseconds
+        :type       value:  int
+        """
+        if isinstance(value, int):
+            if value < 1000:
+                value = 1000
+            self._scan_interval = value
+
+    @property
+    def scanning(self) -> bool:
+        """
+        Get the scanning status.
+
+        :returns:   Flag whether WiFi network scan is running or not.
+        :rtype:     bool
+        """
+        return self._scan_lock.locked()
+
+    @scanning.setter
+    def scanning(self, value: int) -> None:
+        """
+        Start or stop scanning for available WiFi networks.
+
+        :param      value:  The value
+        :type       value:  int
+        """
+        if value and (not self._scan_lock.locked()):
+            # start scanning if not already scanning
+            self._scan_lock.acquire()
+
+            # parameters of the _scan function
+            params = (
+                self.pixel,
+                self.wh,
+                self._scan_net_msg,
+                self._scan_interval,
+                self._scan_lock
+            )
+            _thread.start_new_thread(self._scan, params)
+            self.logger.info('Scanning started')
+        elif (value is False) and self._scan_lock.locked():
+            # stop scanning if not already stopped
+            self._scan_lock.release()
+            self.logger.info('Scanning stoppped')
+
+    @property
+    def latest_scan(self) -> Union[List[dict], str]:
+        gc.collect()
+        # free = gc.mem_free()
+        # self.logger.debug('Free memory: {}'.format(free))
+        latest_scan_result = self._scan_net_msg.value()
+        self.logger.info('Requested latest scan result: {}'.
+                         format(latest_scan_result))
+        return latest_scan_result
+
+    def _render_network_inputs(self,
+                               available_nets: dict,
+                               selected_bssid: str = '') -> str:
+        """
+        Render HTML list of selectable networks
+
+        :param      available_nets:  All available nets
+        :type       available_nets:  dict
+        :param      selected_bssid:  Currently selected network on the webpage
+        :type       selected_bssid:  str
+
+        :returns:   Sub content of WiFi selection page
+        :rtype:     str
+        """
+        content = ""
+        if len(available_nets):
+            for ele in available_nets:
+                selected = ''
+                if ele['bssid'] == selected_bssid:
+                    selected = "checked"
+                content += """
+                <input class="list-group-item-check" type="radio" name="bssid" id="{bssid}" value="{bssid}" onclick="remember_selected_element(this)" {state}>
+                <label class="list-group-item py-3" for="{bssid}">
+                  {ssid}
+                  <span class="d-block small opacity-50">
+                    Signal quality {quality}&#37;, BSSID {bssid}
+                  </span>
+                </label>
+                """.format(bssid=ele['bssid'],
+                           state=selected,
+                           ssid=ele['ssid'],
+                           quality=ele['quality'])
+        else:
+            # as long as no networks are available show a spinner
+            content = """
+            <div class="spinner-border" role="status">
+              <span class="visually-hidden">Loading...</span>
+            </div>
+            """
+
+        return content
+
+    def _save_wifi_config(self, form_data: dict) -> None:
+        """
+        Save a new WiFi configuration to the WiFi configuration file.
+
+        :param      form_data:  The form data
+        :type       form_data:  dict
+        """
+        network_cfg = dict()
+        available_nets = self.latest_scan
+        self.logger.info('Available nets: {}'.format(available_nets))
+        # [
+        #   {
+        #       'ssid': 'TP-LINK_FBFC3C',
+        #       'RSSI': -21,
+        #       'bssid': 'a0f3c1fbfc3c',
+        #       'authmode': 'WPA/WPA2-PSK',
+        #       'quality': 9,
+        #       'channel': 1,
+        #       'hidden': False
+        #   },
+        #   {
+        #       'ssid': 'FRITZ!Box 7490',
+        #       'RSSI': -17,
+        #       'bssid': '3810d517eb39',
+        #       'authmode': 'WPA2-PSK',
+        #       'quality': 27,
+        #       'channel': 11,
+        #       'hidden': False
+        #   }
+        # ]
+
+        # find SSID of network based on given bssid value
+        if form_data['ssid'] != '':
+            network_cfg['ssid'] = form_data['ssid']
+        else:
+            if 'bssid' not in form_data:
+                return
+
+            # selected_bssid = form_data['wifi_network']
+            selected_bssid = form_data['bssid']
+            for ele in available_nets:
+                if (isinstance(selected_bssid, str) and
+                   selected_bssid.startswith("b'")):
+                    # actually a bytes element used as string
+                    # this is a bug due to the XMLHttpRequest updated list as
+                    # JSON which does not handle bytes format
+                    this_bssid = str(ele['bssid'])
+                else:
+                    this_bssid = ele['bssid']   # .decode('ascii')
+
+                if this_bssid == selected_bssid:
+                    # use string, json loading will fail otherwise later
+                    network_cfg['ssid'] = ele['ssid']   # .decode('ascii')
+                    break
+
+        network_cfg['password'] = form_data['password']
+        self.logger.info('Network cfg: {}'.format(network_cfg))
+        # Network cfg: {'ssid': 'TP-LINK_FBFC3C', 'password': 'qwertz'}
+
+        if 'ssid' in network_cfg:
+            if isinstance(network_cfg['ssid'], bytes):
+                network_cfg['ssid'] = network_cfg['ssid'].decode('ascii')
+
+            self.logger.info('Raw network config: {}'.format(network_cfg))
+
+            # save data in encrypted mode
+            self.extend_wifi_config_data(data=network_cfg,
+                                         path=self._config_file,
+                                         encrypted=True)
+            self.logger.info('Saving of network config to {} done'.
+                             format(self._config_file))
+        else:
+            self.logger.info('No valid SSID found, will not save this net')
+
+    def _remove_wifi_config(self, form_data: dict) -> None:
+        """
+        Remove a WiFi network from the WiFi configuration file.
+
+        :param      form_data:  The form data
+        :type       form_data:  dict
+        """
+        if len(form_data):
+            loaded_cfg = self._load_wifi_config_data(path=self._config_file,
+                                                     encrypted=True)
+
+            updated_cfg = list()
+            updated_ssids = list()
+
+            for net in loaded_cfg:
+                if net['ssid'] not in form_data:
+                    updated_cfg.append(net)
+                    updated_ssids.append(net['ssid'])
+
+            self._configured_networks = updated_ssids.copy()
+
+            # create bytes array of the dict and encrypt it
+            encrypted_data = self._encrypt_data(data=updated_cfg)
+
+            # save to file as binary
+            GenericHelper.save_file(data=encrypted_data,
+                                    path=self._config_file,
+                                    mode='wb')
+            self.logger.debug('Saved encrypted data as json: {}'.
+                              format(encrypted_data))
+
+    # -------------------------------------------------------------------------
+    # Webserver functions
+
+    # @app.route('/landing_page')
+    def landing_page(self):
+        """Provide landing aka index page"""
+        return render_template('index.tpl.html')
+
+    # @app.route('/scan_result')
+    def scan_result(self):
+        """Provide latest found networks as JSON"""
+        return jsonify(self.latest_scan)
+
+    # @app.route('/select')
+    def wifi_selection(self):
+        """
+        Provide webpage to select WiFi network from list of available networks
+
+        Scanning just in time of accessing the page would block all processes
+        for approx. 2.5 sec.
+        Using the result provided by the scan thread via a message takes only
+        0.02 sec to complete
+        """
+        available_nets = self.latest_scan
+        content = self._render_network_inputs(available_nets=available_nets)
+
+        # do not stop scanning as page is updating scan results on the fly
+        # with XMLHTTP requests to @see scan_result
+        # stop scanning thread
+        # self.logger.info('Stopping scanning thread')
+        # self.scanning = False
+
+        return render_template('select.tpl.html', content=content)
+
+    # @app.route('/render_network_inputs')
+    def render_network_inputs(self) -> str:
+        """Return rendered network inputs content to webpage"""
+        available_nets = self.latest_scan
+        selected_bssid = self._selected_network_bssid
+        content = self._render_network_inputs(available_nets=available_nets,
+                                              selected_bssid=selected_bssid)
+        return content
+
+    # @app.route('/configure')
+    def wifi_configs(self):
+        """Provide webpage with table of configured networks"""
+        configured_nets = self.configured_networks
+        self.logger.debug('Existing config content: {}'.
+                          format(configured_nets))
+
+        if isinstance(configured_nets, str):
+            configured_nets = [configured_nets]
+
+        # disable submit button by default
+        return render_template('remove.tpl.html',
+                               wifi_nets=configured_nets,
+                               button_mode='disabled')
+
+    # @app.route('/save_wifi_config', methods=['POST', 'GET'])
+    def save_wifi_config(self):
+        form_data = dict(request.form)
+
+        self.logger.info('WiFi user input content: {}'.format(form_data))
+        # result if a network of the list has been selected
+        # {'bssid': 'a0f3c1fbfc3c', 'ssid': '', 'password': 'sdsfv'}
+        # result if a custom network is given
+        # {'ssid': 'myCustom Net', 'password': 'asdf1234'}
+        # result if nothing is selected
+        # {'ssid': '', 'password': ''}
+
+        self._save_wifi_config(form_data=form_data)
+
+        return redirect(url_for('landing_page'))
+
+    # @app.route('/remove_wifi_config', methods=['POST', 'GET'])
+    def remove_wifi_config(self):
+        """Remove a configured WiFi network"""
+        form_data = dict(request.form)
+
+        self.logger.info('Remove networks: {}'.format(form_data))
+        # Remove networks: {'FRITZ!Box 7490': 'FRITZ!Box 7490'}
+
+        self._remove_wifi_config(form_data=form_data)
+
+        return redirect(url_for('landing_page'))
+
+    # @app.route(re.compile('^\/(.+\.css)$'))
+    def styles(self):
+        """
+        Send gzipped CSS content if supported by client.
+
+        Shows specifying headers as a flat binary string, more efficient if
+        such headers are static.
+        """
+        file_path = str(request.url_rule)
+        headers = b'Cache-Control: max-age=86400\r\n'
+
+        if 'gzip' in str(request.headers):
+            self.logger.debug('gzip accepted for CSS style file')
+            file_path += '.gz'
+            headers += b'Content-Encoding: gzip\r\n'
+
+        self.logger.debug('Accessed file {}'.format(file_path))
+        return self.sendfile(writer=request,
+                             fname=file_path,
+                             content_type='text/css',
+                             headers=headers)
+
+    def sendfile(self,
+                 fname: str,
+                 content_type: str,
+                 headers: str,
+                 writer=None):
+        """
+        Fake function to send file in same style as in PicoWeb on Micropython
+
+        :param      fname:         The filename
+        :type       fname:         str
+        :param      content_type:  The content type
+        :type       content_type:  str
+        :param      headers:       The headers
+        :type       headers:       str
+        :param      writer:        The writer
+        :type       writer:        Optional
+        """
+        flask_root_folder = (Path(__file__).parent / '..' / '..').resolve()
+        file_path = str(flask_root_folder) + str(fname)
+
+        self.logger.debug('Open file {}'.format(file_path))
+        with open(file_path, 'rb') as file:
+            content = file.read()
+
+        res = make_response(content)
+        res.headers["Content-Type"] = content_type
+        # res.headers['Content-Length'] = len(content)
+
+        for ele in headers.decode('ascii').splitlines():
+            key, value = ele.split(':')
+            res.headers[key] = value.lstrip()
+
+        return res
+
+    def run(self,
+            host: str = '0.0.0.0',
+            port: int = 80,
+            debug: bool = False) -> None:
+        """
+        Run the web application
+
+        :param      host:   The hostname to listen on
+        :type       host:   str, optional
+        :param      port:   The port of the webserver
+        :type       port:   int, optional
+        :param      debug:  Flag to automatically reload for code changes and
+                            show debugger content
+        :type       debug:  bool, optional
+        """
+        self.logger.debug('Run app on {}:{} with debug: {}'.format(host,
+                                                                   port,
+                                                                   debug))
+        try:
+            # self.app.run()
+            self.app.run(debug=debug)
+            # self.app.run(host=host, port=port, debug=debug)
+        except KeyboardInterrupt:
+            self.logger.debug('Catched KeyboardInterrupt at run of web app')
+        except Exception as e:
+            self.logger.warning(e)
+
+
+if __name__ == "__main__":
+    # app.run()
+    # app.run(debug=True)
+    # app.run(host=host, port=port, debug=debug)
+    wm = WiFiManager(logger=None, quiet=False)
+    result = wm.load_and_connect()
+    print('Result of load_and_connect: {}'.format(result))
+    wm.start_config()
diff --git a/simulation/static/README.md b/simulation/static/README.md
new file mode 100644
index 0000000..2c2223e
--- /dev/null
+++ b/simulation/static/README.md
@@ -0,0 +1,98 @@
+# Static webpage files
+
+JavaScript and CSS files for simulation
+
+## General
+
+This simulation uses [bootstrap 5.1.3][ref-bootstrap]
+
+## Create compressed version
+
+### Why
+
+To speed up the data transfer between the device and a browser, many of them
+accept CSS and JS files as compressed `.gz` files.
+
+### Additional informations
+
+In some cases the following warning can be seen in the web console of the page
+
+	Layout rendering was forced before the page was fully loaded.
+
+This might be due to the reason of loading multiple (CSS) files within a given
+time frame. As a less powerfull device, such as an ESP32 or ESP8266, will have
+difficulties to provide these data in the expected time frame, the layout
+rendering might be forced, leading to a not as expected view.
+
+To avoid such issues, try to serve as less files as possible in the most
+compact way. To do so, use minified versions of CSS files (`*.min.css`) and
+combine multiple files into one. An even better performance is reached by
+compressing the file as shown onwards.
+
+To minify custom CSS files, search online for a CSS minifier or use
+[this one][ref-css-minifier]
+
+### How to
+
+To compress a file use `gzip` instead of `tar`. `tar` seems to break something
+in the compressed file. As a result the style might not be as with the non
+compressed version.
+
+This example shows how to compress `bootstrap.min.css` to a new file called
+`bootstrap.min.css.gz`
+
+```bash
+cd css
+
+gzip -r bootstrap.min.css -c > bootstrap.min.css.gz
+```
+
+<!--
+### How to
+
+Combine the as of now two custom CSS files (`list-groups.css` and `style.css`)
+into one file (`combined.css`)
+
+```bash
+cd css
+
+cat list-groups.css style.css > combined.css
+```
+
+Minify the generated file with a CSS minifier e.g. [this one][ref-css-minifier]
+into a file named `combined.min.css` inside the [css folder](`css`)
+
+Append the minified content to the `bootstrap.min.css` file after removing the
+`/*# sourceMappingURL=bootstrap.min.css.map */` string from the file, see
+[Stackoverflow Delete specific string from text file][ref-stackoverflow-sed]
+
+```bash
+cd css
+
+sed 's?\/\*\# sourceMappingURL=bootstrap.min.css.map \*/??' ./bootstrap.min.css > bootstrap_extended.min.css
+cat combined.min.css >> bootstrap_extended.min.css
+```
+
+Open the `bootstrap_extended.min.css` file and remove any empty lines before
+the attached content of `combined.min.css`
+
+Finally compress the extended bootstrap file
+
+```bash
+cd css
+
+tar -czf bootstrap_extended.min.css.gz bootstrap_extended.min.css
+```
+
+Or use the [provided script](compress_static_files.sh) to perform all these
+steps at once and follow the instructions on the terminal
+
+```bash
+sh create_single_css_file.sh
+```
+-->
+
+<!-- Links -->
+[ref-bootstrap]: https://getbootstrap.com/docs/5.1/getting-started/download/
+[ref-css-minifier]: https://www.toptal.com/developers/cssminifier/
+[ref-stackoverflow-sed]: https://stackoverflow.com/questions/5410757/how-to-delete-from-a-text-file-all-lines-that-contain-a-specific-string
diff --git a/simulation/static/css/bootstrap.min.css b/simulation/static/css/bootstrap.min.css
new file mode 100644
index 0000000..1472dec
--- /dev/null
+++ b/simulation/static/css/bootstrap.min.css
@@ -0,0 +1,7 @@
+@charset "UTF-8";/*!
+ * Bootstrap v5.1.3 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:.2rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}
+/*# sourceMappingURL=bootstrap.min.css.map */
\ No newline at end of file
diff --git a/simulation/static/css/bootstrap.min.css.gz b/simulation/static/css/bootstrap.min.css.gz
new file mode 100644
index 0000000..bd4f875
Binary files /dev/null and b/simulation/static/css/bootstrap.min.css.gz differ
diff --git a/simulation/static/css/bootstrap.min.css.old b/simulation/static/css/bootstrap.min.css.old
new file mode 100644
index 0000000..5b96335
--- /dev/null
+++ b/simulation/static/css/bootstrap.min.css.old
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap v3.4.1 (https://getbootstrap.com/)
+ * Copyright 2011-2019 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:"Glyphicons Halflings";src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format("embedded-opentype"),url(../fonts/glyphicons-halflings-regular.woff2) format("woff2"),url(../fonts/glyphicons-halflings-regular.woff) format("woff"),url(../fonts/glyphicons-halflings-regular.ttf) format("truetype"),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:"Glyphicons Halflings";font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:""}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*=col-]{padding-right:0;padding-left:0}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s,-webkit-box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],.input-group-sm input[type=time],input[type=date].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm,input[type=time].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],.input-group-lg input[type=time],input[type=date].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg,input[type=time].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;background-image:none;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out,-o-transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:12px;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;font-size:14px;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover>.arrow{border-width:11px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out,-o-transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;outline:0;filter:alpha(opacity=90);opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:"\2039"}.carousel-control .icon-next:before{content:"\203a"}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}
+/*# sourceMappingURL=bootstrap.min.css.map */
\ No newline at end of file
diff --git a/simulation/static/css/list-groups.css b/simulation/static/css/list-groups.css
new file mode 100644
index 0000000..11351f8
--- /dev/null
+++ b/simulation/static/css/list-groups.css
@@ -0,0 +1,61 @@
+.b-example-divider {
+  height: 3rem;
+  background-color: rgba(0, 0, 0, .1);
+  border: solid rgba(0, 0, 0, .15);
+  border-width: 1px 0;
+  box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
+}
+
+.bi {
+  vertical-align: -.125em;
+  fill: currentColor;
+}
+
+.opacity-50 { opacity: .5; }
+.opacity-75 { opacity: .75; }
+
+.list-group {
+  width: auto;
+  max-width: 460px;
+  margin: 4rem auto;
+}
+
+.form-check-input:checked + .form-checked-content {
+  opacity: .5;
+}
+
+.form-check-input-placeholder {
+  pointer-events: none;
+  border-style: dashed;
+}
+[contenteditable]:focus {
+  outline: 0;
+}
+
+.list-group-checkable {
+  display: grid;
+  gap: .5rem;
+  border: 0;
+}
+.list-group-checkable .list-group-item {
+  cursor: pointer;
+  border-radius: .5rem;
+}
+.list-group-item-check {
+  position: absolute;
+  clip: rect(0, 0, 0, 0);
+  pointer-events: none;
+}
+.list-group-item-check:hover + .list-group-item {
+  background-color: var(--bs-light);
+}
+.list-group-item-check:checked + .list-group-item {
+  color: #fff;
+  background-color: var(--bs-blue);
+}
+.list-group-item-check[disabled] + .list-group-item,
+.list-group-item-check:disabled + .list-group-item {
+  pointer-events: none;
+  filter: none;
+  opacity: .5;
+}
diff --git a/simulation/static/css/main.css b/simulation/static/css/main.css
new file mode 100644
index 0000000..82337f6
--- /dev/null
+++ b/simulation/static/css/main.css
@@ -0,0 +1,8 @@
+body {
+  padding-top: 50px;
+}
+
+.top-pad {
+  padding: 40px 15px;
+  text-align: center;
+}
\ No newline at end of file
diff --git a/simulation/static/css/style.css b/simulation/static/css/style.css
new file mode 100644
index 0000000..b981f9b
--- /dev/null
+++ b/simulation/static/css/style.css
@@ -0,0 +1,140 @@
+.overlay {
+  position:fixed;
+  top:0;
+  left:0;
+  right:0;
+  bottom:0;
+  background-color: gray;
+  color: white;
+  opacity: 1;
+  transition: 0.5s;
+  visibility: visible;
+}
+.overlay.hidden {
+  opacity: 0;
+  visibility: hidden;
+}
+.loader {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  z-index: 1;
+  width: 120px;
+  height: 120px;
+  margin: -76px 0 0 -76px;
+  border: 16px solid #f3f3f3;
+  border-radius: 50%;
+  border-top: 16px solid #3498db;
+  -webkit-animation: spin 2s linear infinite;
+  animation: spin 2s linear infinite;
+}
+@-webkit-keyframes spin {
+  0% { -webkit-transform: rotate(0deg); }
+  100% { -webkit-transform: rotate(360deg); }
+}
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+.animate-bottom {
+  position: relative;
+  -webkit-animation-name: animatebottom;
+  -webkit-animation-duration: 1s;
+  animation-name: animatebottom;
+  animation-duration: 1s
+}
+@-webkit-keyframes animatebottom {
+  from { bottom:-100px; opacity:0 }
+  to { bottom:0px; opacity:1 }
+}
+@keyframes animatebottom {
+  from{ bottom:-100px; opacity:0 }
+  to{ bottom:0px; opacity:1 }
+}
+
+body {
+  padding:50px 80px;
+  font-family:"Lucida Grande","bitstream vera sans","trebuchet ms",sans-serif,verdana;
+}
+
+/* get rid of those system borders being generated for A tags */
+a:active {
+  outline:none;
+}
+
+:focus {
+  -moz-outline-style:none;
+}
+
+input[type=text], input[type=password], select {
+  width: 200px;
+  padding: 12px 20px;
+  margin: 8px 0;
+  margin-left: auto;
+  margin-right: auto;
+  box-sizing: border-box;
+  border: 3px solid #ccc;
+  -webkit-transition: 0.5s;
+  transition: 0.5s;
+  outline: none;
+  border-radius:10px;
+}
+
+input[type=text]:focus, input[type=password]:focus, select:focus {
+  border: 3px solid #4CAF50;
+}
+
+/*
+input[type=button], input[type=submit], input[type=reset] {
+  background-color: #4CAF50;
+  border: none;
+  color: white;
+  padding: 16px 32px;
+  text-decoration: none;
+  margin: 8px 0px;
+  margin-left: auto;
+  margin-right: auto;
+  cursor: pointer;
+  width: 200px;
+  height: 45px;
+  border-radius:10px;
+  display: inline-block;
+  box-shadow: 0 9px #999;
+}
+*/
+
+table {
+  font-family: arial, sans-serif;
+  border-collapse: collapse;
+  width: 100%;
+}
+
+td, th {
+  border: 1px solid #dddddd;
+  text-align: left;
+  padding: 8px;
+}
+
+tr:nth-child(even) {
+  background-color: #dddddd;
+}
+
+.center {
+  margin: auto;
+  width: 200px;
+
+  margin: 0;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  -ms-transform: translate(-50%, -50%);
+  transform: translate(-50%, -50%);
+}
+
+.button:hover {background-color: #3e8e41}
+
+.button:active {
+  background-color: #3e8e41;
+  box-shadow: 0 5px #666;
+  transform: translateY(4px);
+}
\ No newline at end of file
diff --git a/simulation/static/js/bootstrap.min.js b/simulation/static/js/bootstrap.min.js
new file mode 100644
index 0000000..eb0a8b4
--- /dev/null
+++ b/simulation/static/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+ * Bootstrap v3.4.1 (https://getbootstrap.com/)
+ * Copyright 2011-2019 Twitter, Inc.
+ * Licensed under the MIT license
+ */
+if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");!function(t){"use strict";var e=jQuery.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1||3<e[0])throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(),function(n){"use strict";n.fn.emulateTransitionEnd=function(t){var e=!1,i=this;n(this).one("bsTransitionEnd",function(){e=!0});return setTimeout(function(){e||n(i).trigger(n.support.transition.end)},t),this},n(function(){n.support.transition=function o(){var t=document.createElement("bootstrap"),e={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var i in e)if(t.style[i]!==undefined)return{end:e[i]};return!1}(),n.support.transition&&(n.event.special.bsTransitionEnd={bindType:n.support.transition.end,delegateType:n.support.transition.end,handle:function(t){if(n(t.target).is(this))return t.handleObj.handler.apply(this,arguments)}})})}(jQuery),function(s){"use strict";var e='[data-dismiss="alert"]',a=function(t){s(t).on("click",e,this.close)};a.VERSION="3.4.1",a.TRANSITION_DURATION=150,a.prototype.close=function(t){var e=s(this),i=e.attr("data-target");i||(i=(i=e.attr("href"))&&i.replace(/.*(?=#[^\s]*$)/,"")),i="#"===i?[]:i;var o=s(document).find(i);function n(){o.detach().trigger("closed.bs.alert").remove()}t&&t.preventDefault(),o.length||(o=e.closest(".alert")),o.trigger(t=s.Event("close.bs.alert")),t.isDefaultPrevented()||(o.removeClass("in"),s.support.transition&&o.hasClass("fade")?o.one("bsTransitionEnd",n).emulateTransitionEnd(a.TRANSITION_DURATION):n())};var t=s.fn.alert;s.fn.alert=function o(i){return this.each(function(){var t=s(this),e=t.data("bs.alert");e||t.data("bs.alert",e=new a(this)),"string"==typeof i&&e[i].call(t)})},s.fn.alert.Constructor=a,s.fn.alert.noConflict=function(){return s.fn.alert=t,this},s(document).on("click.bs.alert.data-api",e,a.prototype.close)}(jQuery),function(s){"use strict";var n=function(t,e){this.$element=s(t),this.options=s.extend({},n.DEFAULTS,e),this.isLoading=!1};function i(o){return this.each(function(){var t=s(this),e=t.data("bs.button"),i="object"==typeof o&&o;e||t.data("bs.button",e=new n(this,i)),"toggle"==o?e.toggle():o&&e.setState(o)})}n.VERSION="3.4.1",n.DEFAULTS={loadingText:"loading..."},n.prototype.setState=function(t){var e="disabled",i=this.$element,o=i.is("input")?"val":"html",n=i.data();t+="Text",null==n.resetText&&i.data("resetText",i[o]()),setTimeout(s.proxy(function(){i[o](null==n[t]?this.options[t]:n[t]),"loadingText"==t?(this.isLoading=!0,i.addClass(e).attr(e,e).prop(e,!0)):this.isLoading&&(this.isLoading=!1,i.removeClass(e).removeAttr(e).prop(e,!1))},this),0)},n.prototype.toggle=function(){var t=!0,e=this.$element.closest('[data-toggle="buttons"]');if(e.length){var i=this.$element.find("input");"radio"==i.prop("type")?(i.prop("checked")&&(t=!1),e.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==i.prop("type")&&(i.prop("checked")!==this.$element.hasClass("active")&&(t=!1),this.$element.toggleClass("active")),i.prop("checked",this.$element.hasClass("active")),t&&i.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var t=s.fn.button;s.fn.button=i,s.fn.button.Constructor=n,s.fn.button.noConflict=function(){return s.fn.button=t,this},s(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(t){var e=s(t.target).closest(".btn");i.call(e,"toggle"),s(t.target).is('input[type="radio"], input[type="checkbox"]')||(t.preventDefault(),e.is("input,button")?e.trigger("focus"):e.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(t){s(t.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(t.type))})}(jQuery),function(p){"use strict";var c=function(t,e){this.$element=p(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=e,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",p.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",p.proxy(this.pause,this)).on("mouseleave.bs.carousel",p.proxy(this.cycle,this))};function r(n){return this.each(function(){var t=p(this),e=t.data("bs.carousel"),i=p.extend({},c.DEFAULTS,t.data(),"object"==typeof n&&n),o="string"==typeof n?n:i.slide;e||t.data("bs.carousel",e=new c(this,i)),"number"==typeof n?e.to(n):o?e[o]():i.interval&&e.pause().cycle()})}c.VERSION="3.4.1",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(t){if(!/input|textarea/i.test(t.target.tagName)){switch(t.which){case 37:this.prev();break;case 39:this.next();break;default:return}t.preventDefault()}},c.prototype.cycle=function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(p.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(t){return this.$items=t.parent().children(".item"),this.$items.index(t||this.$active)},c.prototype.getItemForDirection=function(t,e){var i=this.getItemIndex(e);if(("prev"==t&&0===i||"next"==t&&i==this.$items.length-1)&&!this.options.wrap)return e;var o=(i+("prev"==t?-1:1))%this.$items.length;return this.$items.eq(o)},c.prototype.to=function(t){var e=this,i=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(t>this.$items.length-1||t<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){e.to(t)}):i==t?this.pause().cycle():this.slide(i<t?"next":"prev",this.$items.eq(t))},c.prototype.pause=function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&p.support.transition&&(this.$element.trigger(p.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(t,e){var i=this.$element.find(".item.active"),o=e||this.getItemForDirection(t,i),n=this.interval,s="next"==t?"left":"right",a=this;if(o.hasClass("active"))return this.sliding=!1;var r=o[0],l=p.Event("slide.bs.carousel",{relatedTarget:r,direction:s});if(this.$element.trigger(l),!l.isDefaultPrevented()){if(this.sliding=!0,n&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var h=p(this.$indicators.children()[this.getItemIndex(o)]);h&&h.addClass("active")}var d=p.Event("slid.bs.carousel",{relatedTarget:r,direction:s});return p.support.transition&&this.$element.hasClass("slide")?(o.addClass(t),"object"==typeof o&&o.length&&o[0].offsetWidth,i.addClass(s),o.addClass(s),i.one("bsTransitionEnd",function(){o.removeClass([t,s].join(" ")).addClass("active"),i.removeClass(["active",s].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger(d)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(i.removeClass("active"),o.addClass("active"),this.sliding=!1,this.$element.trigger(d)),n&&this.cycle(),this}};var t=p.fn.carousel;p.fn.carousel=r,p.fn.carousel.Constructor=c,p.fn.carousel.noConflict=function(){return p.fn.carousel=t,this};var e=function(t){var e=p(this),i=e.attr("href");i&&(i=i.replace(/.*(?=#[^\s]+$)/,""));var o=e.attr("data-target")||i,n=p(document).find(o);if(n.hasClass("carousel")){var s=p.extend({},n.data(),e.data()),a=e.attr("data-slide-to");a&&(s.interval=!1),r.call(n,s),a&&n.data("bs.carousel").to(a),t.preventDefault()}};p(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),p(window).on("load",function(){p('[data-ride="carousel"]').each(function(){var t=p(this);r.call(t,t.data())})})}(jQuery),function(a){"use strict";var r=function(t,e){this.$element=a(t),this.options=a.extend({},r.DEFAULTS,e),this.$trigger=a('[data-toggle="collapse"][href="#'+t.id+'"],[data-toggle="collapse"][data-target="#'+t.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};function n(t){var e,i=t.attr("data-target")||(e=t.attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"");return a(document).find(i)}function l(o){return this.each(function(){var t=a(this),e=t.data("bs.collapse"),i=a.extend({},r.DEFAULTS,t.data(),"object"==typeof o&&o);!e&&i.toggle&&/show|hide/.test(o)&&(i.toggle=!1),e||t.data("bs.collapse",e=new r(this,i)),"string"==typeof o&&e[o]()})}r.VERSION="3.4.1",r.TRANSITION_DURATION=350,r.DEFAULTS={toggle:!0},r.prototype.dimension=function(){return this.$element.hasClass("width")?"width":"height"},r.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var t,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(t=e.data("bs.collapse"))&&t.transitioning)){var i=a.Event("show.bs.collapse");if(this.$element.trigger(i),!i.isDefaultPrevented()){e&&e.length&&(l.call(e,"hide"),t||e.data("bs.collapse",null));var o=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[o](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var n=function(){this.$element.removeClass("collapsing").addClass("collapse in")[o](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return n.call(this);var s=a.camelCase(["scroll",o].join("-"));this.$element.one("bsTransitionEnd",a.proxy(n,this)).emulateTransitionEnd(r.TRANSITION_DURATION)[o](this.$element[0][s])}}}},r.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var t=a.Event("hide.bs.collapse");if(this.$element.trigger(t),!t.isDefaultPrevented()){var e=this.dimension();this.$element[e](this.$element[e]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var i=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};if(!a.support.transition)return i.call(this);this.$element[e](0).one("bsTransitionEnd",a.proxy(i,this)).emulateTransitionEnd(r.TRANSITION_DURATION)}}},r.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},r.prototype.getParent=function(){return a(document).find(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(t,e){var i=a(e);this.addAriaAndCollapsedClass(n(i),i)},this)).end()},r.prototype.addAriaAndCollapsedClass=function(t,e){var i=t.hasClass("in");t.attr("aria-expanded",i),e.toggleClass("collapsed",!i).attr("aria-expanded",i)};var t=a.fn.collapse;a.fn.collapse=l,a.fn.collapse.Constructor=r,a.fn.collapse.noConflict=function(){return a.fn.collapse=t,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(t){var e=a(this);e.attr("data-target")||t.preventDefault();var i=n(e),o=i.data("bs.collapse")?"toggle":e.data();l.call(i,o)})}(jQuery),function(a){"use strict";var r='[data-toggle="dropdown"]',o=function(t){a(t).on("click.bs.dropdown",this.toggle)};function l(t){var e=t.attr("data-target");e||(e=(e=t.attr("href"))&&/#[A-Za-z]/.test(e)&&e.replace(/.*(?=#[^\s]*$)/,""));var i="#"!==e?a(document).find(e):null;return i&&i.length?i:t.parent()}function s(o){o&&3===o.which||(a(".dropdown-backdrop").remove(),a(r).each(function(){var t=a(this),e=l(t),i={relatedTarget:this};e.hasClass("open")&&(o&&"click"==o.type&&/input|textarea/i.test(o.target.tagName)&&a.contains(e[0],o.target)||(e.trigger(o=a.Event("hide.bs.dropdown",i)),o.isDefaultPrevented()||(t.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",i)))))}))}o.VERSION="3.4.1",o.prototype.toggle=function(t){var e=a(this);if(!e.is(".disabled, :disabled")){var i=l(e),o=i.hasClass("open");if(s(),!o){"ontouchstart"in document.documentElement&&!i.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",s);var n={relatedTarget:this};if(i.trigger(t=a.Event("show.bs.dropdown",n)),t.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),i.toggleClass("open").trigger(a.Event("shown.bs.dropdown",n))}return!1}},o.prototype.keydown=function(t){if(/(38|40|27|32)/.test(t.which)&&!/input|textarea/i.test(t.target.tagName)){var e=a(this);if(t.preventDefault(),t.stopPropagation(),!e.is(".disabled, :disabled")){var i=l(e),o=i.hasClass("open");if(!o&&27!=t.which||o&&27==t.which)return 27==t.which&&i.find(r).trigger("focus"),e.trigger("click");var n=i.find(".dropdown-menu li:not(.disabled):visible a");if(n.length){var s=n.index(t.target);38==t.which&&0<s&&s--,40==t.which&&s<n.length-1&&s++,~s||(s=0),n.eq(s).trigger("focus")}}}};var t=a.fn.dropdown;a.fn.dropdown=function e(i){return this.each(function(){var t=a(this),e=t.data("bs.dropdown");e||t.data("bs.dropdown",e=new o(this)),"string"==typeof i&&e[i].call(t)})},a.fn.dropdown.Constructor=o,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=t,this},a(document).on("click.bs.dropdown.data-api",s).on("click.bs.dropdown.data-api",".dropdown form",function(t){t.stopPropagation()}).on("click.bs.dropdown.data-api",r,o.prototype.toggle).on("keydown.bs.dropdown.data-api",r,o.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",o.prototype.keydown)}(jQuery),function(a){"use strict";var s=function(t,e){this.options=e,this.$body=a(document.body),this.$element=a(t),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.fixedContent=".navbar-fixed-top, .navbar-fixed-bottom",this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};function r(o,n){return this.each(function(){var t=a(this),e=t.data("bs.modal"),i=a.extend({},s.DEFAULTS,t.data(),"object"==typeof o&&o);e||t.data("bs.modal",e=new s(this,i)),"string"==typeof o?e[o](n):i.show&&e.show(n)})}s.VERSION="3.4.1",s.TRANSITION_DURATION=300,s.BACKDROP_TRANSITION_DURATION=150,s.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},s.prototype.toggle=function(t){return this.isShown?this.hide():this.show(t)},s.prototype.show=function(i){var o=this,t=a.Event("show.bs.modal",{relatedTarget:i});this.$element.trigger(t),this.isShown||t.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){o.$element.one("mouseup.dismiss.bs.modal",function(t){a(t.target).is(o.$element)&&(o.ignoreBackdropClick=!0)})}),this.backdrop(function(){var t=a.support.transition&&o.$element.hasClass("fade");o.$element.parent().length||o.$element.appendTo(o.$body),o.$element.show().scrollTop(0),o.adjustDialog(),t&&o.$element[0].offsetWidth,o.$element.addClass("in"),o.enforceFocus();var e=a.Event("shown.bs.modal",{relatedTarget:i});t?o.$dialog.one("bsTransitionEnd",function(){o.$element.trigger("focus").trigger(e)}).emulateTransitionEnd(s.TRANSITION_DURATION):o.$element.trigger("focus").trigger(e)}))},s.prototype.hide=function(t){t&&t.preventDefault(),t=a.Event("hide.bs.modal"),this.$element.trigger(t),this.isShown&&!t.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(s.TRANSITION_DURATION):this.hideModal())},s.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(t){document===t.target||this.$element[0]===t.target||this.$element.has(t.target).length||this.$element.trigger("focus")},this))},s.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(t){27==t.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},s.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},s.prototype.hideModal=function(){var t=this;this.$element.hide(),this.backdrop(function(){t.$body.removeClass("modal-open"),t.resetAdjustments(),t.resetScrollbar(),t.$element.trigger("hidden.bs.modal")})},s.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},s.prototype.backdrop=function(t){var e=this,i=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var o=a.support.transition&&i;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+i).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(t){this.ignoreBackdropClick?this.ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide())},this)),o&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!t)return;o?this.$backdrop.one("bsTransitionEnd",t).emulateTransitionEnd(s.BACKDROP_TRANSITION_DURATION):t()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var n=function(){e.removeBackdrop(),t&&t()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",n).emulateTransitionEnd(s.BACKDROP_TRANSITION_DURATION):n()}else t&&t()},s.prototype.handleUpdate=function(){this.adjustDialog()},s.prototype.adjustDialog=function(){var t=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&t?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!t?this.scrollbarWidth:""})},s.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},s.prototype.checkScrollbar=function(){var t=window.innerWidth;if(!t){var e=document.documentElement.getBoundingClientRect();t=e.right-Math.abs(e.left)}this.bodyIsOverflowing=document.body.clientWidth<t,this.scrollbarWidth=this.measureScrollbar()},s.prototype.setScrollbar=function(){var t=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"";var n=this.scrollbarWidth;this.bodyIsOverflowing&&(this.$body.css("padding-right",t+n),a(this.fixedContent).each(function(t,e){var i=e.style.paddingRight,o=a(e).css("padding-right");a(e).data("padding-right",i).css("padding-right",parseFloat(o)+n+"px")}))},s.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad),a(this.fixedContent).each(function(t,e){var i=a(e).data("padding-right");a(e).removeData("padding-right"),e.style.paddingRight=i||""})},s.prototype.measureScrollbar=function(){var t=document.createElement("div");t.className="modal-scrollbar-measure",this.$body.append(t);var e=t.offsetWidth-t.clientWidth;return this.$body[0].removeChild(t),e};var t=a.fn.modal;a.fn.modal=r,a.fn.modal.Constructor=s,a.fn.modal.noConflict=function(){return a.fn.modal=t,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(t){var e=a(this),i=e.attr("href"),o=e.attr("data-target")||i&&i.replace(/.*(?=#[^\s]+$)/,""),n=a(document).find(o),s=n.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(i)&&i},n.data(),e.data());e.is("a")&&t.preventDefault(),n.one("show.bs.modal",function(t){t.isDefaultPrevented()||n.one("hidden.bs.modal",function(){e.is(":visible")&&e.trigger("focus")})}),r.call(n,s,this)})}(jQuery),function(g){"use strict";var o=["sanitize","whiteList","sanitizeFn"],a=["background","cite","href","itemtype","longdesc","poster","src","xlink:href"],t={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},r=/^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi,l=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;function u(t,e){var i=t.nodeName.toLowerCase();if(-1!==g.inArray(i,e))return-1===g.inArray(i,a)||Boolean(t.nodeValue.match(r)||t.nodeValue.match(l));for(var o=g(e).filter(function(t,e){return e instanceof RegExp}),n=0,s=o.length;n<s;n++)if(i.match(o[n]))return!0;return!1}function n(t,e,i){if(0===t.length)return t;if(i&&"function"==typeof i)return i(t);if(!document.implementation||!document.implementation.createHTMLDocument)return t;var o=document.implementation.createHTMLDocument("sanitization");o.body.innerHTML=t;for(var n=g.map(e,function(t,e){return e}),s=g(o.body).find("*"),a=0,r=s.length;a<r;a++){var l=s[a],h=l.nodeName.toLowerCase();if(-1!==g.inArray(h,n))for(var d=g.map(l.attributes,function(t){return t}),p=[].concat(e["*"]||[],e[h]||[]),c=0,f=d.length;c<f;c++)u(d[c],p)||l.removeAttribute(d[c].nodeName);else l.parentNode.removeChild(l)}return o.body.innerHTML}var m=function(t,e){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",t,e)};m.VERSION="3.4.1",m.TRANSITION_DURATION=150,m.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0},sanitize:!0,sanitizeFn:null,whiteList:t},m.prototype.init=function(t,e,i){if(this.enabled=!0,this.type=t,this.$element=g(e),this.options=this.getOptions(i),this.$viewport=this.options.viewport&&g(document).find(g.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var o=this.options.trigger.split(" "),n=o.length;n--;){var s=o[n];if("click"==s)this.$element.on("click."+this.type,this.options.selector,g.proxy(this.toggle,this));else if("manual"!=s){var a="hover"==s?"mouseenter":"focusin",r="hover"==s?"mouseleave":"focusout";this.$element.on(a+"."+this.type,this.options.selector,g.proxy(this.enter,this)),this.$element.on(r+"."+this.type,this.options.selector,g.proxy(this.leave,this))}}this.options.selector?this._options=g.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},m.prototype.getDefaults=function(){return m.DEFAULTS},m.prototype.getOptions=function(t){var e=this.$element.data();for(var i in e)e.hasOwnProperty(i)&&-1!==g.inArray(i,o)&&delete e[i];return(t=g.extend({},this.getDefaults(),e,t)).delay&&"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),t.sanitize&&(t.template=n(t.template,t.whiteList,t.sanitizeFn)),t},m.prototype.getDelegateOptions=function(){var i={},o=this.getDefaults();return this._options&&g.each(this._options,function(t,e){o[t]!=e&&(i[t]=e)}),i},m.prototype.enter=function(t){var e=t instanceof this.constructor?t:g(t.currentTarget).data("bs."+this.type);if(e||(e=new this.constructor(t.currentTarget,this.getDelegateOptions()),g(t.currentTarget).data("bs."+this.type,e)),t instanceof g.Event&&(e.inState["focusin"==t.type?"focus":"hover"]=!0),e.tip().hasClass("in")||"in"==e.hoverState)e.hoverState="in";else{if(clearTimeout(e.timeout),e.hoverState="in",!e.options.delay||!e.options.delay.show)return e.show();e.timeout=setTimeout(function(){"in"==e.hoverState&&e.show()},e.options.delay.show)}},m.prototype.isInStateTrue=function(){for(var t in this.inState)if(this.inState[t])return!0;return!1},m.prototype.leave=function(t){var e=t instanceof this.constructor?t:g(t.currentTarget).data("bs."+this.type);if(e||(e=new this.constructor(t.currentTarget,this.getDelegateOptions()),g(t.currentTarget).data("bs."+this.type,e)),t instanceof g.Event&&(e.inState["focusout"==t.type?"focus":"hover"]=!1),!e.isInStateTrue()){if(clearTimeout(e.timeout),e.hoverState="out",!e.options.delay||!e.options.delay.hide)return e.hide();e.timeout=setTimeout(function(){"out"==e.hoverState&&e.hide()},e.options.delay.hide)}},m.prototype.show=function(){var t=g.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(t);var e=g.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(t.isDefaultPrevented()||!e)return;var i=this,o=this.tip(),n=this.getUID(this.type);this.setContent(),o.attr("id",n),this.$element.attr("aria-describedby",n),this.options.animation&&o.addClass("fade");var s="function"==typeof this.options.placement?this.options.placement.call(this,o[0],this.$element[0]):this.options.placement,a=/\s?auto?\s?/i,r=a.test(s);r&&(s=s.replace(a,"")||"top"),o.detach().css({top:0,left:0,display:"block"}).addClass(s).data("bs."+this.type,this),this.options.container?o.appendTo(g(document).find(this.options.container)):o.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var l=this.getPosition(),h=o[0].offsetWidth,d=o[0].offsetHeight;if(r){var p=s,c=this.getPosition(this.$viewport);s="bottom"==s&&l.bottom+d>c.bottom?"top":"top"==s&&l.top-d<c.top?"bottom":"right"==s&&l.right+h>c.width?"left":"left"==s&&l.left-h<c.left?"right":s,o.removeClass(p).addClass(s)}var f=this.getCalculatedOffset(s,l,h,d);this.applyPlacement(f,s);var u=function(){var t=i.hoverState;i.$element.trigger("shown.bs."+i.type),i.hoverState=null,"out"==t&&i.leave(i)};g.support.transition&&this.$tip.hasClass("fade")?o.one("bsTransitionEnd",u).emulateTransitionEnd(m.TRANSITION_DURATION):u()}},m.prototype.applyPlacement=function(t,e){var i=this.tip(),o=i[0].offsetWidth,n=i[0].offsetHeight,s=parseInt(i.css("margin-top"),10),a=parseInt(i.css("margin-left"),10);isNaN(s)&&(s=0),isNaN(a)&&(a=0),t.top+=s,t.left+=a,g.offset.setOffset(i[0],g.extend({using:function(t){i.css({top:Math.round(t.top),left:Math.round(t.left)})}},t),0),i.addClass("in");var r=i[0].offsetWidth,l=i[0].offsetHeight;"top"==e&&l!=n&&(t.top=t.top+n-l);var h=this.getViewportAdjustedDelta(e,t,r,l);h.left?t.left+=h.left:t.top+=h.top;var d=/top|bottom/.test(e),p=d?2*h.left-o+r:2*h.top-n+l,c=d?"offsetWidth":"offsetHeight";i.offset(t),this.replaceArrow(p,i[0][c],d)},m.prototype.replaceArrow=function(t,e,i){this.arrow().css(i?"left":"top",50*(1-t/e)+"%").css(i?"top":"left","")},m.prototype.setContent=function(){var t=this.tip(),e=this.getTitle();this.options.html?(this.options.sanitize&&(e=n(e,this.options.whiteList,this.options.sanitizeFn)),t.find(".tooltip-inner").html(e)):t.find(".tooltip-inner").text(e),t.removeClass("fade in top bottom left right")},m.prototype.hide=function(t){var e=this,i=g(this.$tip),o=g.Event("hide.bs."+this.type);function n(){"in"!=e.hoverState&&i.detach(),e.$element&&e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),t&&t()}if(this.$element.trigger(o),!o.isDefaultPrevented())return i.removeClass("in"),g.support.transition&&i.hasClass("fade")?i.one("bsTransitionEnd",n).emulateTransitionEnd(m.TRANSITION_DURATION):n(),this.hoverState=null,this},m.prototype.fixTitle=function(){var t=this.$element;(t.attr("title")||"string"!=typeof t.attr("data-original-title"))&&t.attr("data-original-title",t.attr("title")||"").attr("title","")},m.prototype.hasContent=function(){return this.getTitle()},m.prototype.getPosition=function(t){var e=(t=t||this.$element)[0],i="BODY"==e.tagName,o=e.getBoundingClientRect();null==o.width&&(o=g.extend({},o,{width:o.right-o.left,height:o.bottom-o.top}));var n=window.SVGElement&&e instanceof window.SVGElement,s=i?{top:0,left:0}:n?null:t.offset(),a={scroll:i?document.documentElement.scrollTop||document.body.scrollTop:t.scrollTop()},r=i?{width:g(window).width(),height:g(window).height()}:null;return g.extend({},o,a,r,s)},m.prototype.getCalculatedOffset=function(t,e,i,o){return"bottom"==t?{top:e.top+e.height,left:e.left+e.width/2-i/2}:"top"==t?{top:e.top-o,left:e.left+e.width/2-i/2}:"left"==t?{top:e.top+e.height/2-o/2,left:e.left-i}:{top:e.top+e.height/2-o/2,left:e.left+e.width}},m.prototype.getViewportAdjustedDelta=function(t,e,i,o){var n={top:0,left:0};if(!this.$viewport)return n;var s=this.options.viewport&&this.options.viewport.padding||0,a=this.getPosition(this.$viewport);if(/right|left/.test(t)){var r=e.top-s-a.scroll,l=e.top+s-a.scroll+o;r<a.top?n.top=a.top-r:l>a.top+a.height&&(n.top=a.top+a.height-l)}else{var h=e.left-s,d=e.left+s+i;h<a.left?n.left=a.left-h:d>a.right&&(n.left=a.left+a.width-d)}return n},m.prototype.getTitle=function(){var t=this.$element,e=this.options;return t.attr("data-original-title")||("function"==typeof e.title?e.title.call(t[0]):e.title)},m.prototype.getUID=function(t){for(;t+=~~(1e6*Math.random()),document.getElementById(t););return t},m.prototype.tip=function(){if(!this.$tip&&(this.$tip=g(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},m.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},m.prototype.enable=function(){this.enabled=!0},m.prototype.disable=function(){this.enabled=!1},m.prototype.toggleEnabled=function(){this.enabled=!this.enabled},m.prototype.toggle=function(t){var e=this;t&&((e=g(t.currentTarget).data("bs."+this.type))||(e=new this.constructor(t.currentTarget,this.getDelegateOptions()),g(t.currentTarget).data("bs."+this.type,e))),t?(e.inState.click=!e.inState.click,e.isInStateTrue()?e.enter(e):e.leave(e)):e.tip().hasClass("in")?e.leave(e):e.enter(e)},m.prototype.destroy=function(){var t=this;clearTimeout(this.timeout),this.hide(function(){t.$element.off("."+t.type).removeData("bs."+t.type),t.$tip&&t.$tip.detach(),t.$tip=null,t.$arrow=null,t.$viewport=null,t.$element=null})},m.prototype.sanitizeHtml=function(t){return n(t,this.options.whiteList,this.options.sanitizeFn)};var e=g.fn.tooltip;g.fn.tooltip=function i(o){return this.each(function(){var t=g(this),e=t.data("bs.tooltip"),i="object"==typeof o&&o;!e&&/destroy|hide/.test(o)||(e||t.data("bs.tooltip",e=new m(this,i)),"string"==typeof o&&e[o]())})},g.fn.tooltip.Constructor=m,g.fn.tooltip.noConflict=function(){return g.fn.tooltip=e,this}}(jQuery),function(n){"use strict";var s=function(t,e){this.init("popover",t,e)};if(!n.fn.tooltip)throw new Error("Popover requires tooltip.js");s.VERSION="3.4.1",s.DEFAULTS=n.extend({},n.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),((s.prototype=n.extend({},n.fn.tooltip.Constructor.prototype)).constructor=s).prototype.getDefaults=function(){return s.DEFAULTS},s.prototype.setContent=function(){var t=this.tip(),e=this.getTitle(),i=this.getContent();if(this.options.html){var o=typeof i;this.options.sanitize&&(e=this.sanitizeHtml(e),"string"===o&&(i=this.sanitizeHtml(i))),t.find(".popover-title").html(e),t.find(".popover-content").children().detach().end()["string"===o?"html":"append"](i)}else t.find(".popover-title").text(e),t.find(".popover-content").children().detach().end().text(i);t.removeClass("fade top bottom left right in"),t.find(".popover-title").html()||t.find(".popover-title").hide()},s.prototype.hasContent=function(){return this.getTitle()||this.getContent()},s.prototype.getContent=function(){var t=this.$element,e=this.options;return t.attr("data-content")||("function"==typeof e.content?e.content.call(t[0]):e.content)},s.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var t=n.fn.popover;n.fn.popover=function e(o){return this.each(function(){var t=n(this),e=t.data("bs.popover"),i="object"==typeof o&&o;!e&&/destroy|hide/.test(o)||(e||t.data("bs.popover",e=new s(this,i)),"string"==typeof o&&e[o]())})},n.fn.popover.Constructor=s,n.fn.popover.noConflict=function(){return n.fn.popover=t,this}}(jQuery),function(s){"use strict";function n(t,e){this.$body=s(document.body),this.$scrollElement=s(t).is(document.body)?s(window):s(t),this.options=s.extend({},n.DEFAULTS,e),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",s.proxy(this.process,this)),this.refresh(),this.process()}function e(o){return this.each(function(){var t=s(this),e=t.data("bs.scrollspy"),i="object"==typeof o&&o;e||t.data("bs.scrollspy",e=new n(this,i)),"string"==typeof o&&e[o]()})}n.VERSION="3.4.1",n.DEFAULTS={offset:10},n.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},n.prototype.refresh=function(){var t=this,o="offset",n=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),s.isWindow(this.$scrollElement[0])||(o="position",n=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var t=s(this),e=t.data("target")||t.attr("href"),i=/^#./.test(e)&&s(e);return i&&i.length&&i.is(":visible")&&[[i[o]().top+n,e]]||null}).sort(function(t,e){return t[0]-e[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},n.prototype.process=function(){var t,e=this.$scrollElement.scrollTop()+this.options.offset,i=this.getScrollHeight(),o=this.options.offset+i-this.$scrollElement.height(),n=this.offsets,s=this.targets,a=this.activeTarget;if(this.scrollHeight!=i&&this.refresh(),o<=e)return a!=(t=s[s.length-1])&&this.activate(t);if(a&&e<n[0])return this.activeTarget=null,this.clear();for(t=n.length;t--;)a!=s[t]&&e>=n[t]&&(n[t+1]===undefined||e<n[t+1])&&this.activate(s[t])},n.prototype.activate=function(t){this.activeTarget=t,this.clear();var e=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',i=s(e).parents("li").addClass("active");i.parent(".dropdown-menu").length&&(i=i.closest("li.dropdown").addClass("active")),i.trigger("activate.bs.scrollspy")},n.prototype.clear=function(){s(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var t=s.fn.scrollspy;s.fn.scrollspy=e,s.fn.scrollspy.Constructor=n,s.fn.scrollspy.noConflict=function(){return s.fn.scrollspy=t,this},s(window).on("load.bs.scrollspy.data-api",function(){s('[data-spy="scroll"]').each(function(){var t=s(this);e.call(t,t.data())})})}(jQuery),function(r){"use strict";var a=function(t){this.element=r(t)};function e(i){return this.each(function(){var t=r(this),e=t.data("bs.tab");e||t.data("bs.tab",e=new a(this)),"string"==typeof i&&e[i]()})}a.VERSION="3.4.1",a.TRANSITION_DURATION=150,a.prototype.show=function(){var t=this.element,e=t.closest("ul:not(.dropdown-menu)"),i=t.data("target");if(i||(i=(i=t.attr("href"))&&i.replace(/.*(?=#[^\s]*$)/,"")),!t.parent("li").hasClass("active")){var o=e.find(".active:last a"),n=r.Event("hide.bs.tab",{relatedTarget:t[0]}),s=r.Event("show.bs.tab",{relatedTarget:o[0]});if(o.trigger(n),t.trigger(s),!s.isDefaultPrevented()&&!n.isDefaultPrevented()){var a=r(document).find(i);this.activate(t.closest("li"),e),this.activate(a,a.parent(),function(){o.trigger({type:"hidden.bs.tab",relatedTarget:t[0]}),t.trigger({type:"shown.bs.tab",relatedTarget:o[0]})})}}},a.prototype.activate=function(t,e,i){var o=e.find("> .active"),n=i&&r.support.transition&&(o.length&&o.hasClass("fade")||!!e.find("> .fade").length);function s(){o.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),t.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),n?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu").length&&t.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),i&&i()}o.length&&n?o.one("bsTransitionEnd",s).emulateTransitionEnd(a.TRANSITION_DURATION):s(),o.removeClass("in")};var t=r.fn.tab;r.fn.tab=e,r.fn.tab.Constructor=a,r.fn.tab.noConflict=function(){return r.fn.tab=t,this};var i=function(t){t.preventDefault(),e.call(r(this),"show")};r(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',i).on("click.bs.tab.data-api",'[data-toggle="pill"]',i)}(jQuery),function(l){"use strict";var h=function(t,e){this.options=l.extend({},h.DEFAULTS,e);var i=this.options.target===h.DEFAULTS.target?l(this.options.target):l(document).find(this.options.target);this.$target=i.on("scroll.bs.affix.data-api",l.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",l.proxy(this.checkPositionWithEventLoop,this)),this.$element=l(t),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};function i(o){return this.each(function(){var t=l(this),e=t.data("bs.affix"),i="object"==typeof o&&o;e||t.data("bs.affix",e=new h(this,i)),"string"==typeof o&&e[o]()})}h.VERSION="3.4.1",h.RESET="affix affix-top affix-bottom",h.DEFAULTS={offset:0,target:window},h.prototype.getState=function(t,e,i,o){var n=this.$target.scrollTop(),s=this.$element.offset(),a=this.$target.height();if(null!=i&&"top"==this.affixed)return n<i&&"top";if("bottom"==this.affixed)return null!=i?!(n+this.unpin<=s.top)&&"bottom":!(n+a<=t-o)&&"bottom";var r=null==this.affixed,l=r?n:s.top;return null!=i&&n<=i?"top":null!=o&&t-o<=l+(r?a:e)&&"bottom"},h.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(h.RESET).addClass("affix");var t=this.$target.scrollTop(),e=this.$element.offset();return this.pinnedOffset=e.top-t},h.prototype.checkPositionWithEventLoop=function(){setTimeout(l.proxy(this.checkPosition,this),1)},h.prototype.checkPosition=function(){if(this.$element.is(":visible")){var t=this.$element.height(),e=this.options.offset,i=e.top,o=e.bottom,n=Math.max(l(document).height(),l(document.body).height());"object"!=typeof e&&(o=i=e),"function"==typeof i&&(i=e.top(this.$element)),"function"==typeof o&&(o=e.bottom(this.$element));var s=this.getState(n,t,i,o);if(this.affixed!=s){null!=this.unpin&&this.$element.css("top","");var a="affix"+(s?"-"+s:""),r=l.Event(a+".bs.affix");if(this.$element.trigger(r),r.isDefaultPrevented())return;this.affixed=s,this.unpin="bottom"==s?this.getPinnedOffset():null,this.$element.removeClass(h.RESET).addClass(a).trigger(a.replace("affix","affixed")+".bs.affix")}"bottom"==s&&this.$element.offset({top:n-t-o})}};var t=l.fn.affix;l.fn.affix=i,l.fn.affix.Constructor=h,l.fn.affix.noConflict=function(){return l.fn.affix=t,this},l(window).on("load",function(){l('[data-spy="affix"]').each(function(){var t=l(this),e=t.data();e.offset=e.offset||{},null!=e.offsetBottom&&(e.offset.bottom=e.offsetBottom),null!=e.offsetTop&&(e.offset.top=e.offsetTop),i.call(t,e)})})}(jQuery);
\ No newline at end of file
diff --git a/simulation/static/js/bootstrap.min.js.gz b/simulation/static/js/bootstrap.min.js.gz
new file mode 100644
index 0000000..4a0031d
Binary files /dev/null and b/simulation/static/js/bootstrap.min.js.gz differ
diff --git a/simulation/templates/index.tpl.html b/simulation/templates/index.tpl.html
new file mode 100755
index 0000000..0508a0b
--- /dev/null
+++ b/simulation/templates/index.tpl.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <meta name="description" content="WiFi Manager Index page">
+  <meta name="author" content="Jonas Scharpf aka brainelectronics">
+  <title>Setup</title>
+  <link href="static/css/bootstrap.min.css" rel="stylesheet">
+  <!-- <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico"> -->
+</head>
+<body>
+  <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
+    <div class="list-group text-center">
+      <a href="/" class="list-group-item list-group-item-action active">This page</a>
+      <a href="/select" class="list-group-item list-group-item-action">Select WiFi network</a>
+      <a href="/configure" class="list-group-item list-group-item-action">Manage WiFi networks</a>
+      <a href="/scan_result" class="list-group-item list-group-item-action">Latest Scan result<a>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/simulation/templates/remove.tpl.html b/simulation/templates/remove.tpl.html
new file mode 100644
index 0000000..3cce813
--- /dev/null
+++ b/simulation/templates/remove.tpl.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <meta name="description" content="Remove configured WiFi network">
+  <meta name="author" content="Jonas Scharpf aka brainelectronics">
+  <title>Remove WiFi Network</title>
+  <link href="static/css/bootstrap.min.css" rel="stylesheet">
+  <!-- <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico"> -->
+  <style type="text/css">
+    .overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:gray;color:#fff;opacity:1;transition:.5s;visibility:visible}
+    .overlay.hidden{opacity:0;visibility:hidden}
+    .loader{position:absolute;left:50%;top:50%;z-index:1;width:120px;height:120px;margin:-76px 0 0 -76px;border:16px solid #f3f3f3;border-radius:50%;border-top:16px solid #3498db;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}
+    @-webkit-keyframes spin{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(360deg)}
+    }@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
+  </style>
+  <style type="text/css">
+    .list-group{width:auto;max-width:460px;margin:4rem auto}
+    .form-check-input:checked+.form-checked-content{opacity:.5}
+    .form-check-input-placeholder{pointer-events:none;border-style:dashed}[contenteditable]:focus{outline:0}
+    .list-group-checkable{display:grid;gap:.5rem;border:0}
+    .list-group-checkable .list-group-item{cursor:pointer;border-radius:.5rem}
+    .list-group-item-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}
+    .list-group-item-check:hover+.list-group-item{background-color:var(--bs-light)}
+    .list-group-item-check:checked+.list-group-item{color:#fff;background-color:var(--bs-blue)}
+    .list-group-item-check:disabled+.list-group-item,.list-group-item-check[disabled]+.list-group-item{pointer-events:none;filter:none;opacity:.5}
+  </style>
+  <style type="text/css">
+    body {padding:50px 80px;}
+  </style>
+</head>
+<body>
+  <div id="overlay" class="overlay">
+    <div id="loader" class="loader"></div>
+  </div>
+
+  <div style="display:none;" id="myDiv" class="animate-bottom">
+    <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
+    <h4>Remove a configured WiFi network</h4>
+    <div class="list-group list-group-checkable">
+      <form action="remove_wifi_config" method="post" id="remove_wifi_config_form">
+        <div id="wifi_items">
+          <table class="table table-striped table-hover" name="wifi_network" id="wifi_network">
+            <tr>
+              <th>Network</th>
+            </tr>
+            {% for ele in wifi_nets %}
+              <tr>
+                <td>
+                  <input type="checkbox" class="form-check-input" name="{{ele}}" id="{{ele}}" value="{{ele}}">
+                  <label class="form-check-label" for="{{ele}}">
+                    {{ele}}
+                  </label>
+                </td>
+              </tr>
+            {% endfor %}
+          </table>
+        </div>
+        <div class="list-group">
+          <button type="submit" id="save" value="Save" class="btn btn-lg btn-primary list-group-item active" {{button_mode}}>Remove selected</button>
+          <form>
+            <input type="button" class="btn btn-lg btn-warning list-group-item" onclick="window.location.href = '/';" value="Go Back"/>
+          </form>
+        </div>
+      </form>
+    </div>
+    </div>
+  </div>
+
+  <script>
+    window.onload = function(e) {
+      setTimeout(showPage, 1000);
+    };
+    function showPage() {
+      document.getElementById("loader").style.display = "none";
+      document.getElementById("myDiv").style.display = "block";
+      //document.getElementById("rcorners3").style.display = "block";
+      document.getElementById("overlay").style.display = "none";
+    };
+    document.getElementById("remove_wifi_config_form").onsubmit = function(e) {
+      window.onbeforeunload = null;
+      return true;
+    };
+    const checkboxes = [...document.querySelectorAll('input[type=checkbox]')];
+    function anyChecked() {
+      return checkboxes.some(x=>x.checked);
+    }
+    checkboxes.forEach(checkbox => {
+      checkbox.addEventListener("click", e => {
+        // console.log('Checkbox ' + checkbox.id + ' clicked');
+        is_any_checked = anyChecked();
+        var button = document.getElementById("save");
+        if(is_any_checked) {
+          button.removeAttribute('disabled');
+        }
+        else {
+          button.setAttribute('disabled','');
+        }
+      })
+    })
+  </script>
+</body>
+</html>
diff --git a/simulation/templates/result.html b/simulation/templates/result.html
new file mode 100644
index 0000000..90c52d4
--- /dev/null
+++ b/simulation/templates/result.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+    <meta name="description" content="Result helper page">
+    <meta name="author" content="Jonas Scharpf aka brainelectronics">
+    <title>Result</title>
+    <link href="static/css/bootstrap.min.css" rel="stylesheet">
+    <!-- <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico"> -->
+  </head>
+
+  <body>
+    <!--
+    <div class="container">
+      <header class="d-flex justify-content-center py-3 ">
+        <ul class="nav nav-pills">
+          <li class="nav-item"><a href="#" class="nav-link" aria-current="page">Home</a></li>
+          <li class="nav-item"><a href="javascript:history.back()" class="nav-link" aria-current="page">Back</a></li>
+        </ul>
+      </header>
+    </div>
+    -->
+
+    <div class="container">
+      <div class="top-pad">
+        <table class="table">
+          <thead>
+            <tr>
+              <th scope="col">#</th>
+              <th scope="col">Key</th>
+              <th scope="col">Value</th>
+            </tr>
+          </thead>
+          <tbody>
+            {% for key, value in result.items() %}
+            <tr>
+              <th scope="row">{{ loop.index }}</th>
+              <td> {{ key }} </th>
+              <td> {{ value }} </td>
+            </tr>
+            {% endfor %}
+          </tbody>
+       </table>
+      </div>
+    </div>
+
+    <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/simulation/templates/select.tpl.html b/simulation/templates/select.tpl.html
new file mode 100644
index 0000000..a398739
--- /dev/null
+++ b/simulation/templates/select.tpl.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <meta name="description" content="Select WiFi network">
+  <meta name="author" content="Jonas Scharpf aka brainelectronics">
+  <title>Select WiFi</title>
+  <link href="static/css/bootstrap.min.css" rel="stylesheet">
+  <!-- <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico"> -->
+  <style type="text/css">
+    .overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:gray;color:#fff;opacity:1;transition:.5s;visibility:visible}
+    .overlay.hidden{opacity:0;visibility:hidden}
+    .loader{position:absolute;left:50%;top:50%;z-index:1;width:120px;height:120px;margin:-76px 0 0 -76px;border:16px solid #f3f3f3;border-radius:50%;border-top:16px solid #3498db;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}
+    @-webkit-keyframes spin{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(360deg)}
+    }@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
+  </style>
+  <style type="text/css">
+    .list-group{width:auto;max-width:460px;margin:4rem auto}
+    .form-check-input:checked+.form-checked-content{opacity:.5}
+    .form-check-input-placeholder{pointer-events:none;border-style:dashed}[contenteditable]:focus{outline:0}
+    .list-group-checkable{display:grid;gap:.5rem;border:0}
+    .list-group-checkable .list-group-item{cursor:pointer;border-radius:.5rem}
+    .list-group-item-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}
+    .list-group-item-check:hover+.list-group-item{background-color:var(--bs-light)}
+    .list-group-item-check:checked+.list-group-item{color:#fff;background-color:var(--bs-blue)}
+    .list-group-item-check:disabled+.list-group-item,.list-group-item-check[disabled]+.list-group-item{pointer-events:none;filter:none;opacity:.5}
+  </style>
+  <style type="text/css">
+    body {padding:50px 80px;}
+  </style>
+</head>
+<body>
+  <div id="overlay" class="overlay">
+    <div id="loader" class="loader"></div>
+  </div>
+
+  <div style="display:none;" id="myDiv" class="animate-bottom">
+    <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
+    <h4>Select a WiFi network</h4>
+    <div class="list-group list-group-checkable">
+      <form action="save_wifi_config" method="post" id="save_wifi_config_form">
+        <div name="wifi_network" id="wifi_network">
+          {{ content }}
+        </div>
+        <input type="text" class="w-100" name="ssid" id="ssid" placeholder="Custom Network Name">
+        <br>
+        <input type="password" class="w-100" name="password" id="password" placeholder="Passwort" onkeydown="if(event.keyCode==13)document.getElementById('save').click()"/>
+        <div class="list-group">
+          <button type="submit" id="save" value="Save" class="btn btn-lg btn-primary list-group-item active">Submit</button>
+          <form>
+            <input type="button" class="btn btn-lg btn-warning list-group-item" onclick="window.location.href = '/';" value="Go Back"/>
+          </form>
+        </div>
+      </form>
+    </div>
+    </div>
+  </div>
+
+  <script>
+    var selected_bssid = 0;
+    window.onload = function(e) {
+      setTimeout(showPage, 1000);
+      setTimeout(get_new_networks, 100);
+      var myInterval = setInterval(get_new_networks, 10000);
+    };
+    function showPage() {
+      document.getElementById("loader").style.display = "none";
+      document.getElementById("myDiv").style.display = "block";
+      //document.getElementById("rcorners3").style.display = "block";
+      document.getElementById("overlay").style.display = "none";
+    };
+    document.getElementById("save_wifi_config_form").onsubmit = function(e) {
+      window.onbeforeunload = null;
+      return true;
+    };
+    function remember_selected_element(cb) {
+      selected_bssid = cb.id;
+    }
+    function get_new_networks() {
+      // console.log('Getting new networks');
+      var xmlhttp = new XMLHttpRequest();
+      var url = "render_network_inputs";
+      xmlhttp.onreadystatechange = function() {
+        if (this.readyState == 4 && this.status == 200) {
+          var content = this.responseText;
+          // console.log(content);
+          document.getElementById("wifi_network").innerHTML = content;
+
+          if (selected_bssid) {
+            document.getElementById(selected_bssid).checked = true;
+          }
+        }
+      };
+      xmlhttp.open("GET", url);
+      xmlhttp.send();
+    }
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/simulation/templates/wifi_select_loader.tpl.html b/simulation/templates/wifi_select_loader.tpl.html
new file mode 100644
index 0000000..bac8166
--- /dev/null
+++ b/simulation/templates/wifi_select_loader.tpl.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <title>Select WiFi</title>
+  <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
+  <!-- <link href="style.css" rel="stylesheet"> -->
+</head>
+<body style="margin:0;">
+  <div id="overlay" class="overlay">
+    <div id="loader" class="loader"></div>
+  </div>
+
+  <div class="center">
+    <div style="display:none;" id="myDiv" class="animate-bottom">
+      <h4>Select a WiFi network</h4>
+      <form action="save_wifi_config" method="post" id="save_wifi_config_form">
+        <select name="wifi_network" id="wifi_network" size="5">
+          {% for ele in wifi_nets %}
+            <option value="{{ ele['bssid'] }}">{{ ele['ssid'] }} - {{ ele['quality'] }}&#37;</option>
+          {% endfor %}
+        </select>
+        <input type="text" name="ssid" id="ssid" placeholder="Custom Network Name">
+        <input type="password" name="password" id="password" placeholder="Passwort" onkeydown="if(event.keyCode==13)document.getElementById('save').click()"/>
+        <input type="submit" class="button" id="save" value="Save">
+      </form>
+      <form>
+        <input type="button" onclick="window.location.href = '/';" value="Go Back"/>
+      </form>
+    </div>
+  </div>
+
+  <script>
+    window.onload = function(e) {
+      setTimeout(showPage, 1000);
+      setTimeout(get_new_networks, 100);
+      var myInterval = setInterval(get_new_networks, 10000);
+    };
+    /*
+    window.onunload = function(e) {
+      var xhttp = new XMLHttpRequest();
+      // open(method, url, async)
+      xhttp.open("POST", "unload_data", false);
+      var data = 'name=wifi_select_loader';
+      xhttp.send(data);
+      // console.log('Data sent to unload_data');
+      return undefined; // show NO warning for user
+      // return true; // show warning for user, requires user confirmation
+    };
+    */
+    function showPage() {
+      document.getElementById("loader").style.display = "none";
+      document.getElementById("myDiv").style.display = "block";
+      //document.getElementById("rcorners3").style.display = "block";
+      document.getElementById("overlay").style.display = "none";
+    };
+    document.getElementById("save_wifi_config_form").onsubmit = function(e) {
+      window.onbeforeunload = null;
+      return true;
+    };
+    function get_new_networks() {
+      // console.log('Getting new networks');
+      var xmlhttp = new XMLHttpRequest();
+      var url = "scan_result";
+      xmlhttp.onreadystatechange = function() {
+        if (this.readyState == 4 && this.status == 200) {
+          var myArr = JSON.parse(this.responseText);
+          console.log(myArr);
+          update_wifi_nets_selection(myArr);
+        }
+      };
+      xmlhttp.open("GET", url);
+      xmlhttp.send();
+    }
+    function update_wifi_nets_selection(arr) {
+      // console.log('Updating options list function');
+      var oldSel = document.getElementById("wifi_network");
+      // backup current selection
+      var selected_index = oldSel.selectedIndex;
+      // console.log('Add options to cleared element' + oldSel.options);
+      for (var i = 0; i < arr.length; i++) {
+        var opt = document.createElement('option');
+        var new_option_text = arr[i].ssid + ' - ' + arr[i].quality + '%';
+        // 'NET-WORK_A - 42%'
+        opt.text = new_option_text;
+        opt.value = arr[i].bssid;
+        console.log('checking: ' + new_option_text + ', value: ' + opt.value);
+        is_unique = true;
+        for (var j = 0; j < oldSel.options.length; j++) {
+          let old_text = oldSel.options[j].text;
+          const old_text_array = old_text.split("-");
+          // ['NET', 'WORK_A ', ' 42%']
+          const old_ssid = old_text_array.slice(0, old_text_array.length - 1).join("-");
+          // 'NET-WORK_A '
+          const old_quality = old_text_array[old_text_array.length - 1];
+          // ' 42%'
+          // console.log('checking oldSel thing' + oldSel.options[j]);
+          if (old_ssid.trim() == arr[i].ssid) {
+            // this option already exists in oldSel
+            // console.log(arr[i].ssid + ' exists already, updating text');
+            // update text of option
+            oldSel.options[j].text = new_option_text;
+            is_unique = false;
+            break;
+          }
+        }
+        if (is_unique) {
+          // console.log(opt.text + ' is unique/new');
+          oldSel.add(opt, null);
+        }
+      }
+      // re apply selected index
+      oldSel.selectedIndex = selected_index;
+    }
+  </script>
+</body>
+</html>
diff --git a/static/style.css b/static/style.css
new file mode 100644
index 0000000..b981f9b
--- /dev/null
+++ b/static/style.css
@@ -0,0 +1,140 @@
+.overlay {
+  position:fixed;
+  top:0;
+  left:0;
+  right:0;
+  bottom:0;
+  background-color: gray;
+  color: white;
+  opacity: 1;
+  transition: 0.5s;
+  visibility: visible;
+}
+.overlay.hidden {
+  opacity: 0;
+  visibility: hidden;
+}
+.loader {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  z-index: 1;
+  width: 120px;
+  height: 120px;
+  margin: -76px 0 0 -76px;
+  border: 16px solid #f3f3f3;
+  border-radius: 50%;
+  border-top: 16px solid #3498db;
+  -webkit-animation: spin 2s linear infinite;
+  animation: spin 2s linear infinite;
+}
+@-webkit-keyframes spin {
+  0% { -webkit-transform: rotate(0deg); }
+  100% { -webkit-transform: rotate(360deg); }
+}
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+.animate-bottom {
+  position: relative;
+  -webkit-animation-name: animatebottom;
+  -webkit-animation-duration: 1s;
+  animation-name: animatebottom;
+  animation-duration: 1s
+}
+@-webkit-keyframes animatebottom {
+  from { bottom:-100px; opacity:0 }
+  to { bottom:0px; opacity:1 }
+}
+@keyframes animatebottom {
+  from{ bottom:-100px; opacity:0 }
+  to{ bottom:0px; opacity:1 }
+}
+
+body {
+  padding:50px 80px;
+  font-family:"Lucida Grande","bitstream vera sans","trebuchet ms",sans-serif,verdana;
+}
+
+/* get rid of those system borders being generated for A tags */
+a:active {
+  outline:none;
+}
+
+:focus {
+  -moz-outline-style:none;
+}
+
+input[type=text], input[type=password], select {
+  width: 200px;
+  padding: 12px 20px;
+  margin: 8px 0;
+  margin-left: auto;
+  margin-right: auto;
+  box-sizing: border-box;
+  border: 3px solid #ccc;
+  -webkit-transition: 0.5s;
+  transition: 0.5s;
+  outline: none;
+  border-radius:10px;
+}
+
+input[type=text]:focus, input[type=password]:focus, select:focus {
+  border: 3px solid #4CAF50;
+}
+
+/*
+input[type=button], input[type=submit], input[type=reset] {
+  background-color: #4CAF50;
+  border: none;
+  color: white;
+  padding: 16px 32px;
+  text-decoration: none;
+  margin: 8px 0px;
+  margin-left: auto;
+  margin-right: auto;
+  cursor: pointer;
+  width: 200px;
+  height: 45px;
+  border-radius:10px;
+  display: inline-block;
+  box-shadow: 0 9px #999;
+}
+*/
+
+table {
+  font-family: arial, sans-serif;
+  border-collapse: collapse;
+  width: 100%;
+}
+
+td, th {
+  border: 1px solid #dddddd;
+  text-align: left;
+  padding: 8px;
+}
+
+tr:nth-child(even) {
+  background-color: #dddddd;
+}
+
+.center {
+  margin: auto;
+  width: 200px;
+
+  margin: 0;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  -ms-transform: translate(-50%, -50%);
+  transform: translate(-50%, -50%);
+}
+
+.button:hover {background-color: #3e8e41}
+
+.button:active {
+  background-color: #3e8e41;
+  box-shadow: 0 5px #666;
+  transform: translateY(4px);
+}
\ No newline at end of file
diff --git a/templates/index.tpl b/templates/index.tpl
new file mode 100644
index 0000000..d2617df
--- /dev/null
+++ b/templates/index.tpl
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <meta name="description" content="WiFi Manager Index page">
+  <meta name="author" content="Jonas Scharpf aka brainelectronics">
+  <title>Setup</title>
+  <link href="bootstrap.min.css" rel="stylesheet">
+  <!--
+  <link href="style.css" rel="stylesheet">
+  <link href="bootstrap.min.css" rel="stylesheet">
+  <line href="list-groups.css" rel="stylesheet">
+  -->
+</head>
+<body>
+  <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
+    <div class="list-group text-center">
+      <a href="/" class="list-group-item list-group-item-action active">This page</a>
+      <a href="/select" class="list-group-item list-group-item-action">Select WiFi network</a>
+      <a href="/configure" class="list-group-item list-group-item-action">Manage WiFi networks</a>
+      <a href="/scan_result" class="list-group-item list-group-item-action">Latest Scan result<a>
+    </div>
+  </div>
+</body>
+</html>
diff --git a/templates/remove.tpl b/templates/remove.tpl
new file mode 100644
index 0000000..a25cfda
--- /dev/null
+++ b/templates/remove.tpl
@@ -0,0 +1,109 @@
+{% args req, wifi_nets, button_mode %}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <meta name="description" content="Remove configured WiFi network">
+  <meta name="author" content="Jonas Scharpf aka brainelectronics">
+  <title>Remove WiFi Network</title>
+  <link href="bootstrap.min.css" rel="stylesheet">
+  <!--
+  <link href="style.css" rel="stylesheet">
+  <link href="bootstrap.min.css" rel="stylesheet">
+  <link href="list-groups.css" rel="stylesheet">
+  -->
+  <style type="text/css">
+    .overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:gray;color:#fff;opacity:1;transition:.5s;visibility:visible}
+    .overlay.hidden{opacity:0;visibility:hidden}
+    .loader{position:absolute;left:50%;top:50%;z-index:1;width:120px;height:120px;margin:-76px 0 0 -76px;border:16px solid #f3f3f3;border-radius:50%;border-top:16px solid #3498db;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}
+    @-webkit-keyframes spin{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(360deg)}
+    }@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
+  </style>
+  <style type="text/css">
+    .list-group{width:auto;max-width:460px;margin:4rem auto}
+    .form-check-input:checked+.form-checked-content{opacity:.5}
+    .form-check-input-placeholder{pointer-events:none;border-style:dashed}[contenteditable]:focus{outline:0}
+    .list-group-checkable{display:grid;gap:.5rem;border:0}
+    .list-group-checkable .list-group-item{cursor:pointer;border-radius:.5rem}
+    .list-group-item-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}
+    .list-group-item-check:hover+.list-group-item{background-color:var(--bs-light)}
+    .list-group-item-check:checked+.list-group-item{color:#fff;background-color:var(--bs-blue)}
+    .list-group-item-check:disabled+.list-group-item,.list-group-item-check[disabled]+.list-group-item{pointer-events:none;filter:none;opacity:.5}
+  </style>
+  <style type="text/css">
+    body {padding:50px 80px;}
+  </style>
+</head>
+<body>
+  <div id="overlay" class="overlay">
+    <div id="loader" class="loader"></div>
+  </div>
+
+  <div style="display:none;" id="myDiv" class="animate-bottom">
+    <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
+    <h4>Remove a configured WiFi network</h4>
+    <div class="list-group list-group-checkable">
+      <form action="remove_wifi_config" method="post" id="remove_wifi_config_form">
+        <div id="wifi_items">
+          <table class="table table-striped table-hover" name="wifi_network" id="wifi_network">
+            <tr>
+              <th>Network</th>
+            </tr>
+            {% for ele in wifi_nets %}
+              <tr>
+                <td>
+                  <input type="checkbox" class="form-check-input" name="{{ele}}" id="{{ele}}" value="{{ele}}">
+                  <label class="form-check-label" for="{{ele}}">
+                    {{ele}}
+                  </label>
+                </td>
+              </tr>
+            {% endfor %}
+          </table>
+        </div>
+        <div class="list-group">
+          <button type="submit" id="save" value="Save" class="btn btn-lg btn-primary list-group-item active" {{button_mode}}>Remove selected</button>
+          <form>
+            <input type="button" class="btn btn-lg btn-warning list-group-item" onclick="window.location.href = '/';" value="Go Back"/>
+          </form>
+        </div>
+      </form>
+    </div>
+    </div>
+  </div>
+
+  <script>
+    window.onload = function(e) {
+      setTimeout(showPage, 1000);
+    };
+    function showPage() {
+      document.getElementById("loader").style.display = "none";
+      document.getElementById("myDiv").style.display = "block";
+      //document.getElementById("rcorners3").style.display = "block";
+      document.getElementById("overlay").style.display = "none";
+    };
+    document.getElementById("remove_wifi_config_form").onsubmit = function(e) {
+      window.onbeforeunload = null;
+      return true;
+    };
+    const checkboxes = [...document.querySelectorAll('input[type=checkbox]')];
+    function anyChecked() {
+      return checkboxes.some(x=>x.checked);
+    }
+    checkboxes.forEach(checkbox => {
+      checkbox.addEventListener("click", e => {
+        // console.log('Checkbox ' + checkbox.id + ' clicked');
+        is_any_checked = anyChecked();
+        var button = document.getElementById("save");
+        if(is_any_checked) {
+          button.removeAttribute('disabled');
+        }
+        else {
+          button.setAttribute('disabled','');
+        }
+      })
+    })
+  </script>
+</body>
+</html>
diff --git a/templates/select.tpl b/templates/select.tpl
new file mode 100644
index 0000000..2d898d9
--- /dev/null
+++ b/templates/select.tpl
@@ -0,0 +1,105 @@
+{% args req, content %}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <meta name="description" content="Select WiFi network">
+  <meta name="author" content="Jonas Scharpf aka brainelectronics">
+  <title>Select WiFi</title>
+  <link href="bootstrap.min.css" rel="stylesheet">
+  <!--
+  <link href="style.css" rel="stylesheet">
+  <link href="bootstrap.min.css" rel="stylesheet">
+  <link href="list-groups.css" rel="stylesheet">
+  -->
+  <style type="text/css">
+    .overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:gray;color:#fff;opacity:1;transition:.5s;visibility:visible}
+    .overlay.hidden{opacity:0;visibility:hidden}
+    .loader{position:absolute;left:50%;top:50%;z-index:1;width:120px;height:120px;margin:-76px 0 0 -76px;border:16px solid #f3f3f3;border-radius:50%;border-top:16px solid #3498db;-webkit-animation:spin 2s linear infinite;animation:spin 2s linear infinite}
+    @-webkit-keyframes spin{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(360deg)}
+    }@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
+  </style>
+  <style type="text/css">
+    .list-group{width:auto;max-width:460px;margin:4rem auto}
+    .form-check-input:checked+.form-checked-content{opacity:.5}
+    .form-check-input-placeholder{pointer-events:none;border-style:dashed}[contenteditable]:focus{outline:0}
+    .list-group-checkable{display:grid;gap:.5rem;border:0}
+    .list-group-checkable .list-group-item{cursor:pointer;border-radius:.5rem}
+    .list-group-item-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}
+    .list-group-item-check:hover+.list-group-item{background-color:var(--bs-light)}
+    .list-group-item-check:checked+.list-group-item{color:#fff;background-color:var(--bs-blue)}
+    .list-group-item-check:disabled+.list-group-item,.list-group-item-check[disabled]+.list-group-item{pointer-events:none;filter:none;opacity:.5}
+  </style>
+  <style type="text/css">
+    body {padding:50px 80px;}
+  </style>
+</head>
+<body>
+  <div id="overlay" class="overlay">
+    <div id="loader" class="loader"></div>
+  </div>
+
+  <div style="display:none;" id="myDiv" class="animate-bottom">
+    <div class="d-flex flex-column min-vh-100 justify-content-center align-items-center">
+    <h4>Select a WiFi network</h4>
+    <div class="list-group list-group-checkable">
+      <form action="save_wifi_config" method="post" id="save_wifi_config_form">
+        <div name="wifi_network" id="wifi_network">
+          {{ content }}
+        </div>
+        <input type="text" class="w-100" name="ssid" id="ssid" placeholder="Custom Network Name">
+        <br>
+        <input type="password" class="w-100" name="password" id="password" placeholder="Passwort" onkeydown="if(event.keyCode==13)document.getElementById('save').click()"/>
+        <div class="list-group">
+          <button type="submit" id="save" value="Save" class="btn btn-lg btn-primary list-group-item active">Submit</button>
+          <form>
+            <input type="button" class="btn btn-lg btn-warning list-group-item" onclick="window.location.href = '/';" value="Go Back"/>
+          </form>
+        </div>
+      </form>
+    </div>
+    </div>
+  </div>
+
+  <script>
+    var selected_bssid = 0;
+    window.onload = function(e) {
+      setTimeout(showPage, 1000);
+      setTimeout(get_new_networks, 100);
+      var myInterval = setInterval(get_new_networks, 10000);
+    };
+    function showPage() {
+      document.getElementById("loader").style.display = "none";
+      document.getElementById("myDiv").style.display = "block";
+      //document.getElementById("rcorners3").style.display = "block";
+      document.getElementById("overlay").style.display = "none";
+    };
+    document.getElementById("save_wifi_config_form").onsubmit = function(e) {
+      window.onbeforeunload = null;
+      return true;
+    };
+    function remember_selected_element(cb) {
+      selected_bssid = cb.id;
+    }
+    function get_new_networks() {
+      // console.log('Getting new networks');
+      var xmlhttp = new XMLHttpRequest();
+      var url = "render_network_inputs";
+      xmlhttp.onreadystatechange = function() {
+        if (this.readyState == 4 && this.status == 200) {
+          var content = this.responseText;
+          // console.log(content);
+          document.getElementById("wifi_network").innerHTML = content;
+
+          if (selected_bssid) {
+            document.getElementById(selected_bssid).checked = true;
+          }
+        }
+      };
+      xmlhttp.open("GET", url);
+      xmlhttp.send();
+    }
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/templates/wifi_configs.tpl b/templates/wifi_configs.tpl
new file mode 100644
index 0000000..41213b7
--- /dev/null
+++ b/templates/wifi_configs.tpl
@@ -0,0 +1,94 @@
+{% args req, wifi_nets %}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <title>Remove WiFi Network</title>
+  <link href="style.css" rel="stylesheet">
+</head>
+<body style="margin:0;">
+  <div id="overlay" class="overlay">
+    <div id="loader" class="loader"></div>
+  </div>
+
+  <div class="center" style="width:400px;">
+    <div style="display:none;" id="myDiv" class="animate-bottom">
+      <h4>Remove a configured WiFi network</h4>
+      <table name="wifi_network" id="wifi_network">
+        <tr>
+          <th>Network</th>
+          <th></th>
+        </tr>
+        {% for ele in wifi_nets %}
+          <tr>
+            <td>{{ele}}</td>
+            <td>
+              <button class="btnDelete">Remove</button>
+            </td>
+          </tr>
+        {% endfor %}
+      </table>
+    </div>
+  </div>
+  <script>
+    window.onload = function(e) {
+      setTimeout(showPage, 1000);
+    };
+    function showPage() {
+      document.getElementById("loader").style.display = "none";
+      document.getElementById("myDiv").style.display = "block";
+      //document.getElementById("rcorners3").style.display = "block";
+      document.getElementById("overlay").style.display = "none";
+    };
+    document.getElementById("wifi_network").addEventListener("click", e => {
+      console.log('Button hit');
+      var xhttp = new XMLHttpRequest();
+      var url = 'remove_wifi_config';
+      xhttp.open("POST", url, true);
+      xhttp.setRequestHeader("Content-Type", "application/json;charset=utf-8")
+      xhttp.send(JSON.stringify({"name": "all", "index": 0}));
+    })
+
+    /*
+    document.getElementById("wifi_network").addEventListener("click", e => {
+      console.log('Button hit');
+
+      var closest_match = e.target.closest('tr');
+      // console.log('closest_match: ' + closest_match);
+      // console.log('closest_match rowIndex: ' + closest_match.rowIndex);
+      // console.log('Available cells: ' + closest_match.cells);
+      // console.log('Available cells length: ' + closest_match.cells.length);
+
+      network_to_remove = closest_match.cells[0].innerText;
+      row_index = closest_match.rowIndex;
+      console.log('Remove row with name: ' + network_to_remove + ' at index ' + row_index);
+      if (row_index) {
+        var confirm_text = network_to_remove + ' will be deleted from configured networks';
+        if (confirm(confirm_text)) {
+          // OK clicked
+          report_delete_network(network_to_remove, row_index);
+          // remove this row from the table
+          closest_match.remove();
+        }
+        // else {
+        //   // cancel clicked
+        // }
+      }
+    })
+
+    function report_delete_network(name, index) {
+      console.log('Report removal of ' + name);
+
+      var xhttp = new XMLHttpRequest();
+      var url = 'remove_wifi_config';
+      var params = 'name=' + name + '&index=' + index;
+
+      console.log('POST data: ' + params)
+      xhttp.open("POST", url, true);
+      xhttp.send(params);
+    };
+    */
+  </script>
+</body>
+</html>
diff --git a/templates/wifi_select_loader.tpl b/templates/wifi_select_loader.tpl
new file mode 100644
index 0000000..88a59fb
--- /dev/null
+++ b/templates/wifi_select_loader.tpl
@@ -0,0 +1,118 @@
+{% args req, wifi_nets %}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
+  <title>Select WiFi</title>
+  <link href="style.css" rel="stylesheet">
+</head>
+<body style="margin:0;">
+  <div id="overlay" class="overlay">
+    <div id="loader" class="loader"></div>
+  </div>
+
+  <div class="center">
+    <div style="display:none;" id="myDiv" class="animate-bottom">
+      <h4>Select a WiFi network</h4>
+      <form action="save_wifi_config" method="post" id="save_wifi_config_form">
+        <select name="wifi_network" id="wifi_network" size="5">
+          {% for ele in wifi_nets %}
+            <option value="{{ele['bssid']}}">{{ele['ssid'].decode('ascii')}} - {{ele['quality']}}&#37;</option>
+          {% endfor %}
+        </select>
+        <input type="text" name="ssid" id="ssid" placeholder="Custom Network Name">
+        <input type="password" name="password" id="password" placeholder="Passwort" onkeydown="if(event.keyCode==13)document.getElementById('save').click()"/>
+        <input type="submit" class="button" id="save" value="Save">
+      </form>
+      <form>
+        <input type="button" onclick="window.location.href = '/';" value="Go Back"/>
+      </form>
+    </div>
+  </div>
+
+  <script>
+    window.onload = function(e) {
+      setTimeout(showPage, 1000);
+      setTimeout(get_new_networks, 100);
+      var myInterval = setInterval(get_new_networks, 10000);
+    };
+    /*
+    window.onunload = function(e) {
+      var xhttp = new XMLHttpRequest();
+      // open(method, url, async)
+      xhttp.open("POST", "unload_data", false);
+      var data = 'name=wifi_select_loader';
+      xhttp.send(data);
+      // console.log('Data sent to unload_data');
+      return undefined; // show NO warning for user
+      // return true; // show warning for user, requires user confirmation
+    };
+    */
+    function showPage() {
+      document.getElementById("loader").style.display = "none";
+      document.getElementById("myDiv").style.display = "block";
+      //document.getElementById("rcorners3").style.display = "block";
+      document.getElementById("overlay").style.display = "none";
+    };
+    document.getElementById("save_wifi_config_form").onsubmit = function(e) {
+      window.onbeforeunload = null;
+      return true;
+    };
+    function get_new_networks() {
+      // console.log('Getting new networks');
+      var xmlhttp = new XMLHttpRequest();
+      var url = "scan_result";
+      xmlhttp.onreadystatechange = function() {
+        if (this.readyState == 4 && this.status == 200) {
+          var myArr = JSON.parse(this.responseText);
+          console.log(myArr);
+          update_wifi_nets_selection(myArr);
+        }
+      };
+      xmlhttp.open("GET", url);
+      xmlhttp.send();
+    }
+    function update_wifi_nets_selection(arr) {
+      // console.log('Updating options list function');
+      var oldSel = document.getElementById("wifi_network");
+      // backup current selection
+      var selected_index = oldSel.selectedIndex;
+      // console.log('Add options to cleared element' + oldSel.options);
+      for (var i = 0; i < arr.length; i++) {
+        var opt = document.createElement('option');
+        var new_option_text = arr[i].ssid + ' - ' + arr[i].quality + '%';
+        // 'NET-WORK_A - 42%'
+        opt.text = new_option_text;
+        opt.value = arr[i].bssid;
+        console.log('checking: ' + new_option_text + ', value: ' + opt.value);
+        is_unique = true;
+        for (var j = 0; j < oldSel.options.length; j++) {
+          let old_text = oldSel.options[j].text;
+          const old_text_array = old_text.split("-");
+          // ['NET', 'WORK_A ', ' 42%']
+          const old_ssid = old_text_array.slice(0, old_text_array.length - 1).join("-");
+          // 'NET-WORK_A '
+          const old_quality = old_text_array[old_text_array.length - 1];
+          // ' 42%'
+          // console.log('checking oldSel thing' + oldSel.options[j]);
+          if (old_ssid.trim() == arr[i].ssid) {
+            // this option already exists in oldSel
+            // console.log(arr[i].ssid + ' exists already, updating text');
+            // update text of option
+            oldSel.options[j].text = new_option_text;
+            is_unique = false;
+            break;
+          }
+        }
+        if (is_unique) {
+          // console.log(opt.text + ' is unique/new');
+          oldSel.add(opt, null);
+        }
+      }
+      // re apply selected index
+      oldSel.selectedIndex = selected_index;
+    }
+  </script>
+</body>
+</html>
diff --git a/wifi_manager.py b/wifi_manager.py
new file mode 100644
index 0000000..c230aae
--- /dev/null
+++ b/wifi_manager.py
@@ -0,0 +1,765 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+main script, do your stuff here, similar to the loop() function on Arduino
+"""
+
+# system packages
+import gc
+import json
+import machine
+# import os
+import _thread
+import time
+import ubinascii
+import ucryptolib
+
+from primitives.message import Message
+# https://github.com/peterhinch/micropython-async/blob/a87bda1b716090da27fd288cc8b19b20525ea20c/v3/primitives/
+
+# pip installed packages
+import picoweb
+# import ulogging as logging
+import ure as re
+# https://github.com/pfalcon/picoweb
+# https://github.com/pfalcon/pycopy-lib/blob/9b8bbae774140563e9be138724de083267f99ff9/logging/
+
+# custom packages
+from helpers.generic_helper import GenericHelper
+from helpers.led_helper import Neopixel     # Led
+from helpers.path_helper import PathHelper
+from helpers.wifi_helper import WifiHelper
+
+# not natively supported on micropython, see lib/typing.py
+from typing import List, Union
+
+
+class WiFiManager(object):
+    """docstring for WiFiManager"""
+    def __init__(self, logger=None, quiet=False, name=__name__):
+        # setup and configure logger if none is provided
+        if logger is None:
+            logger = GenericHelper.create_logger(logger_name=self.__class__.__name__)
+            GenericHelper.set_level(logger, 'debug')
+        self.logger = logger
+        self.logger.disabled = quiet
+        self._config_file = 'wifi-secure.json'
+
+        self.app = picoweb.WebApp(pkg=None)
+        self.wh = WifiHelper()
+        self.pixel = Neopixel()
+        self.pixel.color = 'yellow'
+        self.pixel.intensity = 20
+
+        self.event_sinks = set()
+
+        self._add_app_routes()
+
+        # other setup and init here
+        # AES key shall be 16 or 32 bytes
+        required_len = 16
+        uuid = ubinascii.hexlify(machine.unique_id())
+        amount = required_len // len(uuid) + (required_len % len(uuid) > 0)
+        self._enc_key = (uuid * amount).decode('ascii')[:required_len]
+
+        self._configured_networks = list()
+        self._selected_network_bssid = ''
+
+        # WiFi scan specific defines
+        self._scan_lock = _thread.allocate_lock()
+        self._scan_interval = 5000  # milliseconds
+        # Queue also works, but in this case there is no need for a history
+        self._scan_net_msg = Message()
+        self._scan_net_msg.set([])  # empty list, required by save_wifi_config
+        self._latest_scan = None
+
+        # start the WiFi scanning thread as soon as "start_config" is called
+        self.scanning = False
+
+    def load_and_connect(self) -> bool:
+        """
+        Load configured network credentials and try to connect to those
+
+        :returns:   Result of connection
+        :rtype:     bool
+        """
+        result = False
+
+        # check wifi config file existance
+        if PathHelper.exists(path=self._config_file):
+            self.logger.debug('Encrypted wifi config file exists')
+            loaded_cfg = self._load_wifi_config_data(path=self._config_file,
+                                                     encrypted=True)
+
+            private_cfg = None
+            ssids = list()
+            passwords = list()
+
+            if isinstance(loaded_cfg, list):
+                # deepcopy would be necessary but not builtin in micropython
+                private_cfg = json.loads(json.dumps(loaded_cfg))
+                for net in private_cfg:
+                    if 'ssid' in net:
+                        ssids.append(net['ssid'])
+                    if 'password' in net:
+                        passwords.append(net['password'])
+                        net['password'] = '*' * 8
+                    else:
+                        passwords.append('')
+                self._configured_networks = list(ssids).copy()
+            elif isinstance(loaded_cfg, dict):
+                private_cfg = loaded_cfg.copy()
+                if 'ssid' in loaded_cfg:
+                    ssids = loaded_cfg['ssid']
+                if 'password' in private_cfg:
+                    passwords = loaded_cfg['password']
+                    private_cfg['password'] = '*' * 8
+
+                self._configured_networks = ssids
+
+            # self._configured_networks = list(ssids).copy()
+            # self.logger.debug('All SSIDs: {}'.format(ssids))
+            self.logger.debug('Config content: {}'.format(loaded_cfg))
+            self.logger.debug('Private config content: {}'.
+                              format(private_cfg))
+            self.logger.debug('Configured networks: {}'.
+                              format(self._configured_networks))
+
+            self.logger.info('Connecting to loaded network config...')
+            result = WifiHelper.connect(ssid=ssids,
+                                        password=passwords,
+                                        timeout=5,
+                                        reconnect=False)
+            self.logger.debug('Result of connection: {}'.format(result))
+        else:
+            self.logger.debug('WiFi config file does not (yet) exist')
+
+        return result
+
+    def start_config(self) -> None:
+        """Start WiFi manager accesspoint and webserver."""
+        self.logger.info('Starting Manager with AccessPoint')
+        result = self.wh.create_ap(ssid='WiFiManager',
+                                   password='',
+                                   channel=11,
+                                   timeout=5)
+        self.logger.debug('Created AP: {}'.format(result))
+        ifconfig = self.wh.ifconfig_ap
+        self.logger.debug(ifconfig)
+
+        # start scanning for available networks
+        self.scanning = True
+
+        # finally
+        self.run(host=ifconfig.ip, port=80, debug=True)
+
+        self.logger.debug('Finished running the PicoWeb application')
+        self.scanning = False
+        self.logger.debug('Stopped scanning thread')
+
+        # wait some time to end all threads savely
+        time.sleep(5)
+
+        gc.collect()
+        self.logger.debug('Goodbye from WiFiManager')
+
+    def _add_app_routes(self) -> None:
+        """Add all application routes to the webserver."""
+
+        # self.app.add_url_rule(url='/', func=self.index)
+        # @app.route('/index')
+        # def index(self, req, resp): pass
+        # https://github.com/pfalcon/picoweb/blob/b74428ebdde97ed1795338c13a3bdf05d71366a0/picoweb/__init__.py#L251
+        self.app.add_url_rule(url="/", func=self.landing_page)
+        self.app.add_url_rule(url='/select', func=self.wifi_selection)
+        self.app.add_url_rule("/render_network_inputs",
+                              func=self.render_network_inputs)
+        self.app.add_url_rule(url='/configure', func=self.wifi_configs)
+        self.app.add_url_rule(url='/save_wifi_config',
+                              func=self.save_wifi_config)
+        self.app.add_url_rule(url='/remove_wifi_config',
+                              func=self.remove_wifi_config)
+        self.app.add_url_rule(url='/scan_result', func=self.scan_result)
+
+        self.app.add_url_rule(url=re.compile('^\/(.+\.css)$'),
+                              func=self.styles)
+
+    def _encrypt_data(self, data: Union[str, list, dict]) -> bytes:
+        """
+        Encrypt data with encryption key
+
+        :param      data:  The data
+        :type       data:  Union[str, list, dict]
+
+        :returns:   Encrypted data
+        :rtype:     bytes
+        """
+        # https://forum.micropython.org/viewtopic.php?t=6726
+        # create bytes array of the data and encrypt it
+        if not isinstance(data, str):
+            data = str(data)
+
+        data_bytes = data.encode()
+        enc = ucryptolib.aes(self._enc_key, 1)
+
+        # add '\x00' to fill up the data string to reach a multiple of 16
+        encrypted_data = enc.encrypt(data_bytes + b'\x00' * ((16 - (len(data_bytes) % 16)) % 16))
+
+        return encrypted_data
+
+    def _decrypt_data(self, data: bytes) -> str:
+        """
+        Decrypt data with decryption key
+
+        :param      data:  The data
+        :type       data:  bytes
+
+        :returns:   Decrypted data
+        :rtype:     str
+        """
+        # https://forum.micropython.org/viewtopic.php?t=6726
+        # decrypt bytes array
+        dec = ucryptolib.aes(self._enc_key, 1)
+        decrypted_data = dec.decrypt(data)
+
+        # remove added '\x00' stuff after decoding it to ascii
+        decrypted_data_str = decrypted_data.decode('ascii').rstrip('\x00')
+
+        return decrypted_data_str
+
+    def extend_wifi_config_data(self,
+                                data: Union[dict, List[dict]],
+                                path: str,
+                                encrypted: bool = False) -> None:
+        """
+        Extend WiFi configuration data of file.
+
+        :param      data:       The data
+        :type       data:       Union[dict, List[dict]]
+        :param      path:       The full path to the file
+        :type       path:       str
+        :param      encrypted:  Flag to save data encrypted
+        :type       encrypted:  bool, optional
+        """
+        # in case the file already exists, extend its data content
+        if PathHelper.exists(path=path):
+            existing_data = self._load_wifi_config_data(path=path,
+                                                        encrypted=encrypted)
+            self.logger.debug('Existing WiFi config data: {}'.
+                              format(existing_data))
+            if isinstance(existing_data, dict):
+                if isinstance(data, dict):
+                    data = [existing_data, data]
+                elif isinstance(data, list):
+                    data = [existing_data] + data
+            elif isinstance(existing_data, list):
+                if isinstance(data, dict):
+                    existing_data.append(data)
+                    data = existing_data
+                elif isinstance(data, list):
+                    data = existing_data + data
+            else:
+                # unknown content, overwrite it
+                pass
+
+        self.logger.debug('Updated data: {}'.format(data))
+
+        ssids = list()
+        if isinstance(data, list):
+            for net in data:
+                if 'ssid' in net:
+                    ssids.append(net['ssid'])
+        elif isinstance(data, dict):
+            if 'ssid' in data:
+                ssids = [data['ssid']]
+
+        self._configured_networks = ssids.copy()
+
+        if encrypted:
+            # create bytes array of the dict and encrypt it
+            encrypted_data = self._encrypt_data(data=data)
+
+            # save data to file as binary as it contains encrypted data
+            GenericHelper.save_file(data=encrypted_data,
+                                    path=path,
+                                    mode='wb')
+            self.logger.debug('Saved encrypted data as json: {}'.
+                              format(encrypted_data))
+        else:
+            # save data to file, no need for binary mode
+            GenericHelper.save_json(data=data,
+                                    path=path,
+                                    mode='w')
+            self.logger.debug('Saved data as json: {}'.format(data))
+
+    def _load_wifi_config_data(self,
+                               path: str,
+                               encrypted: bool = False) -> Union[dict,
+                                                                 List[dict]]:
+        """
+        Load WiFi configuration data from file.
+
+        :param      path:       The full path to the file
+        :type       path:       str
+        :param      encrypted:  Flag to decrypt data
+        :type       encrypted:  bool, optional
+
+        :returns:   The loaded data
+        :rtype:     Union[dict, List[dict]]
+        """
+        data = dict()
+
+        if encrypted:
+            # read file in binary as it contains encrypted data
+            encrypted_read_data = GenericHelper.load_file(path=path,
+                                                          mode='rb')
+            self.logger.debug('Read encrypted data: {}'.
+                              format(encrypted_read_data))
+
+            # decrypt read data
+            decrypted_data_str = self._decrypt_data(data=encrypted_read_data)
+            self.logger.debug('Decrypted data str: {}'.
+                              format(decrypted_data_str))
+
+            # convert string to dict
+            data = GenericHelper.str_to_dict(data=decrypted_data_str)
+            self.logger.debug('Decrypted data dict: {}'.format(data))
+        else:
+            data = GenericHelper.load_json(path=path, mode='r')
+            self.logger.debug('Read non encrypted data: {}'.
+                              format(data))
+
+        return data
+
+    @property
+    def configured_networks(self) -> List[str]:
+        """
+        Get SSIDs of all configured networks
+
+        :returns:   SSIDs of configured networks
+        :rtype:     List[str]
+        """
+        return self._configured_networks
+
+    def _scan(self,
+              pixel: Neopixel,
+              wh: WifiHelper,
+              msg: Message,
+              scan_interval: int,
+              lock: int) -> None:
+        """
+        Scan for available networks.
+
+        :param      pixel:          Neopixel helper object
+        :type       pixel:          Neopixel
+        :param      wh:             Wifi helper object
+        :type       wh:             WifiHelper
+        :param      msg:            The shared message from this thread
+        :type       msg:            Message
+        :param      scan_interval:  The scan interval in milliseconds
+        :type       scan_interval:  int
+        :param      lock:           The lock object
+        :type       lock:           _thread.lock
+        """
+        pixel.fading = True
+
+        while lock.locked():
+            try:
+                # rescan for available networks
+                found_nets = wh.get_wifi_networks_sorted(rescan=True,
+                                                         scan_if_empty=True)
+
+                # wait for specified time
+                time.sleep_ms(scan_interval)
+
+                msg.set(found_nets)
+            except KeyboardInterrupt:
+                break
+
+        pixel.fading = False
+        print('Finished scanning')
+
+    @property
+    def scan_interval(self) -> int:
+        """
+        Get the WiFi scan interval in milliseconds.
+
+        :returns:   Interval of WiFi scans in milliseconds
+        :rtype:     int
+        """
+        return self._scan_interval
+
+    @scan_interval.setter
+    def scan_interval(self, value: int) -> None:
+        """
+        Set the WiFi scan interval in milliseconds.
+
+        Values below 1000 ms are set to 1000 ms.
+        One scan takes around 3 sec, which leads to maximum 15 scans per min
+
+        :param      value:  Interval of WiFi scans in milliseconds
+        :type       value:  int
+        """
+        if isinstance(value, int):
+            if value < 1000:
+                value = 1000
+            self._scan_interval = value
+
+    @property
+    def scanning(self) -> bool:
+        """
+        Get the scanning status.
+
+        :returns:   Flag whether WiFi network scan is running or not.
+        :rtype:     bool
+        """
+        return self._scan_lock.locked()
+
+    @scanning.setter
+    def scanning(self, value: int) -> None:
+        """
+        Start or stop scanning for available WiFi networks.
+
+        :param      value:  The value
+        :type       value:  int
+        """
+        if value and (not self._scan_lock.locked()):
+            # start scanning if not already scanning
+            self._scan_lock.acquire()
+
+            # parameters of the _scan function
+            params = (
+                self.pixel,
+                self.wh,
+                self._scan_net_msg,
+                self._scan_interval,
+                self._scan_lock
+            )
+            _thread.start_new_thread(self._scan, params)
+            self.logger.info('Scanning started')
+        elif (value is False) and self._scan_lock.locked():
+            # stop scanning if not already stopped
+            self._scan_lock.release()
+            self.logger.info('Scanning stoppped')
+
+    @property
+    def latest_scan(self) -> Union[List[dict], str]:
+        gc.collect()
+        free = gc.mem_free()
+        self.logger.debug('Free memory: {}'.format(free))
+        latest_scan_result = self._scan_net_msg.value()
+        self.logger.info('Requested latest scan result: {}'.
+                         format(latest_scan_result))
+        return latest_scan_result
+
+    def _render_network_inputs(self,
+                               available_nets: dict,
+                               selected_bssid: str = '') -> str:
+        """
+        Render HTML list of selectable networks
+
+        :param      available_nets:  All available nets
+        :type       available_nets:  dict
+        :param      selected_bssid:  Currently selected network on the webpage
+        :type       selected_bssid:  str
+
+        :returns:   Sub content of WiFi selection page
+        :rtype:     str
+        """
+        content = ""
+        if len(available_nets):
+            for ele in available_nets:
+                selected = ''
+                if ele['bssid'].decode('ascii') == selected_bssid:
+                    selected = "checked"
+                content += """
+                <input class="list-group-item-check" type="radio" name="bssid" id="{bssid}" value="{bssid}" onclick="remember_selected_element(this)" {state}>
+                <label class="list-group-item py-3" for="{bssid}">
+                  {ssid}
+                  <span class="d-block small opacity-50">
+                    Signal quality {quality}&#37;, BSSID {bssid}
+                  </span>
+                </label>
+                """.format(bssid=ele['bssid'],
+                           state=selected,
+                           ssid=ele['ssid'],
+                           quality=ele['quality'])
+        else:
+            # as long as no networks are available show a spinner
+            content = """
+            <div class="spinner-border" role="status">
+              <span class="visually-hidden">Loading...</span>
+            </div>
+            """
+
+        return content
+
+    def _save_wifi_config(self, form_data: dict) -> None:
+        """
+        Save a new WiFi configuration to the WiFi configuration file.
+
+        :param      form_data:  The form data
+        :type       form_data:  dict
+        """
+        network_cfg = dict()
+        available_nets = self.latest_scan
+        self.logger.info('Available nets: {}'.format(available_nets))
+        # [
+        #   {
+        #       'ssid': 'TP-LINK_FBFC3C',
+        #       'RSSI': -21,
+        #       'bssid': 'a0f3c1fbfc3c',
+        #       'authmode': 'WPA/WPA2-PSK',
+        #       'quality': 9,
+        #       'channel': 1,
+        #       'hidden': False
+        #   },
+        #   {
+        #       'ssid': 'FRITZ!Box 7490',
+        #       'RSSI': -17,
+        #       'bssid': '3810d517eb39',
+        #       'authmode': 'WPA2-PSK',
+        #       'quality': 27,
+        #       'channel': 11,
+        #       'hidden': False
+        #   }
+        # ]
+
+        # find SSID of network based on given bssid value
+        if form_data['ssid'] != '':
+            network_cfg['ssid'] = form_data['ssid']
+        else:
+            if 'bssid' not in form_data:
+                return
+
+            # selected_bssid = form_data['wifi_network']
+            selected_bssid = form_data['bssid']
+            for ele in available_nets:
+                if (isinstance(selected_bssid, str) and
+                   selected_bssid.startswith("b'")):
+                    # actually a bytes element used as string
+                    # this is a bug due to the XMLHttpRequest updated list as
+                    # JSON which does not handle bytes format
+                    this_bssid = str(ele['bssid'])
+                else:
+                    this_bssid = ele['bssid'].decode('ascii')
+
+                if this_bssid == selected_bssid:
+                    # use string, json loading will fail otherwise later
+                    network_cfg['ssid'] = ele['ssid'].decode('ascii')
+                    break
+
+        network_cfg['password'] = form_data['password']
+        self.logger.info('Network cfg: {}'.format(network_cfg))
+        # Network cfg: {'ssid': 'TP-LINK_FBFC3C', 'password': 'qwertz'}
+
+        if 'ssid' in network_cfg:
+            if isinstance(network_cfg['ssid'], bytes):
+                network_cfg['ssid'] = network_cfg['ssid'].decode('ascii')
+
+            self.logger.info('Raw network config: {}'.format(network_cfg))
+
+            # save data in encrypted mode
+            self.extend_wifi_config_data(data=network_cfg,
+                                         path=self._config_file,
+                                         encrypted=True)
+            self.logger.info('Saving of network config to {} done'.
+                             format(self._config_file))
+        else:
+            self.logger.info('No valid SSID found, will not save this net')
+
+    def _remove_wifi_config(self, form_data: dict) -> None:
+        """
+        Remove a WiFi network from the WiFi configuration file.
+
+        :param      form_data:  The form data
+        :type       form_data:  dict
+        """
+        if len(form_data):
+            loaded_cfg = self._load_wifi_config_data(path=self._config_file,
+                                                     encrypted=True)
+
+            updated_cfg = list()
+            updated_ssids = list()
+
+            for net in loaded_cfg:
+                if net['ssid'] not in form_data:
+                    updated_cfg.append(net)
+                    updated_ssids.append(net['ssid'])
+
+            self._configured_networks = updated_ssids.copy()
+
+            # create bytes array of the dict and encrypt it
+            encrypted_data = self._encrypt_data(data=updated_cfg)
+
+            # save to file as binary
+            GenericHelper.save_file(data=encrypted_data,
+                                    path=self._config_file,
+                                    mode='wb')
+            self.logger.debug('Saved encrypted data as json: {}'.
+                              format(encrypted_data))
+
+    # -------------------------------------------------------------------------
+    # Webserver functions
+
+    # @app.route('/landing_page')
+    def landing_page(self, req, resp) -> None:
+        yield from picoweb.start_response(resp)
+        yield from self.app.render_template(writer=resp,
+                                            tmpl_name='index.tpl',
+                                            args=(req, ))
+
+    # @app.route("/scan_result")
+    def scan_result(self, req, resp) -> None:
+        """Provide latest found networks as JSON"""
+        yield from picoweb.start_response(writer=resp,
+                                          content_type="application/json")
+
+        encoded = json.dumps(self.latest_scan)
+        yield from resp.awrite(encoded)
+        # https://github.com/pfalcon/picoweb/blob/b74428ebdde97ed1795338c13a3bdf05d71366a0/picoweb/__init__.py#L39
+        # yield from resp.jsonify(self.latest_scan)
+
+    # @app.route("/select")
+    def wifi_selection(self, req, resp) -> None:
+        """
+        Provide webpage to select WiFi network from list of available networks
+
+        Scanning just in time of accessing the page would block all processes
+        for approx. 2.5sec.
+        Using the result provided by the scan thread via a message takes only
+        0.02sec to complete
+        """
+        available_nets = self.latest_scan
+        content = self._render_network_inputs(available_nets=available_nets)
+
+        # do not stop scanning as page is updating scan results on the fly
+        # with XMLHTTP requests to @see scan_result
+        # stop scanning thread
+        # self.logger.info('Stopping scanning thread')
+        # self.scanning = False
+
+        yield from picoweb.start_response(resp)
+        yield from self.app.render_template(writer=resp,
+                                            tmpl_name='select.tpl',
+                                            args=(req, content, ))
+
+    # @app.route("/render_network_inputs")
+    def render_network_inputs(self, req, resp) -> str:
+        """Return rendered network inputs content to webpage"""
+        available_nets = self.latest_scan
+        selected_bssid = self._selected_network_bssid
+        content = self._render_network_inputs(available_nets=available_nets,
+                                              selected_bssid=selected_bssid)
+
+        yield from picoweb.start_response(resp)
+        yield from resp.awrite(content)
+
+    # @app.route("/configure")
+    def wifi_configs(self, req, resp) -> None:
+        """Provide webpage with table of configured networks"""
+        configured_nets = self.configured_networks
+        self.logger.debug('Existing config content: {}'.
+                          format(configured_nets))
+
+        if isinstance(configured_nets, str):
+            configured_nets = [configured_nets]
+
+        yield from picoweb.start_response(resp)
+        yield from self.app.render_template(writer=resp,
+                                            tmpl_name='remove.tpl',
+                                            args=(req, configured_nets, 'disabled'))
+
+    # @app.route("/save_wifi_config")
+    def save_wifi_config(self, req, resp) -> None:
+        """Process saving the specified WiFi network to the WiFi config file"""
+        if req.method == 'POST':
+            yield from req.read_form_data()
+        else:  # GET, apparently
+            # Note: parse_qs() is not a coroutine, but a normal function.
+            # But you can call it using yield from too.
+            req.parse_qs()
+
+        form_data = req.form
+
+        # Whether form data comes from GET or POST request, once parsed,
+        # it's available as req.form dictionary
+        self.logger.info('WiFi user input content: {}'.format(form_data))
+        # {'ssid': '', 'wifi_network': 'a0f3c1fbfc3c', 'password': 'qwertz'}
+
+        self._save_wifi_config(form_data=form_data)
+
+        # redirect to '/'
+        headers = {'Location': '/'}
+        yield from picoweb.start_response(resp, status='303', headers=headers)
+
+    # @app.route("/remove_wifi_config")
+    def remove_wifi_config(self, req, resp) -> None:
+        """Remove a network from the list of configured networks"""
+        if req.method == 'POST':
+            yield from req.read_form_data()
+        else:  # GET, apparently
+            # Note: parse_qs() is not a coroutine, but a normal function.
+            # But you can call it using yield from too.
+            req.parse_qs()
+
+        # Whether form data comes from GET or POST request, once parsed,
+        # it's available as req.form dictionary
+        form_data = req.form
+        self.logger.info('Remove networks: {}'.format(form_data))
+        # Remove networks: {'FRITZ!Box 7490': 'FRITZ!Box 7490'}
+
+        self._remove_wifi_config(form_data=form_data)
+
+        # redirect to '/'
+        headers = {'Location': '/'}
+        yield from picoweb.start_response(resp, status='303', headers=headers)
+
+    # @app.route(re.compile('^\/(.+\.css)$'))
+    def styles(self, req, resp) -> None:
+        """
+        Send gzipped content if supported by client.
+        Shows specifying headers as a flat binary string, more efficient if
+        such headers are static.
+        """
+        file_path = req.url_match.group(1)
+        headers = b'Cache-Control: max-age=86400\r\n'
+
+        if b'gzip' in req.headers.get(b'Accept-Encoding', b''):
+            self.logger.debug('gzip accepted for CSS style file')
+            file_path += '.gz'
+            headers += b'Content-Encoding: gzip\r\n'
+
+        self.logger.debug('Accessed file {}'.format('static/' + file_path))
+        yield from self.app.sendfile(writer=resp,
+                                     fname='static/' + file_path,
+                                     content_type='text/css',
+                                     headers=headers)
+
+    def run(self,
+            host: str = '0.0.0.0',
+            port: int = 80,
+            debug: bool = False) -> None:
+        """
+        Run the web application
+
+        :param      host:   The hostname to listen on
+        :type       host:   str, optional
+        :param      port:   The port of the webserver
+        :type       port:   int, optional
+        :param      debug:  Flag to automatically reload for code changes and
+                            show debugger content
+        :type       debug:  bool, optional
+        """
+        self.logger.debug('Run app on {}:{} with debug: {}'.format(host,
+                                                                   port,
+                                                                   debug))
+        try:
+            # self.app.run()
+            # self.app.run(debug=debug)
+            self.app.run(host=host, port=port, debug=debug)
+        except KeyboardInterrupt:
+            self.logger.debug('Catched KeyboardInterrupt at run of web app')
+        except Exception as e:
+            self.logger.warning(e)


Test patch:
diff --git a/simulation/prepare_test.sh b/simulation/prepare_test.sh
new file mode 100644
index 0000000..8871ff0
--- /dev/null
+++ b/simulation/prepare_test.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# title         :prepare_test.sh
+# description   :Prepare directories for unittest report files
+# author        :brainelectronics
+# date          :20220206
+# version       :0.1.0
+# usage         :sh prepare_test.sh
+# notes         :None
+# bash_version  :3.2.53(1)-release
+#=============================================================================
+
+mkdir -p reports/sca
+mkdir -p reports/test_results
+mkdir -p reports/coverage
\ No newline at end of file
diff --git a/simulation/tests/data/encrypted/multi-network.json b/simulation/tests/data/encrypted/multi-network.json
new file mode 100644
index 0000000..5b1e26d
--- /dev/null
+++ b/simulation/tests/data/encrypted/multi-network.json
@@ -0,0 +1,1 @@
+=�B��Ž]I���V󂊃xx�D:�c�53�1-Jo���M�x�n;j�3�b�E�zmD���l��,�J:4\�ds�_���$@�+���zܦ�~�b���lD���h��M�Y)
\ No newline at end of file
diff --git a/simulation/tests/data/encrypted/single-network.json b/simulation/tests/data/encrypted/single-network.json
new file mode 100644
index 0000000..2b35a75
--- /dev/null
+++ b/simulation/tests/data/encrypted/single-network.json
@@ -0,0 +1,1 @@
+E�Ƥ�4(�6������u��bn�|����w	Gw��ƣ�F�~ 1�Z
\ No newline at end of file
diff --git a/simulation/tests/data/unencrypted/multi-network.json b/simulation/tests/data/unencrypted/multi-network.json
new file mode 100644
index 0000000..3310649
--- /dev/null
+++ b/simulation/tests/data/unencrypted/multi-network.json
@@ -0,0 +1,1 @@
+[{"ssid": "SSID Name", "password": "1234qwertz@"}, {"ssid": "Other-Network@1", "password": "password"}]
\ No newline at end of file
diff --git a/simulation/tests/data/unencrypted/no-json.txt b/simulation/tests/data/unencrypted/no-json.txt
new file mode 100644
index 0000000..d70b971
--- /dev/null
+++ b/simulation/tests/data/unencrypted/no-json.txt
@@ -0,0 +1,1 @@
+{'ssid': 'MyNet', 'password': 'empty'}
\ No newline at end of file
diff --git a/simulation/tests/data/unencrypted/single-network.json b/simulation/tests/data/unencrypted/single-network.json
new file mode 100644
index 0000000..40ea2b8
--- /dev/null
+++ b/simulation/tests/data/unencrypted/single-network.json
@@ -0,0 +1,1 @@
+{"ssid": "MyNet", "password": "empty"}
\ No newline at end of file
diff --git a/simulation/tests/test_absolute_truth.py b/simulation/tests/test_absolute_truth.py
new file mode 100644
index 0000000..e63af4f
--- /dev/null
+++ b/simulation/tests/test_absolute_truth.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+"""Unittest for testing the absolute truth"""
+
+import logging
+import sys
+import unittest
+
+
+class TestAbsoluteTruth(unittest.TestCase):
+    def setUp(self) -> None:
+        """Run before every test method"""
+        # define a format
+        custom_format = "[%(asctime)s][%(levelname)-8s][%(filename)-20s @" \
+                        " %(funcName)-15s:%(lineno)4s] %(message)s"
+
+        # set basic config and level for all loggers
+        logging.basicConfig(level=logging.INFO,
+                            format=custom_format,
+                            stream=sys.stdout)
+
+        # create a logger for this TestSuite
+        self.test_logger = logging.getLogger(__name__)
+
+        # set the test logger level
+        self.test_logger.setLevel(logging.DEBUG)
+
+        # enable/disable the log output of the device logger for the tests
+        # if enabled log data inside this test will be printed
+        self.test_logger.disabled = False
+
+    def test_absolute_truth(self) -> None:
+        """Test the unittest itself"""
+        x = 0
+        y = 1
+        z = 2
+        none_thing = None
+        some_dict = dict()
+        some_list = [x, y, 40, "asdf", z]
+
+        self.assertTrue(True)
+        self.assertFalse(False)
+
+        self.assertEqual(y, 1)
+        assert y == 1
+        self.assertNotEqual(x, y)
+        assert x != y
+
+        self.assertIsNone(none_thing)
+        self.assertIsNotNone(some_dict)
+
+        self.assertIn(y, some_list)
+        self.assertNotIn(12, some_list)
+
+        # self.assertRaises(exc, fun, args, *kwds)
+
+        self.assertIsInstance(some_dict, dict)
+
+        self.assertGreater(a=y, b=x)
+        self.assertGreaterEqual(a=y, b=x)
+        self.assertLess(a=x, b=y)
+
+        self.test_logger.debug("Sample debug message")
+
+    def tearDown(self) -> None:
+        """Run after every test method"""
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_generic_helper.py b/simulation/tests/test_generic_helper.py
new file mode 100644
index 0000000..a102d7e
--- /dev/null
+++ b/simulation/tests/test_generic_helper.py
@@ -0,0 +1,306 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of Generic Helper"""
+
+import gc
+import logging
+import os
+from nose2.tools import params
+from typing import List, Union
+import unittest
+from unittest.mock import patch, mock_open, Mock
+
+# custom imports
+from generic_helper import GenericHelper
+
+
+class TestGenericHelper(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    @params(
+        (None),
+        ('Sauerkraut')
+    )
+    def test_create_logger(self, logger_name: Union[None, str]) -> None:
+        """
+        Test creation of loggers
+
+        :param      logger_name:  The logger name
+        :type       logger_name:  Union[None, str]
+        """
+        logger = GenericHelper.create_logger(logger_name=logger_name)
+
+        if not logger_name:
+            logger_name = 'generic_helper.generic_helper'
+
+        self.assertIsInstance(logger, logging.Logger)
+        self.assertEqual(logger.level, logging.DEBUG)
+        self.assertEqual(logger.name, logger_name)
+
+    @unittest.skip("Not yet implemented")
+    def test_set_level(self) -> None:
+        """Test setting the logger level"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_set_logger_verbose_level(self) -> None:
+        """Test setting the logger level"""
+        pass
+
+    @params(
+        (0),
+        (123)
+    )
+    def test_get_random_value(self, expectation: int) -> None:
+        """
+        Test getting a random value within some range
+
+        :param      expectation:  The expectation
+        :type       expectation:  int
+        """
+        # mocking random values feels so random
+        with patch('random.randint', return_value=expectation):
+            value = GenericHelper.get_random_value()
+            self.assertEqual(value, expectation)
+
+    @params(
+        (None, 61354754048),
+        ('byte', '61354754048 byte'),
+        ('BYTE', '61354754048 byte'),
+        ('kb', '59916752.000 kB'),
+        ('kB', '59916752.000 kB'),
+        ('Kb', '59916752.000 kB'),
+        ('Kb', '59916752.000 kB'),
+        ('KB', '59916752.000 kB'),
+        ('mb', '58512.453 MB'),
+        ('mB', '58512.453 MB'),
+        ('Mb', '58512.453 MB'),
+        ('MB', '58512.453 MB'),
+        ('unknown', -1)
+    )
+    def test_df(self,
+                unit: Union[None, str],
+                expectation: Union[int, str]) -> None:
+        """
+        Test getting free diskspace in different size formats
+
+        :param      unit:         The unit
+        :type       unit:         Union[None, str]
+        :param      expectation:  The expectation
+        :type       expectation:  Union[int, str]
+        """
+        return_value = os.statvfs_result((4096, 4096, 61069442, 14979188, 14915188, 61069440, 14915188, 14915188, 0, 255))
+
+        with patch('os.statvfs', return_value=return_value):
+            result = GenericHelper.df(unit=unit)
+
+        if unit:
+            if unit != 'unknown':
+                self.assertIsInstance(result, str)
+            else:
+                self.assertIsInstance(result, int)
+        else:
+            self.assertIsInstance(result, int)
+
+        if expectation:
+            self.assertEqual(result, expectation)
+
+    @params(
+        (0, 0, '100.00%'),
+        (100, 0, '100.00%'),
+        (0, 100, '0.00%'),
+        (100, 100, '50.00%'),
+        (10, 20, '33.33%')
+    )
+    def test_get_free_memory(self,
+                             free: int,
+                             allocated: int,
+                             expectation: str) -> None:
+        """
+        Test getting free memory (RAM)
+
+        :param      free:         Free bytes
+        :type       free:         int
+        :param      allocated:    Allocated bytes
+        :type       allocated:    int
+        :param      expectation:  Expected ratio
+        :type       expectation:  str
+        """
+        gc.mem_free = Mock(return_value=free)
+        gc.mem_alloc = Mock(return_value=allocated)
+
+        result = GenericHelper.get_free_memory()
+
+        self.assertIsInstance(result, dict)
+        self.assertIn('free', result)
+        self.assertIn('total', result)
+        self.assertIn('percentage', result)
+
+        self.assertEqual(result['free'], free)
+        self.assertEqual(result['total'], allocated + free)
+        self.assertEqual(result['percentage'], expectation)
+
+    @params(
+        (False, 0, 0, '100.00%'),
+        (True, 0, 0, 'Total: 0.0 kB, Free: 0.00 kB (100.00%)'),
+        (True, 100, 0, 'Total: 0.1 kB, Free: 0.10 kB (100.00%)'),
+        (True, 0, 100, 'Total: 0.1 kB, Free: 0.00 kB (0.00%)'),
+        (True, 100, 100, 'Total: 0.2 kB, Free: 0.10 kB (50.00%)'),
+        (True, 100, 200, 'Total: 0.3 kB, Free: 0.10 kB (33.33%)'),
+        (True, 1000, 2000, 'Total: 2.9 kB, Free: 0.98 kB (33.33%)'),
+        (True, 1024, 2048, 'Total: 3.0 kB, Free: 1.00 kB (33.33%)'),
+    )
+    def test_free(self,
+                  full: bool,
+                  free: int,
+                  allocated: int,
+                  expectation: str) -> None:
+        """
+        Test getting detailed informations about free memory (RAM)
+
+        :param      full:         Flag to get full details
+        :type       full:         bool
+        :param      free:         Free bytes
+        :type       free:         int
+        :param      allocated:    Allocated bytes
+        :type       allocated:    int
+        :param      expectation:  Expected ratio
+        :type       expectation:  str
+        """
+        gc.mem_free = Mock(return_value=free)
+        gc.mem_alloc = Mock(return_value=allocated)
+
+        result = GenericHelper.free(full=full)
+
+        self.assertIsInstance(result, str)
+        self.assertEqual(result, expectation)
+
+    @params(
+        (
+            "{'test': 123}",
+            {'test': 123}
+        ),
+        (
+            "{'val': true}",
+            {'val': True}
+        ),
+        (
+            "{'nested': {'this': 'that'}}",
+            {'nested': {'this': 'that'}}
+        ),
+        (
+            "[{'val': true}, {'other': false}]",
+            [{'val': True}, {'other': False}]
+        ),
+    )
+    def test_str_to_dict(self,
+                         data: str,
+                         expectation: Union[dict, List[dict]]) -> None:
+        """
+        Test converting a string to a dictionary
+
+        :param      data:         The data
+        :type       data:         str
+        :param      expectation:  The expectation
+        :type       expectation:  Union[dict, List[dict]]
+        """
+        result = GenericHelper.str_to_dict(data=data)
+
+        self.assertIsInstance(result, type(expectation))
+        self.assertEqual(result, expectation)
+
+    def test_save_json(self) -> None:
+        """Test saving data as JSON to a file"""
+        fake_file_path = "fake/file/path"
+        fake_content = {
+            "data": "Content which is never written to a file",
+            "other": 123
+        }
+        file_mode = 'w'
+
+        with patch("builtins.open", mock_open()) as mock_file:
+            with patch("json.dump",
+                       Mock(return_value=fake_content)) as mock_dump:
+                GenericHelper.save_json(data=fake_content,
+                                        path=fake_file_path,
+                                        mode=file_mode)
+
+                mock_dump.assert_called_once_with(fake_content,
+                                                  open(fake_file_path,
+                                                       file_mode))
+
+            # assert if opened file on write mode 'w'
+            mock_file.assert_called_with(fake_file_path, file_mode)
+
+    def test_load_json(self) -> None:
+        """Test loading data as JSON from a file"""
+        fake_file_path = "fake/file/path"
+        fake_content = {
+            "data": "Content which is never written to a file",
+            "other": 123
+        }
+        file_mode = 'r'
+
+        with patch("builtins.open", mock_open()) as mock_file:
+            with patch("json.load", Mock(return_value=fake_content)):
+                actual = GenericHelper.load_json(path=fake_file_path,
+                                                 mode=file_mode)
+            mock_file.assert_called_once_with(fake_file_path, file_mode)
+
+        self.assertEqual(actual, fake_content)
+        self.assertIsInstance(actual, dict)
+
+    def test_save_file(self) -> None:
+        """Test saving data to a file"""
+        fake_file_path = "fake/file/path"
+        fake_content = "Content which is never written to a file"
+        file_mode = 'w'
+
+        with patch("builtins.open", mock_open()) as mock_file:
+            GenericHelper.save_file(data=fake_content,
+                                    path=fake_file_path,
+                                    mode=file_mode)
+
+            # assert if opened file on write mode 'w'
+            mock_file.assert_called_once_with(fake_file_path, file_mode)
+
+            # assert if write(content) was called from the file opened
+            # in another words, assert if the specific content was written
+            mock_file().write.assert_called_once_with(fake_content)
+
+    def test_load_file(self) -> None:
+        """Test loading data from a file"""
+        fake_file_path = "fake/file/path"
+        fake_content = "Content which is never written to a file"
+        file_mode = 'r'
+
+        with patch("builtins.open",
+                   mock_open(read_data=fake_content)) as mock_file:
+            actual = GenericHelper.load_file(path=fake_file_path,
+                                             mode=file_mode)
+            mock_file.assert_called_once_with(fake_file_path, file_mode)
+
+        self.assertEqual(actual, fake_content)
+
+    def test_read_file(self) -> None:
+        """Test reading data from a file"""
+        fake_file_path = "fake/file/path"
+        fake_content = "Content which is never written to a file"
+        file_mode = 'r'
+
+        with patch("builtins.open",
+                   mock_open(read_data=fake_content)) as mock_file:
+            actual = GenericHelper.read_file(path=fake_file_path,
+                                             mode=file_mode)
+            mock_file.assert_called_once_with(fake_file_path, file_mode)
+
+        self.assertEqual(actual, fake_content)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_led_helper.py b/simulation/tests/test_led_helper.py
new file mode 100644
index 0000000..a4913c8
--- /dev/null
+++ b/simulation/tests/test_led_helper.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of LED Helper"""
+
+from nose2.tools import params
+import _thread
+from typing import Union
+import unittest
+
+# custom imports
+from led_helper import Neopixel
+from led_helper import NeoPixel
+
+
+class TestNeopixel(unittest.TestCase):
+    def setUp(self) -> None:
+        self.pixel = Neopixel()
+
+    def tearDown(self) -> None:
+        pass
+
+    def test__init__(self) -> None:
+        """Test initial values"""
+        self.assertEqual(self.pixel._neopixel_amount, 1)
+        self.assertIsInstance(self.pixel.pixel, NeoPixel)
+
+        # check values of the pixel
+        self.assertEqual(self.pixel._color, [30, 0, 0])
+        self.assertEqual(self.pixel._intensity, 30)
+        self.assertEqual(self.pixel._last_intensity, 30)
+        self.assertEqual(self.pixel._active, False)
+
+        # check thread specific values
+        # self.assertIsInstance(self.pixel._fade_lock, _thread.allocate_lock)
+        self.assertEqual(self.pixel._fade_delay, 50)
+        self.assertEqual(self.pixel._fading, False)
+        self.assertEqual(self.pixel._fade_pixel_amount, -1)
+
+    @unittest.skip("Not yet implemented")
+    def test_clear(self) -> None:
+        """Test clearing Neopixel color"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_set(self) -> None:
+        """Test setting Neopixel color"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_red(self) -> None:
+        """Test setting Neopixel color to red"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_green(self) -> None:
+        """Test setting Neopixel color to green"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_blue(self) -> None:
+        """Test setting Neopixel color to blue"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_pixels(self) -> None:
+        """Test getting number of Neopixels"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_color(self) -> None:
+        """Test getting and setting the current Neopixel color"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_intensity(self) -> None:
+        """Test getting and setting the current Neopixel intensity"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_active(self) -> None:
+        """Test getting and setting the current Neopixel status"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_colors(self) -> None:
+        """Test getting and setting the available Neopixel colors"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_fade(self) -> None:
+        """Test fading the Neopixel"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test__fade(self) -> None:
+        """Test fade thread"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_fade_delay(self) -> None:
+        """Test getting and setting the fade delay"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_fade_pixel_amount(self) -> None:
+        """Test getting and setting the amount of faidng Neopixels"""
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_fading(self) -> None:
+        """Test getting and setting the fading status"""
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_machine.py b/simulation/tests/test_machine.py
new file mode 100644
index 0000000..42207c8
--- /dev/null
+++ b/simulation/tests/test_machine.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of Machine"""
+
+import unittest
+
+# custom imports
+from machine import machine
+
+
+class TestMachine(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    def test_reset_cause(self) -> None:
+        result = machine.reset_cause()
+        self.assertEqual(result, 5)
+        self.assertEqual(result, machine.SOFT_RESET)
+
+    def test_unique_id(self) -> None:
+        result = machine.unique_id()
+        self.assertIsInstance(result, bytes)
+        self.assertEqual(result, b'DEADBEEF')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_message.py b/simulation/tests/test_message.py
new file mode 100644
index 0000000..47f278d
--- /dev/null
+++ b/simulation/tests/test_message.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of Message"""
+
+from nose2.tools import params
+from typing import Any
+import unittest
+
+# custom imports
+from generic_helper import Message
+
+
+class TestMessage(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    @unittest.skip("Checked by test_set")
+    def test_clear(self) -> None:
+        pass
+
+    @params(
+        (None),
+        (False),
+        (True),
+        (0),
+        (1),
+        (3.14),
+        ("string"),
+        (["string", True]),
+        ({'key': ["string", True], 'key2': 42}),
+    )
+    def test_set(self, data: Any) -> None:
+        msg = Message()
+        msg.set(data)
+
+        result = msg.value()
+        self.assertEqual(result, data)
+
+        for x in range(3, 10):
+            msg.set(x)
+            result = msg.value()
+            self.assertNotEqual(result, data)
+            self.assertEqual(result, x)
+
+        msg2 = Message()
+        msg.set(msg2)
+
+        result = msg.value()
+        self.assertEqual(result, msg2)
+
+    @unittest.skip("Checked by test_set")
+    def test_is_set(self) -> None:
+        pass
+
+    @unittest.skip("Checked by test_set")
+    def test_value(self) -> None:
+        """Test getting latest value of Message"""
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_neopixel.py b/simulation/tests/test_neopixel.py
new file mode 100644
index 0000000..2259273
--- /dev/null
+++ b/simulation/tests/test_neopixel.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of NeoPixel"""
+
+from nose2.tools import params
+import unittest
+
+# custom imports
+from led_helper import NeoPixel
+from machine import Pin
+
+
+class TestNeoPixel(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    def test__init__(self) -> None:
+        """Test __init__ function"""
+        pixel_pin = Pin(12, Pin.OUT)
+
+        pixel = NeoPixel(pin=pixel_pin, n=34)
+
+        self.assertIsInstance(pixel._pin, Pin)
+        self.assertEqual(pixel._amount, 34)
+
+    @unittest.skip("Not yet implemented")
+    @params(
+        (None),
+        ('Sauerkraut')
+    )
+    def test_fill(self, pixel) -> None:
+        """Test setting the value of all pixels to the pixel value"""
+        pass
+
+    def test__len__(self) -> None:
+        """Test getting the number of LEDs"""
+        pixel_pin = Pin(32, Pin.OUT)
+
+        pixels = 42
+        pixel = NeoPixel(pin=pixel_pin, n=pixels)
+
+        amount_of_pixels = len(pixel)
+        self.assertEqual(amount_of_pixels, pixels)
+
+    @unittest.skip("Not yet implemented")
+    def test_write(self) -> None:
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_network.py b/simulation/tests/test_network.py
new file mode 100644
index 0000000..ee88ca6
--- /dev/null
+++ b/simulation/tests/test_network.py
@@ -0,0 +1,247 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of Network"""
+
+from nose2.tools import params
+import random
+from typing import Union
+import unittest
+from unittest.mock import patch
+import sys
+
+# custom imports
+from wifi_helper.network import NetworkHelper
+from wifi_helper.network import Station
+from wifi_helper.network import Client
+from wifi_helper.network import WLAN
+
+
+class TestNetworkHelper(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test__internal_scan(self) -> None:
+        pass
+        # with patch('sys.platform', 'unsupported') as mock_platform:
+        #     nets = NetworkHelper._internal_scan()
+
+    @unittest.skipIf(sys.platform == "linux" or sys.platform == "linux2",
+                     'Linux not tested on this system')
+    def test__scan_linux(self) -> None:
+        pass
+
+    @unittest.skipIf(sys.platform == 'darwin',
+                     'Linux not tested on this system')
+    def test__scan_mac(self) -> None:
+        pass
+
+    @unittest.skipIf(sys.platform == 'win32',
+                     'Windows not tested on this system')
+    @unittest.skip("Not yet implemented")
+    def test__scan_windows(self) -> None:
+        pass
+
+    @params(
+        (50, -75),  # percentage, dBm
+    )
+    def test__quality_to_dbm(self, quality: int, expectation: int) -> None:
+        result = NetworkHelper._quality_to_dbm(quality=quality)
+        self.assertEqual(result, expectation)
+
+    def test__dummy_data(self) -> None:
+        # get two random values for RSSI in range of 0 to 100
+        mocked_rssi = random.sample(range(0, 100), 2)
+
+        # patch call of random.randint inside of _dummy_data with mocked_rssi
+        with patch('random.randint') as mock_randint:
+            mock_randint.side_effect = mocked_rssi
+            nets = NetworkHelper._dummy_data()
+
+        self.assertIsInstance(nets, list)
+        self.assertEqual(len(nets), 2)
+
+        for net in nets:
+            self.assertIsInstance(net, dict)
+            required_keys = ['ssid', 'RSSI', 'bssid', 'authmode', 'channel', 'hidden']
+            self.assertEqual(len(net.keys()), len(required_keys))
+            self.assertTrue(all(name in required_keys for name in net.keys()))
+
+        # check first network
+        self.assertEqual(nets[0]['ssid'], 'TP-LINK_FBFC3C')
+        self.assertEqual(nets[0]['RSSI'], -mocked_rssi[0])
+        self.assertEqual(nets[0]['bssid'], 'a0f3c1fbfc3c')
+        self.assertEqual(nets[0]['authmode'], 'WPA/WPA2-PSK')
+        self.assertEqual(nets[0]['channel'], 1)
+        self.assertEqual(nets[0]['hidden'], False)
+
+        # check second network
+        self.assertEqual(nets[1]['ssid'], 'FRITZ!Box 7490')
+        self.assertEqual(nets[1]['RSSI'], -mocked_rssi[1])
+        self.assertEqual(nets[1]['bssid'], '3810d517eb39')
+        self.assertEqual(nets[1]['authmode'], 'WPA2-PSK')
+        self.assertEqual(nets[1]['channel'], 11)
+        self.assertEqual(nets[1]['hidden'], False)
+
+    def test__dummy_data_upy(self) -> None:
+        """Test getting dummy data in same style as Micropython"""
+        # get two random values for RSSI in range of 0 to 100
+        mocked_rssi = random.sample(range(0, 100), 2)
+
+        # patch call of random.randint inside of _dummy_data with mocked_rssi
+        with patch('random.randint') as mock_randint:
+            mock_randint.side_effect = mocked_rssi
+            dummy_data_upy = NetworkHelper._dummy_data_upy()
+
+        self.assertIsInstance(dummy_data_upy, list)
+        self.assertEqual(len(dummy_data_upy), 2)
+
+        for net in dummy_data_upy:
+            self.assertIsInstance(net, tuple)
+
+        # check first network
+        self.assertEqual(dummy_data_upy[0][0], 'TP-LINK_FBFC3C')
+        self.assertEqual(dummy_data_upy[0][1], 'a0f3c1fbfc3c')
+        self.assertEqual(dummy_data_upy[0][2], 1)
+        self.assertEqual(dummy_data_upy[0][3], -mocked_rssi[0])
+        self.assertEqual(dummy_data_upy[0][4], 'WPA/WPA2-PSK')
+        self.assertEqual(dummy_data_upy[0][5], False)
+
+        # check second network
+        self.assertEqual(dummy_data_upy[1][0], 'FRITZ!Box 7490')
+        self.assertEqual(dummy_data_upy[1][1], '3810d517eb39')
+        self.assertEqual(dummy_data_upy[1][2], 11)
+        self.assertEqual(dummy_data_upy[1][3], -mocked_rssi[1])
+        self.assertEqual(dummy_data_upy[1][4], 'WPA2-PSK')
+        self.assertEqual(dummy_data_upy[1][5], False)
+
+    def test__convert_scan_to_upy_format(self) -> None:
+        """Test converting the dummy data to same style as Micropython"""
+        # get two random values for RSSI in range of 0 to 100
+        mocked_rssi = random.sample(range(0, 100), 2)
+
+        # patch call of random.randint inside of _dummy_data with mocked_rssi
+        with patch('random.randint') as mock_randint:
+            mock_randint.side_effect = mocked_rssi
+            dummy_data = NetworkHelper._dummy_data()
+
+        dummy_data_upy = NetworkHelper._convert_scan_to_upy_format(data=dummy_data)
+
+        self.assertIsInstance(dummy_data_upy, list)
+        self.assertEqual(len(dummy_data_upy), 2)
+
+        for net in dummy_data_upy:
+            self.assertIsInstance(net, tuple)
+
+        # check first network
+        self.assertEqual(dummy_data_upy[0][0], 'TP-LINK_FBFC3C')
+        self.assertEqual(dummy_data_upy[0][1], 'a0f3c1fbfc3c')
+        self.assertEqual(dummy_data_upy[0][2], 1)
+        self.assertEqual(dummy_data_upy[0][3], -mocked_rssi[0])
+        self.assertEqual(dummy_data_upy[0][4], 'WPA/WPA2-PSK')
+        self.assertEqual(dummy_data_upy[0][5], False)
+
+        # check second network
+        self.assertEqual(dummy_data_upy[1][0], 'FRITZ!Box 7490')
+        self.assertEqual(dummy_data_upy[1][1], '3810d517eb39')
+        self.assertEqual(dummy_data_upy[1][2], 11)
+        self.assertEqual(dummy_data_upy[1][3], -mocked_rssi[1])
+        self.assertEqual(dummy_data_upy[1][4], 'WPA2-PSK')
+        self.assertEqual(dummy_data_upy[1][5], False)
+
+    @unittest.skip("Not yet implemented")
+    def test__gather_ifconfig_data(self) -> None:
+        pass
+
+
+class TestStation(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test__init__(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_active(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_connect(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_disconnect(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_isconnected(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_scan(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_status(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_ifconfig(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_config(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_connected(self) -> None:
+        pass
+
+
+class TestClient(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    def test__init__(self) -> None:
+        ap = Client()
+
+        self.assertIsNone(ap._bssid)
+        self.assertIsNone(ap._authmode)
+        self.assertIsNone(ap._password)
+        self.assertIsNone(ap._channel)
+        self.assertFalse(ap._active)
+
+    @unittest.skip("Not yet implemented")
+    def test_ifconfig(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_config(self) -> None:
+        pass
+
+
+class TestWLAN(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test__init__(self) -> None:
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_path_helper.py b/simulation/tests/test_path_helper.py
new file mode 100644
index 0000000..782721c
--- /dev/null
+++ b/simulation/tests/test_path_helper.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of Path Helper"""
+
+from pathlib import Path
+import unittest
+from unittest.mock import patch
+
+# custom imports
+from path_helper import PathHelper
+
+
+class TestPathHelper(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    def test_exists(self) -> None:
+        with patch.object(Path, 'exists') as mock_exists:
+            # mock file existance
+            mock_exists.return_value = True
+            result = PathHelper.exists(path='/some/path')
+            self.assertTrue(result)
+
+            # mock file unexistance
+            mock_exists.return_value = False
+            result = PathHelper.exists(path='/some/path')
+            self.assertFalse(result)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_pin.py b/simulation/tests/test_pin.py
new file mode 100644
index 0000000..e3e3e48
--- /dev/null
+++ b/simulation/tests/test_pin.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of Machine Pin"""
+
+import unittest
+
+# custom imports
+from machine import Pin
+
+
+class TestPin(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    def test__init__(self) -> None:
+        pin = Pin(pin=12, mode=Pin.IN)
+
+        self.assertEqual(pin._pin, 12)
+        self.assertEqual(pin._mode, 1)
+
+        pin = Pin(pin=34, mode=Pin.OUT)
+
+        self.assertEqual(pin._pin, 34)
+        self.assertEqual(pin._mode, 2)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_rtc.py b/simulation/tests/test_rtc.py
new file mode 100644
index 0000000..a8af2b5
--- /dev/null
+++ b/simulation/tests/test_rtc.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of Machine RTC"""
+
+import unittest
+
+# custom imports
+from machine import RTC
+
+
+class TestRTC(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_template.py b/simulation/tests/test_template.py
new file mode 100644
index 0000000..6ddbc95
--- /dev/null
+++ b/simulation/tests/test_template.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of ClassName"""
+
+from nose2.tools import params
+from typing import Union
+import unittest
+
+# custom imports
+# from xxx import ClassName
+
+
+class TestTemplate(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    @params(
+        (None),
+        ('Sauerkraut')
+    )
+    def test_asdf(self, var: Union[None, str]) -> None:
+        """
+        Test asdf function
+
+        :param      var:  Variable description
+        :type       var:  Union[None, str]
+        """
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_qwertz(self) -> None:
+        """Test quertz function"""
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_time_helper.py b/simulation/tests/test_time_helper.py
new file mode 100644
index 0000000..515d6dc
--- /dev/null
+++ b/simulation/tests/test_time_helper.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of TimeHelper"""
+
+import unittest
+
+# custom imports
+from time_helper import TimeHelper
+
+
+class TestTimeHelper(unittest.TestCase):
+    def setUp(self) -> None:
+        pass
+
+    def tearDown(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test_sync_time(self) -> None:
+        """Test sync_time"""
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_wifi_helper.py b/simulation/tests/test_wifi_helper.py
new file mode 100644
index 0000000..5976d11
--- /dev/null
+++ b/simulation/tests/test_wifi_helper.py
@@ -0,0 +1,318 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of WiFi Helper"""
+
+from nose2.tools import params
+from typing import List, Union
+import unittest
+from unittest.mock import patch
+
+# custom imports
+from .unittest_helper import UnitTestHelper
+from machine import machine
+from wifi_helper import network
+from wifi_helper import WifiHelper
+
+
+class TestWifiHelper(unittest.TestCase):
+    # Set maximum size of the assertion error message when Unit Test fail
+    maxDiff = None
+
+    def setUp(self) -> None:
+        self.wh = WifiHelper()
+        self.uth = UnitTestHelper(quiet=False)
+
+        # activate WiFi before usage
+        self.wh.station.active(True)
+
+    def tearDown(self) -> None:
+        self.wh.station.active(False)
+        self.wh.station.connected = False
+
+    def test__init(self) -> None:
+        """Test initial values if WifiHelper"""
+        wh = WifiHelper()
+
+        required_keys = ['ssid', 'bssid', 'channel', 'RSSI', 'authmode', 'hidden']
+        self.assertIsInstance(wh._scan_info, list)
+        self.assertEqual(len(wh._scan_info), len(required_keys))
+        self.assertTrue(all(name in required_keys for name in wh._scan_info))
+
+        self.assertIsInstance(wh._network_list, list)
+        self.assertEqual(len(wh._network_list), 0)
+
+        self.assertIsInstance(wh._station, network.WLAN)
+
+    def test__do_connect_connected(self) -> None:
+        """
+        Test _do_connect in case a SOFT_RESET occured and system is already
+        connected to a network
+        """
+        # mock SOFT_RESET
+        with patch('machine.machine.reset_cause') as mock_reset_cause:
+            mock_reset_cause.return_value = machine.SOFT_RESET
+            # mock already connected
+            with patch('wifi_helper.network.Station.isconnected',
+                       return_value=True) as mock_isconnected:
+                station = network.WLAN(network.STA_IF)
+                result = WifiHelper._do_connect(station=station,
+                                                ssid='qwertz',
+                                                password=1234,
+                                                timeout=10)
+                self.assertEqual(result, True)
+
+    @params(
+        (10, 0.3),
+        (10, 0.5),
+        (10, 0.9)
+    )
+    def test__do_connect_unconnected(self, timeout: int, ratio: int) -> None:
+        """
+        Test _do_connect within a given timeout
+
+        :param      timeout:  The timeout to wait for a successul connection
+        :type       timeout:  int
+        :param      ratio:    The ratio after which a connection is established
+        :type       ratio:    int
+        """
+        # mock unknown reset cause
+        with patch('machine.machine.reset_cause') as mock_reset_cause:
+            mock_reset_cause.return_value = machine.UNKNOWN_RESET
+            # mock not yet connected and change to success on second call
+            # mock unconnected status for n calls
+            side_effect_unconnected = [False] * int(timeout * 10 * ratio)
+            side_effect_connected = [True] * int(timeout * 10 * (1 - ratio))
+            side_effect = side_effect_unconnected + side_effect_connected
+            with patch('wifi_helper.network.Station.isconnected',
+                       side_effect=side_effect) as mock_isconnected:
+                station = network.WLAN(network.STA_IF)
+
+                with patch.object(station, 'disconnect') as mock_disconnect:
+                    result = WifiHelper._do_connect(station=station,
+                                                    ssid='qwertz',
+                                                    password=1234,
+                                                    timeout=timeout)
+
+                    mock_disconnect.assert_called_once()
+                    self.assertEqual(result, True)
+
+    @params(
+        (1),
+        (10),
+        (20)
+    )
+    def test__do_connect_timeout(self, timeout: int) -> None:
+        """
+        Test _do_connect without a successful connection within a given timeout
+
+        The device sleeps for 100ms after each connection check call, it is
+        called 100 times at a timeout of 10 sec.
+
+        Let connection timeout trigger and fail the connection
+
+        :param      timeout:  The timeout to wait for a successul connection
+        :type       timeout:  int
+        """
+        # mock unknown reset cause
+        with patch('machine.machine.reset_cause') as mock_reset_cause:
+            mock_reset_cause.return_value = machine.UNKNOWN_RESET
+            # mock unconnected status for n+1 calls
+            side_effect = [False]*(timeout * 10 + 1)
+
+            with patch('wifi_helper.network.Station.isconnected',
+                       side_effect=side_effect) as mock_isconnected:
+                station = network.WLAN(network.STA_IF)
+
+                with patch.object(station, 'disconnect') as mock_disconnect:
+                    result = WifiHelper._do_connect(station=station,
+                                                    ssid='qwertz',
+                                                    password=1234,
+                                                    timeout=timeout)
+
+                    mock_disconnect.assert_called_once()
+                    self.assertEqual(result, False)
+
+    @params(
+        # single network, 10 sec timeout, no reconnection
+        ('qwertz', 'asdf', 10, False),
+        ('qwertz', 'asdf', 10, True),
+    )
+    def test_connect_already_connected(self,
+                                       ssid: Union[None, List[str], str],
+                                       password: Union[None, List[str], str],
+                                       timeout: int,
+                                       reconnect: bool) -> None:
+        with patch('wifi_helper.network.Station.isconnected',
+                   return_value=True) as mock_isconnected:
+            with patch('wifi_helper.network.Station.config',
+                       return_value=ssid) as mock_config:
+                if reconnect:
+                    # check call of disonnect
+                    with patch.object(network.Station,
+                                      'disconnect') as mock_disconnect:
+                        result = WifiHelper.connect(ssid=ssid,
+                                                    password=password,
+                                                    timeout=timeout,
+                                                    reconnect=reconnect)
+                        mock_disconnect.assert_called_once()
+                else:
+                    # only if no reconnection is required, function will return
+                    result = WifiHelper.connect(ssid=ssid,
+                                                password=password,
+                                                timeout=timeout,
+                                                reconnect=reconnect)
+                self.assertEqual(result, True)
+
+    def test_isconnected(self) -> None:
+        is_connected = self.wh.isconnected
+        self.assertFalse(is_connected)
+
+        self.wh.station.connected = True
+        is_connected = self.wh.isconnected
+        self.assertTrue(is_connected)
+
+    def test_station(self) -> None:
+        station = self.wh.station
+
+        self.assertIsInstance(station, network.WLAN)
+
+    @params(
+        ('qwertz', '1234', 9, 10)
+    )
+    def test_create_ap(self, ssid: str, password: str, channel: int, timeout: int) -> None:
+        """
+        Test creation of an AccessPoint
+
+        :param      ssid:      The ssid
+        :type       ssid:      str
+        :param      password:  The password
+        :type       password:  str
+        :param      channel:   The channel
+        :type       channel:   int
+        :param      timeout:   The timeout
+        :type       timeout:   int
+        """
+        pass
+        """
+        client = network.WLAN(network.AP_IF)
+        self.assertTrue(client.dummy())
+
+        client.config(essid=ssid)
+        print(client._essid)
+
+        with patch.object(network.WLAN(2), 'dummy') as mock_dummy:
+            client = network.WLAN(network.AP_IF)
+            self.assertTrue(client.dummy())
+
+            mock_dummy.return_value = False
+            self.assertFalse(client.dummy())
+        """
+
+        """
+        with patch.object(network.WLAN(2), 'dummy') as mock_essid:
+            # result = WifiHelper.create_ap(ssid=ssid,
+            #                               password=password,
+            #                               channel=channel,
+            #                               timeout=timeout)
+            #
+            # self.assertEqual(result, True)
+
+            client = network.WLAN(network.AP_IF)
+            self.assertTrue(client.dummy)
+
+            # self.assertEqual(mock_essid._essid, ssid)
+            # print(dir(mock_essid))
+            # print(mock_essid.return_value)
+        """
+
+    def test_scan_info(self) -> None:
+        expectation = [
+            'ssid', 'bssid', 'channel', 'RSSI', 'authmode', 'hidden'
+        ]
+
+        result = self.wh.scan_info
+
+        self.assertEqual(result, expectation)
+
+    def test_auth_modes(self) -> None:
+        expectation = {
+            0: "open",
+            1: "WEP",
+            2: "WPA-PSK",
+            3: "WPA2-PSK",
+            4: "WPA/WPA2-PSK"
+        }
+
+        result = self.wh.auth_modes
+
+        self.assertEqual(result, expectation)
+
+    @unittest.skip("Checked by test_networks")
+    def test_scan_networks(self) -> None:
+        pass
+
+    def test_networks(self) -> None:
+        # perform scan
+        self.wh.scan_networks()
+
+        result = self.wh.networks
+
+        print(result)
+
+        self.assertIsInstance(result, list)
+
+        # check networks have been found
+        if len(result):
+            for net in result:
+                self.assertEqual(self.isinstance_namedtuple(net), True)
+                scan_info = self.wh.scan_info.copy()
+                scan_info.append('quality')
+                self.assertEqual(list(net._fields), scan_info)
+
+    @unittest.skip("Not yet implemented")
+    def test_get_wifi_networks_sorted(self) -> None:
+        pass
+
+    @params(
+        (-96, 8),   # dBm, percentage
+    )
+    def test_dbm_to_quality(self, dBm: int, expectation: int) -> None:
+        result = self.wh.dbm_to_quality(dBm=dBm)
+        self.assertEqual(result, expectation)
+
+    @params(
+        (50, -75),  # percentage, dBm
+    )
+    def test_quality_to_dbm(self, quality: int, expectation: int) -> None:
+        result = self.wh.quality_to_dbm(quality=quality)
+        self.assertEqual(result, expectation)
+
+    def test_ifconfig_client(self) -> None:
+        # fake a connection to a network
+        self.wh.station.connected = True
+
+        result = self.wh.ifconfig_client
+        self.assertEqual(self.isinstance_namedtuple(result), True)
+        self.assertEqual(list(result._fields),
+                         ['ip', 'subnet', 'gateway', 'dns'])
+
+    def test_ifconfig_ap(self) -> None:
+        # fake a connection to a network
+        self.wh.station.connected = True
+
+        result = self.wh.ifconfig_client
+        self.assertEqual(self.isinstance_namedtuple(result), True)
+        self.assertEqual(list(result._fields),
+                         ['ip', 'subnet', 'gateway', 'dns'])
+
+    def isinstance_namedtuple(self, obj: tuple) -> bool:
+        return (
+            isinstance(obj, tuple) and
+            hasattr(obj, '_asdict') and
+            hasattr(obj, '_fields')
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/test_wifi_manager.py b/simulation/tests/test_wifi_manager.py
new file mode 100644
index 0000000..7622718
--- /dev/null
+++ b/simulation/tests/test_wifi_manager.py
@@ -0,0 +1,678 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""Unittest of WiFi Manager"""
+
+import logging
+from nose2.tools import params
+from pathlib import Path
+import shutil
+import tempfile
+from typing import List, Union
+import unittest
+from unittest.mock import patch, mock_open
+
+# custom imports
+from .unittest_helper import UnitTestHelper
+from generic_helper import GenericHelper
+from generic_helper import Message
+from led_helper import Neopixel     # Led
+from machine import machine
+from wifi_manager import WiFiManager
+from wifi_helper import network
+from wifi_helper import WifiHelper
+
+
+class TestWiFiManager(unittest.TestCase):
+    # Set maximum size of the assertion error message when Unit Test fail
+    maxDiff = None
+
+    def setUp(self) -> None:
+        self.wm = WiFiManager(logger=None, quiet=False)
+        self.uth = UnitTestHelper(quiet=False)
+
+        # create a tmp directory
+        # self.tmp_dir = tempfile.mkdtemp()
+
+    def tearDown(self) -> None:
+        self.wm.pixel.fading = False
+        self.wm.scanning = False
+
+        # shutil.rmtree(self.tmp_dir)
+
+    def test__init__(self) -> None:
+        self.assertIsInstance(self.wm.logger, logging.Logger)
+        self.assertFalse(self.wm.logger.disabled)
+
+        self.assertIsNotNone(self.wm._config_file)
+
+        self.assertIsInstance(self.wm.wh, WifiHelper)
+        self.assertIsInstance(self.wm.pixel, Neopixel)
+        self.assertEqual(self.wm.pixel.color, [20, 20, 0])
+        self.assertEqual(self.wm.pixel.intensity, 20)
+
+        self.assertEqual(self.wm._enc_key, 'DEADBEEFDEADBEEF')
+        self.assertEqual(len(self.wm._enc_key), 16)
+
+        self.assertIsInstance(self.wm._configured_networks, list)
+        self.assertEqual(len(self.wm._configured_networks), 0)
+
+        self.assertEqual(self.wm._scan_interval, 5000)
+        self.assertIsInstance(self.wm._scan_net_msg, Message)
+        self.assertEqual(self.wm._scan_net_msg.value(), list())
+        self.assertIsNone(self.wm._latest_scan)
+
+        self.assertTrue(self.wm.scanning)
+
+    @params(
+        (
+            'not-existing-file.json',   # path to config file
+            [],                         # expected list of available networks
+            False,                      # let connection not timeout
+            False                       # expected function return value
+        ),
+        (
+            'single-network.json',
+            'MyNet',
+            True,                   # let connection timeout
+            False
+        ),
+        (
+            'multi-network.json',
+            ['SSID Name', 'Other-Network@1'],
+            False,                  # let connection not timeout
+            True
+        ),
+        (
+            'multi-network.json',
+            ['SSID Name', 'Other-Network@1'],
+            True,                   # let connection timeout
+            False
+        )
+    )
+    def test_load_and_connect(self,
+                              path: str,
+                              network_definitions: Union[str, List[str]],
+                              do_timeout: bool,
+                              expectation: bool) -> None:
+        """
+        Test loading WiFi configuration from encrypted files and connecting to
+        the specified network(s)
+
+        :param      path:           Path to configuration file
+        :type       path:           str
+        :param      network_definitions:    Expected decrypted network names
+        :type       network_definitions:    Union[str, List[str]]
+        :param      do_timeout:     Let connect fail due to timeout
+        :type       do_timeout:     bool
+        :param      expectation:    Expected return value of connection to net
+        :type       expectation:    bool
+        """
+        # overwrite config file path with given path
+        file_path = self.uth.get_current_path() / 'data' / 'encrypted' / path
+        self.wm._config_file = str(file_path)
+
+        # mock unknown reset cause
+        with patch('machine.machine.reset_cause') as mock_reset_cause:
+            mock_reset_cause.return_value = machine.UNKNOWN_RESET
+            # mock not yet connected and change to success on nth call
+            # timeout is set to 5 sec by default, sleep time is 0.1 per call
+            if do_timeout:
+                # create list of all False with n entries
+                # n = timeout(sec) * calls(sec) + calls before entering connect
+                if isinstance(network_definitions, str):
+                    side_effect = [False] * (int(5 * 1/0.1) + 1)
+                elif isinstance(network_definitions, list):
+                    side_effect = [False] * (int(5 * 1/0.1) + 2) * len(network_definitions)
+                else:
+                    raise TypeError('Expectation can only be string or list')
+            else:
+                # create list of results of the isconnected call
+                # n = timeout(sec) * calls(sec) + calls before entering connect
+                # mock unconnected status for first 5 calls (t = 0.5sec)
+                return_unconnected = [False] * 5
+                if isinstance(network_definitions, str):
+                    return_connected = [True] * int(5 * 1/0.1)
+                elif isinstance(network_definitions, list):
+                    return_connected = [True] * int(5 * 1/0.1) * len(network_definitions)
+                else:
+                    raise TypeError('Expectation can only be string or list')
+                side_effect = return_unconnected + return_connected
+            with patch('wifi_helper.network.Station.isconnected',
+                       side_effect=side_effect) as mock_isconnected:
+                result = self.wm.load_and_connect()
+                self.assertEqual(result, expectation)
+
+        if result:
+            self.assertEqual(self.wm.configured_networks, network_definitions)
+
+    @unittest.skip("Not yet implemented to create an AccessPoint")
+    def test_start_config(self) -> None:
+        pass
+
+    def test__add_app_routes(self) -> None:
+        expectation = [
+            '/',
+            '/remove_wifi_config',
+            '/save_wifi_config',
+            '/wifi_selection',
+            '/wifi_configs',
+            '/scan_result',
+            '/static/<path:filename>',
+        ]
+        for rule in self.wm.app.url_map.iter_rules():
+            self.assertIn("{}".format(rule), expectation)
+
+    @params(
+        (
+            'hello',
+            b''',\xddZ\xa2\x16\xf3p\xd3D\xc1\x9cqN\xc2\xb7-'''
+        ),
+        (
+            [True, 'val'],
+            b'''`\x99\xd4\xcdU\xbf\x90\x8e\x16J\xa6\xc6\x18\x1b-J'''
+        ),
+        (
+            {'some': True, 'val': 123},
+            b'''ib\x03\xb5\x8a\xc1}QS\xfd\x1dl,\x80d\xcb/\xe1\xb6G\xe4]\x9c\xee\x84\xb1U\xaa#\x97\x93\xb6'''
+        ),
+        # single network
+        (
+            {"ssid": "MyNet", "password": "empty"},
+            b'''E\xcd\xc6\xa4\xbc4\x05(\xc96\xf4\xa9\xf7\x83\x83\xb7\x08u\x94\xa6bn\xbf|\x19\xd5\xd2\xff\xddw\tGw\x9f\xda\xc6\xa3\xe1\x9b\x16F\xa2~ 1\xa1Z\x19'''
+        ),
+        # multi network
+        (
+            [{"ssid": "SSID Name", "password": "1234qwertz@"}, {"ssid": "Other-Network@1", "password": "password"}],
+            b'''=\xd0B\xa4\xad\xc5\xbd\x07]\x0cI\x1a\xe1\x0c\xe2\x06\xe6V\xf3\x82\x8a\x83xx\xea\x13D:\xd9c\xab\x0e53\xbc1-Jo\xbb\xfb\xf4M\xd9x\xbbn;j\x10\xa63\xe9b\xbeE\x19\x8czmD\xfb\xbe\xecl\xd8\xce,\xddJ:4\\\xc1ds\xc5_\x91\x85\x16\xce$@\xab+\x9b\xf3\xe4z\xdc\xa6\xce~\x82b\x01\x83\x8b\xe4lD\x8f\xad\xc2h\xce\xeaM\xa3Y)'''
+        )
+    )
+    def test__encrypt_data(self,
+                           data: Union[str, list, dict],
+                           expectation: bytes) -> None:
+        """
+        Test encryption of data
+
+        :param      data:           The data
+        :type       data:           Union[str, list, dict]
+        :param      expectation:    Expected encrypted data as bytes
+        :type       expectation:    bytes
+        """
+        result = self.wm._encrypt_data(data=data)
+
+        self.assertEqual(result, expectation)
+
+    @params(
+        (
+            b''',\xddZ\xa2\x16\xf3p\xd3D\xc1\x9cqN\xc2\xb7-''',
+            None,
+            'hello'
+        ),
+        (
+            b'''`\x99\xd4\xcdU\xbf\x90\x8e\x16J\xa6\xc6\x18\x1b-J''',
+            None,
+            "[True, 'val']"
+        ),
+        (
+            b'''ib\x03\xb5\x8a\xc1}QS\xfd\x1dl,\x80d\xcb/\xe1\xb6G\xe4]\x9c\xee\x84\xb1U\xaa#\x97\x93\xb6''',
+            None,
+            "{'some': True, 'val': 123}"
+        ),
+        (
+            b'',
+            'single-network.json',
+            {"ssid": "MyNet", "password": "empty"}
+        ),
+        (
+            b'',
+            'multi-network.json',
+            [{"ssid": "SSID Name", "password": "1234qwertz@"}, {"ssid": "Other-Network@1", "password": "password"}]
+        )
+    )
+    def test__decrypt_data(self,
+                           data: bytes,
+                           file_path: str,
+                           expectation: str) -> None:
+        """
+        Test decryption of data
+
+        :param      data:           The data
+        :type       data:           bytes
+        :param      file_path:      Path to file to decrypt
+        :type       file_path:      str
+        :param      expectation:    Expected encrypted data as bytes
+        :type       expectation:    bytes
+        """
+        if file_path:
+            path = self.uth.get_current_path() / 'data' / 'encrypted'
+            file_path = str(path / file_path)
+
+            data = b''
+            with open(file_path, 'rb') as file:
+                data = file.read()
+
+            result = self.wm._decrypt_data(data=data)
+            result = GenericHelper.str_to_dict(data=result)
+        else:
+            result = self.wm._decrypt_data(data=data)
+
+        self.assertEqual(result, expectation)
+
+    @params(
+        # test extension of unexisting network config file with new networks
+        # extend single network with single new network
+        (
+            'not-existing-file.json',   # path to file to be extended
+            {"ssid": "new_network", "password": "some_password"},   # new net
+            ["new_network"],            # expected known networks
+            {"ssid": "new_network", "password": "some_password"},   # expected file content
+            False                       # file is not encrypted
+        ),
+        (
+            'not-existing-file.json',
+            {"ssid": "new_network", "password": "some_password"},
+            ["new_network"],
+            {"ssid": "new_network", "password": "some_password"},
+            True
+        ),
+
+        # test extension of unexisting network config file with new networks
+        # extend single network with multiple new networks
+        (
+            'not-existing-file.json',
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            ["SSID Name", "Other-Network@1"],
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            False
+        ),
+        (
+            'not-existing-file.json',
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            ["SSID Name", "Other-Network@1"],
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            True
+        ),
+
+        # test extension of existing network config file with new networks
+        # extend single network with single new network
+        (
+            'single-network.json',
+            {"ssid": "SSID Name", "password": "1234qwertz@"},
+            ["MyNet", "SSID Name"],
+            [
+                {"ssid": "MyNet", "password": "empty"},
+                {"ssid": "SSID Name", "password": "1234qwertz@"}
+            ],
+            False
+        ),
+        (
+            'single-network.json',
+            {"ssid": "SSID Name", "password": "1234qwertz@"},
+            ["MyNet", "SSID Name"],
+            [
+                {"ssid": "MyNet", "password": "empty"},
+                {"ssid": "SSID Name", "password": "1234qwertz@"}
+            ],
+            True
+        ),
+
+        # test extension of existing network config file with new networks
+        # extend single network with multiple new networks
+        (
+            'single-network.json',
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            ["MyNet", "SSID Name", "Other-Network@1"],
+            [
+                {"ssid": "MyNet", "password": "empty"},
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            False
+        ),
+        (
+            'single-network.json',
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            ["MyNet", "SSID Name", "Other-Network@1"],
+            [
+                {"ssid": "MyNet", "password": "empty"},
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ],
+            True
+        ),
+
+        # test extension of existing network config file with new networks
+        # extend multiple networks with single new network
+        (
+            'multi-network.json',
+            {"ssid": "new_net_1", "password": "password_1"},
+            ["SSID Name", "Other-Network@1", "new_net_1"],
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"},
+                {"ssid": "new_net_1", "password": "password_1"}
+            ],
+            False
+        ),
+
+        (
+            'multi-network.json',
+            {"ssid": "new_net_1", "password": "password_1"},
+            ["SSID Name", "Other-Network@1", "new_net_1"],
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"},
+                {"ssid": "new_net_1", "password": "password_1"}
+            ],
+            True
+        ),
+
+        # test extension of existing network config file with new networks
+        # extend multiple networks with multiple new networks
+        (
+            'multi-network.json',
+            [
+                {"ssid": "new_net_1", "password": "password_1"},
+                {"ssid": "new_net_2", "password": "password_2"}
+            ],
+            ["SSID Name", "Other-Network@1", "new_net_1", "new_net_2"],
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"},
+                {"ssid": "new_net_1", "password": "password_1"},
+                {"ssid": "new_net_2", "password": "password_2"}
+            ],
+            False
+        ),
+        (
+            'multi-network.json',
+            [
+                {"ssid": "new_net_1", "password": "password_1"},
+                {"ssid": "new_net_2", "password": "password_2"}
+            ],
+            ["SSID Name", "Other-Network@1", "new_net_1", "new_net_2"],
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"},
+                {"ssid": "new_net_1", "password": "password_1"},
+                {"ssid": "new_net_2", "password": "password_2"}
+            ],
+            True
+        ),
+    )
+    def test_extend_wifi_config_data(self,
+                                     file_path: str,
+                                     data: Union[dict, List[dict]],
+                                     expected_networks: list,
+                                     expected_file_content: Union[dict,
+                                                                  List[dict]],
+                                     is_encrypted: bool) -> None:
+        root_path = self.uth.get_current_path() / 'data'
+        if is_encrypted:
+            path = str(root_path / 'encrypted' / file_path)
+        else:
+            path = str(root_path / 'unencrypted' / file_path)
+
+        # more efficient to create temporary directory here, than in setUp
+        tmp_dir = tempfile.mkdtemp()
+        print('Using tmp directory at: {}'.format(tmp_dir))
+
+        if Path(path).exists():
+            print('Copied {} to {}'.format(file_path, tmp_dir))
+            shutil.copy(src=path, dst=tmp_dir)
+
+            # pre-load existing networks
+            pre_known_nets = list()
+
+            if isinstance(data, dict):
+                if isinstance(expected_file_content, dict):
+                    pass
+                elif isinstance(expected_file_content, list):
+                    for net in expected_file_content:
+                        if net is not data:
+                            pre_known_nets.append(net['ssid'])
+            elif isinstance(data, list):
+                if isinstance(expected_file_content, dict):
+                    for net in data:
+                        if net is not expected_file_content:
+                            pre_known_nets.append(net['ssid'])
+                elif isinstance(expected_file_content, list):
+                    for net in expected_file_content:
+                        if net not in data:
+                            pre_known_nets.append(net['ssid'])
+
+            print('Pre known nets: {}'.format(pre_known_nets))
+            self.wm._configured_networks = pre_known_nets
+
+        path = str(Path(tmp_dir) / file_path)
+        print('Play with file at {}'.format(path))
+
+        self.wm.extend_wifi_config_data(data=data,
+                                        path=path,
+                                        encrypted=is_encrypted)
+        self.assertEqual(self.wm.configured_networks, expected_networks)
+
+        self.assertTrue(Path(path).exists())
+
+        saved_data = self.wm._load_wifi_config_data(path=path,
+                                                    encrypted=is_encrypted)
+        self.assertEqual(saved_data, expected_file_content)
+
+        shutil.rmtree(tmp_dir)
+        print('Deleted tmp directory: {}'.format(tmp_dir))
+
+        """
+        fake_file_path = "fake/file/path"
+        fake_content = "Content which is never written to a file"
+        file_mode = 'w'
+
+        with patch("builtins.open", mock_open()) as mock_file:
+            GenericHelper.save_file(data=fake_content,
+                                    path=fake_file_path,
+                                    mode=file_mode)
+
+            # assert if opened file on write mode 'w'
+            mock_file.assert_called_once_with(fake_file_path, file_mode)
+
+            # assert if write(content) was called from the file opened
+            # in another words, assert if the specific content was written
+            mock_file().write.assert_called_once_with(fake_content)
+        """
+
+    @params(
+        (
+            'single-network.json',                  # path to config file
+            False,                                  # data encrypted
+            {"ssid": "MyNet", "password": "empty"}  # expectation
+        ),
+        (
+            'multi-network.json',
+            False,
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ]
+        ),
+        (
+            'single-network.json',
+            True,
+            {"ssid": "MyNet", "password": "empty"}
+        ),
+        (
+            'multi-network.json',
+            True,
+            [
+                {"ssid": "SSID Name", "password": "1234qwertz@"},
+                {"ssid": "Other-Network@1", "password": "password"}
+            ]
+        ),
+    )
+    def test__load_wifi_config_data(self,
+                                    path: str,
+                                    encrypted: bool,
+                                    expectation: Union[dict,
+                                                       List[dict]]) -> None:
+        """
+        Test loading WiFi config data from existing files
+
+        :param      path:           Path to file
+        :type       path:           str
+        :param      encrypted:      Flag whether file is encrypted
+        :type       encrypted:      bool
+        :param      expectation:    Expected encrypted data as bytes
+        :type       expectation:    Union[dict, List[dict]]
+        """
+        root_path = self.uth.get_current_path() / 'data'
+        if encrypted:
+            path = str(root_path / 'encrypted' / path)
+        else:
+            path = str(root_path / 'unencrypted' / path)
+
+        result = self.wm._load_wifi_config_data(path=path, encrypted=encrypted)
+
+        self.assertEqual(result, expectation)
+
+    @unittest.skip("Tested with test_load_and_connect")
+    def test_configured_networks(self) -> None:
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test__scan(self) -> None:
+        pass
+
+    @params(
+        (
+            10,     # desired value
+            1000    # expected value
+        ),
+        (
+            1000,
+            1000
+        ),
+        (
+            1001,
+            1001
+        ),
+        (
+            'test',
+            5000
+        ),
+        (
+            1234.5,
+            5000
+        )
+    )
+    def test_scan_interval(self, value: int, expectation: int) -> None:
+        self.wm.scan_interval = value
+        self.assertEqual(self.wm.scan_interval, expectation)
+
+    @unittest.skip("Not yet implemented")
+    def test_scanning(self) -> None:
+        pass
+
+    def test_latest_scan(self) -> None:
+        required_keys = [
+            'ssid', 'RSSI', 'bssid', 'authmode', 'quality', 'channel', 'hidden'
+        ]
+
+        # get dummy data as expectation
+        self.wm.wh.scan_networks()
+        expectation = self.wm.wh.get_wifi_networks_sorted()
+
+        # set latest Message content as expectation
+        self.wm._scan_net_msg.set(expectation)
+
+        result = self.wm.latest_scan
+
+        self.assertIsInstance(result, list)
+        self.assertEqual(len(result), 2)
+        for ele in result:
+            self.assertIsInstance(ele, dict)
+            for key in ele.keys():
+                self.assertIn(key, required_keys)
+
+    @unittest.skip("Function only works while Flask app is running")
+    def test_scan_result(self) -> None:
+        pass
+
+    @unittest.skip("Function only works while Flask app is running")
+    def test_wifi_selection(self) -> None:
+        pass
+
+    @unittest.skip("Function only works while Flask app is running")
+    def test_wifi_configs(self) -> None:
+        pass
+
+    @unittest.skip("Function only works while Flask app is running")
+    def test_save_wifi_config(self):
+        pass
+
+    @unittest.skip("Not yet implemented")
+    @params(
+        (
+            {'bssid': 'a0f3c1fbfc3c', 'ssid': '', 'password': 'qwertz1234@'},
+            {'ssid': 'TP-LINK_FBFC3C', 'password': 'qwertz1234@'}
+        ),
+        (
+            {'bssid': '3810d517eb39', 'ssid': '', 'password': 'easy'},
+            {'ssid': 'FRITZ!Box 7490', 'password': 'easy'}
+        ),
+        (
+            {'ssid': 'TP-LINK_FBFC3C', 'password': 'nopw_needed1'},
+            {'ssid': 'TP-LINK_FBFC3C', 'password': 'nopw_needed1'}
+        ),
+        (
+            {'ssid': 'FRITZ!Box 7490', 'password': ''},
+            {'ssid': 'FRITZ!Box 7490', 'password': ''}
+        ),
+        (
+            # unknown (not in latest scan) BSSID given
+            {'bssid': 'unknown', 'ssid': '', 'password': 'something'},
+            {'password': 'something'}
+        ),
+        (
+            # unknown (not in latest scan) SSID given
+            {'ssid': 'unknown', 'password': 'something'},
+            {'ssid': 'unknown', 'password': 'something'}
+        )
+    )
+    def test__save_wifi_config(self,
+                               form_data: dict,
+                               expectation: dict) -> None:
+        result = self.wm._save_wifi_config(form_data=form_data)
+        self.assertIsInstance(result, dict)
+        self.assertEqual(result, expectation)
+
+    @unittest.skip("Function only works while Flask app is running")
+    def test_remove_wifi_config(self):
+        pass
+
+    @unittest.skip("Not yet implemented")
+    def test__remove_wifi_config(self):
+        pass
+
+    @unittest.skip("Function only works while Flask app is running")
+    def test_run(self) -> None:
+        pass
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/simulation/tests/unittest.cfg b/simulation/tests/unittest.cfg
new file mode 100644
index 0000000..2d838f9
--- /dev/null
+++ b/simulation/tests/unittest.cfg
@@ -0,0 +1,38 @@
+[unittest]
+# start-dir = tests
+# code-directories = src
+plugins = nose2.plugins.junitxml
+# plugins = nose2.plugins.attrib
+
+[coverage]
+always-on = True
+coverage =
+coverage-config =
+coverage-report = html
+
+[discovery]
+always-on = True
+
+[functions]
+always-on = True
+
+[output-buffer]
+always-on = True
+stderr = False
+stdout = True
+
+[parameters]
+always-on = True
+
+[pretty-assert]
+always-on = True
+
+[test-result]
+always-on = True
+descriptions = True
+
+[junit-xml]
+always-on = True
+keep_restricted = False
+path = reports/test_results/nose2-junit.xml
+test_fullname = True
\ No newline at end of file
diff --git a/simulation/tests/unittest_helper.py b/simulation/tests/unittest_helper.py
new file mode 100644
index 0000000..d0e9807
--- /dev/null
+++ b/simulation/tests/unittest_helper.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+"""
+Unittest helper
+
+Common modules used for unittesting
+"""
+
+import logging
+from pathlib import Path
+
+# custom imports
+from generic_helper import GenericHelper
+
+
+class UnitTestHelper(object):
+    """docstring for UnitTestHelper"""
+    def __init__(self, logger: logging.Logger = None, quiet: bool = False):
+        if logger is None:
+            logger_name = self.__class__.__name__
+            logger = GenericHelper.create_logger(logger_name=logger_name)
+        self.logger = logger
+        self.logger.disabled = quiet
+
+    @staticmethod
+    def get_current_path() -> Path:
+        """
+        Get the path to this file.
+
+        :returns:   The path of this file
+        :rtype:     Path object
+        """
+        here = Path(__file__).parent.resolve()
+
+        return here