239 lines
12 KiB
HTML
239 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Bristol to {{ destination }} via Eurostar{% endblock %}
|
|
{% block og_title %}Bristol to {{ destination }} via Eurostar{% endblock %}
|
|
{% block og_description %}Train options from Bristol Temple Meads to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
|
|
{% block twitter_title %}Bristol to {{ destination }} via Eurostar{% endblock %}
|
|
{% block twitter_description %}Train options from Bristol Temple Meads to {{ destination }} on {{ travel_date_display }} via Paddington, St Pancras, and Eurostar.{% endblock %}
|
|
{% block content %}
|
|
<style>
|
|
@media (max-width: 640px) {
|
|
.col-transfer { display: none; }
|
|
}
|
|
</style>
|
|
|
|
<p style="margin-bottom:1rem">
|
|
<a href="{{ url_for('index') }}">← New search</a>
|
|
</p>
|
|
|
|
<div class="card" style="margin-bottom:1.5rem">
|
|
<h2 style="margin-top:0">
|
|
Bristol Temple Meads → {{ destination }}
|
|
</h2>
|
|
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.5rem">
|
|
<a href="{{ url_for('results', slug=slug, travel_date=prev_date, min_connection=min_connection, max_connection=max_connection) }}"
|
|
style="padding:0.3rem 0.75rem;border:1px solid #cbd5e0;border-radius:4px;
|
|
text-decoration:none;color:#00539f;font-size:0.9rem">← Prev</a>
|
|
<strong>{{ travel_date_display }}</strong>
|
|
<a href="{{ url_for('results', slug=slug, travel_date=next_date, min_connection=min_connection, max_connection=max_connection) }}"
|
|
style="padding:0.3rem 0.75rem;border:1px solid #cbd5e0;border-radius:4px;
|
|
text-decoration:none;color:#00539f;font-size:0.9rem">Next →</a>
|
|
</div>
|
|
<div style="margin:0.9rem 0 1rem">
|
|
<div style="font-size:0.9rem;font-weight:600;margin-bottom:0.45rem">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', slug=destination_slug, travel_date=travel_date, min_connection=min_connection, max_connection=max_connection) }}"
|
|
>{{ destination_name }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:0.75rem;display:flex;gap:1.5rem;align-items:center">
|
|
<div>
|
|
<label for="min_conn_select" style="font-size:0.9rem;font-weight:600;margin-right:0.5rem">
|
|
Min connection:
|
|
</label>
|
|
<select id="min_conn_select"
|
|
onchange="applyConnectionFilter()"
|
|
style="padding:0.3rem 0.6rem;font-size:0.9rem;border:1px solid #cbd5e0;border-radius:4px">
|
|
{% 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" style="font-size:0.9rem;font-weight:600;margin-right:0.5rem">
|
|
Max connection:
|
|
</label>
|
|
<select id="max_conn_select"
|
|
onchange="applyConnectionFilter()"
|
|
style="padding:0.3rem 0.6rem;font-size:0.9rem;border:1px solid #cbd5e0;border-radius:4px">
|
|
{% 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 = document.getElementById('min_conn_select').value;
|
|
var max = document.getElementById('max_conn_select').value;
|
|
window.location = '{{ url_for('results', slug=slug, travel_date=travel_date) }}?min_connection=' + min + '&max_connection=' + max;
|
|
}
|
|
</script>
|
|
<p style="color:#4a5568;margin:0">
|
|
{{ gwr_count }} GWR service{{ 's' if gwr_count != 1 }}
|
|
·
|
|
{{ eurostar_count }} Eurostar service{{ 's' if eurostar_count != 1 }}
|
|
{% if unreachable_morning_services %}
|
|
·
|
|
<span style="color:#718096">
|
|
{{ unreachable_morning_services | length }} Eurostar service{{ 's' if unreachable_morning_services | length != 1 }} unavailable from Bristol
|
|
</span>
|
|
{% endif %}
|
|
{% if from_cache %}
|
|
· <span style="color:#718096;font-size:0.85rem">(cached)</span>
|
|
{% endif %}
|
|
</p>
|
|
{% if error %}
|
|
<div style="margin-top:1rem;padding:0.75rem 1rem;background:#fff5f5;border:1px solid #fc8181;border-radius:4px;color:#c53030">
|
|
<strong>Warning:</strong> {{ error }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if trips or unreachable_morning_services %}
|
|
<div class="card" style="overflow-x:auto">
|
|
<table style="width:100%;border-collapse:collapse;font-size:0.95rem">
|
|
<thead>
|
|
<tr style="border-bottom:2px solid #e2e8f0;text-align:left">
|
|
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Bristol</th>
|
|
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Paddington</th>
|
|
<th style="padding:0.6rem 0.8rem;white-space:nowrap">GWR Fare</th>
|
|
<th class="col-transfer" style="padding:0.6rem 0.8rem;white-space:nowrap">Transfer</th>
|
|
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Depart St Pancras</th>
|
|
<th style="padding:0.6rem 0.8rem">{{ destination }}</th>
|
|
<th style="padding:0.6rem 0.8rem;white-space:nowrap">ES Std</th>
|
|
<th style="padding:0.6rem 0.8rem;white-space: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_bg = 'background:#f0fff4' %}
|
|
{% elif row.row_type == 'trip' and row.total_minutes >= worst_mins - 5 and trips | length > 1 %}
|
|
{% set row_bg = 'background:#fff5f5' %}
|
|
{% elif row.row_type == 'unreachable' %}
|
|
{% set row_bg = 'background:#f7fafc;color:#a0aec0' %}
|
|
{% elif loop.index is odd %}
|
|
{% set row_bg = 'background:#f7fafc' %}
|
|
{% else %}
|
|
{% set row_bg = '' %}
|
|
{% endif %}
|
|
<tr style="border-bottom:1px solid #e2e8f0;{{ row_bg }}">
|
|
{% if row.row_type == 'trip' %}
|
|
<td style="padding:0.6rem 0.8rem;font-weight:600">
|
|
{{ row.depart_bristol }}
|
|
{% if row.headcode %}<br><span style="font-size:0.75rem;font-weight:400;color:#718096">{{ row.headcode }}</span>{% endif %}
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem">
|
|
{{ row.arrive_paddington }}
|
|
<span style="font-size:0.8rem;color:#718096;white-space:nowrap">({{ row.gwr_duration }})</span>
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem;white-space:nowrap">
|
|
£{{ "%.2f"|format(row.ticket_price) }}
|
|
<br><span style="font-size:0.75rem;color:#718096">{{ row.ticket_name }}</span>
|
|
</td>
|
|
<td class="col-transfer" style="padding:0.6rem 0.8rem;color:#4a5568;white-space:nowrap">
|
|
{{ row.connection_duration }}{% if row.connection_minutes < 80 %} <span title="Tight connection">⚠️</span>{% endif %}
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem;font-weight:600">
|
|
{{ row.depart_st_pancras }}
|
|
{% if row.train_number %}<br><span style="font-size:0.75rem;font-weight:400;color:#718096">{{ row.train_number }}</span>{% endif %}
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem">
|
|
{{ row.arrive_destination }}
|
|
<span style="font-weight:400;color:#718096;font-size:0.85em">(CET)</span>
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem;white-space:nowrap">
|
|
{% if row.eurostar_price is not none %}
|
|
£{{ row.eurostar_price }}
|
|
{% else %}
|
|
<span style="color:#718096">–</span>
|
|
{% endif %}
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem;font-weight:600;white-space:nowrap">
|
|
{% if row.total_minutes <= best_mins + 5 and trips | length > 1 %}
|
|
<span style="color:#276749" title="Fastest journey">{{ row.total_duration }} ⚡</span>
|
|
{% elif row.total_minutes >= worst_mins - 5 and trips | length > 1 %}
|
|
<span style="color:#c53030" title="Slowest journey">{{ row.total_duration }} 🐢</span>
|
|
{% else %}
|
|
<span style="color:#00539f">{{ row.total_duration }}</span>
|
|
{% endif %}
|
|
{% if row.total_price is not none %}
|
|
<br><span style="font-size:0.8rem;font-weight:700;color:#276749">£{{ "%.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 style="padding:0.6rem 0.8rem;font-weight:600">—</td>
|
|
<td style="padding:0.6rem 0.8rem">—</td>
|
|
<td class="col-transfer" style="padding:0.6rem 0.8rem">—</td>
|
|
<td style="padding:0.6rem 0.8rem">n/a</td>
|
|
<td style="padding:0.6rem 0.8rem;font-weight:600">
|
|
{{ row.depart_st_pancras }}
|
|
{% if row.train_number %}<br><span style="font-size:0.75rem;font-weight:400;color:#a0aec0">{{ row.train_number }}</span>{% endif %}
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem">
|
|
{{ row.arrive_destination }}
|
|
<span style="font-weight:400;color:#a0aec0;font-size:0.85em">(CET)</span>
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem;white-space:nowrap">
|
|
{% if row.eurostar_price is not none %}
|
|
<span style="color:#a0aec0">£{{ row.eurostar_price }}</span>
|
|
{% else %}
|
|
<span style="color:#a0aec0">–</span>
|
|
{% endif %}
|
|
</td>
|
|
<td style="padding:0.6rem 0.8rem;font-weight:600">
|
|
<span title="No same-day Bristol connection" style="color:#a0aec0">Too early</span>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<p style="margin-top:1rem;font-size:0.82rem;color:#718096">
|
|
Paddington → St Pancras connection: {{ min_connection }}–{{ max_connection }} min.
|
|
GWR walk-on single prices for Bristol Temple Meads → Paddington.
|
|
Eurostar Standard prices are for 1 adult in GBP; always check
|
|
<a href="{{ eurostar_url }}" target="_blank" rel="noopener">eurostar.com</a> to book.
|
|
·
|
|
<a href="{{ rtt_bristol_url }}" target="_blank" rel="noopener">Bristol departures on RTT</a>
|
|
·
|
|
<a href="{{ rtt_url }}" target="_blank" rel="noopener">Paddington arrivals on RTT</a>
|
|
</p>
|
|
|
|
{% else %}
|
|
<div class="card" style="color:#4a5568;text-align:center;padding:3rem 2rem">
|
|
<p style="font-size:1.1rem;margin:0 0 0.5rem">No valid journeys found.</p>
|
|
<p style="font-size:0.9rem;margin:0">
|
|
{% 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 + Eurostar combination has at least a {{ min_connection }}-minute connection at Paddington/St Pancras.
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|