paddington-eurostar/templates/base.html
Edward Betts 1bc7631863 Five UI and data features for return journeys and results page
- Replace native date inputs with always-open custom calendar; return
  journeys show two months side-by-side with Airbnb-style range selection
- Add min-connection filter (30/40/50/60 min) for the inbound leg of
  return journeys, separate from the outbound connection filter
- Fix total journey time: naive datetime subtraction across CET/BST was
  1 h too long outbound and 1 h too short inbound
- Filter inbound circle line suggestions when connection ≥ 40 min: only
  show services arriving ≥ 5 min before GWR departure at Paddington
- Add Std / SP labels to Eurostar fare lines so users can distinguish
  Standard from Standard Premier
- Row selection with a fixed summary bar showing NR + Eurostar + circle
  totals; selection is preserved in the URL
- Load walk-on fares sequentially, outbound section first
- Mobile: card-grid table layout, hide headcode/platform on small screens

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 14:58:32 +01:00

431 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}South West England to Europe via Eurostar{% endblock %}</title>
<meta property="og:type" content="website">
<meta property="og:site_name" content="South West England to Europe via Eurostar">
<meta property="og:title" content="{% block og_title %}South West England to Europe via Eurostar{% endblock %}">
<meta property="og:description" content="{% block og_description %}Find South West England Temple Meads to Europe itineraries via Paddington, St Pancras, and Eurostar.{% endblock %}">
<meta property="og:url" content="{{ request.url }}">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{% block twitter_title %}South West England to Europe via Eurostar{% endblock %}">
<meta name="twitter:description" content="{% block twitter_description %}Find South West England Temple Meads to Europe itineraries via Paddington, St Pancras, and Eurostar.{% endblock %}">
<link rel="icon" href="{{ url_for('static', filename='favicon.svg') }}" type="image/svg+xml">
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f0f4f8;
color: #1a202c;
margin: 0;
padding: 0;
}
header {
background: #00539f;
color: #fff;
padding: 1rem 2rem;
}
header h1 {
margin: 0;
font-size: 1.4rem;
font-weight: 600;
}
header p {
margin: 0.2rem 0 0;
font-size: 0.85rem;
opacity: 0.85;
}
main {
max-width: 1100px;
margin: 2rem auto;
padding: 0 1rem;
}
.card {
background: #fff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0,0,0,0.12);
padding: 2rem;
}
.field-label {
display: block;
font-weight: 600;
margin-bottom: 0.4rem;
}
.form-control {
width: 100%;
padding: 0.6rem 0.8rem;
font-size: 1rem;
border: 1px solid #cbd5e0;
border-radius: 4px;
}
.fixed-station {
padding: 0.85rem 1rem;
border: 1px solid #cbd5e0;
border-radius: 10px;
background: linear-gradient(180deg, #f8fbff 0%, #eef4fa 100%);
}
.fixed-station strong {
display: block;
color: #0f172a;
font-size: 1rem;
margin-bottom: 0.2rem;
}
.fixed-station span {
display: block;
color: #475569;
font-size: 0.9rem;
}
.destination-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.75rem;
}
.destination-option {
position: relative;
}
.destination-option input {
position: absolute;
opacity: 0;
inset: 0;
margin: 0;
cursor: pointer;
}
.destination-option label {
display: block;
min-height: 100%;
padding: 0.95rem 1rem;
border: 1px solid #cbd5e0;
border-radius: 10px;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
cursor: pointer;
transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.destination-option label strong {
display: block;
color: #0f172a;
font-size: 1rem;
margin-bottom: 0.2rem;
}
.destination-option label span {
display: block;
color: #475569;
font-size: 0.9rem;
}
.destination-option input:hover + label {
border-color: #7aa7d9;
box-shadow: 0 4px 12px rgba(0, 83, 159, 0.08);
}
.destination-option input:focus-visible + label {
border-color: #00539f;
box-shadow: 0 0 0 3px rgba(0, 83, 159, 0.2);
}
.destination-option input:checked + label {
border-color: #00539f;
background: linear-gradient(180deg, #eef6ff 0%, #dcecff 100%);
box-shadow: 0 8px 20px rgba(0, 83, 159, 0.16);
transform: translateY(-1px);
}
.chip-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.chip-link,
.chip-current {
display: inline-block;
padding: 0.35rem 0.8rem;
border: 1px solid #cbd5e0;
border-radius: 999px;
font-size: 0.9rem;
text-decoration: none;
}
.chip-link {
color: #00539f;
background: #fff;
}
.chip-link:hover {
border-color: #7aa7d9;
background: #f8fbff;
}
.chip-current {
color: #fff;
background: #00539f;
border-color: #00539f;
font-weight: 600;
}
@media (max-width: 640px) {
.card { padding: 1.25rem; }
/* Convert results table to a 2-column card layout per row */
.results-table, .results-table tbody { display: block; }
.results-table thead { display: none; }
.results-table tr {
display: grid;
grid-template-columns: 1fr 1fr;
border-bottom: 2px solid #e2e8f0;
padding: 0.1rem 0;
}
.results-table td { padding: 0.35rem 0.45rem; font-size: 0.8rem; border-bottom: none; }
/* First journey leg (NR outbound / Eurostar inbound) */
.results-table td:nth-child(1) { grid-column: 1; grid-row: 1; }
/* Transfer column: hidden */
.col-transfer { display: none !important; }
/* Second journey leg */
.results-table td:nth-child(3) { grid-column: 2; grid-row: 1; }
/* Total: spans both columns, right-aligned */
.results-table td:nth-child(4) {
grid-column: 1 / -1; grid-row: 2;
text-align: right;
border-top: 1px solid #e2e8f0;
padding: 0.25rem 0.45rem 0.3rem;
}
/* Hide non-essential detail on mobile */
.mobile-hide { display: none !important; }
.fare-seats { display: none !important; }
/* Show connection time hint */
.mobile-conn { display: block !important; }
/* Flow arrow: hide on mobile */
.results-table thead th.flow-step::after { display: none; }
.results-table thead th.flow-step { padding-right: 0; }
/* Selection bar: smaller on mobile */
#selection-bar { padding: 0.5rem 0.75rem; font-size: 0.8rem; }
.sel-totals { gap: 0.75rem; }
}
a { color: #00539f; }
/* Card helpers */
.card > h2:first-child { margin-top: 0; }
/* Form groups */
.form-group { margin-bottom: 1.2rem; }
.form-group-lg { margin-bottom: 1.5rem; }
#return-date-group:has(input:disabled) { cursor: pointer; }
/* Buttons */
.btn-primary {
background: #00539f;
color: #fff;
border: none;
padding: 0.75rem 2rem;
font-size: 1rem;
font-weight: 600;
border-radius: 4px;
cursor: pointer;
}
.btn-nav {
padding: 0.3rem 0.75rem;
border: 1px solid #cbd5e0;
border-radius: 4px;
text-decoration: none;
color: #00539f;
font-size: 0.9rem;
}
/* Inline select */
.select-inline {
padding: 0.3rem 0.6rem;
font-size: 0.9rem;
border: 1px solid #cbd5e0;
border-radius: 4px;
}
/* Alert boxes */
.alert {
margin-top: 1rem;
padding: 0.75rem 1rem;
border-radius: 4px;
}
.alert-error {
background: #fff5f5;
border: 1px solid #fc8181;
color: #c53030;
}
.alert-warning {
background: #fffbeb;
border: 1px solid #f6e05e;
color: #744210;
}
/* Results page layout */
.back-link { margin-bottom: 1rem; }
.date-nav { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; }
.date-nav-label { min-width: 6rem; font-weight: 600; font-size: 0.9rem; }
.switcher-section { margin: 0.9rem 0 1rem; }
.section-label { font-size: 0.9rem; font-weight: 600; margin-bottom: 0.45rem; }
.filter-row { margin-top: 0.75rem; display: flex; gap: 1.5rem; align-items: center; flex-wrap: wrap; }
.filter-label { font-size: 0.9rem; font-weight: 600; margin-right: 0.5rem; }
.btn-secondary {
padding: 0.3rem 0.75rem;
border: 1px solid #00539f;
border-radius: 4px;
color: #00539f;
background: #fff;
cursor: pointer;
font-size: 0.9rem;
}
.btn-secondary:hover { background: #f8fbff; }
.btn-secondary:disabled { opacity: 0.5; cursor: default; }
.card-meta { color: #4a5568; margin: 0; }
.footnote { margin-top: 1rem; font-size: 0.82rem; color: #718096; }
/* Results table */
.results-table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
.results-table th,
.results-table td { padding: 0.6rem 0.8rem; }
.results-table thead tr { border-bottom: 2px solid #e2e8f0; text-align: left; }
.results-table th { position: sticky; top: 0; background: #fff; z-index: 1; }
.results-table tbody tr { border-bottom: 1px solid #e2e8f0; }
.row-fast { background: #f0fff4; }
.row-slow { background: #fff5f5; }
.row-alt { background: #f7fafc; }
.row-unreachable { background: #f7fafc; color: #a0aec0; }
.row-selected { background: #ebf8ff !important; }
tr.row-selectable { cursor: pointer; }
tr.row-selectable:hover:not(.row-selected) { filter: brightness(0.97); }
/* Journey flow arrow between column headers */
.results-table thead th.flow-step { position: relative; padding-right: 1.4rem; }
.results-table thead th.flow-step::after {
content: '';
position: absolute; right: 0.2rem; top: 50%; transform: translateY(-50%);
color: #cbd5e0; font-size: 1.5rem; font-weight: 300; line-height: 1;
}
/* Mobile: hidden by default, shown on mobile */
.mobile-conn { display: none; }
.fare-seats { display: inline; }
/* Selection summary bar */
#selection-bar {
display: none; position: fixed; bottom: 0; left: 0; right: 0;
background: #fff; border-top: 2px solid #00539f;
padding: 0.65rem 1rem;
box-shadow: 0 -2px 10px rgba(0,0,0,0.12); z-index: 200;
font-size: 0.88rem;
}
.sel-bar-inner {
max-width: 1100px; margin: 0 auto;
display: flex; justify-content: space-between; align-items: center;
flex-wrap: wrap; gap: 0.5rem;
}
.sel-totals { display: flex; gap: 1.25rem; align-items: center; flex-wrap: wrap; }
.sel-clear {
background: none; border: 1px solid #cbd5e0; border-radius: 4px;
padding: 0.2rem 0.6rem; font-size: 0.8rem; cursor: pointer; color: #718096;
}
.sel-clear:hover { background: #f0f4f8; }
/* Empty state */
.empty-state { color: #4a5568; text-align: center; padding: 3rem 2rem; }
.empty-state p { margin: 0; }
.empty-state p:first-child { font-size: 1.1rem; margin-bottom: 0.5rem; }
.empty-state p:last-child { font-size: 0.9rem; }
/* Ticket class button group */
.btn-group { display: inline-flex; border: 1px solid #cbd5e0; border-radius: 4px; overflow: hidden; vertical-align: middle; }
.btn-group-option { padding: 0.28rem 0.65rem; font-size: 0.82rem; background: #fff; border: none; border-right: 1px solid #cbd5e0; cursor: pointer; color: #374151; white-space: nowrap; }
.btn-group-option:last-child { border-right: none; }
.btn-group-option.active { background: #00539f; color: #fff; font-weight: 600; }
.btn-group-option:hover:not(.active) { background: #f0f4f8; }
/* Flash animation for total price */
@keyframes price-flash { 0%,100% { background-color: transparent; } 40% { background-color: #fef08a; } }
.price-flash { animation: price-flash 0.7s ease-out; border-radius: 3px; }
/* Loading state */
#advance-loading { font-size: 0.82rem; color: #718096; margin-left: 0.5rem; }
.loading-panel {
display: flex;
gap: 1rem;
align-items: flex-start;
margin-top: 1rem;
padding: 1rem;
border: 1px solid #cbd5e0;
border-radius: 6px;
background: #f8fbff;
}
.loading-panel p { margin: 0.25rem 0 0; }
.spinner {
width: 1.5rem;
height: 1.5rem;
border: 3px solid #cbd5e0;
border-top-color: #00539f;
border-radius: 50%;
flex: 0 0 auto;
animation: spin 0.8s linear infinite;
}
.spinner-inline {
width: 0.85rem;
height: 0.85rem;
border-width: 2px;
margin-right: 0.35rem;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Fare lines — show all, dim inactive */
.fare-line { display: block; line-height: 1.6; transition: opacity 0.15s; }
.fare-inactive { opacity: 0.4; }
/* Utilities */
.text-muted { color: #718096; }
.text-dimmed { color: #a0aec0; }
.text-green { color: #276749; }
.text-red { color: #c53030; }
.text-blue { color: #00539f; }
.text-sm { font-size: 0.85rem; }
.text-xs { font-size: 0.75rem; }
.nowrap { white-space: nowrap; }
.font-bold { font-weight: 600; }
.font-normal { font-weight: 400; }
</style>
</head>
<body>
<header>
<h1>South West England to Europe via Eurostar</h1>
<p>GWR to Paddington &rarr; St Pancras &rarr; Eurostar
&nbsp;&middot;&nbsp;
<a href="https://git.4angle.com/edward/bristol-eurostar"
style="color:rgba(255,255,255,0.75);font-size:0.8rem"
target="_blank" rel="noopener">source</a>
</p>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>