Initial commit.

This commit is contained in:
Edward Betts 2026-03-30 19:34:46 +01:00
commit a8e0bd39e5
16 changed files with 981 additions and 0 deletions

61
templates/base.html Normal file
View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bristol to Europe via Eurostar</title>
<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: 960px;
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;
}
a { color: #00539f; }
</style>
</head>
<body>
<header>
<h1>Bristol to Europe via Eurostar</h1>
<p>GWR to Paddington &rarr; St Pancras &rarr; Eurostar</p>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>

43
templates/index.html Normal file
View file

@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block content %}
<div class="card">
<h2 style="margin-top:0">Plan your journey</h2>
<form id="journey-form">
<div style="margin-bottom:1.2rem">
<label for="destination" style="display:block;font-weight:600;margin-bottom:0.4rem">
Eurostar destination
</label>
<select id="destination" name="destination" required
style="width:100%;padding:0.6rem 0.8rem;font-size:1rem;border:1px solid #cbd5e0;border-radius:4px">
<option value="" disabled selected>Select destination&hellip;</option>
{% for slug, name in destinations.items() %}
<option value="{{ slug }}">{{ name }}</option>
{% endfor %}
</select>
</div>
<div style="margin-bottom:1.5rem">
<label for="travel_date" style="display:block;font-weight:600;margin-bottom:0.4rem">
Travel date
</label>
<input type="date" id="travel_date" name="travel_date" required
min="{{ today }}" value="{{ today }}"
style="width:100%;padding:0.6rem 0.8rem;font-size:1rem;border:1px solid #cbd5e0;border-radius:4px">
</div>
<button type="submit"
style="background:#00539f;color:#fff;border:none;padding:0.75rem 2rem;
font-size:1rem;font-weight:600;border-radius:4px;cursor:pointer">
Search journeys
</button>
</form>
</div>
<script>
document.getElementById('journey-form').addEventListener('submit', function(e) {
e.preventDefault();
const slug = this.querySelector('[name="destination"]').value;
const date = this.querySelector('[name="travel_date"]').value;
if (slug && date) window.location.href = '/results/' + slug + '/' + date;
});
</script>
{% endblock %}

92
templates/results.html Normal file
View file

@ -0,0 +1,92 @@
{% extends "base.html" %}
{% block content %}
<p style="margin-bottom:1rem">
<a href="/">&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="/results/{{ slug }}/{{ prev_date }}"
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="/results/{{ slug }}/{{ next_date }}"
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>
<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 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 %}
<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">Depart Bristol</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Arrive Paddington</th>
<th 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;white-space:nowrap">Arrive {{ destination }}</th>
<th style="padding:0.6rem 0.8rem;white-space:nowrap">Total</th>
</tr>
</thead>
<tbody>
{% for trip in trips %}
<tr style="border-bottom:1px solid #e2e8f0{% if loop.index is odd %};background:#f7fafc{% endif %}">
<td style="padding:0.6rem 0.8rem;font-weight:600">{{ trip.depart_bristol }}</td>
<td style="padding:0.6rem 0.8rem">
{{ trip.arrive_paddington }}
<span style="font-size:0.8rem;color:#718096">({{ trip.gwr_duration }})</span>
</td>
<td style="padding:0.6rem 0.8rem;color:#4a5568">
{{ trip.connection_duration }}
</td>
<td style="padding:0.6rem 0.8rem;font-weight:600">{{ trip.depart_st_pancras }}</td>
<td style="padding:0.6rem 0.8rem">{{ trip.arrive_destination }}</td>
<td style="padding:0.6rem 0.8rem;font-weight:600;color:#00539f">{{ trip.total_duration }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<p style="margin-top:1rem;font-size:0.82rem;color:#718096">
Paddington &rarr; St&nbsp;Pancras connection: 75&nbsp;min minimum, 2h&nbsp;20m maximum.
Eurostar times are from the general timetable and may vary; always check
<a href="https://www.eurostar.com" target="_blank" rel="noopener">eurostar.com</a> to book.
</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 allows an 80-minute connection at Paddington/St&nbsp;Pancras.
{% endif %}
</p>
</div>
{% endif %}
{% endblock %}