ctucx.git: trainsearch

web based trip-planner, fork of https://cyberchaos.dev/yuka/trainsearch

commit fb46f86ab273a99008c5cddacd3971b64181b892
parent 73de80c74a1ae1e3b5c205a3e1555578a3661c2c
Author: Katja (ctucx) <git@ctu.cx>
Date: Sun, 2 Feb 2025 23:00:48 +0100

refactor stuff
18 files changed, 636 insertions(+), 604 deletions(-)
M
src/app_functions.js
|
81
++++++++++++++++++++++++++++++++++++++-----------------------------------------
A
src/assets/icons.css
|
76
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
src/assets/index.html
|
20
++++----------------
M
src/assets/style.css
|
450
+++++++++++++++++++++++++++++++------------------------------------------------
M
src/departuresView.js
|
24
+++++++++++++++---------
M
src/formatters.js
|
72
++++++++++++++++++++++++++++++++++++++++++++++--------------------------
M
src/hafasClient.js
|
17
+++++++++++++----
M
src/helpers.js
|
12
++++++------
M
src/journeyView.js
|
127
++++++++++++++++++++++++++++++++++++++++++-------------------------------------
M
src/journeysView.js
|
83
++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
M
src/journeysViewCanvas.js
|
10
++++------
M
src/languages.js
|
23
++++++++++++++++-------
M
src/overlays.js
|
12
++++--------
M
src/router.js
|
5
+++++
M
src/searchView.js
|
73
+++++++++++++++++++++++++++++++++++--------------------------------------
M
src/settingsView.js
|
22
+++++++++++-----------
M
src/templates.js
|
18
++++++++++++++----
M
src/tripView.js
|
115
++++++++++++++++++++++++++++++++++++++++---------------------------------------
diff --git a/src/app_functions.js b/src/app_functions.js
@@ -24,7 +24,7 @@ const journeySettings = () => { return {
 export const getFrom = journeys => journeys[0].legs[0].origin;
 export const getTo   = journeys => journeys[0].legs[journeys[0].legs.length-1].destination;
 
-const addJourneys = async data => {
+export const addJourneys = async data => {
 	if (!data) return false;
 
 	const historyEntry = {

@@ -73,6 +73,28 @@ export const processLeg = leg => {
 	}
 };
 
+export const newJourneys = async (params) => {
+	const { from, to, ...moreOpts } = params;
+	let data;
+
+	data = await client.journeys(from, to, { ...journeySettings(), ...moreOpts });
+
+	for (const journey of data.journeys) {
+		journey.refreshToken = hafasToTrainsearch(journey.refreshToken);
+	}
+
+	data.slug        = generateSlug();
+	data.indexOffset = 0;
+	data.params      = params;
+	data.settings    = journeySettings();
+	data.profile     = settings.profile;
+
+	await addJourneys(data);
+	processJourneys(data);
+
+	return data;
+};
+
 export const getJourneys = async slug => {
 	let data = await db.getJourneysOverview(slug);
 

@@ -84,18 +106,6 @@ export const getJourneys = async slug => {
 	};
 };
 
-export const getJourney = async (refreshToken, profile) => {
-	let journeyObject  = await db.getJourney(refreshToken);
-
-	if (!journeyObject || JSON.stringify(journeyObject.settings) != JSON.stringify(journeySettings())) {
-		journeyObject = await refreshJourney(refreshToken, profile);
-	}
-
-	processJourney(journeyObject);
-
-	return journeyObject;
-};
-
 export const getMoreJourneys = async (slug, mode) => {
 	const saved  = await db.getJourneysOverview(slug);
 	const params = { ...saved.params, ...journeySettings() };

@@ -115,9 +125,7 @@ export const getMoreJourneys = async (slug, mode) => {
 		...newData,
 	};
 
-	for (const journey of newData.journeys) {
-		journey.refreshToken = hafasToTrainsearch(journey.refreshToken);
-	}
+	for (const journey of newData.journeys) journey.refreshToken = hafasToTrainsearch(journey.refreshToken);
 
 	if (mode === 'earlier') {
 		res.journeys = newData.journeys.concat(existingJourneys);

@@ -138,6 +146,17 @@ export const refreshJourneys = async (slug) => {
 	await Promise.all(saved.journeys.map(x => refreshJourney(x, saved.profile || "db")));
 };
 
+export const getJourney = async (refreshToken, profile) => {
+	let journeyObject = await db.getJourney(refreshToken);
+
+	if (!journeyObject || JSON.stringify(journeyObject.settings) != JSON.stringify(journeySettings()))
+		journeyObject = await refreshJourney(refreshToken, profile);
+
+	processJourney(journeyObject);
+
+	return journeyObject;
+};
+
 export const refreshJourney = async (refreshToken, profile) => {
 	const client        = await getHafasClient(profile || settings.profile || "db");
 	const [saved, data] = await Promise.all([

@@ -157,33 +176,11 @@ export const refreshJourney = async (refreshToken, profile) => {
 	return journey;
 };
 
-export const newJourneys = async (params) => {
-	const { from, to, ...moreOpts } = params;
-	let data;
-
-	data = await client.journeys(from, to, { ...journeySettings(), ...moreOpts });
-
-	for (const journey of data.journeys) {
-		journey.refreshToken = hafasToTrainsearch(journey.refreshToken);
-	}
-
-	data.slug        = generateSlug();
-	data.indexOffset = 0;
-	data.params      = params;
-	data.settings    = journeySettings();
-	data.profile     = settings.profile;
-
-	await addJourneys(data);
-	processJourneys(data);
-
-	return data;
-};
-
-export const ds100Names = (id) => {
-	if (!settings.showDS100) return '';
-	if (!ds100[Number(id)])  return '';
+export const ds100Name = (id) => {
+	if (!settings.showDS100) return null;
+	if (!ds100[Number(id)])  return null;
 
-	return '('+ds100[Number(id)]+')';
+	return ds100[Number(id)];
 };
 
 export const ds100Reverse = (name) => {
diff --git a/src/assets/icons.css b/src/assets/icons.css
@@ -0,0 +1,76 @@
+.icon-back {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z" fill="white"/></svg>');
+}
+
+.icon-reload {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z" fill="white"/></svg>');
+}
+
+.icon-close {
+	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="30" height="30"><path d="M5.293 5.293a1 1 0 0 1 1.414 0L12 10.586l5.293-5.293a1 1 0 1 1 1.414 1.414L13.414 12l5.293 5.293a1 1 0 0 1-1.414 1.414L12 13.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L10.586 12 5.293 6.707a1 1 0 0 1 0-1.414" fill="white"/></svg>');
+}
+
+.icon-hint {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14c0 .53-.21 1.04-.59 1.41-.37.38-.88.59-1.41.59H5c-.53 0-1.04-.21-1.41-.59C3.21 20.04 3 19.53 3 19V5c0-1.11.89-2 2-2m6 6h2V7h-2zm3 8v-2h-1v-4h-3v2h1v2h-1v2z"/></svg>');
+}
+
+.icon-status {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m8 10V7h-2v6zm0 4v-2h-2v2z"/></svg>');
+}
+
+.icon-warning {
+	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13 13h-2V7h2m-2 8h2v2h-2m4.73-14H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27z"/></svg>');
+}
+
+.icon-other {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 17h-2v-2h2zm2.07-7.75-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25"/></svg>');
+}
+
+.icon-arrow1 {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>');
+}
+
+.icon-arrow2 {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"/></svg>');
+}
+
+.icon-swap {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M16 17.01V10h-2v7.01h-3L15 21l4-3.99zM9 3 5 6.99h3V14h2V6.99h3z"/></svg>');
+}
+
+.icon-clock {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M12 20a8 8 0 0 0 8-8 8 8 0 0 0-8-8 8 8 0 0 0-8 8 8 8 0 0 0 8 8m0-18a10 10 0 0 1 10 10 10 10 0 0 1-10 10C6.47 22 2 17.5 2 12A10 10 0 0 1 12 2m.5 5v5.25l4.5 2.67-.75 1.23L11 13V7z"/></svg>');
+}
+
+.icon-settings {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.49.49 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6"/></svg>');
+}
+
+.icon-walk-fast {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2m-3.6 13.9 1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2z"/></svg>');
+}
+
+.icon-walk {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2M9.8 8.9 7 23h2.1l1.8-8 2.1 2v6h2v-7.5l-2.1-2 .6-3C14.8 12 16.8 13 19 13v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1L6 8.3V13h2V9.6z"/></svg>');
+}
+
+.icon-weelchair {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><circle cx="12" cy="4" r="2"/><path d="M19 13v-2c-1.54.02-3.09-.75-4.07-1.83l-1.29-1.43c-.17-.19-.38-.34-.61-.45-.01 0-.01-.01-.02-.01H13c-.35-.2-.75-.3-1.19-.26C10.76 7.11 10 8.04 10 9.09V15c0 1.1.9 2 2 2h5v5h2v-5.5c0-1.1-.9-2-2-2h-3v-3.45c1.29 1.07 3.25 1.94 5 1.95m-6.17 5c-.41 1.16-1.52 2-2.83 2-1.66 0-3-1.34-3-3 0-1.31.84-2.41 2-2.83V12.1a5 5 0 1 0 5.9 5.9z"/></svg>');
+}
+
+.icon-bike {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 20.5A3.5 3.5 0 0 1 1.5 17 3.5 3.5 0 0 1 5 13.5 3.5 3.5 0 0 1 8.5 17 3.5 3.5 0 0 1 5 20.5M5 12a5 5 0 0 0-5 5 5 5 0 0 0 5 5 5 5 0 0 0 5-5 5 5 0 0 0-5-5m9.8-2H19V8.2h-3.2l-1.94-3.27c-.29-.5-.86-.83-1.46-.83-.47 0-.9.19-1.2.5L7.5 8.29C7.19 8.6 7 9 7 9.5c0 .63.33 1.16.85 1.47L11.2 13v5H13v-6.5l-2.25-1.65 2.32-2.35m5.93 13a3.5 3.5 0 0 1-3.5-3.5 3.5 3.5 0 0 1 3.5-3.5 3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m0-8.5a5 5 0 0 0-5 5 5 5 0 0 0 5 5 5 5 0 0 0 5-5 5 5 0 0 0-5-5m-3-7.2c1 0 1.8-.8 1.8-1.8S17 1.2 16 1.2 14.2 2 14.2 3 15 4.8 16 4.8"/></svg>');
+}
+
+.icon-seat {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M9 19h6v2H9c-2.76 0-5-2.24-5-5V7h2v9c0 1.66 1.34 3 3 3m1.42-13.59c.78-.78.78-2.05 0-2.83s-2.05-.78-2.83 0-.78 2.05 0 2.83c.78.79 2.04.79 2.83 0M11.5 9c0-1.1-.9-2-2-2H9c-1.1 0-2 .9-2 2v6c0 1.66 1.34 3 3 3h5.07l3.5 3.5L20 20.07 14.93 15H11.5z"/></svg>');
+}
+
+.icon-table {
+	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M3 9h14V7H3zm0 4h14v-2H3zm0 4h14v-2H3zm16 0h2v-2h-2zm0-10v2h2V7zm0 6h2v-2h-2z" fill="white" /></svg>');
+}
+
+.icon-canvas {
+	content: url('data:image/svg+xml;utf8,<svg version="1.1" viewBox="0 0 24 24" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m11 5v14h2v-14zm-4-2v14h2v-14zm10 4h-2v14h2z" fill="white"/></svg>');
+}
+
diff --git a/src/assets/index.html b/src/assets/index.html
@@ -11,36 +11,24 @@
 		<style>
 			body {
 				background-color: #333;
-				min-height: 100vh;
-				overflow-x: hidden;
-				overflow-y: visible;
 			}
 
 			#overlay {
 				position: fixed;
+				display: flex;
 				top: 0;
 				left: 0;
 				height: 100vh;
 				width: 100vw;
 				background-color: rgba(0, 0, 0, .7);
-				display: flex;
-			}
-
-			#overlay>* {
-				margin: auto;
-			}
-
-			svg {
-				width: 50vmin;
-				height: 50vmin;
 			}
 		</style>
 	</head>
 	<body>
-		<div id="content" class="column"></div>
+		<div id="content"></div>
 		<div id="overlay">
-			<noscript>JavaScript is required to use <%= htmlWebpackPlugin.options.title %></noscript>
-			<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><rect rx="4" height="28" width="28" fill="green"/><path d="M14 5.5c-4 0-8 .5-8 4V19c0 1.93 1.57 3.5 3.5 3.5L8 24v.5h2.23l2-2H16l2 2h2V24l-1.5-1.5c1.93 0 3.5-1.57 3.5-3.5V9.5c0-3.5-3.58-4-8-4m-4.5 15c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5m3.5-7H8v-4h5zm2 0v-4h5v4zm3.5 7c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5" fill="white"/></svg>
+			<noscript style="margin: auto;">JavaScript is required to use <%= htmlWebpackPlugin.options.title %></noscript>
+			<svg style="margin: auto; width: 50vmin; height: 50vmin;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><rect rx="4" height="28" width="28" fill="green"/><path d="M14 5.5c-4 0-8 .5-8 4V19c0 1.93 1.57 3.5 3.5 3.5L8 24v.5h2.23l2-2H16l2 2h2V24l-1.5-1.5c1.93 0 3.5-1.57 3.5-3.5V9.5c0-3.5-3.58-4-8-4m-4.5 15c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5m3.5-7H8v-4h5zm2 0v-4h5v4zm3.5 7c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5" fill="white"/></svg>
 		</div>
 	</body>
 </html>
diff --git a/src/assets/style.css b/src/assets/style.css
@@ -1,34 +1,26 @@
+@import url('./icons.css');
+
 font-face {
 	font-weight: normal;
 	font-tyle: normal;
 }
 
+:root {
+	overscroll-behavior-y: none;
+}
+
 * {
 	font-family: sans-serif;
 	box-sizing: border-box;
 	border-collapse: collapse;
 }
 
-:root {
-	overscroll-behavior-y: none;
-}
 
-html, body {
+body {
 	margin: 0;
-	min-height: 100vh;
-	overflow-x: hidden;
-	overflow-y: visible;
-}
-
-#content {
-	min-height: 100vh;
+	background-color: #333;
 }
 
-#overlay {
-	z-index: 1;
-	backdrop-filter: blur(10px);
-}
- 
 a {
 	color: inherit;
 }

@@ -46,26 +38,30 @@ a {
 	transform: rotate(180deg);
 }
 
-.row {
+.flex-row {
 	display: flex;
 	flex-direction: row;
 }
 
-.column {
+.flex-column {
 	display: flex;
 	flex-direction: column;
 }
 
-.center {
+.flex-center {
 	display: flex;
 	justify-content: center;
 	align-items: center;
 }
 
+.center {
+	margin: auto;
+}
+
 .spinner {
 	margin: calc(50vh - 60px) auto;
 	border: 5px solid rgba(255, 255, 255, .4);
-	border-top: 5px solid #fff;
+	border-top: 5px solid white;
 	border-radius: 50%;
 	width: 120px;
 	height: 120px;

@@ -81,59 +77,94 @@ a {
 	100% { transform: rotate(360deg); }
 }
 
-header {
-	display: flex;
-	flex-direction: row;
-	justify-content: center;
-	color: white;
-	background-color: #33691E;
-	border-bottom: 1px solid rgba(255, 255, 255, .3);
+.container {
+	margin: 0 auto;
+	max-width: 1000px;
+}
 
-	h3 {
-		margin-right: 1.5em;
-	}
+.header-container {
+	position: sticky;
+	top: 0;
+	z-index: 10;
 
-	.content {
+	header {
 		display: flex;
 		flex-direction: row;
-		flex-wrap: wrap;
-		flex-grow: 1;
-		max-width: 1000px;
-		width: 80vw;
-	}
+		justify-content: center;
+		color: white;
+		background-color: #33691E;
+		border-bottom: 1px solid rgba(255, 255, 255, .3);
 
-	.icon-back,
-	.icon-reload {
-		cursor: pointer;
-		width: 32px;
-		height: 32px;
-		margin: 12px;
-		user-select: none;
-	}
+		.container {
+			max-width: 1000px;
+			width: 80vw;
+			margin: 0;
+		}
 
-	.mode-changers {
-		display: flex;
-		margin-left: auto;
-	
-		a.active {
-			border-bottom: 3px solid white;
+		h3 {
+			margin-right: 1.5em;
 		}
-	
-		a {
-			border-bottom: 3px solid transparent;
-			align-items: center;
-			display: flex;
-			padding: 0 1em;
+
+		.icon-dots {
+			float: right;
+		}
+
+		.icon-back,
+		.icon-reload,
+		.icon-share,
+		.icon-dots {
 			cursor: pointer;
-			text-decoration: none;
-	
-		 	span {
-				font-weight: bold;
-				margin: 1em .4em;
+			width: 32px;
+			height: 32px;
+			margin: 12px;
+			user-select: none;
+		}
+
+		.mode-changers {
+			margin-top: auto;
+			margin-left: auto;
+			height: max-content;
+
+			a {
+				border-bottom: 3px solid transparent;
+				align-items: center;
+				display: flex;
+				padding: 0 1em;
+				cursor: pointer;
+				text-decoration: none;
+				width: max-content;
+		
+			 	span {
+					font-weight: bold;
+					margin: 1em .4em;
+				}
+			}
+
+			a.active {
+				border-bottom: 3px solid white;
 			}
 		}
 	}
-	
+
+}
+
+footer {
+	color: #ddd;
+	padding: 2em;
+	width: max-content;
+
+	a {
+		text-decoration: none;
+	}
+
+	a:after {
+		margin: 0 8px;
+		content: "•";
+	}
+
+	:last-child:after {
+		content: none;
+	}
 }
 
 .card {

@@ -142,7 +173,7 @@ header {
 	table {
 		border-bottom: 1px solid rgba(0, 0, 0, 0.3);
 		width: 100%;
-		background-color: #fff;
+		background-color: white;
 		min-width: 390px;
 		max-width: 1000px;
 	}

@@ -210,17 +241,17 @@ header {
 }
 
 .settingsView {
-	.row,
-	.column {
+	.flex-row,
+	.flex-column {
 		padding: 1em;
 		border-bottom: 1px solid rgba(0, 0, 0, .4);
 	}
 
-	.row {
+	.flex-row {
 		align-items: center;
 	}
 
-	.row:last-child {
+	.flex-row:last-child {
 		padding: .5em;
 		border-bottom: unset;
 	    justify-content: right;

@@ -239,23 +270,15 @@ header {
 	}
 
 	select,
-	.row div {
+	.flex-row div {
 		margin-left: auto;
 	}
 }
 
-.searchView {	
-	background-color: #222;
-	flex-grow: 1;
-
-	.title::before {
-		content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><rect rx="4" height="28" width="28" fill="green"/><path d="M14 5.5c-4 0-8 .5-8 4V19c0 1.93 1.57 3.5 3.5 3.5L8 24v.5h2.23l2-2H16l2 2h2V24l-1.5-1.5c1.93 0 3.5-1.57 3.5-3.5V9.5c0-3.5-3.58-4-8-4m-4.5 15c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5m3.5-7H8v-4h5zm2 0v-4h5v4zm3.5 7c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5" fill="white"/></svg>');
-		width: 50px;
-		height: 50px;
-		margin: -0.7em .3em -0.5em -0.3em;
-	}
-
+.searchView {
 	.title {
+		padding-top: 3em;
+
 		h1 {
 			color: white;
 			font-weight: normal;

@@ -269,6 +292,13 @@ header {
 		}
 	}
 
+	.title::before {
+		content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><rect rx="4" height="28" width="28" fill="green"/><path d="M14 5.5c-4 0-8 .5-8 4V19c0 1.93 1.57 3.5 3.5 3.5L8 24v.5h2.23l2-2H16l2 2h2V24l-1.5-1.5c1.93 0 3.5-1.57 3.5-3.5V9.5c0-3.5-3.58-4-8-4m-4.5 15c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5m3.5-7H8v-4h5zm2 0v-4h5v4zm3.5 7c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5" fill="white"/></svg>');
+		width: 50px;
+		height: 50px;
+		margin: -0.7em .3em -0.5em -0.3em;
+	}
+
 	form {
 		color: white;
 

@@ -282,12 +312,9 @@ header {
 
 		.button.icon-arrow1,
 		.button.icon-arrow2,
-		.button.icon-swap {
-			padding: 0;
-		}
-
+		.button.icon-swap,
 		.button.icon-clock {
-			padding: 4px;
+			padding: .3em .5em;
 		}
 
 		button[type="submit"],

@@ -320,11 +347,12 @@ header {
 	}
 
 	.suggestions {
+		position: relative;
 		overflow: visible;
-		z-index: 999;
+		z-index: 100;
 		height: 0;
 		margin-left: 4px;
-		margin-right: 3.55rem;
+		margin-right: 3.23rem;
 
 		p {
 			font-size: 1.2em;

@@ -338,7 +366,6 @@ header {
 
 		p:first-child {
 			margin-top: -4px;
-			border-top: 0px;
 		}
 
 		p:hover {

@@ -357,7 +384,7 @@ header {
 		overflow: hidden;
 		user-select: none;
 
-		.row {
+		.flex-row {
 			justify-content: space-between;
 			cursor: pointer;
 			padding: .3em .6em .3em .3em;

@@ -368,11 +395,12 @@ header {
 			border-bottom: 1px solid rgba(0, 0, 0, .2);
 		}
 
-		.row:last-child {
+		.flex-row:last-child {
 			border-bottom: unset;
 		}
 
 		.via {
+			font-size: smaller;	
 			font-weight: 200;
 		}
 

@@ -394,32 +422,6 @@ header {
 			width: 25px;
 		}
 	}
-
-	footer {
-		display: flex;
-		justify-content: center;
-		color: #ddd;
-		padding: 5em 0 1em 0;
-
-		a {
-			text-decoration: none;
-		}
-
-		a:after {
-			margin:0 8px;
-			content: "•";
-		}
-
-		:last-child:after {
-			content: none;
-		}
-	}
-}
-
-.journeyView,
-.journeysView,
-.departuresView {
-	min-height: 100vh;
 }
 
 .journeyView {

@@ -480,14 +482,14 @@ header {
 }
 
 .remarksView {
-	.row {
+	.flex-row {
 		align-items: center;
 		flex-wrap: nowrap;
 		padding: .5em;
 		border-bottom: 1px solid rgba(0, 0, 0, .4);
 	}
 
-	.row:last-child {
+	.flex-row:last-child {
 		border-bottom: unset;
 	}
 

@@ -497,59 +499,62 @@ header {
 	}
 }
 
+#overlay {
+	z-index: 100;
+	backdrop-filter: blur(10px);
 
-
-.modal {
-	z-index: 1050;
-	margin: auto;
-	background-color: white;
-	width: max-content;
-	padding: 15px;
-	-webkit-overflow-scrolling: touch;
-}
-
-.modal.alert button {
-	float: right;
-}
-
-.modal.select {
-	a {
-		width: 100%;
-		margin: 5px 0;
-		text-align: center;
+	.modal {
+		z-index: 1050;
+		margin: auto;
+		background-color: white;
+		width: max-content;
+		padding: 15px;
+		-webkit-overflow-scrolling: touch;
 	}
 
-	a:first-child {
-		margin-top: unset;
+	.modal.alert button {
+		float: right;
 	}
 
-	a:last-child {
-		margin-bottom: unset;
+	.modal.select {
+		a {
+			width: 100%;
+			margin: 5px 0;
+			text-align: center;
+		}
+
+		a:first-child {
+			margin-top: unset;
+		}
+
+		a:last-child {
+			margin-bottom: unset;
+		}
 	}
-}
 
-.modal.dialog {
-	padding: unset;
+	.modal.dialog {
+		padding: unset;
 
-	.header {
-		justify-content: space-between;
-		background-color: #33691E;
-		color: white;
-		padding: 15px;
+		.header {
+			justify-content: space-between;
+			background-color: #33691E;
+			color: white;
+			padding: 15px;
 
-		h4 {
-			margin: 0;
- 		}
+			h4 {
+				margin: 0;
+	 		}
 
-		.icon-close {
-			margin: -15px;
-			padding: 10px;
-			border-left: 1px solid rgba(0, 0, 0, .4);
-			cursor: pointer;
-		}
+			.icon-close {
+				margin: -15px;
+				padding: 10px;
+				border-left: 1px solid rgba(0, 0, 0, .4);
+				cursor: pointer;
+			}
 
-		.icon-close:hover {
-			background: rgba(0, 0, 0, .4);
+			.icon-close:hover {
+				background: rgba(0, 0, 0, .4);
+			}
 		}
 	}
 }

@@ -577,20 +582,6 @@ input[type="text"] {
 	border-radius: 0;
 }
 
-input[type="datetime-local"],
-input[type="date"],
-input[type="time"] {
-	font-size: 1.6em;
-}
-
-input[type="text"] {
-	border: 1px solid transparent;
-}
-
-input[type="text"]:focus {
-	border-bottom: 1px solid rgba(0, 0, 0, .2);
-}
-
 input[type="checkbox"] {
 	transform: scale(1.5);
 }

@@ -616,12 +607,12 @@ button:hover, .button:hover {
 }
 
 button.color, .button.color {
-	background-color: rgba(20, 30, 255, .7);
+	background-color: #43a047;
 	color: white;
 }
 
 button.color:hover, .button.color:hover {
-	background-color: rgba(70, 100, 255, .8);
+	background-color: #388e3c;
 }
 
 .arrowButton {

@@ -646,7 +637,7 @@ button.color:hover, .button.color:hover {
 	}
 
 	input:checked + label {
-		background: #fff;
+		background: white
 	}
 
 	label {

@@ -731,10 +722,6 @@ button.color:hover, .button.color:hover {
 
 @media (max-width: 799px) {
 	header {
-		.content {
-			flex-grow: 1;
-		}
-
 		.icon-back {
 			left: 10px;
 		}

@@ -744,11 +731,11 @@ button.color:hover, .button.color:hover {
 		padding: 10px;
 	}
 
-	.row {
+	.flex-row {
 		flex-wrap: wrap;
 	}
 
-	.row.nowrap {
+	.flex-row.nowrap {
 		flex-wrap: unset;
 	}
 

@@ -762,20 +749,10 @@ button.color:hover, .button.color:hover {
 		width: 48px;
 		height: 48px;
 	}
-
 }
 
 @media (min-width: 800px) {
-	#content {
-		justify-content: center;
-	}
-
 	.searchView {
-		display: flex;
-		justify-content: center;
-		align-items: center;
-		flex-direction: column;
-
 		form,
 		.history {
 			width: 80vw;

@@ -792,13 +769,6 @@ button.color:hover, .button.color:hover {
 		}
 	}
 	
-	table {
-		overflow: hidden;
-		border: none;
-		margin: 50px auto;
-		width: 80vw;
-	}
-
 	.journeysView {
 		.arrowButton.flipped {
 			margin-top: 45px;

@@ -809,84 +779,14 @@ button.color:hover, .button.color:hover {
 		}
 	}
 
+	table {
+		overflow: hidden;
+		border: none;
+		margin: 50px auto;
+		width: 80vw;
+	}
 
 	.modal.dialog {
 		width: 400px;
 	}
 }
-
-.icon-back {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z" fill="white"/></svg>');
-}
-
-.icon-reload {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M17.65 6.35A7.96 7.96 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z" fill="white"/></svg>');
-}
-
-.icon-close {
-	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="30" height="30"><path d="M5.293 5.293a1 1 0 0 1 1.414 0L12 10.586l5.293-5.293a1 1 0 1 1 1.414 1.414L13.414 12l5.293 5.293a1 1 0 0 1-1.414 1.414L12 13.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L10.586 12 5.293 6.707a1 1 0 0 1 0-1.414" fill="white"/></svg>');
-}
-
-.icon-hint {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14c0 .53-.21 1.04-.59 1.41-.37.38-.88.59-1.41.59H5c-.53 0-1.04-.21-1.41-.59C3.21 20.04 3 19.53 3 19V5c0-1.11.89-2 2-2m6 6h2V7h-2zm3 8v-2h-1v-4h-3v2h1v2h-1v2z"/></svg>');
-}
-
-.icon-status {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2m8 10V7h-2v6zm0 4v-2h-2v2z"/></svg>');
-}
-
-.icon-warning {
-	content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13 13h-2V7h2m-2 8h2v2h-2m4.73-14H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27z"/></svg>');
-}
-
-.icon-other {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 17h-2v-2h2zm2.07-7.75-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25"/></svg>');
-}
-
-.icon-arrow1 {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>');
-}
-
-.icon-arrow2 {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"/></svg>');
-}
-
-.icon-swap {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M16 17.01V10h-2v7.01h-3L15 21l4-3.99zM9 3 5 6.99h3V14h2V6.99h3z"/></svg>');
-}
-
-.icon-clock {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M12 20a8 8 0 0 0 8-8 8 8 0 0 0-8-8 8 8 0 0 0-8 8 8 8 0 0 0 8 8m0-18a10 10 0 0 1 10 10 10 10 0 0 1-10 10C6.47 22 2 17.5 2 12A10 10 0 0 1 12 2m.5 5v5.25l4.5 2.67-.75 1.23L11 13V7z"/></svg>');
-}
-
-.icon-settings {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.49.49 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6"/></svg>');
-}
-
-.icon-walk-fast {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2m-3.6 13.9 1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2z"/></svg>');
-}
-
-.icon-walk {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M13.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2M9.8 8.9 7 23h2.1l1.8-8 2.1 2v6h2v-7.5l-2.1-2 .6-3C14.8 12 16.8 13 19 13v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1L6 8.3V13h2V9.6z"/></svg>');
-}
-
-.icon-weelchair {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><circle cx="12" cy="4" r="2"/><path d="M19 13v-2c-1.54.02-3.09-.75-4.07-1.83l-1.29-1.43c-.17-.19-.38-.34-.61-.45-.01 0-.01-.01-.02-.01H13c-.35-.2-.75-.3-1.19-.26C10.76 7.11 10 8.04 10 9.09V15c0 1.1.9 2 2 2h5v5h2v-5.5c0-1.1-.9-2-2-2h-3v-3.45c1.29 1.07 3.25 1.94 5 1.95m-6.17 5c-.41 1.16-1.52 2-2.83 2-1.66 0-3-1.34-3-3 0-1.31.84-2.41 2-2.83V12.1a5 5 0 1 0 5.9 5.9z"/></svg>');
-}
-
-.icon-bike {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M5 20.5A3.5 3.5 0 0 1 1.5 17 3.5 3.5 0 0 1 5 13.5 3.5 3.5 0 0 1 8.5 17 3.5 3.5 0 0 1 5 20.5M5 12a5 5 0 0 0-5 5 5 5 0 0 0 5 5 5 5 0 0 0 5-5 5 5 0 0 0-5-5m9.8-2H19V8.2h-3.2l-1.94-3.27c-.29-.5-.86-.83-1.46-.83-.47 0-.9.19-1.2.5L7.5 8.29C7.19 8.6 7 9 7 9.5c0 .63.33 1.16.85 1.47L11.2 13v5H13v-6.5l-2.25-1.65 2.32-2.35m5.93 13a3.5 3.5 0 0 1-3.5-3.5 3.5 3.5 0 0 1 3.5-3.5 3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m0-8.5a5 5 0 0 0-5 5 5 5 0 0 0 5 5 5 5 0 0 0 5-5 5 5 0 0 0-5-5m-3-7.2c1 0 1.8-.8 1.8-1.8S17 1.2 16 1.2 14.2 2 14.2 3 15 4.8 16 4.8"/></svg>');
-}
-
-.icon-seat {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M9 19h6v2H9c-2.76 0-5-2.24-5-5V7h2v9c0 1.66 1.34 3 3 3m1.42-13.59c.78-.78.78-2.05 0-2.83s-2.05-.78-2.83 0-.78 2.05 0 2.83c.78.79 2.04.79 2.83 0M11.5 9c0-1.1-.9-2-2-2H9c-1.1 0-2 .9-2 2v6c0 1.66 1.34 3 3 3h5.07l3.5 3.5L20 20.07 14.93 15H11.5z"/></svg>');
-}
-
-.icon-table {
-	content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" height="24" width="24"><path d="M3 9h14V7H3zm0 4h14v-2H3zm0 4h14v-2H3zm16 0h2v-2h-2zm0-10v2h2V7zm0 6h2v-2h-2z" fill="white" /></svg>');
-}
-
-.icon-canvas {
-	content: url('data:image/svg+xml;utf8,<svg version="1.1" viewBox="0 0 24 24" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m11 5v14h2v-14zm-4-2v14h2v-14zm10 4h-2v14h2z" fill="white"/></svg>');
-}
diff --git a/src/departuresView.js b/src/departuresView.js
@@ -14,15 +14,16 @@ const departuresTemplate = (data, profile) => {
 	let lastArrival;
 
 	return html`
-		<div class="departuresView column">
+		<div class="header-container">
 			<header>
-				<a id="back" class="icon-back hidden" title="${t('back')}" @click=${() => history.back()}></a>
-				<div class="content">
+				<a id="back" class="icon-back invisible" title="${t('back')}" @click=${() => history.back()}></a>
+				<div class="container">
 					<h3>Departures from ${data.name}</h3>
 				</div>
 				<a id="reload" class="icon-reload invisible" title="${t("reload")}"></a>
 			</header>
-
+		</div>
+		<div class="container departuresView">
 			<div class="card">
 			<table>
 				<thead>

@@ -34,7 +35,7 @@ const departuresTemplate = (data, profile) => {
 				</thead>
 				<tbody>
 					${(data.departures || []).map(departure => html`
-						<tr class="departure" @click=${() => go(`/t/${profile}/${departure.tripId}`)}>
+						<tr @click=${() => go(`/t/${profile}/${departure.tripId}`)}>
 							<td class="${departure.cancelled ? 'cancelled' : nothing}"><span>${timeTemplate(departure)}</span></td>
 							<td class="${departure.cancelled ? 'cancelled' : nothing}"><span>${departure.line.name}${departure.direction ? html` → ${departure.direction}` : nothing}</span></td>
 							${departure.cancelled ? html`

@@ -53,30 +54,35 @@ const departuresTemplate = (data, profile) => {
 
 export const departuresView = async (match, isUpdate) => {
 	if (!isUpdate) showLoader();
+
 	let profile, stopId, when, data;
+
 	try {
 		profile = match[0];
-		stopId = match[1];
+		stopId  = match[1];
+
 		if (match[2]) when = new Date(parseInt(match[2].substring(1)));
+
 		const client = await getHafasClient(profile);
 		const [ {departures}, stopInfo ] = await Promise.all([
 			client.departures(stopId, { when }),
 			client.stop(stopId),
 		]);
+
 		for (let departure of departures) {
 			processLeg(departure);
 		};
+
 		data = { ...stopInfo, departures };
 	} catch(e) {
 		showAlertModal(e.toString());
 		throw e;
 	}
+
 	hideOverlay();
 
 	render(departuresTemplate(data, profile), ElementById('content'));
 	setThemeColor(queryBackgroundColor('header'));
 
-	if (history.length > 0) {
-		ElementById('back').classList.remove('hidden');
-	}
+	if (history.length > 0) ElementById('back').classList.remove('invisible');
 };
diff --git a/src/formatters.js b/src/formatters.js
@@ -1,23 +1,25 @@
-import { ds100Names } from './app_functions.js';
+import { ds100Name } from './app_functions.js';
 import { padZeros } from './helpers.js';
 import { languages } from './languages.js';
 
 export const formatName = (point) => {
-	let nameHTML = '';
-
-	if (point.type === 'stop' || point.type === 'station') {
-		nameHTML += point.name+ds100Names(point.id);
-	} else if (point.type == 'location') {
-		if (point.name) {
-			nameHTML += point.name;
-		} else if (point.address) {
-			nameHTML += point.address;
-		}
-	} else {
-		return '';
-	}
+	switch (point.type) {
+		case 'stop':
+		case 'station':
+			let nameHTML = point.name;
+
+			const ds100 = ds100Name(point.id);
+			if (ds100 !== null) nameHTML += ` (${ds100})`;
 
-	return nameHTML;
+			return nameHTML;
+
+		case 'location':
+			if (point.address) return point.address;
+			return point.name;
+
+		default:
+			return '';
+	};
 };
 
 export const formatDateTime = (date, format) => {

@@ -31,6 +33,10 @@ export const formatDateTime = (date, format) => {
 				return date.getDate() + '.' + (date.getMonth() + 1) + '.' + date.getFullYear();
 				break;
 
+			case 'time':
+				return `${padZeros(date.getHours())}:${padZeros(date.getMinutes())}`;
+				break;
+
 			default:
 				return false;
 				break;

@@ -47,12 +53,10 @@ export const formatDateTime = (date, format) => {
 
 export const formatDuration = (duration) => {
 	const mins = duration / 60000;
-	const h = Math.floor(mins / 60);
-	const m = mins % 60;
+	const h    = Math.floor(mins / 60);
+	const m    = mins % 60;
 
-	if (h > 0) {
-		return h+'h '+m+'min';
-	}
+	if (h > 0) return h+'h '+m+'min';
 
 	return m+'min';
 };

@@ -76,28 +80,42 @@ export const formatFromTo = obj => {
 
 export const formatPrice = price => {
 	if (!price) return '-';
-	const currencies = { USD: '$', EUR: '€', GBP: '£' };
+
+	const currencies = {
+		USD: '$',
+		EUR: '€',
+		GBP: '£'
+	};
+
 	let ret = currencies[price.currency] || price.currency;
+
 	ret += `${Math.floor(price.amount)}.${padZeros(price.amount * 100 % 100, 2)}`;
+
 	return ret;
 };
 
 export const formatTrainTypes = info => {
 	const counts = {};
+
 	for (let group of info.sequence?.groups) {
 		const name = group.baureihe?.name;
+
 		if (!name) continue;
+
 		counts[name] = (counts[name] ? counts[name] : 0) + 1;
 	}
+
 	return Object.entries(counts).map(([name, count]) => {
 		let text = "";
-		if (count > 1) {
-			text += `${count} x `;
-		}
+
+		if (count > 1) text += `${count} x `;
+
 		text += name;
+
 		while (text.length < 12) {
 			text = ' ' + text + ' ';
 		}
+
 		return text;
 	}).join(" + ");
 };

@@ -105,10 +123,12 @@ export const formatTrainTypes = info => {
 export const formatLineAdditionalName = (line) => {
 	if (!line.name) return null;
 	const splitName = line.name.split(' ');
-	if (splitName.length === 2 && line.fahrtNr && line.fahrtNr != splitName[1])
+
+	if (splitName.length === 2 && line.fahrtNr && line.fahrtNr != splitName[1]) {
 		return `${splitName[0]} ${line.fahrtNr}`;
-	else
+	} else {
 		return null;
+	}
 };
 
 export const formatLineDisplayName = (line) => line?.name || line?.operator?.name || "???";
diff --git a/src/hafasClient.js b/src/hafasClient.js
@@ -6,45 +6,54 @@ let   createHafasClient;
 
 export let client;
 
+export const initHafasClient = async profileName => {
+	client = await getHafasClient(profileName);
+};
+
 export const getHafasClient = async profileName => {
 	if (!clients[profileName]) {
 		let profile;
+
 		switch(profileName) {
 			case "db":
 				clients[profileName] = createVendoClient(dbnavProfile, "trainsearch", {enrichStations: false});
 				console.info("initialized vendo client");
 				return clients[profileName];
+
 			case "bvg":
 				const { profile: bvgProfile } = await import('hafas-client/p/bvg/index.js');
 				profile = bvgProfile;
 				break;
+
 			case "nahsh":
 				const { profile: nahshProfile } = await import('hafas-client/p/nahsh/index.js');
 				profile = nahshProfile;
 				break;
+
 			case "rmv":
 				const { profile: rmvProfile } = await import('hafas-client/p/rmv/index.js');
 				profile = rmvProfile;
 				break;
+
 			case "vrn":
 				const { profile: vrnProfile } = await import('hafas-client/p/vrn/index.js');
 				profile = vrnProfile;
 				break;
+
 			case "oebb":
 				const { profile: oebbProfile } = await import('hafas-client/p/oebb/index.js');
 				profile = oebbProfile;
 				break;
+
 			default:
 				throw "Unknown profile name: " + profileName;
 		}
+
 		if (!createHafasClient) createHafasClient = (await import("hafas-client")).createClient;
+
 		clients[profileName] = createHafasClient(profile, "trainsearch");
 	}
 
 	console.info("initialized hafas client with profile " + profileName);
 	return clients[profileName];
 }
-
-export const initHafasClient = async profileName => {
-	client = await getHafasClient(profileName);
-};
diff --git a/src/helpers.js b/src/helpers.js
@@ -1,10 +1,10 @@
 const loyaltyCards = {
-	NONE: Symbol('no loyalty card'),
-	BAHNCARD: Symbol('Bahncard'),
-	VORTEILSCARD: Symbol('VorteilsCard'),
-	HALBTAXABO: Symbol('HalbtaxAbo'),
-	VOORDEELURENABO: Symbol('Voordeelurenabo'),
-	SHCARD: Symbol('SH-Card'),
+	NONE:              Symbol('no loyalty card'),
+	BAHNCARD:          Symbol('Bahncard'),
+	VORTEILSCARD:      Symbol('VorteilsCard'),
+	HALBTAXABO:        Symbol('HalbtaxAbo'),
+	VOORDEELURENABO:   Symbol('Voordeelurenabo'),
+	SHCARD:            Symbol('SH-Card'),
 	GENERALABONNEMENT: Symbol('General-Abonnement'),
 };
 
diff --git a/src/journeyView.js b/src/journeyView.js
@@ -1,7 +1,7 @@
 import { html, nothing, render } from 'lit-html';
 import { cachedCoachSequence } from './coach-sequence/index.js';
 import { settings } from './settings.js';
-import { remarksModalTemplate, platformTemplate, stopTemplate, timeTemplate } from './templates.js';
+import { remarksModalTemplate, platformTemplate, stopTemplate, timeTemplate, footerTemplate } from './templates.js';
 import { ElementById, setThemeColor, queryBackgroundColor } from './helpers.js';
 import { getJourney, refreshJourney } from './app_functions.js';
 import { formatName, formatDateTime, formatDuration, formatPrice, formatTrainTypes, formatLineAdditionalName, formatLineDisplayName } from './formatters.js';

@@ -25,61 +25,61 @@ const legTemplate = (leg, profile) => {
 			<p class="change">${t('changeinfo', formatDuration(leg.duration))}</p>
 		` : html`
 			<div class="card">
-			<table>
-				<thead>
-					<tr>
-						<td colspan="4">
-							<div class="center"><a href="#/t/${profile}/${leg.tripId}">${formatLineDisplayName(leg.line)}${leg.direction ? html` → ${leg.direction}` : nothing}</a>
-							${leg.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : nothing}
-							${!!remarks.length ? html`
-								<a class="link ${remarksIcon}" @click=${() => showModal(t('remarks'), remarksModalTemplate(remarks))}></a>
-							` : nothing}</div>
-						</td>
-					</tr>
-					<tr>
-						<td colspan="4">
-							<div class="train-details center">
-								${formatLineAdditionalName(leg.line) ? html`
-									<div>
-										Trip: ${formatLineAdditionalName(leg.line)}
-									</div>
-								` : nothing}
-								${leg.line.trainType ? html`
-									<div>
-										Train type: ${leg.line.trainType}
-									</div>
-								` : nothing}
-								${(leg.arrival && leg.departure) ? html`
-									<div>
-										${t('duration')}: ${formatDuration(leg.arrival - leg.departure)}
-									</div>
-								` : nothing}
-								${leg.loadFactor ? html`
-									<div>
-										${t("load-"+leg.loadFactor)}
-									</div>
-								` : nothing}
-							</div>
-						</td>
-					</tr>
-					<tr>
-						<th>${t('arrival')}</th>
-						<th>${t('departure')}</th>
-						<th class="station">${t('station')}</th>
-						<th>${t('platform')}</th>
-					</tr>
-				</thead>
-				<tbody>
-					${(leg.stopovers || []).map(stop => html`
-						<tr class="stop ${stop.cancelled ? 'cancelled' : nothing}">
-							<td><span>${timeTemplate(stop, 'arrival')}</span></td>
-							<td><span>${timeTemplate(stop, 'departure')}</span></td>
-							<td>${stopTemplate(profile, stop.stop)}</td>
-							<td><span>${platformTemplate(stop)}</span></td>
+				<table>
+					<thead>
+						<tr>
+							<td colspan="4">
+								<div class="center"><a href="#/t/${profile}/${leg.tripId}">${formatLineDisplayName(leg.line)}${leg.direction ? html` → ${leg.direction}` : nothing}</a>
+								${leg.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : nothing}
+								${!!remarks.length ? html`
+									<a class="link ${remarksIcon}" @click=${() => showModal(t('remarks'), remarksModalTemplate(remarks))}></a>
+								` : nothing}</div>
+							</td>
 						</tr>
-					`)}
-				</tbody>
-			</table>
+						<tr>
+							<td colspan="4">
+								<div class="train-details flex-center">
+									${formatLineAdditionalName(leg.line) ? html`
+										<div>
+											Trip: ${formatLineAdditionalName(leg.line)}
+										</div>
+									` : nothing}
+									${leg.line.trainType ? html`
+										<div>
+											Train type: ${leg.line.trainType}
+										</div>
+									` : nothing}
+									${(leg.arrival && leg.departure) ? html`
+										<div>
+											${t('duration')}: ${formatDuration(leg.arrival - leg.departure)}
+										</div>
+									` : nothing}
+									${leg.loadFactor ? html`
+										<div>
+											${t("load-"+leg.loadFactor)}
+										</div>
+									` : nothing}
+								</div>
+							</td>
+						</tr>
+						<tr>
+							<th>${t('arrival')}</th>
+							<th>${t('departure')}</th>
+							<th class="station">${t('station')}</th>
+							<th>${t('platform')}</th>
+						</tr>
+					</thead>
+					<tbody>
+						${(leg.stopovers || []).map(stop => html`
+							<tr class="stop ${stop.cancelled ? 'cancelled' : nothing}">
+								<td><span>${timeTemplate(stop, 'arrival')}</span></td>
+								<td><span>${timeTemplate(stop, 'departure')}</span></td>
+								<td>${stopTemplate(profile, stop.stop)}</td>
+								<td><span>${platformTemplate(stop)}</span></td>
+							</tr>
+						`)}
+					</tbody>
+				</table>
 			</div>
 		`}
 	`;

@@ -87,19 +87,21 @@ const legTemplate = (leg, profile) => {
 
 const journeyTemplate = (data, profile) => {
 	let duration = null;
-	if (data.legs[data.legs.length - 1].arrival && data.legs[0].departure) {
+
+	if (data.legs[data.legs.length - 1].arrival && data.legs[0].departure)
 		duration = data.legs[data.legs.length - 1].arrival - data.legs[0].departure;
-	}
 
 	const legs = [];
 	let changes = 0;
 	let lastArrival;
+
 	for (const leg of data.legs) {
 		if (!leg.walking && !leg.transfer) {
 
 			// add change
 			if (lastArrival) {
 				let duration = null;
+
 				if (leg.departure && lastArrival) {
 					duration = leg.departure - lastArrival;
 				}

@@ -118,22 +120,27 @@ const journeyTemplate = (data, profile) => {
 			// insert a 0 minutes change entry for this
 			lastArrival = leg.arrival;
 		}
+
 		legs.push(leg);
 	}
 
 	return html`
-		<div class="journeyView column">
+		<div class="header-container">
 			<header>
 				${data.slug ? html`<a class="icon-back" href="#/${data.slug}/${settings.journeysViewMode}" title="${t('back')}"></a>` : nothing}
-				<div class="content">
+				<div class="container">
 					<h3>${formatName(data.legs[0].origin)} → ${formatName(data.legs[data.legs.length - 1].destination)}</h3>
 					<p><b>${t('duration')}: ${formatDuration(duration)} | ${t('changes')}: ${changes-1} | ${t('date')}: ${formatDateTime(data.legs[0].plannedDeparture, 'date')}${settings.showPrices && settings.profile === 'db' && data.price ? html` | ${t('price')}: <td><span>${formatPrice(data.price)}</span></td>` : nothing}</b></p>
 				</div>
 				<a class="icon-reload" title="${t("reload")}" @click=${() => refreshJourneyView(data.refreshToken, profile)}></a>
 			</header>
+		</div>
 
+		<div class="container journeyView">
 			${legs.map(leg => legTemplate(leg, profile))}
 		</div>
+
+		${footerTemplate}
 	`;
 };
 

@@ -163,7 +170,7 @@ export const journeyView = async (match, isUpdate) => {
 
 	for (const leg of journeyObject.legs) {
 		if (leg.line && leg.line.name) {
-			const [category, number] = leg.line.name.split(" ");
+			const [category, number] = leg.line.name.split(' ');
 			const info               = await cachedCoachSequence(category, leg.line.fahrtNr || number, leg.origin.id, leg.plannedDeparture);
 
 			if (info) leg.line.trainType = formatTrainTypes(info);
diff --git a/src/journeysView.js b/src/journeysView.js
@@ -2,7 +2,7 @@ import { html, nothing, render } from 'lit-html';
 import { ElementById, setThemeColor, queryBackgroundColor, padZeros } from './helpers.js';
 import { getJourneys, getMoreJourneys, refreshJourneys, getFrom, getTo } from './app_functions.js';
 import { formatName, formatDuration, formatFromTo, formatPrice } from './formatters.js';
-import { timeTemplate } from './templates.js';
+import { timeTemplate, footerTemplate } from './templates.js';
 import { settings, modifySettings } from './settings.js';
 import { setupCanvas } from './journeysViewCanvas.js';
 import { go } from './router.js';

@@ -10,13 +10,15 @@ import { showAlertModal, showLoader, hideOverlay } from './overlays.js';
 import { t } from './languages.js';
 
 const journeysTemplate = (data) => html`
-	<div class="journeysView column">
+	<div class="header-container">
 		<header id="header">
 			<a class="icon-back" href="#/" title="${t('back')}"></a>
-			<div class="content">
-				<h3>${t('from')}: ${formatName(getFrom(data.journeys))}</h3>
-				<h3>${t('to')}: ${formatName(getTo(data.journeys))}</h3>
-				<div class="mode-changers">
+			<div class="container flex-row">
+				<div>
+					<h3>${t('from')}: ${formatName(getFrom(data.journeys))}</h3>
+					<h3>${t('to')}: ${formatName(getTo(data.journeys))}</h3>
+				</div>
+				<div class="mode-changers flex-row">
 					<a href="#/${data.slug}/table" class="${settings.journeysViewMode === 'table' ? 'active' : ''}">
 						<div class="icon-table"></div>
 						<span>${t('table-view')}</span>

@@ -29,43 +31,51 @@ const journeysTemplate = (data) => html`
 			</div>
 			<a class="icon-reload" title="${t("reload")}" @click=${() => refreshJourneysView(data.slug)}></a>
 		</header>
+	</div>
 
-		${settings.journeysViewMode === 'canvas' ? html`
-			<div id="journeysCanvas">
-				<canvas id="canvas"></canvas>
-			</div>
-		` : nothing}
-
-		${settings.journeysViewMode === 'table' ? html`
-			${data.earlierRef ? html`<a class="arrowButton flipped icon-arrow2" title="${t('label_earlier')}" @click=${() => moreJourneys(data.slug, 'earlier')}></a>` : nothing}
-
-			<div class="card">
-			<table>
-				<thead>
-					<tr>
-						<th>${t('departure')}</th>
-						<th>${t('arrival')}</th>
-						<th>${t('duration')}</th>
-						<th>${t('changes')}</th>
-						<th>${t('products')}</th>
-						${settings.showPrices && settings.profile === 'db' ? html`<th>${t('price')}</th>` : nothing}
-						<th></th>
-					</tr>
-				</thead>
-				<tbody>
-					${Object.entries(data.journeys).map(([key, value]) => journeyOverviewTemplate(data.profile || "db", value, data.slug, key - data.indexOffset))}
-				</tbody>
-			</table>
-			</div>
+	${settings.journeysViewMode === 'canvas' ? html`
+
+	<div id="journeysCanvas">
+		<canvas id="canvas"></canvas>
+	</div>
 
-			${data.laterRef ? html`<a class="arrowButton icon-arrow2" title="${t('label_later')}" @click=${() => moreJourneys(data.slug, 'later')}></a>` : nothing}
-		` : nothing}
+	` : nothing}
+
+	${settings.journeysViewMode === 'table' ? html`
+
+	<div class="container journeysView">
+		${data.earlierRef ? html`<a class="arrowButton icon-arrow2 flipped flex-center" title="${t('label_earlier')}" @click=${() => moreJourneys(data.slug, 'earlier')}></a>` : nothing}
+
+		<div class="card">
+		<table>
+			<thead>
+				<tr>
+					<th>${t('departure')}</th>
+					<th>${t('arrival')}</th>
+					<th>${t('duration')}</th>
+					<th>${t('changes')}</th>
+					<th>${t('products')}</th>
+					${settings.showPrices && settings.profile === 'db' ? html`<th>${t('price')}</th>` : nothing}
+					<th></th>
+				</tr>
+			</thead>
+			<tbody>
+				${Object.entries(data.journeys).map(([key, value]) => journeyOverviewTemplate(data.profile || "db", value, data.slug, key - data.indexOffset))}
+			</tbody>
+		</table>
+		</div>
+
+		${data.laterRef ? html`<a class="arrowButton icon-arrow2 flex-center" title="${t('label_later')}" @click=${() => moreJourneys(data.slug, 'later')}></a>` : nothing}
 	</div>
+
+	${footerTemplate}
+	` : nothing}
 `;
 
 const journeyOverviewTemplate = (profile, entry, slug, key) => {
 	const firstLeg = entry.legs[0];
 	const lastLeg = entry.legs[entry.legs.length - 1];
+
 	let changes = 0;
 	const products = {};
 	let productsString = '';

@@ -124,7 +134,9 @@ export const journeysView = async (match, isUpdate) => {
 	}
 
 	let data;
+
 	if (!isUpdate) showLoader();
+
 	try {
 		data = await getJourneys(slug);
 	} catch(e) {

@@ -132,6 +144,7 @@ export const journeysView = async (match, isUpdate) => {
 		go('/');
 		throw e;
 	}
+
 	if (!data) {
 		await showAlertModal(html`journeys overview id invalid. <br />journeys overview links can not be shared across devices in ${APPNAME}.`);
 		go('/');
diff --git a/src/journeysViewCanvas.js b/src/journeysViewCanvas.js
@@ -1,17 +1,15 @@
 import { moreJourneys } from './journeysView.js';
 import { go } from './router.js';
 import { padZeros } from './helpers.js';
-import { formatTrainTypes, formatLineDisplayName } from './formatters.js'
+import { formatTrainTypes, formatLineDisplayName, formatDateTime } from './formatters.js'
 import { cachedCoachSequence, coachSequenceCache, coachSequenceCacheKey } from './coach-sequence/index.js';
 
-const formatTime = (date) => {
-	return `${padZeros(date.getHours())}:${padZeros(date.getMinutes())}`;
-};
 
 const colorFor = (leg, type) => {
 	const product = leg.line?.product || 'walk';
 	return colors[type][product] || colors[type].default;
 };
+
 const loadFactorColors = {
 	'low-to-medium':      [ '#777', '#ccc', '#ccc' ],
 	'high':               [ '#777', '#777', '#ccc' ],

@@ -205,7 +203,7 @@ const renderJourneys = () => {
 	ctx.fillStyle = '#aaa';
 	while (time < lastArrival) {
 		const y = (time - firstDeparture) * scaleFactor + 32;
-		ctx.fillText(formatTime(time), (window.innerWidth / dpr) > 600 ? 30 : 10, y);
+		ctx.fillText(formatDateTime(time, 'time'), (window.innerWidth / dpr) > 600 ? 30 : 10, y);
 		ctx.fillRect(0, y, canvas.width / dpr, 1);
 		time = new Date(Number(time) + 3600000);//Math.floor(120/scaleFactor));
 	}

@@ -313,7 +311,7 @@ const renderJourneys = () => {
 			if (journey.legs.indexOf(leg) == journey.legs.length - 1) times.push([leg.departure || leg.plannedDeparture, y - 9.5]);
 			if (journey.legs.indexOf(leg) == 0) times.push([leg.arrival || leg.plannedArrival, y + duration + 7.5]);
 			for (const [time, y] of times) {
-				preRenderedText = getTextCache(formatTime(time), '#fff', 15);
+				preRenderedText = getTextCache(formatDateTime(time, 'time'), '#fff', 15);
 				ctx.scale(1 / dpr, 1 / dpr);
 				ctx.drawImage(preRenderedText, Math.ceil(dpr * (x + ((rectWidth - preRenderedText.width/dpr)) / 2)), dpr * (y - 7.5));
 				ctx.scale(dpr, dpr);
diff --git a/src/languages.js b/src/languages.js
@@ -8,8 +8,19 @@ export const getDefaultLanguage = () => {
 	return 'en';
 };
 
+export const getLanguages = () => {
+	let availableLanguages = {};
+
+	Object.keys(languages).forEach((element) => {
+		availableLanguages[element] = languages[settings.language][element];
+	});
+
+	return availableLanguages;
+}
+
 export const t = (key, ...params) => {
 	let translation = languages[settings.language][key];
+
 	if (!translation) translation = languages['en'][key]
 	if (!translation) return key;
 

@@ -55,7 +66,7 @@ const languages = {
 		'optional':           '(optional)',
 		'options':            'Optionen',
 		'platform':           'Gleis',
-		'products':           'Produkte',
+		'product':            'Produkt',
 		'regionaltrain':      'Regionalverkehr',
 		'remarks':            'Hinweise',
 		'save':               'Speichern',

@@ -82,8 +93,8 @@ const languages = {
 		'load-very-high':     'Sehr hohe Auslastung',
 		'load-exceptionally-high': 'Extrem hohe Auslastung',
 		'table-view':         'Tabelle',
-		'experimental-features': 'Experimentelle Funktionen',
-		'show-prices':       'Preise anzeigen',
+		'show-prices':        'Preise anzeigen',
+		'combineDateTime':    'Kombinierte Zeit & Datumseingabe verwenden',
 		'titleSetDateTimeNow': 'Setze Uhrzeit & Datum auf jetzt',
 		'titleBikeFriendly':   'Fahrradmitnahme möglich',
 		'loyaltyCard':         'Ermäßigungskarte',

@@ -159,8 +170,7 @@ const languages = {
 		'load-very-high':     'Zéér druk',
 		'load-exceptionally-high': 'Extreem druk',
 		'table-view':         'Tabel',
-		'experimental-features': 'Experimentele functies',
-		'show-prices':       'Prijzen tonen',
+		'show-prices':        'Prijzen tonen',
 	},
 
 	'en': {

@@ -206,7 +216,7 @@ const languages = {
 		'setfromto':         'Set as from/to',
 		'settings':          'Settings',
 		'showdebug':         'Write debug messages to log',
-		'showds100':         'Show RIL100 (if available)',
+		'showds100':         'Show DS100 (if available)',
 		'station':           'Station',
 		'suburbantrain':     'Suburban Trains',
 		'subway':            'Subway',

@@ -226,7 +236,6 @@ const languages = {
 		'load-exceptionally-high': 'Exceptionally high load',
 		'table-view':        'Table',
 		'canvas-view':       'Canvas',
-		'experimental-features': 'Experimental features',
 		'show-prices':       'Show prices',
 		'price':             'Price',
 		'back':              'Back',
diff --git a/src/overlays.js b/src/overlays.js
@@ -1,5 +1,5 @@
-import { ElementById, showElement, hideElement } from './helpers.js';
 import { html, render } from 'lit-html';
+import { ElementById, showElement, hideElement } from './helpers.js';
 
 const overlayElement = ElementById('overlay');
 

@@ -20,7 +20,7 @@ export const showSelectModal = (items) => {
 	showElement(overlayElement);
 	return new Promise(resolve => {
 		render(html`
-			<div class="modal select column">
+			<div class="modal select flex-column">
 				${items.map(
 					(item) => html`<a class="button color" @click=${item.action}>${item.label}</a>`
 				)}

@@ -35,7 +35,7 @@ export const showModal = (title, content) => {
 	return new Promise(resolve => {
 		render(html`
 			<div class="modal dialog">
-				<div class="header row">
+				<div class="header flex-row">
 					<h4>${title}</h4>
 					<div class="icon-close" title="Close" @click=${() => { hideOverlay(); resolve(); }}></div>
 				</div>

@@ -47,11 +47,7 @@ export const showModal = (title, content) => {
 
 export const showLoader = () => {
 	showElement(overlayElement);
-	render(html`
-		<div class="loading">
-			<div class="spinner"></div>
-		</div>
-	`, overlayElement);
+	render(html`<div class="spinner"></div>`, overlayElement);
 };
 
 export const hideOverlay = () => hideElement(overlayElement);
diff --git a/src/router.js b/src/router.js
@@ -14,11 +14,16 @@ export const go = (dest) => {
 
 export const start = async () => {
 	const dest = window.location.hash.slice(1);
+
 	if (currentRoute && currentRoute.unload) currentRoute.unload();
+
 	for (const route of routes) {
 		const match = route.pattern.exec(dest);
+
 		if (!match) continue;
+
 		currentRoute = await route.handler(match.slice(1));
+
 		return;
 	}
 };
diff --git a/src/searchView.js b/src/searchView.js
@@ -6,6 +6,7 @@ import { formatName, formatFromTo } from './formatters.js';
 import { modifySettings, settings } from './settings.js';
 import { go } from './router.js';
 import { showAlertModal, showSelectModal, showLoader, hideOverlay} from './overlays.js';
+import { footerTemplate } from './templates.js';
 import { t } from './languages.js';
 import { showSettings } from './settingsView.js';
 import { client } from './hafasClient.js';

@@ -70,34 +71,35 @@ const iconFor = id => {
 };
 
 const searchTemplate = (journeysHistory) => html`
-	<div class="searchView">
-		<div class="title center">
+	<div class="container searchView">
+		<div class="title flex-center">
 			<h1>${APPNAME}</h1>
 		</div>
-		<form class="column" id="form" @submit=${submitForm}>
-			<div class="row nowrap">
+
+		<form class="center" id="form" @submit=${submitForm}>
+			<div class="flex-row nowrap">
 				<input type="text" name="from" id="from" title="${t('from')}" placeholder="${t('from')}" value="${viewState.fromValue}" autocomplete="off"
 					@focus=${focusHandler} @blur=${blurHandler} @keydown=${keydownHandler} @keyup=${keyupHandler} @input=${loadSuggestions} required>
 				<div class="button icon-arrow2 ${!settings.showVia ? '' : 'flipped'}" id="viaButton" title="${t('via')}" @click=${toggleVia}></div>
 			</div>
-			<div class="suggestions" id="fromSuggestions" @mouseover=${mouseOverHandler} @mouseout=${mouseOutHandler}></div>
+			<div class="suggestions" id="fromSuggestions"></div>
 
-			<div class="row nowrap ${!settings.showVia ? 'hidden' : ''}" id="viaRow">
+			<div class="flex-row nowrap ${!settings.showVia ? 'hidden' : ''}" id="viaRow">
 				<input type="text" name="via" id="via" title="${t('via')}" placeholder="${t('via')}" value="${viewState.viaValue}" autocomplete="off"
 					@focus=${focusHandler} @blur=${blurHandler} @keydown=${keydownHandler} @keyup=${keyupHandler} @input=${loadSuggestions}>
 				<div class="button icon-arrow2 invisible"></div>
 			</div>
-			<div class="suggestions" id="viaSuggestions" @mouseover=${mouseOverHandler} @mouseout=${mouseOutHandler}></div>
+			<div class="suggestions" id="viaSuggestions"></div>
 
 
-			<div class="row nowrap">
+			<div class="flex-row nowrap">
 				<input type="text" name="to" id="to" title="${t('to')}" placeholder="${t('to')}" value="${viewState.toValue}" autocomplete="off"
 					@focus=${focusHandler} @blur=${blurHandler} @keydown=${keydownHandler} @keyup=${keyupHandler} @input=${loadSuggestions} required>
 				<div class="button icon-swap" title="${t('swap')}" @click=${swapFromTo}></div>
 			</div>
-			<div class="suggestions" id="toSuggestions" @mouseover=${mouseOverHandler} @mouseout=${mouseOutHandler}></div>
+			<div class="suggestions" id="toSuggestions"></div>
 
-			<div class="row">
+			<div class="flex-row">
 				<div class="selector">
 					<input type="radio" id="departure" name="deparr" ?checked=${!viewState.isArrival}>
 					<label for="departure">${t('departure')}</label>

@@ -111,13 +113,14 @@ const searchTemplate = (journeysHistory) => html`
 				<div class="button icon-clock" title="${t('titleSetDateTimeNow')}" @click=${setDateTimeNow}></div>
 			</div>
 
-			<div class="row">
+			<div class="flex-row">
 				<div class="selector rectangular">
 					${client.profile.products.map(prod => html`
 						<input type="checkbox" id="${prod.id}" name="${prod.id}" checked>
 						<label class="${iconFor(prod.id)}" for="${prod.id}" title="${t('product')}: ${prod.name}"></label>
 					`)}
 				</div>
+
 				<div class="selector rectangular ${settings.profile === 'db' ? 'hidden' : ''}">
 					<input type="radio" id="accessibilityNone" name="accessibility" value="none" ?checked=${settings.accessibility === 'none'}>
 					<label class="icon-walk-fast" for="accessibilityNone" title="${t('accessibility')}: ${t('access_none')}"></label>

@@ -128,10 +131,12 @@ const searchTemplate = (journeysHistory) => html`
 					<input type="radio" id="accessibilityComplete" name="accessibility" value="complete" ?checked=${settings.accessibility === 'complete'}>
 					<label class="icon-weelchair" for="accessibilityComplete" title="${t('accessibility')}: ${t('access_full')}"></label>
 				</div>
+
 				<div class="selector rectangular">
 					<input type="checkbox" id="bikeFriendly" name="bikeFriendly" ?checked=${settings.bikeFriendly}>
 					<label class="icon-bike" for="bikeFriendly" title="${t('titleBikeFriendly')}"></label>
 				</div>
+
 				<div class="selector rectangular">
 					<input type="checkbox" id="noTransfers" name="noTransfers" ?checked=${viewState.noTransfers}>
 					<label class="icon-seat" for="noTransfers" title="${t('titleNoTransfers')}"></label>

@@ -148,16 +153,14 @@ const searchTemplate = (journeysHistory) => html`
 			` : nothing}
 		</form>
 
-		<div id="history" class="history hidden">
+		<div id="history" class="history center hidden">
 			${journeysHistory.map(element => html`
-			<div class="row" @click="${() => {journeysHistoryAction(journeysHistory, element);}}">
+			<div class="flex-row" @click="${() => {journeysHistoryAction(journeysHistory, element);}}">
 				<div class="from">
 					<small>${t('from')}:</small>
 					${formatName(element.fromPoint)}
 					${element.viaPoint ? html`
-						<div class="via">
-							<small>${t('via')} ${formatName(element.viaPoint)}</small>
-						</div>
+						<div class="via">${t('via')} ${formatName(element.viaPoint)}</div>
 					` : nothing}
 				</div>
 				<div class="icon-arrow1"></div>

@@ -168,12 +171,9 @@ const searchTemplate = (journeysHistory) => html`
 			</div>
 			`)}
 		</div>
-
-		<footer>
-			<a href="https://git.ctu.cx/trainsearch" title="commit ${COMMIT} from ${COMMITDATE}">Source-Code (${VERSION})</a>
-			<a href="https://ctu.cx/imprint.html">Imprint</a>
-		</footer>
 	</div>
+
+	${footerTemplate}
 `;
 
 const journeysHistoryAction = (journeysHistory, element) => {

@@ -192,7 +192,7 @@ export const searchView = async (clearInputs) => {
 	const journeysHistory = (await db.getHistory(settings.profile)).slice().reverse();
 
 	render(searchTemplate(journeysHistory), ElementById('content'));
-	setThemeColor(queryBackgroundColor('[class="searchView"]'));
+	setThemeColor();
 
 	if (clearInputs === true) {
 		viewState.fromValue = '';

@@ -204,9 +204,9 @@ export const searchView = async (clearInputs) => {
 		ElementById('from').value = '';
 		ElementById('via').value = '';
 		ElementById('to').value = '';
-		ElementById('fromSuggestions').innerHTML= '';
-		ElementById('viaSuggestions').innerHTML = '';
-		ElementById('toSuggestions').innerHTML = '';
+		ElementById('fromSuggestions').textContent = '';
+		ElementById('viaSuggestions').textContent = '';
+		ElementById('toSuggestions').textContent = '';
 	}
 
 	for (const [product, enabled] of Object.entries(settings.products)) {

@@ -370,8 +370,6 @@ const submitForm = async (event) => {
 
 	hideOverlay();
 
-	console.log(responseData)
-
 	go(`/${responseData.slug}/${settings.journeysViewMode}`);
 };
 

@@ -464,7 +462,8 @@ const hideSuggestions = (id) => {
 };
 
 const suggestionsTemplate = (data, inputId) => data.map((element, index) => html`
-	<p id="${index == 0 ? inputId+'Selected' : ''}" class="suggestion" @click=${(event) => setSuggestion(encodeURI(JSON.stringify(element)), inputId, event.pointerType)}>${formatName(element)}</p>
+	<p id="${index == 0 ? inputId+'Selected' : ''}" class="suggestion"
+		@mouseover=${mouseOverHandler} @mouseout=${mouseOutHandler} @click=${(event) => setSuggestion(encodeURI(JSON.stringify(element)), inputId, event.pointerType)}>${formatName(element)}</p>
 `);
 
 const loadSuggestions = async (event) => {

@@ -475,18 +474,16 @@ const loadSuggestions = async (event) => {
 
 	if (elementValue === '') return;
 
-	const results = elementValue ? await Promise.all([
-		(async () => {
-			const ds100Result = await ds100Reverse(elementValue);
-			if (ds100Result !== null) return await client.locations(ds100Result, {'results': 1});
-			return [];
-		}) (),
-		client.locations(elementValue, {'results': 10})
-	]) : [[], []];
+	let results;
+	const ds100Result = ds100Reverse(elementValue);
 
-	const data = results[0].concat(results[1]);
+	if (ds100Result !== null) {
+		results = await client.locations(ds100Result, {'results': 1});
+	} else {
+		results = await client.locations(elementValue, {'results': 10})
+	}
 
-	render(suggestionsTemplate(data, elementId), ElementById(`${elementId}Suggestions`));
+	render(suggestionsTemplate(results, elementId), ElementById(`${elementId}Suggestions`));
 };
 
 const focusNextElement = (currentElementId) => {
diff --git a/src/settingsView.js b/src/settingsView.js
@@ -14,7 +14,7 @@ export const showSettings = async () => {
 
 const settingsTemplate = () => html`
 	<div class="settingsView">
-		<div class="row">
+		<div class="flex-row">
 			<label for="language">${t('language')}:</label>
 			<select id="language">
 				<option value="en" ?selected=${settings.language === 'en'}>${t('en')}</option>

@@ -23,7 +23,7 @@ const settingsTemplate = () => html`
 			</select>
 		</div>
 
-		<div class="row">
+		<div class="flex-row">
 			<label for="profile">${t('datasource')}:</label>
 			<select id="profile" @change="${(event) => {profileChangeHandler(event.target.value)}}">
 				<option value="db"    ?selected=${settings.profile === 'db'}>DB</option>

@@ -35,7 +35,7 @@ const settingsTemplate = () => html`
 			</select>
 		</div>
 
-		<div class="row" id="walkingSpeedElement">
+		<div class="flex-row" id="walkingSpeedElement">
 			<label for="walkingSpeed">${t('walkingSpeed')}:</label>
 			<select id="walkingSpeed">
 				<option value="slow"   ?selected=${settings.walkingSpeed === 'slow'}>${t('walkingSpeedSlow')}</option>

@@ -44,7 +44,7 @@ const settingsTemplate = () => html`
 			</select>
 		</div>
 
-		<div class="row" id="loyaltyCardElement">
+		<div class="flex-row" id="loyaltyCardElement">
 			<label for="loyaltyCard">${t('loyaltyCard')}:</label>
 			<select id="loyaltyCard">
 				<option value="NONE"          ?selected=${settings.loyaltyCard === 'NONE'}>${t('loyaltyCardNone')}</option>

@@ -57,14 +57,14 @@ const settingsTemplate = () => html`
 			</select>
 		</div>
 
-		<div class="column">
+		<div class="flex-column">
 			<span>${t('options')}:</span>
 			<label id="showPricesElement"><input type="checkbox" id="showPrices" ?checked=${settings.showPrices}> ${t('show-prices')} (${t("experimental")})<br></label>
 			<label id="showDS100Element"><input type="checkbox" id="showDS100" ?checked=${settings.showDS100}> ${t('showds100')}<br></label>
 			<label><input type="checkbox" id="combineDateTime" ?checked=${settings.combineDateTime}> ${t('combineDateTime')}</label>
 		</div>
 
-		<div class="row">
+		<div class="flex-row">
 			<button class="color" id="clear" @click=${clearStorage}>${t('clearstorage')}</button>
 			<button class="color" id="save" @click=${saveSettings}>${t('save')}</button>
 		</div>

@@ -98,23 +98,23 @@ const clearStorage = async () => {
 };
 
 const saveSettings = async () => {
-	const oldSettings = { ...settings };
+	const profile     = ElementById('profile').value;
+	let   clearInputs = false;
+
+	if (profile !== settings.profile) clearInputs = true;
 
 	await modifySettings(settings => {
 		settings.combineDateTime = ElementById('combineDateTime').checked;
 		settings.showDS100       = ElementById('showDS100').checked;
 		settings.showPrices      = ElementById('showPrices').checked;
 		settings.language        = ElementById('language').value;
-		settings.profile         = ElementById('profile').value;
+		settings.profile         = profile;
 		settings.loyaltyCard     = ElementById('loyaltyCard').value;
 		settings.walkingSpeed    = ElementById('walkingSpeed').value;
 
 		return settings;
 	});
 
-	let clearInputs = false;
-	if (oldSettings.profile !== settings.profile) clearInputs = true;
-
 	await initHafasClient(settings.profile);
 	searchView(clearInputs);
 	hideOverlay();
diff --git a/src/templates.js b/src/templates.js
@@ -1,15 +1,15 @@
-import { html } from 'lit-html';
+import { html, nothing } from 'lit-html';
 import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
 import { formatDateTime, lineAdditionalName } from './formatters.js';
 import { showModal } from './overlays.js';
 import { settings } from './settings.js';
-import { ds100Names } from './app_functions.js';
+import { ds100Name } from './app_functions.js';
 import { t } from './languages.js';
 
 export const remarksModalTemplate = (remarks) => html`
 	<div class="remarksView">
 		${remarks.map(remark => html`
-			<div class="row">
+			<div class="flex-row">
 				<span class="icon-${remark.type}"></span>
 				<span>${unsafeHTML(remark.text)}</span>
 			</div>

@@ -18,7 +18,8 @@ export const remarksModalTemplate = (remarks) => html`
 `;
 
 export const stopTemplate = (profile, stop) => {
-	return html`<a class="center" href="#/d/${profile}/${stop.id}">${stop.name} ${ds100Names(stop.id)}</a>`;
+	const ds100 = ds100Name(stop.id);
+	return html`<a class="flex-center" href="#/d/${profile}/${stop.id}">${stop.name} ${ds100 !== null ? ` (${ds100})` : nothing}</a>`;
 }
 
 export const platformTemplate = (data) => {

@@ -75,3 +76,11 @@ export const timeTemplate = (data, mode) => {
 		`}
 	`;
 };
+
+
+export const footerTemplate = html`
+	<footer class="center">
+		<a href="https://git.ctu.cx/trainsearch" title="commit ${COMMIT} from ${COMMITDATE}">Source-Code (${VERSION})</a>
+		<a href="https://ctu.cx/imprint.html">Imprint</a>
+	</footer>
+`;+
\ No newline at end of file
diff --git a/src/tripView.js b/src/tripView.js
@@ -27,71 +27,73 @@ const tripTemplate = (data, profile) => {
 	}
 
 	return html`
-		<div class="journeyView column">
+		<div class="header-container">
 			<header>
 				<a id="back" class="icon-back hidden" title="${t('back')}" @click=${() => history.back()}>$</a>
-				<div class="content">
+				<div class="container">
 					<h3>Trip of ${formatLineDisplayName(data.line)} to ${data.direction}</h3>
 				</div>
 				<a class="icon-reload invisible" title="${t('title')}"></a>
 			</header>
+		</div>
 
+		<div class="container journeyView">
 			<div class="card">
-			<table>
-				<thead>
-					<tr>
-						<td colspan="4">
-							<div class="center">${bahnExpertUrl ? html`
-								<a href="${bahnExpertUrl}">${formatLineDisplayName(data.line)}${data.direction ? html` → ${data.direction}` : ''}</a>
-							` : html `
-								${formatLineDisplayName(data.line)}${data.direction ? html` → ${data.direction}` : ''}
-							`}
-							${data.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : ''}
-							${!!remarks.length ? html`<a class="link ${remarksIcon}" @click=${() => showModal(t('remarks'), remarksModalTemplate(remarks))}></a>` : nothing}</div>
-						</td>
-					</tr>
-					<tr>
-						<td colspan="4">
-							<div class="train-details center">
-								${formatLineAdditionalName(data.line) ? html`
-									<div>
-										Trip: ${formatLineAdditionalName(data.line)}
-									</div>
-								` : nothing}
-								${data.line.trainType ? html`
-									<div>
-										Train type: ${data.line.trainType}
+				<table>
+					<thead>
+						<tr>
+							<td colspan="4">
+								<div class="center">${bahnExpertUrl ? html`
+									<a href="${bahnExpertUrl}">${formatLineDisplayName(data.line)}${data.direction ? html` → ${data.direction}` : ''}</a>
+								` : html `
+									${formatLineDisplayName(data.line)}${data.direction ? html` → ${data.direction}` : ''}
+								`}
+								${data.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : ''}
+								${!!remarks.length ? html`<a class="link ${remarksIcon}" @click=${() => showModal(t('remarks'), remarksModalTemplate(remarks))}></a>` : nothing}</div>
+							</td>
+						</tr>
+						<tr>
+							<td colspan="4">
+								<div class="train-details center">
+									${formatLineAdditionalName(data.line) ? html`
+										<div>
+											Trip: ${formatLineAdditionalName(data.line)}
+										</div>
+									` : nothing}
+									${data.line.trainType ? html`
+										<div>
+											Train type: ${data.line.trainType}
+										</div>
+									` : nothing}
+									<div ${data.cancelled ? 'cancelled' : ''}">
+										${t('duration')}: ${formatDuration(data.arrival - (data.departure ? data.departure : data.plannedDeparture))} ${data.departure ? '' : ('(' + t('planned') + ')')}
 									</div>
-								` : nothing}
-								<div ${data.cancelled ? 'cancelled' : ''}">
-									${t('duration')}: ${formatDuration(data.arrival - (data.departure ? data.departure : data.plannedDeparture))} ${data.departure ? '' : ('(' + t('planned') + ')')}
+									${data.loadFactor ? html`
+										<div
+											${t("load-"+data.loadFactor)}
+										</div>
+									` : nothing}
 								</div>
-								${data.loadFactor ? html`
-									<div
-										${t("load-"+data.loadFactor)}
-									</div>
-								` : nothing}
-							</div>
-						</td>
-					</tr>
-					<tr>
-						<th>${t('arrival')}</th>
-						<th>${t('departure')}</th>
-						<th class="station">${t('station')}</th>
-						<th>${t('platform')}</th>
-					</tr>
-				</thead>
-				<tbody>
-					${(data.stopovers || []).map(stop => html`
-						<tr class="${stop.cancelled ? 'cancelled' : ''}">
-							<td>${timeTemplate(stop, 'arrival')}</td>
-							<td>${timeTemplate(stop, 'departure')}</td>
-							<td>${stopTemplate(profile, stop.stop)}</td>
-							<td>${platformTemplate(stop)}</td>
+							</td>
 						</tr>
-					`)}
-				</tbody>
-			</table>
+						<tr>
+							<th>${t('arrival')}</th>
+							<th>${t('departure')}</th>
+							<th class="station">${t('station')}</th>
+							<th>${t('platform')}</th>
+						</tr>
+					</thead>
+					<tbody>
+						${(data.stopovers || []).map(stop => html`
+							<tr class="${stop.cancelled ? 'cancelled' : ''}">
+								<td>${timeTemplate(stop, 'arrival')}</td>
+								<td>${timeTemplate(stop, 'departure')}</td>
+								<td>${stopTemplate(profile, stop.stop)}</td>
+								<td>${platformTemplate(stop)}</td>
+							</tr>
+						`)}
+					</tbody>
+				</table>
 			</div>
 		</div>
 	`;

@@ -100,6 +102,7 @@ const tripTemplate = (data, profile) => {
 export const tripView = async (match, isUpdate) => {
 	if (!isUpdate) showLoader();
 	let profile, refreshToken, data;
+
 	try {
 		profile = match[0];
 		refreshToken = decodeURIComponent(match[1]);

@@ -116,7 +119,5 @@ export const tripView = async (match, isUpdate) => {
 	render(tripTemplate(data.trip, profile), ElementById('content'));
 	setThemeColor(queryBackgroundColor('header'));
 
-	if (history.length > 0) {
-		showElement(ElementById('back'));
-	}
+	if (history.length > 0) showElement(ElementById('back'));
 };