night-trains-in-europ / prompts.txt
ArthurZ's picture
ArthurZ HF Staff
<!DOCTYPE html>
32f3fa8 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Europe Night Trains (10h+)</title>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<style>
:root { --bg:#0b0f14; --panel:#10161f; --muted:#9fb0c3; --accent:#56b0ff; }
* { box-sizing: border-box; }
html, body { height:100%; margin:0; background:var(--bg); color:#e6edf3; font:14px system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, "Apple Color Emoji","Segoe UI Emoji"; }
/* Ensure the map actually gets a concrete height via the grid parent */
#app { display:grid; grid-template-columns: 320px 1fr; grid-template-rows: 56px 1fr; height:100vh; min-height:0; }
header { grid-column:1 / span 2; display:flex; align-items:center; gap:.75rem; padding:12px 16px; background:var(--panel); border-bottom:1px solid #1b2532; }
header h1 { font-size:16px; margin:0; font-weight:600; letter-spacing:.2px; }
header .sub { color:var(--muted); font-weight:400; }
#sidebar { background:var(--panel); border-right:1px solid #1b2532; overflow:auto; min-height:0; }
#map { width:100%; height:100%; min-height:0; }
.group { padding:10px 12px; border-bottom:1px solid #192330; }
.group h3 { margin:0 0 8px 0; font-size:12px; color:#87a3bd; text-transform:uppercase; letter-spacing:.12em; }
.route { display:flex; align-items:center; gap:.5rem; padding:8px; border-radius:10px; cursor:pointer; transition:opacity .15s ease, background .15s ease; }
.route:hover { background:#0f1722; }
.route[aria-pressed="false"] { opacity:0.45; }
.dot { width:10px; height:10px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 2px rgba(86,176,255,.2); flex:0 0 10px; }
.citypair { display:flex; flex-direction:column; line-height:1.15; }
.citypair strong { font-size:13px; }
.meta { font-size:12px; color:var(--muted); }
.controls { display:flex; gap:.5rem; padding:8px 12px; position:sticky; top:0; background:linear-gradient(var(--panel), var(--panel)); border-bottom:1px solid #1b2532; z-index:5; }
.btn { padding:6px 10px; border-radius:10px; background:#0f1722; border:1px solid #1b2532; color:#cfe3f6; cursor:pointer; }
.btn:hover { background:#122033; }
.legend { position:absolute; right:12px; bottom:12px; background:var(--panel); border:1px solid #1b2532; padding:8px 10px; border-radius:12px; color:#cfe3f6; font-size:12px; }
.legend .swatch { display:inline-block; width:12px; height:3px; background:var(--accent); margin:0 6px 0 0; vertical-align:middle; border-radius:2px; }
.leaflet-container { background:#0a0e13; }
a, .leaflet-popup-content a { color:#9bd1ff; }
@media (max-width: 880px) {
#app { grid-template-columns: 1fr; grid-template-rows: 56px 220px 1fr; }
#sidebar { grid-row: 2; border-right: none; border-bottom:1px solid #1b2532; }
#map { grid-row: 3; }
}
</style>
</head>
<body>
<div id="app">
<header>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18" stroke="#56b0ff" stroke-width="1.6" stroke-linecap="round"/></svg>
<h1>Europe Night Trains <span class="sub">(10h+ start → terminus)</span></h1>
</header>
<aside id="sidebar" aria-label="Routes sidebar">
<div class="controls">
<button class="btn" id="showAll" type="button" title="Show all routes (S)">Show all</button>
<button class="btn" id="hideAll" type="button" title="Hide all routes (H)">Hide all</button>
<button class="btn" id="fitAll" type="button" title="Fit to Europe (F)">Fit to Europe</button>
</div>
<div class="group" id="list"></div>
</aside>
<main id="map" aria-label="Map of Europe with night train routes"></main>
</div>
<div class="legend" role="note"><span class="swatch"></span> Night train route (start → terminus)</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script>
// --- City coordinates (approximate) ---
const CITIES = {
Amsterdam:[52.379, 4.9],
Vienna:[48.208, 16.373],
Innsbruck:[47.269, 11.404],
Zürich:[47.376, 8.541],
Brussels:[50.847, 4.357],
Berlin:[52.52, 13.405],
Prague:[50.075, 14.437],
Paris:[48.857, 2.351],
Nice:[43.703, 7.266],
"Latour-de-Carol/Enveitg":[42.453, 1.918],
Briançon:[44.897, 6.643],
Stockholm:[59.33, 18.06],
Narvik:[68.438, 17.427],
Helsinki:[60.171, 24.941],
Rovaniemi:[66.503, 25.728],
Kolari:[67.35, 23.78],
Hamburg:[53.55, 9.993],
Rome:[41.902, 12.496],
Munich:[48.137, 11.575],
Zagreb:[45.815, 15.981],
Budapest:[47.497, 19.04],
Bucharest:[44.426, 26.102],
London:[51.507, -0.128],
Inverness:[57.477, -4.224],
"Fort William":[56.82, -5.105]
};
// --- Routes ≥10h (start → terminus) ---
const ROUTES = [
{from:"Amsterdam", to:"Vienna", op:"Nightjet"},
{from:"Amsterdam", to:"Innsbruck", op:"Nightjet"},
{from:"Amsterdam", to:"Zürich", op:"Nightjet"},
{from:"Brussels", to:"Berlin", op:"European Sleeper"},
{from:"Brussels", to:"Prague", op:"European Sleeper"},
{from:"Paris", to:"Berlin", op:"Nightjet"},
{from:"Paris", to:"Nice", op:"Intercités de Nuit"},
{from:"Paris", to:"Latour-de-Carol/Enveitg", op:"Intercités de Nuit"},
{from:"Paris", to:"Briançon", op:"Intercités de Nuit"},
{from:"Stockholm", to:"Berlin", op:"SJ EuroNight"},
{from:"Stockholm", to:"Narvik", op:"Vy/SJ"},
{from:"Helsinki", to:"Rovaniemi", op:"VR"},
{from:"Helsinki", to:"Kolari", op:"VR"},
{from:"Hamburg", to:"Vienna", op:"Nightjet"},
{from:"Hamburg", to:"Innsbruck", op:"Nightjet"},
{from:"Vienna", to:"Rome", op:"Nightjet"},
{from:"Munich", to:"Rome", op:"Nightjet"},
{from:"Zürich", to:"Prague", op:"EN Canopus"},
{from:"Zürich", to:"Zagreb", op:"EN Lisinski"},
{from:"Budapest", to:"Bucharest", op:"EN Dacia"},
{from:"Vienna", to:"Bucharest", op:"EN Dacia"},
{from:"London", to:"Inverness", op:"Caledonian Sleeper"},
{from:"London", to:"Fort William", op:"Caledonian Sleeper"}
];
// --- Map init ---
const map = L.map('map', { zoomControl: true, scrollWheelZoom: true, preferCanvas:true }).setView([51.2, 10], 5);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 10,
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map);
// --- Draw cities ---
const cityMarkers = {};
Object.entries(CITIES).forEach(([name, [lat, lng]]) => {
const m = L.circleMarker([lat, lng], { radius:5, weight:1, color:'#56b0ff', fillColor:'#56b0ff', fillOpacity:0.85 })
.bindTooltip(name, { permanent:false, direction:'top', offset:[0,-2]})
.addTo(map);
cityMarkers[name] = m;
});
// --- Route layers + rows registry ---
const registry = []; // { layer, data, row }
const makePopup = (r) => `<strong>${r.from} → ${r.to}</strong><br><span style="color:#9fb0c3">${r.op}</span>`;
ROUTES.forEach((r) => {
const a = CITIES[r.from], b = CITIES[r.to];
if(!a || !b) return;
const layer = L.polyline([a, b], { weight:3, opacity:0.9, color:'#56b0ff', lineCap:'round' })
.bindPopup(makePopup(r))
.addTo(map);
registry.push({ layer, data: r, row: null });
});
function fitAllVisible(){
const visible = registry.filter(o => map.hasLayer(o.layer));
if(visible.length === 0){ map.setView([51.2,10], 4); return; }
const group = L.featureGroup(visible.map(o => o.layer));
map.fitBounds(group.getBounds().pad(0.15));
}
fitAllVisible();
// --- Sidebar list ---
const list = document.getElementById('list');
function addRouteRow(entry){
const { data, layer } = entry;
const row = document.createElement('button');
row.type = 'button';
row.className = 'route';
row.setAttribute('aria-pressed', 'true');
row.innerHTML = `<span class="dot" aria-hidden="true"></span><div class="citypair"><strong>${data.from} → ${data.to}</strong><span class="meta">${data.op}</span></div>`;
row.addEventListener('click', () => {
const isVisible = map.hasLayer(layer);
if(isVisible){
layer.closePopup();
map.removeLayer(layer);
row.setAttribute('aria-pressed','false');
}else{
layer.addTo(map);
row.setAttribute('aria-pressed','true');
const bounds = L.latLngBounds([CITIES[data.from], CITIES[data.to]]).pad(0.25);
map.fitBounds(bounds);
layer.openPopup();
}
});
list.appendChild(row);
entry.row = row;
}
registry.forEach(addRouteRow);
// --- Buttons ---
document.getElementById('showAll').onclick = () => {
registry.forEach(o => { if(!map.hasLayer(o.layer)) o.layer.addTo(map); o.row?.setAttribute('aria-pressed','true'); });
fitAllVisible();
};
document.getElementById('hideAll').onclick = () => {
registry.forEach(o => { if(map.hasLayer(o.layer)) { o.layer.closePopup(); map.removeLayer(o.layer); } o.row?.setAttribute('aria-pressed','false'); });
};
document.getElementById('fitAll').onclick = fitAllVisible;
// --- Keyboard UX ---
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
if(key === 'f') fitAllVisible();
if(key === 'h') document.getElementById('hideAll').click();
if(key === 's') document.getElementById('showAll').click();
});
// Resize observer to keep map sized correctly if layout changes
new ResizeObserver(() => { map.invalidateSize(); }).observe(document.getElementById('app'));
</script>
</body>
</html>
make it fancy and work