- 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>
431 lines
13 KiB
HTML
431 lines
13 KiB
HTML
<!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 → St Pancras → Eurostar
|
||
·
|
||
<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>
|