bristol-eurostar/templates/results.html
Edward Betts e7695a5e49 Drive search form dropdowns from VALID_MIN/MAX_CONNECTIONS; warn on short transfers
Index page connection time dropdowns now iterate over valid_min_connections
and valid_max_connections passed from the view, so any change to the sets
in app.py is reflected automatically (also adds the missing 45 min option).

Add ⚠️ next to transfer times under 80 minutes in the results table;
store connection_minutes in each trip dict to support the comparison.

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

234 lines
11 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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') }}">&larr; New search</a>
</p>
<div class="card" style="margin-bottom:1.5rem">
<h2 style="margin-top:0">
Bristol Temple Meads &rarr; {{ 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">&larr; 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 &rarr;</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 }}
&nbsp;&middot;&nbsp;
{{ eurostar_count }} Eurostar service{{ 's' if eurostar_count != 1 }}
{% if unreachable_morning_services %}
&nbsp;&middot;&nbsp;
<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 %}
&nbsp;&middot;&nbsp; <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&nbsp;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 %}
{% 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">
{{ row.connection_duration }}{% if row.connection_minutes < 80 %} {% 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">&ndash;</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 option">{{ row.total_duration }} ⚡</span>
{% elif row.total_minutes >= worst_mins - 5 and trips | length > 1 %}
<span style="color:#c53030" title="Slowest option">{{ 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) }}</span>
{% endif %}
</td>
{% else %}
<td style="padding:0.6rem 0.8rem;font-weight:600">&mdash;</td>
<td style="padding:0.6rem 0.8rem">&mdash;</td>
<td class="col-transfer" style="padding:0.6rem 0.8rem">&mdash;</td>
<td style="padding:0.6rem 0.8rem">Unavailable</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">&ndash;</span>
{% endif %}
</td>
<td style="padding:0.6rem 0.8rem;font-weight:600">
<span title="No same-day Bristol connection" style="color:#a0aec0">Unavailable from Bristol</span>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<p style="margin-top:1rem;font-size:0.82rem;color:#718096">
Paddington &rarr; St&nbsp;Pancras connection: {{ min_connection }}&ndash;{{ max_connection }}&nbsp;min.
GWR walk-on single prices for Bristol Temple Meads&nbsp;&rarr;&nbsp;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.
&nbsp;&middot;&nbsp;
<a href="{{ rtt_bristol_url }}" target="_blank" rel="noopener">Bristol 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" 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&nbsp;+&nbsp;Eurostar combination has at least a {{ min_connection }}-minute connection at Paddington/St&nbsp;Pancras.
{% endif %}
</p>
</div>
{% endif %}
{% endblock %}