paddington-eurostar/templates/results.html
Edward Betts 5583a20143 Add station sub-labels to results table column headers
Show the through-route in each header: National Rail (origin → Paddington),
Transfer (Paddington → St Pancras), Eurostar (St Pancras → destination).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:21:25 +01:00

249 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ departure_station_name }} to {{ destination }} via Eurostar{% endblock %}
{% block og_title %}{{ departure_station_name }} to {{ destination }} via Eurostar{% endblock %}
{% block og_description %}Train options from {{ departure_station_name }} to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
{% block twitter_title %}{{ departure_station_name }} to {{ destination }} via Eurostar{% endblock %}
{% block twitter_description %}Train options from {{ departure_station_name }} to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
{% block content %}
<p class="back-link">
<a href="{{ url_for('index') }}">&larr; New search</a>
</p>
<div class="card" style="margin-bottom:1.5rem">
<h2>
{{ departure_station_name }} &rarr; {{ destination }}
</h2>
<div class="date-nav">
<a href="{{ url_for('results', station_crs=station_crs, slug=slug, travel_date=prev_date, min_connection=url_min_connection, max_connection=url_max_connection) }}"
class="btn-nav">&larr; Prev</a>
<strong>{{ travel_date_display }}</strong>
<a href="{{ url_for('results', station_crs=station_crs, slug=slug, travel_date=next_date, min_connection=url_min_connection, max_connection=url_max_connection) }}"
class="btn-nav">Next &rarr;</a>
</div>
<div class="switcher-section">
<div class="section-label">Switch destination for {{ travel_date_display }}</div>
<div class="chip-row">
{% for destination_slug, destination_name in destinations.items() %}
{% if destination_slug == slug %}
<span class="chip-current">{{ destination_name }}</span>
{% else %}
<a
class="chip-link"
href="{{ url_for('results', station_crs=station_crs, slug=destination_slug, travel_date=travel_date, min_connection=url_min_connection, max_connection=url_max_connection) }}"
>{{ destination_name }}</a>
{% endif %}
{% endfor %}
</div>
</div>
<div class="filter-row">
<div>
<label for="min_conn_select" class="filter-label">
Min connection:
</label>
<select id="min_conn_select"
onchange="applyConnectionFilter()"
class="select-inline">
{% for mins in valid_min_connections %}
<option value="{{ mins }}" {% if mins == min_connection %}selected{% endif %}>{{ mins }} min</option>
{% endfor %}
</select>
</div>
<div>
<label for="max_conn_select" class="filter-label">
Max connection:
</label>
<select id="max_conn_select"
onchange="applyConnectionFilter()"
class="select-inline">
{% for mins in valid_max_connections %}
<option value="{{ mins }}" {% if mins == max_connection %}selected{% endif %}>{{ mins }} min</option>
{% endfor %}
</select>
</div>
</div>
<script>
function applyConnectionFilter() {
var min = parseInt(document.getElementById('min_conn_select').value);
var max = parseInt(document.getElementById('max_conn_select').value);
var base = '{{ url_for('results', station_crs=station_crs, slug=slug, travel_date=travel_date) }}';
var params = [];
if (min !== {{ default_min_connection }}) params.push('min_connection=' + min);
if (max !== {{ default_max_connection }}) params.push('max_connection=' + max);
window.location = params.length ? base + '?' + params.join('&') : base;
}
</script>
<p class="card-meta">
{{ gwr_count }} GWR service{{ 's' if gwr_count != 1 }}
&nbsp;&middot;&nbsp;
{{ eurostar_count }} Eurostar service{{ 's' if eurostar_count != 1 }}
{% if from_cache %}
&nbsp;&middot;&nbsp; <span class="text-muted text-sm">(cached)</span>
{% endif %}
</p>
{% if error %}
<div class="alert alert-error">
<strong>Warning:</strong> {{ error }}
</div>
{% endif %}
{% if no_prices_note %}
<div class="alert alert-warning">
{{ no_prices_note }}
</div>
{% endif %}
</div>
{% if trips or unreachable_morning_services %}
<div class="card">
<table class="results-table">
<thead>
<tr>
<th>National Rail<br><span class="text-xs font-normal text-muted">{{ departure_station_name }} &rarr; Paddington</span></th>
<th class="col-transfer">Transfer<br><span class="text-xs font-normal text-muted">Paddington &rarr; St Pancras</span></th>
<th>Eurostar<br><span class="text-xs font-normal text-muted">St Pancras &rarr; {{ destination }}</span></th>
<th class="nowrap">Total</th>
</tr>
</thead>
<tbody>
{% if trips %}
{% set best_mins = trips | map(attribute='total_minutes') | min %}
{% set worst_mins = trips | map(attribute='total_minutes') | max %}
{% set priced_trips = trips | selectattr('total_price') | list %}
{% if priced_trips | length > 1 %}
{% set min_price = priced_trips | map(attribute='total_price') | min %}
{% set max_price = priced_trips | map(attribute='total_price') | max %}
{% endif %}
{% endif %}
{% for row in result_rows %}
{% if row.row_type == 'trip' and row.total_minutes <= best_mins + 5 and trips | length > 1 %}
{% set row_class = 'row-fast' %}
{% elif row.row_type == 'trip' and row.total_minutes >= worst_mins - 5 and trips | length > 1 %}
{% set row_class = 'row-slow' %}
{% elif row.row_type == 'unreachable' %}
{% set row_class = 'row-unreachable' %}
{% elif loop.index is odd %}
{% set row_class = 'row-alt' %}
{% else %}
{% set row_class = '' %}
{% endif %}
<tr class="{{ row_class }}">
{% if row.row_type == 'trip' %}
<td>
<span class="font-bold nowrap">{{ row.depart_bristol }} &rarr; {{ row.arrive_paddington }}</span>
<span class="text-sm text-muted nowrap">({{ row.gwr_duration }})</span>
{% if row.headcode or row.arrive_platform %}
<br><span class="text-xs text-muted">
{%- if row.headcode %}{{ row.headcode }}{% endif %}
{%- if row.headcode and row.arrive_platform %} &middot; {% endif %}
{%- if row.arrive_platform %}Plat {{ row.arrive_platform }}{% endif %}
</span>
{% endif %}
{% if row.ticket_price is not none %}
<br><span class="text-sm font-bold">£{{ "%.2f"|format(row.ticket_price) }}</span>
<span class="text-xs text-muted">{{ row.ticket_name }}</span>
{% else %}
<br><span class="text-sm text-muted">&ndash;</span>
{% endif %}
</td>
<td class="col-transfer" style="color:#4a5568">
<span class="nowrap">{{ row.connection_duration }}{% if row.connection_minutes < 80 %} <span title="Tight connection">⚠️</span>{% endif %}</span>
{% if row.circle_services %}
{% set c = row.circle_services[0] %}
<br><span class="text-xs text-muted nowrap">Circle {{ c.depart }} → KX {{ c.arrive_kx }}</span>
{% if row.circle_services | length > 1 %}
{% set c2 = row.circle_services[1] %}
<br><span class="text-xs text-muted nowrap" style="opacity:0.7">next {{ c2.depart }} → KX {{ c2.arrive_kx }}</span>
{% endif %}
{% endif %}
</td>
<td>
<span class="font-bold nowrap">{{ row.depart_st_pancras }} &rarr; {{ row.arrive_destination }} <span class="font-normal text-muted" style="font-size:0.85em">(CET)</span></span>
{% if row.eurostar_duration or row.train_number %}
<br><span class="text-xs text-muted">
{%- if row.eurostar_duration %}<span class="nowrap">({{ row.eurostar_duration }})</span>{% endif %}
{%- if row.eurostar_duration and row.train_number %} &middot; {% endif %}
{%- if row.train_number %}{% for part in row.train_number.split(' + ') %}<span class="nowrap">{{ part }}</span>{% if not loop.last %} + {% endif %}{% endfor %}{% endif %}
</span>
{% endif %}
{% if row.eurostar_price is not none %}
<br><span class="text-sm font-bold">£{{ "%.2f"|format(row.eurostar_price) }}</span>
{% if row.eurostar_seats is not none %}
<span class="text-xs text-muted">{{ row.eurostar_seats }} at this price</span>
{% endif %}
{% else %}
<br><span class="text-sm text-muted">&ndash;</span>
{% endif %}
</td>
<td class="font-bold nowrap">
{% if row.total_minutes <= best_mins + 5 and trips | length > 1 %}
<span class="text-green" title="Fastest journey">{{ row.total_duration }} ⚡</span>
{% elif row.total_minutes >= worst_mins - 5 and trips | length > 1 %}
<span class="text-red" title="Slowest journey">{{ row.total_duration }} 🐢</span>
{% else %}
<span class="text-blue">{{ row.total_duration }}</span>
{% endif %}
{% if row.total_price is not none %}
<br><span class="text-sm text-green" style="font-weight:700">£{{ "%.2f"|format(row.total_price) }}{% if min_price is defined and max_price is defined %}{% if row.total_price <= min_price + 10 %} <span title="Cheapest journey">🪙</span>{% elif row.total_price >= max_price - 10 %} <span title="Most expensive journey">💸</span>{% endif %}{% endif %}</span>
{% endif %}
</td>
{% else %}
<td>
<span class="text-dimmed text-sm" title="Too early to reach from {{ departure_station_name }}">Too early</span>
</td>
<td class="col-transfer text-dimmed">&mdash;</td>
<td>
<span class="font-bold nowrap text-dimmed">{{ row.depart_st_pancras }} &rarr; {{ row.arrive_destination }} <span class="font-normal" style="font-size:0.85em">(CET)</span></span>
{% if row.eurostar_duration or row.train_number %}
<br><span class="text-xs text-dimmed">
{%- if row.eurostar_duration %}<span class="nowrap">({{ row.eurostar_duration }})</span>{% endif %}
{%- if row.eurostar_duration and row.train_number %} &middot; {% endif %}
{%- if row.train_number %}{% for part in row.train_number.split(' + ') %}<span class="nowrap">{{ part }}</span>{% if not loop.last %} + {% endif %}{% endfor %}{% endif %}
</span>
{% endif %}
{% if row.eurostar_price is not none %}
<br><span class="text-sm text-dimmed">£{{ "%.2f"|format(row.eurostar_price) }}</span>
{% if row.eurostar_seats is not none %}
<span class="text-xs text-dimmed">{{ row.eurostar_seats }} at this price</span>
{% endif %}
{% else %}
<br><span class="text-sm text-dimmed">&ndash;</span>
{% endif %}
</td>
<td class="text-dimmed">&mdash;</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<p class="footnote">
Paddington &rarr; St&nbsp;Pancras connection: {{ min_connection }}&ndash;{{ max_connection }}&nbsp;min.
GWR walk-on single prices from
<a href="https://www.gwr.com/" target="_blank" rel="noopener">gwr.com</a>.
Eurostar Standard prices are for 1 adult in GBP; always check
<a href="{{ eurostar_url }}" target="_blank" rel="noopener">eurostar.com</a> to book.
&nbsp;&middot;&nbsp;
<a href="{{ rtt_station_url }}" target="_blank" rel="noopener">{{ departure_station_name }} departures on RTT</a>
&nbsp;&middot;&nbsp;
<a href="{{ rtt_url }}" target="_blank" rel="noopener">Paddington arrivals on RTT</a>
</p>
{% else %}
<div class="card empty-state">
<p>No valid journeys found.</p>
<p>
{% if gwr_count == 0 and eurostar_count == 0 %}
Could not retrieve train data. Check your network connection or try again.
{% elif gwr_count == 0 %}
No GWR trains found for this date.
{% elif eurostar_count == 0 %}
No Eurostar services found for {{ destination }} on this date.
{% else %}
No GWR&nbsp;+&nbsp;Eurostar combination has at least a {{ min_connection }}-minute connection at Paddington/St&nbsp;Pancras.
{% endif %}
</p>
</div>
{% endif %}
{% endblock %}