katja's git: oeffisearch

fast and simple tripplanner

commit 7b45770da6e2074391ba68377c35120d20f11f77
parent 43f3f3b3a91367eebc813ae8e221394ac61d37a8
Author: Katja (ctucx) <git@ctu.cx>
Date: Thu, 17 Apr 2025 20:58:40 +0200

cleanup
16 files changed, 354 insertions(+), 412 deletions(-)
D
ds100.py
|
39
---------------------------------------
M
src/app_functions.js
|
10
++++++----
M
src/baseView.js
|
65
+++++++++++++++++++++++++++++++++--------------------------------
M
src/departuresView.js
|
124
++++++++++++++++++++++++++++++++++++++++---------------------------------------
M
src/footerComponent.js
|
3
+--
M
src/journeyView.js
|
101
+++++++++++++++++++++++++++++++++++--------------------------------------------
M
src/journeysCanvas.js
|
4
++--
M
src/journeysView.js
|
89
++++++++++++++++++++++++++++++++++++++-----------------------------------------
M
src/main.js
|
20
++++++++------------
M
src/searchView.js
|
99
+++++++++++++++++++++++++++++++++++++------------------------------------------
M
src/settingsView.js
|
6
+++---
M
src/styles.js
|
1
-
M
src/styles/base.css
|
17
+++++++++++++++++
D
src/styles/helpers.css
|
16
----------------
M
src/templates.js
|
10
+++++-----
M
src/tripView.js
|
162
++++++++++++++++++++++++++++++++++++++++---------------------------------------
diff --git a/ds100.py b/ds100.py
@@ -1,39 +0,0 @@
-#!/usr/bin/env python3
-import json
-import re
-
-spaces = re.compile(" +")
-
-# json list of station info, probably from hafas or something
-with open("stations.json") as f:
-    data = json.load(f)
-
-fiddle = dict()
-
-for station in data:
-    ds100 = spaces.sub(" ", station["ds100"])
-    if station["name"] in fiddle:
-        fiddle[station["name"]]["ds100"].add(ds100)
-        fiddle[station["name"]]["eva"] = min(
-            int(station["eva"]), fiddle[station["name"]]["eva"]
-        )
-    else:
-        fiddle[station["name"]] = {
-            "ds100": {ds100},
-            "eva": int(station["eva"]),
-        }
-
-output = dict()
-
-for name, station in fiddle.items():
-    if station["eva"] < 8000000 or station["eva"] > 8100000 or station["ds100"] == "":
-        # print(station["eva"], name)
-        continue
-
-    output[station["eva"]] = station["ds100"]
-
-for id, codes in output.items():
-    output[id] = ", ".join(codes)
-
-print("export const ds100 = ", end="")
-print(json.dumps(output, indent=2, ensure_ascii=False, sort_keys=True))
diff --git a/src/app_functions.js b/src/app_functions.js
@@ -24,7 +24,7 @@ const loyaltyCardsReverse = {
 	'Symbol(General-Abonnement)': 'GENERALABONNEMENT',
 };
 
-export const loyaltyCardToString   = loyaltyCard =>
+export const loyaltyCardToString = loyaltyCard =>
 	loyaltyCardsReverse[loyaltyCard.type.toString()] !== 'NONE' ?
 	`${loyaltyCardsReverse[loyaltyCard.type.toString()]}-${loyaltyCard.discount}-${loyaltyCard.class}`
 	: 'NONE';

@@ -83,7 +83,7 @@ export const processLeg = leg => {
 export const getFromPoint = journeys => journeys[0].legs[0].origin;
 export const getToPoint   = journeys => journeys[0].legs[journeys[0].legs.length-1].destination;
 
- export const newJourneys = async params => {
+export const newJourneys = async params => {
 	const { from, to, ...moreOpts } = params;
 	let data;
 

@@ -149,7 +149,7 @@ export const getJourneys = async slug => {
 
 export const getMoreJourneys = async (slug, mode) => {
 	const saved  = await db.getJourneysOverview(slug);
-	const params = { ...journeySettings(), ...saved.params };
+	const params = { ...saved.params, ...journeySettings() };
 
 	if (typeof params.loyaltyCard === 'string') params.loyaltyCard = loyaltyCardFromString(params.loyaltyCard);
 

@@ -166,7 +166,9 @@ export const getMoreJourneys = async (slug, mode) => {
 		...newData,
 	};
 
-	for (const journey of newData.journeys) journey.refreshToken = hafasToTrainsearch(journey.refreshToken);
+	newData.journeys.forEach(journey => {
+		journey.refreshToken = hafasToTrainsearch(journey.refreshToken);
+	});
 
 	if (mode === 'earlier') {
 		res.journeys = newData.journeys.concat(existingJourneys);
diff --git a/src/baseView.js b/src/baseView.js
@@ -1,21 +1,17 @@
 import { LitElement, html, nothing } from 'lit';
 import { t } from './languages.js';
 
-import { baseStyles, helperStyles, flexboxStyles, overlaysStyles, buttonInputStyles, iconStyles } from './styles.js';
+import { baseStyles, flexboxStyles, overlaysStyles, buttonInputStyles, iconStyles } from './styles.js';
 
 export class BaseView extends LitElement {
 	static properties = {
-		isOffline:      { state: true },
-		isUpdating:     { state: true },
-		overlayType:    { state: true },
-		overlayTitle:   { state: true },
-		overlayVisible: { state: true },
-		overlayContent: { state: true },
+		isOffline:    { state: true },
+		isUpdating:   { state: true },
+		overlayState: { state: true },
 	};
 
 	static styles = [
 		baseStyles,
-		helperStyles,
 		flexboxStyles,
 		overlaysStyles,
 		buttonInputStyles,

@@ -28,19 +24,21 @@ export class BaseView extends LitElement {
 		this.isOffline  = !navigator.onLine;
 		this.isUpdating = false;
 
-		this.overlayType    = 'plain';
-		this.overlayVisible = false;
-		this.overlayContent = null;
-		this.overlayTitle   = null;
+		this.overlayState = {
+			type:    'plain',
+			visible: false,
+			content: null,
+			title:   null,
+		};
 	}
 
-	connectedCallback() {
+	connectedCallback () {
 		super.connectedCallback();
 		window.addEventListener('online',  this.connectionHandler);
 		window.addEventListener('offline', this.connectionHandler);
 	}
 
-	disconnectedCallback() {
+	disconnectedCallback () {
 		super.disconnectedCallback();
 		window.removeEventListener('online',  this.connectionHandler);
 		window.removeEventListener('offline', this.connectionHandler);

@@ -52,21 +50,24 @@ export class BaseView extends LitElement {
 	showDialogOverlay = (title, body) => this.showOverlay('dialog', body, title);
 	showAlertOverlay  = text          => this.showOverlay('alert', text);
 	showSelectOverlay = items         => this.showOverlay('select', items);
-	hideOverlay       = () => { this.overlayVisible = false; }
-	showOverlay       = (type, content, title) => {
-		this.overlayType    = type;
-		this.overlayContent = content;
-		this.overlayTitle   = title;
-		this.overlayVisible = true;
-	}
+	hideOverlay = () => {
+		this.overlayState.visible = false;
+		this.requestUpdate();
+	};
+	showOverlay = (type, content, title) => {
+		this.overlayState = {
+			type, content, title,
+			visible: true,
+		}
+	};
 
-	overlayHandler = event => event.target === event.currentTarget && this.overlayType !== 'loader' ? this.hideOverlay() : true;
+	overlayHandler = event => event.target === event.currentTarget && this.overlayState.type !== 'loader' ? this.hideOverlay() : true;
 
 	render () {
 		let overlayContent;
 
-		if (this.overlayVisible) {
-			switch (this.overlayType) {
+		if (this.overlayState.visible) {
+			switch (this.overlayState.type) {
 				case 'loader':
 					overlayContent = html`<div class="spinner"></div>`;
 					break;

@@ -74,37 +75,37 @@ export class BaseView extends LitElement {
 					overlayContent = html`
 						<div class="modal dialog">
 							<div class="header flex-row">
-								<h4>${t(this.overlayTitle)}</h4>
+								<h4>${t(this.overlayState.title)}</h4>
 								<div class="icon-close" title="${t('close')}" @click=${this.hideOverlay}></div>
 							</div>
-							<div class="body">${this.overlayContent}</div>
+							<div class="body">${this.overlayState.content}</div>
 						</div>
 					`;
 					break;
 				case 'select':
 					overlayContent = html`
 						<div class="modal select flex-column">
-							${this.overlayContent.map(item => html`<a class="button color" @click=${item.action}>${t(item.label)}</a>`)}
-							<a class="button color" @click=${() => { this.hideOverlay();}}>Close</a>
+							${this.overlayState.content.map(item => html`<a class="button color" @click=${item.action}>${t(item.label)}</a>`)}
+							<a class="button color" @click=${this.hideOverlay}>${t('close')}</a>
 						</div>
 					`;
 					break;
 				case 'alert':
 					overlayContent = html`
 						<div class="modal alert" style="overflow:auto">
-							${this.overlayContent}<br><button class="color" style="float:right" @click=${this.hideOverlay}>OK</button>
+							${this.overlayState.content}<br><button class="color" style="float:right" @click=${this.hideOverlay}>OK</button>
 						</div>
 					`;
 					break;
 				default:
-					overlayContent = this.overlayContent;
+					overlayContent = this.overlayState.content;
 					break;
 			}
 		}
 
 		return [
-			this.renderContent(),
-			!this.overlayVisible ? nothing : html`<div class="overlay" @click=${this.overlayHandler}>${overlayContent}</div>`
+			this.renderView(),
+			!this.overlayState.visible ? nothing : html`<div class="overlay" @click=${this.overlayHandler}>${overlayContent}</div>`
 		];
 	}
 }
diff --git a/src/departuresView.js b/src/departuresView.js
@@ -7,7 +7,7 @@ import { processLeg } from './app_functions.js';
 import { getHafasClient } from './hafasClient.js';
 import { t } from './languages.js';
 
-import { baseStyles, helperStyles, flexboxStyles, iconStyles, headerStyles, cardStyles, departuresViewStyles } from './styles.js';
+import { headerStyles, cardStyles, departuresViewStyles } from './styles.js';
 
 class DeparturesView extends BaseView {
 	static styles = [

@@ -24,83 +24,85 @@ class DeparturesView extends BaseView {
 		viewState:  { state: true },
 	};
 
-	constructor(...args) {
-		super(...args);
+	constructor () {
+		super();
 
 		this.viewState  = null;
 	}
 
-	async connectedCallback() {
+	async connectedCallback () {
 		super.connectedCallback();
 		await sleep(100);
 
 		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
+
+		if (!this.isOffline) await this.updateViewState();
+	}
+
+	disconnectedCallback () {
+		super.disconnectedCallback();
+
+		this.viewState = null;
 	}
 
-	async updated(previous) {
+
+	async updated (previous) {
 		super.updated(previous);
 
 		if (isDevServer) console.info('updated(): ', previous);
 
-		if (previous.has('stopId')) {
-			this.viewState  = null;
-			if (!this.isOffline) await this.updateViewState();
-		}
-
 		if (previous.has('isOffline') && this.viewState === null) await this.updateViewState();
 	}
 
-	renderContent () {
-		return [
-			html`
-			<div class="header-container">
-				<header>
-					<a class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a>
-					<div class="container">
-						<h3>Departures from ${this.viewState !== null ? this.viewState.name : '...'}</h3>
-					</div>
-					<a class="icon-reload ${!this.isUpdating ? '' : 'spinning'} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a>
-				</header>
-			</div>
-			`,
-
-			this.viewState !== null ? html`
-			<div class="container">
-				<div class="card">
-				<table>
-					<thead>
-						<tr>
-							<th>Time</th>
-							<th class="station"></th>
-							<th>${t('platform')}</th>
-						</tr>
-					</thead>
-					<tbody>
-						${(this.viewState.departures || []).map(departure => html`
-							<tr @click=${() => window.location = `#/t/${this.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`
-									<td><span class="cancelled-text">${t('cancelled-ride')}</span></td>
-								` : html`
-									<td>${platformTemplate(departure)}</td>
-								`}
-							</tr>
-						`)}
-					</tbody>
-				</table>
+	renderView = () => [
+		html`
+		<div class="header-container">
+			<header>
+				<a class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a>
+				<div class="container">
+					<h3>Departures from ${this.viewState !== null ? this.viewState.name : '...'}</h3>
 				</div>
+				<a class="icon-reload ${!this.isUpdating ? '' : 'spinning'} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a>
+			</header>
+		</div>
+		`,
+
+		this.viewState !== null ? html`
+		<div class="container">
+			<div class="card">
+			<table>
+				<thead>
+					<tr>
+						<th>Time</th>
+						<th class="station"></th>
+						<th>${t('platform')}</th>
+					</tr>
+				</thead>
+				<tbody>
+					${(this.viewState.departures || []).map(departure => html`
+						<tr @click=${() => window.location = `#/t/${this.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`
+								<td><span class="cancelled-text">${t('cancelled-ride')}</span></td>
+							` : html`
+								<td>${platformTemplate(departure)}</td>
+							`}
+						</tr>
+					`)}
+				</tbody>
+			</table>
 			</div>
-			<footer-component></footer-component>
-			` : !this.isOffline ?
-			html`<div class="spinner"></div>`
-			: html`<div class="offline"></div>`
-		];
-	}
+		</div>
+		<footer-component></footer-component>
+		` : !this.isOffline ?
+		html`<div class="spinner"></div>`
+		: html`<div class="offline"></div>`
+	];
 
 	updateViewState = async () => {
 		if (this.isOffline !== false) return;

@@ -110,7 +112,7 @@ class DeparturesView extends BaseView {
 			const when   = this.when;
 			const client = await getHafasClient(this.profile);
 
-			const [ {departures}, stopInfo ] = await Promise.all([
+			const [ { departures }, stopInfo ] = await Promise.all([
 				client.departures(this.stopId, { when }),
 				client.stop(this.stopId),
 			]);

@@ -122,7 +124,7 @@ class DeparturesView extends BaseView {
 				departures
 			};
 
-			if (isDevServer) console.info('viewState: ', this.viewState);
+			if (isDevServer) console.info('viewState:', this.viewState);
 		} catch(e) {
 			this.showAlertOverlay(e.toString());
 			console.error(e);
diff --git a/src/footerComponent.js b/src/footerComponent.js
@@ -1,11 +1,10 @@
 import { LitElement, html } from 'lit';
 
-import { baseStyles, helperStyles, footerStyles } from './styles.js';
+import { baseStyles, footerStyles } from './styles.js';
 
 class FooterElement extends LitElement {
 	static styles = [
 		baseStyles,
-		helperStyles,
 		footerStyles,
 	];
 
diff --git a/src/journeyView.js b/src/journeyView.js
@@ -11,7 +11,7 @@ import { remarksModal, platformTemplate, stopTemplate, timeTemplate } from './te
 import { formatPoint, formatDuration, formatPrice, formatTrainTypes, formatLineAdditionalName, formatLineDisplayName } from './formatters.js';
 import { cachedCoachSequence } from './coach-sequence/index.js';
 
-import { baseStyles, helperStyles, flexboxStyles, headerStyles, iconStyles, cardStyles, journeyViewStyles } from './styles.js';
+import { headerStyles, cardStyles, journeyViewStyles } from './styles.js';
 
 class JourneyView extends BaseView {
 	static styles = [

@@ -28,8 +28,8 @@ class JourneyView extends BaseView {
 		settingsState: { state: true },
 	};
 
-	constructor (...args) {
-		super(...args);
+	constructor () {
+		super();
 
 		this.viewState     = null;
 		this.settingsState = settings.getState();

@@ -41,7 +41,7 @@ class JourneyView extends BaseView {
 
 		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
 
-		if (this.viewState === null) await this.updateViewState();
+		await this.updateViewState();
 
 		this._unsubscribeSettingsState = settings.subscribe(state => {
 			this.settingsState = state;

@@ -59,46 +59,36 @@ class JourneyView extends BaseView {
 		}
 	}
 
-	async updated (previous) {
-		super.updated(previous);
-
-		if (isDevServer) console.info('updated(): ', previous);
-
-		if (previous.has('refreshToken')) {
-			this.viewState = null;
-			await this.updateViewState();
-		}
-	}
-
-	renderContent () {
-		return html`
-			<div class="header-container">
-				<header>
-					<a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a>
-					<div class="container">
-						<a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.refreshJourney}></a>
-						${this.viewState !== null ? html`
-						<h3>${formatPoint(this.viewState.legs[0].origin)} → ${formatPoint(this.viewState.legs[this.viewState.legs.length - 1].destination)}</h3>
-						<p><b>${t('duration')}: ${formatDuration(this.viewState.duration)} | ${t('changes')}: ${this.viewState.changes-1} | ${t('date')}: ${this.viewState.legs[0].plannedDeparture.formatDate()}${this.settingsState.showPrices && this.viewState.price ? html` | ${t('price')}: <td><span>${formatPrice(this.viewState.price)}</span></td>` : nothing}</b></p>
-						` : html`
-						<h3>... → ...</h3>
-						<p><b>${t('duration')}: ... | ${t('changes')}: ... | ${t('date')}: ...</b></p>
-						`}
-					</div>
-					<a class="icon-dots" title="${t("more")}" @click=${this.moreModal}></a>
-				</header>
-			</div>
-
-			${this.viewState !== null ? html`
-			<div class="container journeyView">
-				${this.viewState.legs.map(leg => this.legTemplate(leg))}
-			</div>
-			<footer-component></footer-component>
-			` : html`<div class="spinner"></div>`}
-		`;
-	}
+	renderView = () => [
+		html`
+		<div class="header-container">
+			<header>
+				<a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a>
+				<div class="container">
+					<a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.refreshJourney}></a>
+					${this.viewState !== null ? html`
+					<h3>${formatPoint(this.viewState.legs[0].origin)} → ${formatPoint(this.viewState.legs[this.viewState.legs.length - 1].destination)}</h3>
+					<p><b>${t('duration')}: ${formatDuration(this.viewState.duration)} | ${t('changes')}: ${this.viewState.changes-1} | ${t('date')}: ${this.viewState.legs[0].plannedDeparture.formatDate()}${this.settingsState.showPrices && this.viewState.price ? html` | ${t('price')}: <td><span>${formatPrice(this.viewState.price)}</span></td>` : nothing}</b></p>
+					` : html`
+					<h3>... → ...</h3>
+					<p><b>${t('duration')}: ... | ${t('changes')}: ... | ${t('date')}: ...</b></p>
+					`}
+				</div>
+				<a class="icon-dots" title="${t("more")}" @click=${this.moreModal}></a>
+			</header>
+		</div>
+		`,
+		this.viewState !== null ? html`
+		<div class="container journeyView">
+			${this.viewState.legs.map(leg => this.legTemplate(leg))}
+		</div>
+		<footer-component></footer-component>
+		` : html`
+		<div class="spinner"></div>
+		`,
+	];
 
-	legTemplate (leg) {
+	legTemplate = leg => {
 		if (leg.walking) {
 			return html`<p class="walk">${t(leg.distance === null ? 'walkinfo' : 'walkinfo_meters', formatPoint(leg.destination), leg.distance)}</p>`;
 		} else if (leg.transfer) {

@@ -152,7 +142,7 @@ class JourneyView extends BaseView {
 		}
 	}
 
-	async updateViewState (viewState ) {
+	updateViewState = async viewState => {
 		this.isUpdating = true;
 		try {
 			if (viewState === undefined) viewState = await getJourney(this.profile, this.refreshToken);

@@ -166,9 +156,8 @@ class JourneyView extends BaseView {
 				await db.updateHistoryEntry(overviewObject.historyEntryId, historyObject);
 			}
 
-
-			viewState.changes     = 0;
-			viewState.duration    = null;
+			viewState.changes  = 0;
+			viewState.duration = null;
 
 			if (viewState.legs[viewState.legs.length - 1].arrival && viewState.legs[0].departure)
 				viewState.duration = viewState.legs[viewState.legs.length - 1].arrival - viewState.legs[0].departure;

@@ -212,7 +201,7 @@ class JourneyView extends BaseView {
 			this.viewState = viewState;
 
 			//fetch train types after setting the viewState
-			if (!this.isOffline !== false) {
+			if (!this.isOffline) {
 				for (const leg of this.viewState.legs) {
 					if (leg.line && leg.line.name) {
 						const [category, number] = leg.line.name.split(' ');

@@ -224,7 +213,7 @@ class JourneyView extends BaseView {
 				}
 			}
 
-			if (isDevServer) console.info('viewState: ', this.viewState);
+			if (isDevServer) console.info('viewState:', this.viewState);
 		} catch(e) {
 			this.showAlertOverlay(e.toString());
 			console.error(e);

@@ -232,7 +221,7 @@ class JourneyView extends BaseView {
 		this.isUpdating = false;
 	}
 
-	async refreshJourney () {
+	refreshJourney = async () => {
 		if (this.isOffline !== false) return;
 		if (this.isUpdating !== false) return false;
 

@@ -247,19 +236,19 @@ class JourneyView extends BaseView {
 		this.isUpdating = false;
 	}
 
-	async moreModal () {
+	moreModal = async () => {
 		const options = [
 			{ 'label': !navigator.canShare ? 'copyURL' : 'shareURL', 'action': async () => await this.shareAction()    },
 		];
 
 		if (isDevServer) options.push({ 'label': 'addCalendar', 'action': async () => await this.calendarAction() });
 
-		if (this.profile === 'db') options.push({ 'label': 'tickets', 'action': async () => await this.showTicketsModal() });
+		if (this.profile === 'db') options.push({ 'label': 'tickets', 'action': async () => await this.ticketsAction() });
 
 		this.showSelectOverlay(options);
 	};
 
-	async shareAction () {
+	shareAction = async () => {
 		try {
 			await navigator.share({ url: window.location });
 		} catch (error) {

@@ -268,11 +257,11 @@ class JourneyView extends BaseView {
 		}
 	}
 
-	async showTicketsModal () {
+	ticketsAction = async () => {
 		try {
 			this.showLoaderOverlay();
 
-			if (this.isOffline !== false) await this.refreshJourney();
+			if (!this.isOffline) await this.refreshJourney();
 
 			if (this.viewState.tickets === undefined) {
 				await this.showAlertOverlay('No ticket data available');

@@ -314,7 +303,7 @@ class JourneyView extends BaseView {
 		}
 	};
 
-	async calendarAction () {
+	calendarAction = async () => {
 		let events = [];
 
 		this.viewState.legs.forEach(leg => {
diff --git a/src/journeysCanvas.js b/src/journeysCanvas.js
@@ -47,8 +47,8 @@ export class JourneysCanvas extends BaseView {
 		}
 	};
 
-	constructor (...args) {
-		super(...args);
+	constructor () {
+		super();
 
 		this.canvasElement = null;
 		this.canvasContext = null;
diff --git a/src/journeysView.js b/src/journeysView.js
@@ -7,14 +7,13 @@ import { timeTemplate } from './templates.js';
 import { settings } from './settings.js';
 import { t } from './languages.js';
 
-import { baseStyles, helperStyles, flexboxStyles, buttonInputStyles, iconStyles, headerStyles, cardStyles, journeysViewStyles } from './styles.js';
+import { headerStyles, cardStyles, journeysViewStyles } from './styles.js';
 
 import { JourneysCanvas } from './journeysCanvas.js';
 
 export class JourneysView extends JourneysCanvas {
 	static styles = [
 		super.styles,
-		baseStyles,
 		headerStyles,
 		cardStyles,
 		journeysViewStyles

@@ -27,8 +26,8 @@ export class JourneysView extends JourneysCanvas {
 		settingsState: { state:     true },
 	};
 
-	constructor (...args) {
-		super(...args);
+	constructor () {
+		super();
 
 		this.viewState     = null;
 		this.settingsState = settings.getState();

@@ -62,6 +61,7 @@ export class JourneysView extends JourneysCanvas {
 	}
 
 	async updated (previous) {
+		await super.updated(previous);
 		if (isDevServer) console.info('updated(): ', previous);
 
 		if (previous.has('mode') && this.settingsState.journeysViewMode !== this.mode) this.settingsState.setJourneysViewMode(this.mode);

@@ -83,42 +83,40 @@ export class JourneysView extends JourneysCanvas {
 				await this.getCoachSequences();
 			}
 		}
-
-		await super.updated(previous);
 	}
 
-	renderContent () {
-		return html`
-			<div class="header-container">
-				<header>
-					<a id="back" class="icon-back" title="${t('back')}" href="#/"></a>
-					<div class="container flex-row">
-						<div>
-							<h3>${t('from')}: ${this.viewState !== null ? formatPoint(getFromPoint(this.viewState.journeys)) : '...'}</h3>
-							<h3>${t('to')}:   ${this.viewState !== null ? formatPoint(getToPoint(this.viewState.journeys))   : '...'}</h3>
-						</div>
-						<div class="mode-changers flex-row">
-							<a href="#/${this.slug}/table" class="${this.settingsState.journeysViewMode === 'table' ? 'active' : ''}">
-								<div class="icon-table"></div>
-								<span>${t('table-view')}</span>
-							</a>
-							<a href="#/${this.slug}/canvas" class="${this.settingsState.journeysViewMode === 'canvas' ? 'active' : ''}">
-								<div class="icon-canvas"></div>
-								<span>${t('canvas-view')}</span>
-							</a>
-						</div>
+	renderView = () => [
+		html`
+		<div class="header-container">
+			<header>
+				<a id="back" class="icon-back" title="${t('back')}" href="#/"></a>
+				<div class="container flex-row">
+					<div>
+						<h3>${t('from')}: ${this.viewState !== null ? formatPoint(getFromPoint(this.viewState.journeys)) : '...'}</h3>
+						<h3>${t('to')}:   ${this.viewState !== null ? formatPoint(getToPoint(this.viewState.journeys))   : '...'}</h3>
 					</div>
-					<a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.refreshJourneys}></a>
-				</header>
-			</div>
-
-			${this.viewState !== null ? html`
-			${this.settingsState.journeysViewMode === 'canvas' ? this.getCanvas() : nothing}
-			${this.settingsState.journeysViewMode === 'table'  ? html`
-			<div class="container journeysView">
-				${this.viewState.earlierRef ? html`
+					<div class="mode-changers flex-row">
+						<a href="#/${this.slug}/table" class="${this.settingsState.journeysViewMode === 'table' ? 'active' : ''}">
+							<div class="icon-table"></div>
+							<span>${t('table-view')}</span>
+						</a>
+						<a href="#/${this.slug}/canvas" class="${this.settingsState.journeysViewMode === 'canvas' ? 'active' : ''}">
+							<div class="icon-canvas"></div>
+							<span>${t('canvas-view')}</span>
+						</a>
+					</div>
+				</div>
+				<a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.refreshJourneys}></a>
+			</header>
+		</div>
+		`,
+
+		this.viewState !== null ? [
+			this.settingsState.journeysViewMode === 'canvas' ? this.getCanvas() : nothing,
+			this.settingsState.journeysViewMode === 'table' ? html`
+			<div class="container">
+				${!this.viewState.earlierRef ? nothing : html`
 				<a class="arrowButton icon-arrow2 flipped flex-center" title="${t('label_earlier')}" @click=${() => this.moreJourneys('earlier')}></a>
-				` : nothing}
 
 				<div class="card">
 				<table>

@@ -139,17 +137,16 @@ export class JourneysView extends JourneysCanvas {
 				</table>
 				</div>
 
-				${this.viewState.laterRef ? html`
+				${!this.viewState.laterRef ? nothing : html`
 				<a class="arrowButton icon-arrow2 flex-center" title="${t('label_later')}" @click=${() => this.moreJourneys('later')}></a>
-				` : nothing}
+				`}
 			</div>
 			<footer-component></footer-component>
-			` : nothing}
-			` : html`<div class="spinner"></div>`}
-		`;
-	}
+			` : nothing
+		] : html`<div class="spinner"></div>`
+	];
 
-	journeyTemplate (journey) {
+	journeyTemplate = journey => {
 		const firstLeg = journey.legs[0];
 		const lastLeg  = journey.legs[journey.legs.length - 1];
 

@@ -172,7 +169,7 @@ export class JourneysView extends JourneysCanvas {
 		`;
 	}
 
-	async updateViewState () {
+	updateViewState = async () => {
 		try {
 			let viewState = await getJourneys(this.slug);
 

@@ -223,7 +220,7 @@ export class JourneysView extends JourneysCanvas {
 		}
 	}
 
-	async refreshJourneys () {
+	refreshJourneys = async () => {
 		if (this.isOffline !== false) return;
 		if (this.isUpdating !== false) return false;
 

@@ -240,7 +237,7 @@ export class JourneysView extends JourneysCanvas {
 		}
 	}
 
-	async moreJourneys (mode) {
+	moreJourneys = async mode => {
 		if (this.isOffline !== false) {
 			this.showAlertOverlay(t('offline'));
 			return;
diff --git a/src/main.js b/src/main.js
@@ -5,7 +5,7 @@ import { initDataStorage }  from './dataStorage.js';
 import { initHafasClient }  from './hafasClient.js';
 import { initSettingsState, settingsState } from './settings.js'
 
-import { baseStyles, helperStyles } from './styles.js';
+import { baseStyles } from './styles.js';
 
 import './searchView.js';
 import './journeysView.js';

@@ -23,28 +23,24 @@ class Oeffisearch extends LitElement {
 	routes = [
 		{
 			pattern: /^\/$/,
-			render: () => html`<search-view></search-view>`
-		},
-		{
+			render: param => html`<search-view></search-view>`
+		}, {
 			pattern: /^\/([a-zA-Z0-9]+)\/([a-z]+)$/,
 			render: param => html`<journeys-view slug="${param[0]}" mode="${param[1]}"></journeys-view>`
-		},
-		{
+		}, {
 			pattern: /^\/j\/([a-z]+)\/(.+)$/,
 			render: param => html`<journey-view profile="${param[0]}" refreshToken="${param[1]}"></journey-view>`
-		},
-		{
+		}, {
 			pattern: /^\/t\/([a-z]+)\/(.+)$/,
 			render: param => html`<trip-view profile="${param[0]}" refreshToken="${param[1]}"></trip-view>`
-		},
-		{
+		}, {
 			pattern: /^\/d\/([a-z]+)\/([^/]+)(\/[0-9]+)?$/,
 			render: param => html`<departures-view profile="${param[0]}" stopId="${param[1]}" when="${param[2]}"></departures-view>`
 		}
 	];
 
-	constructor (...args) {
-		super(...args);
+	constructor () {
+		super();
 		this.outlet = html``;
 		if (!window.location.hash.length) window.location = '#/';
 	}
diff --git a/src/searchView.js b/src/searchView.js
@@ -9,7 +9,7 @@ import { formatPoint } from './formatters.js';
 import { newJourneys } from './app_functions.js';
 import { CustomDate, sleep, queryBackgroundColor, setThemeColor } from './helpers.js';
 
-import { baseStyles, helperStyles, flexboxStyles, buttonInputStyles, iconStyles, searchViewStyles } from './styles.js';
+import { searchViewStyles } from './styles.js';
 import { settings } from './settings.js';
 
 class SearchView extends BaseView {

@@ -29,8 +29,8 @@ class SearchView extends BaseView {
 		searchViewStyles
 	];
 
-	constructor (...args) {
-		super(...args);
+	constructor () {
+		super();
 
 		this.settingsState = settings.getState();
 		this.date          = new CustomDate();

@@ -66,16 +66,19 @@ class SearchView extends BaseView {
 
 	async connectedCallback() {
 		super.connectedCallback();
-		await sleep(200);
+		await sleep(100);
 
 		setThemeColor(queryBackgroundColor(document, 'body'));
 
-		await this.updateHistoryState();
+		this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse();
 
 		this._unsubscribeSettingsState = settings.subscribe(state => {
 			this.settingsState = state;
 			this.performUpdate();
 		});
+
+		await sleep(500);
+		this.renderRoot.querySelector('input[name=from]').focus();
 	}
 
 	disconnectedCallback () {

@@ -90,9 +93,7 @@ class SearchView extends BaseView {
 	async updated (previous) {
 		super.updated(previous);
 
-		if (isDevServer) console.info('updated(): ', previous);
-
-		if (previous.has('history') && previous.get('history') === undefined) await this.updateHistoryState()
+		if (isDevServer) console.info('updated():', previous);
 
 		if (
 			previous.has('settingsState') &&

@@ -100,15 +101,10 @@ class SearchView extends BaseView {
 			previous.get('settingsState').profile !== this.settingsState.profile
 		) await this.profileChangeHandler();
 
-		if (isDevServer) console.info('settingsState: ', this.settingsState);
-	}
-
-	async firstUpdated () {
-		await sleep(500);
-		this.renderRoot.querySelector('input[name=from]').focus();
+		if (isDevServer) console.info('settingsState:', this.settingsState);
 	}
 
-	renderContent () {
+	renderView = () => {
 		return html`
 			<div class="container">
 				<div class="title flex-center"><h1>${APP_NAME}</h1></div>

@@ -175,7 +171,7 @@ class SearchView extends BaseView {
 						</div>
 
 						<div class="selector rectangular">
-							<input type="checkbox" id="noTransfers" name="noTransfers"   @change=${this.changeHandler} .checked=${this.noTransfers}>
+							<input type="checkbox" id="noTransfers" name="noTransfers" @change=${this.changeHandler} .checked=${this.noTransfers}>
 							<label class="icon-seat" for="noTransfers" title="${t('titleNoTransfers')}"></label>
 						</div>
 

@@ -223,10 +219,8 @@ class SearchView extends BaseView {
 		this.requestUpdate();
 	}
 
-	showSettings = () => this.showDialogOverlay('settings', html`<settings-view></settings-view>`);
-
-	updateHistoryState = async () => this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse();
-	toggleHistory      = ()       => this.showHistory = !this.showHistory;
+	showSettings  = () => this.showDialogOverlay('settings', html`<settings-view></settings-view>`);
+	toggleHistory = () => this.showHistory = !this.showHistory;
 
 	journeysHistoryAction = num => {
 		const element = this.history[num];

@@ -292,6 +286,32 @@ class SearchView extends BaseView {
 		return productIcons[id] || `icon-${id}`;
 	}
 
+	focusNextElement = currentElementId => {
+		switch (currentElementId) {
+			case 'from':
+				this.renderRoot.querySelector('input[name=to]').focus();
+
+				if (this.settingsState.showVia) this.renderRoot.querySelector('input[name=via]').focus();
+				break;
+
+			case 'via':
+				this.renderRoot.querySelector('input[name=to]').focus();
+				break;
+
+			case 'to':
+				this.renderRoot.querySelector('[type=submit]').focus();
+				break;
+		}
+	}
+
+	setSuggestion = (name, num, pointerType) => {
+		this.location[name].value              = formatPoint(this.location[name].suggestions[num]);
+		this.location[name].suggestionSelected = this.location[name].suggestions[num];
+		this.location[name].suggestionsVisible = false;
+		this.requestUpdate();
+		if (pointerType !== '') this.focusNextElement(name);
+	}
+
 	profileChangeHandler = async () => {
 		[ 'from', 'via','to' ].forEach(name => {
 			this.location[name] = {

@@ -362,7 +382,7 @@ class SearchView extends BaseView {
 			params.walkingSpeed  = this.settingsState.walkingSpeed;
 		}
 
-		if (isDevServer) console.info('submitHandler(): ',params);
+		if (isDevServer) console.info('submitHandler():',params);
 
 		this.showLoaderOverlay();
 

@@ -398,32 +418,6 @@ class SearchView extends BaseView {
 		}
 	}
 
-	focusNextElement = currentElementId => {
-		switch (currentElementId) {
-			case 'from':
-				this.renderRoot.querySelector('input[name=to]').focus();
-
-				if (this.settingsState.showVia) this.renderRoot.querySelector('input[name=via]').focus();
-				break;
-
-			case 'via':
-				this.renderRoot.querySelector('input[name=to]').focus();
-				break;
-
-			case 'to':
-				this.renderRoot.querySelector('[type=submit]').focus();
-				break;
-		}
-	}
-
-	setSuggestion = (name, num, pointerType) => {
-		this.location[name].value              = formatPoint(this.location[name].suggestions[num]);
-		this.location[name].suggestionSelected = this.location[name].suggestions[num];
-		this.location[name].suggestionsVisible = false;
-		this.requestUpdate();
-		if (pointerType !== '') this.focusNextElement(name);
-	}
-
 	keyupHandler = event => {
 		const name  = event.target.name;
 		const value = event.target.value;

@@ -508,12 +502,11 @@ class SearchView extends BaseView {
 		const name  = event.target.name;
 		const value = event.target.value;
 
-		if (name === 'accessibility') this.settingsState.setAccessibility(value);
-		if (name === 'noTransfers')   this.noTransfers = !this.noTransfers;
-		if (name === 'isArrival')     this.isArrival = !this.isArrival;
-		if (name === 'dateTime')      this.date.setDateTime(value);
-		if (name === 'date')          this.date.setDate(value);
-		if (name === 'time')          this.date.setTime(value);
+		if (name === 'noTransfers') this.noTransfers = !this.noTransfers;
+		if (name === 'isArrival')   this.isArrival   = !this.isArrival;
+		if (name === 'dateTime')    this.date.setDateTime(value);
+		if (name === 'date')        this.date.setDate(value);
+		if (name === 'time')        this.date.setTime(value);
 
 		this.requestUpdate();
 	}
diff --git a/src/settingsView.js b/src/settingsView.js
@@ -7,7 +7,7 @@ import { settings } from './settings.js';
 import { initHafasClient, profiles } from './hafasClient.js';
 import { clearDataStorage } from './dataStorage.js';
 
-import { baseStyles, helperStyles, flexboxStyles, buttonInputStyles, settingsViewStyles } from './styles.js';
+import { settingsViewStyles } from './styles.js';
 
 class SettingsView extends BaseView {
 	static styles = [

@@ -19,8 +19,8 @@ class SettingsView extends BaseView {
 		viewState: { state: true },
 	};
 
-	constructor (...args) {
-		super(...args);
+	constructor () {
+		super();
 		this.viewState = settings.getState();
 	}
 
diff --git a/src/styles.js b/src/styles.js
@@ -1,5 +1,4 @@
 import baseStyles from './styles/base.css' assert { type: 'css' };
-import helperStyles from './styles/helpers.css' assert { type: 'css' };
 import flexboxStyles from './styles/flexbox.css' assert { type: 'css' };
 import buttonInputStyles from './styles/buttonInput.css' assert { type: 'css' };
 import headerStyles from './styles/header.css' assert { type: 'css' };
diff --git a/src/styles/base.css b/src/styles/base.css
@@ -35,6 +35,23 @@ a {
 
 }
 
+.invisible {
+	visibility: hidden !important;
+}
+
+.hidden {
+	display: none !important;
+}
+
+.flipped {
+	transform-origin: center center;
+	transform: rotate(180deg);
+}
+
+.center {
+	margin: auto;
+}
+
 .spinner {
 	margin: calc(50vh - 120px) auto;
 	border: 5px solid rgba(255, 255, 255, .4);
diff --git a/src/styles/helpers.css b/src/styles/helpers.css
@@ -1,16 +0,0 @@
-.invisible {
-	visibility: hidden !important;
-}
-
-.hidden {
-	display: none !important;
-}
-
-.flipped {
-	transform-origin: center center;
-	transform: rotate(180deg);
-}
-
-.center {
-	margin: auto;
-}
diff --git a/src/templates.js b/src/templates.js
@@ -5,14 +5,14 @@ import { settingsState } from './settings.js';
 import { getDS100byIBNR } from './ds100.js';
 import { CustomDate } from './helpers.js';
 
-export const remarksModal = (element, remarks) => element.showDialogOverlay('remarks', [].concat(
+export const remarksModal = (element, remarks) => element.showDialogOverlay('remarks', [
 	remarks.map(remark => html`
 		<div class="flex-row">
 			<span class="icon-${remark.type}"></span>
 			<span>${unsafeHTML(remark.text)}</span>
 		</div>
 	`),
-	[ html`<style>${css`
+	html`<style>${css`
 		.flex-row {
 			align-items: center;
 			flex-wrap: nowrap;

@@ -28,8 +28,8 @@ export const remarksModal = (element, remarks) => element.showDialogOverlay('rem
 			align-self: start;
 			padding-right: .3em;
 		}
-	`}</style>`]
-));
+	`}</style>`
+]);
 
 export const stopTemplate = (profile, stop) => {
 	let stopName = stop.name;

@@ -41,7 +41,7 @@ export const stopTemplate = (profile, stop) => {
 	return html`<a class="flex-center" href="#/d/${profile}/${stop.id}">${stopName}</a>`;
 }
 
-export const platformTemplate = (data) => {
+export const platformTemplate = data => {
 	if (data.departurePlatform) {
 		if (data.departurePlatform != data.plannedDeparturePlatform) {
 			return html`<b>${data.departurePlatform}</b>`;
diff --git a/src/tripView.js b/src/tripView.js
@@ -9,7 +9,7 @@ import { formatDuration, formatLineAdditionalName, formatLineDisplayName, format
 import { getHafasClient } from './hafasClient.js';
 import { t } from './languages.js';
 
-import { baseStyles, helperStyles, flexboxStyles, iconStyles, headerStyles, cardStyles, journeyViewStyles } from './styles.js';
+import { headerStyles, cardStyles, journeyViewStyles } from './styles.js';
 
 class TripView extends BaseView {
 	static styles = [

@@ -25,8 +25,8 @@ class TripView extends BaseView {
 		viewState:    { state: true },
 	};
 
-	constructor (...args) {
-		super(...args);
+	constructor () {
+		super();
 
 		this.viewState  = null;
 	}

@@ -36,96 +36,98 @@ class TripView extends BaseView {
 		await sleep(100);
 
 		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
+
+		if (!this.isOffline) await this.updateViewState();
+	}
+
+	disconnectedCallback () {
+		super.disconnectedCallback();
+
+		this.viewState = null;
 	}
 
+
 	async updated (previous) {
 		super.updated(previous);
 
 		if (isDevServer) console.info('updated(): ', previous);
 
-		if (previous.has('refreshToken')) {
-			this.viewState  = null;
-			if (!this.isOffline) await this.updateViewState();
-		}
-
 		if (previous.has('isOffline') && this.viewState === null) await this.updateViewState();
 	}
 
-	renderContent () {
-		return [
-			html`
-			<div class="header-container">
-				<header>
-					<a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a>
-					<div class="container">
-						${this.viewState !== null ?
-						html`<h3>Trip of ${formatLineDisplayName(this.viewState.trip.line)} to ${this.viewState.trip.direction}</h3>`
-						: html`<h3>Trip of ...</h3>
-						`}
-					</div>
-					<a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a>
-				</header>
-			</div>
-			`,
-
-			this.viewState !== null ? html`
-			<div class="container">
-				<div class="card">
-					<table>
-						<thead>
-							<tr>
-								<td colspan="4">
-									<div class="center">
-										${this.viewState.trip.bahnExpertUrl ? html`
-										<a href="${this.viewState.trip.bahnExpertUrl}">${formatLineDisplayName(this.viewState.trip.line)}${this.viewState.trip.direction ? html` → ${this.viewState.trip.direction}` : ''}</a>
-										` : html `
-										${formatLineDisplayName(this.viewState.trip.line)}${this.viewState.trip.direction ? html` → ${this.viewState.trip.direction}` : ''}
-										`}
-										${this.viewState.trip.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : ''}
-										${this.viewState.trip.remarks.length !== 0 ? html`
-										<a class="link ${this.viewState.trip.remarksIcon}" @click=${() => remarksModal(this, this.viewState.trip.remarks)}></a>
-										` : nothing}
-									</div>
-								</td>
-							</tr>
-							<tr>
-								<td colspan="4">
-									<div class="train-details flex-center">
-										${formatLineAdditionalName(this.viewState.trip.line) ? html`<div>Trip: ${formatLineAdditionalName(this.viewState.trip.line)}</div>` : nothing}
-										${this.viewState.trip.line.trainType ? html`<div>Train type: ${this.viewState.trip.line.trainType}</div>` : nothing}
-										<div class="${!this.viewState.trip.cancelled ? nothing : 'cancelled'}">
-											${t('duration')}: ${formatDuration(this.viewState.trip.arrival - (this.viewState.trip.departure ? this.viewState.trip.departure : this.viewState.trip.plannedDeparture))} ${this.viewState.trip.departure ? '' : ('(' + t('planned') + ')')}
-										</div>
-										${this.viewState.trip.loadFactor ? html`<div>${t("load-"+this.viewState.trip.loadFactor)}</div>` : nothing}
+	renderView = () => [
+		html`
+		<div class="header-container">
+			<header>
+				<a id="back" class="icon-back ${history.length !== 1 ? '': 'invisible'}" title="${t('back')}" @click=${() => history.back()}></a>
+				<div class="container">
+					${this.viewState !== null ?
+					html`<h3>Trip of ${formatLineDisplayName(this.viewState.trip.line)} to ${this.viewState.trip.direction}</h3>`
+					: html`<h3>Trip of ...</h3>
+					`}
+				</div>
+				<a class="icon-reload ${this.isUpdating ? 'spinning' : ''} ${!this.isOffline ? '' : 'invisible'}" title="${t("reload")}" @click=${this.updateViewState}></a>
+			</header>
+		</div>
+		`,
+
+		this.viewState !== null ? html`
+		<div class="container">
+			<div class="card">
+				<table>
+					<thead>
+						<tr>
+							<td colspan="4">
+								<div class="center">
+									${this.viewState.trip.bahnExpertUrl ? html`
+									<a href="${this.viewState.trip.bahnExpertUrl}">${formatLineDisplayName(this.viewState.trip.line)}${this.viewState.trip.direction ? html` → ${this.viewState.trip.direction}` : ''}</a>
+									` : html `
+									${formatLineDisplayName(this.viewState.trip.line)}${this.viewState.trip.direction ? html` → ${this.viewState.trip.direction}` : ''}
+									`}
+									${this.viewState.trip.cancelled ? html`<b class="cancelled-text">${t('cancelled-ride')}</b>` : ''}
+									${this.viewState.trip.remarks.length !== 0 ? html`
+									<a class="link ${this.viewState.trip.remarksIcon}" @click=${() => remarksModal(this, this.viewState.trip.remarks)}></a>
+									` : nothing}
+								</div>
+							</td>
+						</tr>
+						<tr>
+							<td colspan="4">
+								<div class="train-details flex-center">
+									${formatLineAdditionalName(this.viewState.trip.line) ? html`<div>Trip: ${formatLineAdditionalName(this.viewState.trip.line)}</div>` : nothing}
+									${this.viewState.trip.line.trainType ? html`<div>Train type: ${this.viewState.trip.line.trainType}</div>` : nothing}
+									<div class="${!this.viewState.trip.cancelled ? nothing : 'cancelled'}">
+										${t('duration')}: ${formatDuration(this.viewState.trip.arrival - (this.viewState.trip.departure ? this.viewState.trip.departure : this.viewState.trip.plannedDeparture))} ${this.viewState.trip.departure ? '' : ('(' + t('planned') + ')')}
 									</div>
-								</td>
-							</tr>
-							<tr>
-								<th>${t('arrival')}</th>
-								<th>${t('departure')}</th>
-								<th class="station">${t('station')}</th>
-								<th>${t('platform')}</th>
+									${this.viewState.trip.loadFactor ? html`<div>${t("load-"+this.viewState.trip.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>
+						${(this.viewState.trip.stopovers || []).map(stop => html`
+							<tr class="${stop.cancelled ? 'cancelled' : ''}">
+								<td>${timeTemplate(stop, 'arrival')}</td>
+								<td>${timeTemplate(stop, 'departure')}</td>
+								<td>${stopTemplate(this.profile, stop.stop)}</td>
+								<td>${platformTemplate(stop)}</td>
 							</tr>
-						</thead>
-						<tbody>
-							${(this.viewState.trip.stopovers || []).map(stop => html`
-								<tr class="${stop.cancelled ? 'cancelled' : ''}">
-									<td>${timeTemplate(stop, 'arrival')}</td>
-									<td>${timeTemplate(stop, 'departure')}</td>
-									<td>${stopTemplate(this.profile, stop.stop)}</td>
-									<td>${platformTemplate(stop)}</td>
-								</tr>
-							`)}
-						</tbody>
-					</table>
-				</div>
+						`)}
+					</tbody>
+				</table>
 			</div>
-			<footer-component></footer-component>
-			` : !this.isOffline ?
-			html`<div class="spinner"></div>`
-			: html`<div class="offline"></div>`
-		];
-	}
+		</div>
+		<footer-component></footer-component>
+		` : !this.isOffline ?
+		html`<div class="spinner"></div>`
+		: html`<div class="offline"></div>`
+	];
 
 	updateViewState = async () => {
 		if (this.isOffline !== false) return;