42 Commits

Author SHA1 Message Date
1393f458db Adjust tunnel component to offer mid-tunnel section. 2026-02-13 22:30:48 +00:00
3f1ad2cf39 Ensure 'Kemble Tunnel' name is present 2026-02-13 22:28:02 +00:00
2016497c9e Route 0230: Draft covers as far as Kemble from Swindon 2026-02-13 22:27:34 +00:00
454561e481 Add additional meta tags 2026-02-12 20:49:43 +00:00
488217923f Update favicon 2026-02-12 20:12:36 +00:00
6232277de1 Re-introduce visible SVG overflow. Add electrification type to electrificationChange component.
Begin route 0230 (Swindon Jn - Standish Jn
2026-02-12 16:28:13 +00:00
cff62fa343 Remove 'checked' values from incomplete maps 2026-02-11 21:03:55 +00:00
f359938d78 Standardize junction abbreviation to Jn 2026-02-11 21:02:14 +00:00
7e68192312 Standardise & styles and improve performance on small displays.
Add inter-route linking from Junctions.
2026-02-11 20:58:01 +00:00
e94b0e811a Adjust sizes on manifest icons 2026-02-11 19:17:43 +00:00
04f6a28100 Provide new 'short_name' OB Maps to better fit on Android app drawer.
Update theme colour to accent colour to provide thematic separation of notification bar and header bar when running full screen on android.
2026-02-11 19:03:49 +00:00
54e3483a39 Include tabler icons and switch buttons to use icons 2026-02-11 19:01:24 +00:00
117d1f752e Switch to new resized logo with text 2026-02-11 18:37:10 +00:00
1207edf12b Add placeholders for next routes 2026-02-11 10:57:01 +00:00
b3d9eb6f33 Move to 'updated' and 'checked' dates, rather than 'created' 'checked & updated' 2026-02-11 10:52:15 +00:00
869a7296e8 Adjust margins/padding 2026-02-10 22:13:15 +00:00
7777671cbb Run prettify 2026-02-10 22:11:17 +00:00
353fd07b92 Fix: Lint warnings about non-interavtive content and onclick handlers. 2026-02-10 22:10:07 +00:00
a036b0ba63 Add apple-touch-icon <link> 2026-02-10 22:06:39 +00:00
0b53f8ed81 Add base for Wooton Basset Jn - Stoke Gifford Jn 2026-02-10 22:06:26 +00:00
ee92b52552 Add icons to webmanifest 2026-02-10 22:06:04 +00:00
4cc6140d86 Add typing 2026-02-10 22:05:49 +00:00
a458cc5e76 Ensure types are configured 2026-02-10 22:05:37 +00:00
cb9f826943 Adjust filtering to enable filtering by map contents 2026-02-10 22:05:25 +00:00
fa1da3686f Adjust 'home link' to use logo 2026-02-10 22:05:09 +00:00
d5270dc889 Add PIN logo SVG source 2026-02-10 20:55:07 +00:00
6366cb601b Add manifest icons. 2026-02-10 20:55:07 +00:00
fe1061875b Add new logo 2026-02-10 20:55:07 +00:00
699f2b760d Switch to Svelte resolve API 2026-02-10 20:54:31 +00:00
c8790e2aae Switch to new logo for favicon & provide ico fallback. 2026-02-10 20:54:30 +00:00
3fbffc7589 Adjust map boundaries.
Now: PAD-RDG, RDG-BRI

Was: PAD-Reading West Jn, Reading West Jn - BRI
2026-02-09 22:55:49 +00:00
84312abc75 Fix global CSS Import 2026-02-09 22:35:44 +00:00
c6fa8794e0 Add id field to RouteRow components 2026-02-09 22:24:42 +00:00
8e65fd396e Add manifest generator 2026-02-09 22:24:29 +00:00
8a05d8358f Update route information and enable direct linking. 2026-02-09 22:24:22 +00:00
a8c3117259 Add webmanifest and improve Vite build 2026-02-09 22:23:20 +00:00
2b2095604c Use standardised fonts 2026-02-09 22:23:01 +00:00
08f3d30e44 Update favicon 2026-02-07 23:11:00 +00:00
39511dd428 Improve 'branding' and mobile layout 2026-02-07 20:50:52 +00:00
cfcf651e41 Add favicon 2026-02-07 20:50:39 +00:00
cfa1d44dbc Run prettier 2026-02-07 00:38:56 +00:00
ca5e78e2b1 Add OwlBoard styling 2026-02-07 00:31:37 +00:00
35 changed files with 2801 additions and 1534 deletions

View File

@@ -6,4 +6,4 @@ bun.lock
bun.lockb
# Miscellaneous
/static/
#/static/

29
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@sveltejs/adapter-node": "^5.5.2",
"@sveltejs/kit": "^2.50.1",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tabler/icons-svelte": "^3.36.1",
"@types/node": "^22",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
@@ -1344,6 +1345,34 @@
"vite": "^6.3.0 || ^7.0.0"
}
},
"node_modules/@tabler/icons": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.36.1.tgz",
"integrity": "sha512-f4Jg3Fof/Vru5ioix/UO4GX+sdDsF9wQo47FbtvG+utIYYVQ/QVAC0QYgcBbAjQGfbdOh2CCf0BgiFOF9Ixtjw==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
}
},
"node_modules/@tabler/icons-svelte": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/@tabler/icons-svelte/-/icons-svelte-3.36.1.tgz",
"integrity": "sha512-f48RDkXJr7dMbbWHho81rR91QiPleHTlOwJUM5uFhTqo7dXH4mNZxJo3tksQNmlIauh7PqoS3i+RY7YlZxg5yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tabler/icons": ""
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/codecalm"
},
"peerDependencies": {
"svelte": ">=3 <6 || >=5.0.0-next.0"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",

View File

@@ -19,6 +19,7 @@
"@sveltejs/adapter-node": "^5.5.2",
"@sveltejs/kit": "^2.50.1",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tabler/icons-svelte": "^3.36.1",
"@types/node": "^22",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",

View File

@@ -6,6 +6,8 @@ const inputDir = './static/mapFiles/yaml';
const outputDir = './static/mapFiles/json';
const indexFile = './static/map-index.json';
const noiseRegex = /\s+(single line|junction|jn|junc|jct|gf|north|south|east|west)\.?$/i;
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
const mapList = [];
@@ -18,12 +20,37 @@ fs.readdirSync(inputDir).forEach((file) => {
const fileName = file.replace('.yaml', '.json');
fs.writeFileSync(path.join(outputDir, fileName), JSON.stringify(content));
const contentSet = new Set();
if (Array.isArray(content.routeDetail)) {
content.routeDetail.forEach((item) => {
if ((item.type === 'junction' || item.type === 'station') && item.name) {
let cleanName = item.name;
// Run the replacement in a loop or twice to catch nested noise
// e.g., "Reading West Junction"
// Pass 1: "Reading West"
// Pass 2: "Reading"
let previousName;
do {
previousName = cleanName;
cleanName = cleanName.replace(noiseRegex, '').trim();
} while (cleanName !== previousName);
if (cleanName) {
contentSet.add(cleanName);
}
}
});
}
mapList.push({
routeId: content.routeId || null,
routeStart: content.routeStart || null,
routeEnd: content.routeEnd || null,
created: content.created || null,
checked: content.checked || null
updated: content.updated || null,
checked: content.checked || null,
contents: Array.from(contentSet)
});
}
});

View File

@@ -6,6 +6,14 @@
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
/>
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="title" content="OwlBoard Maps | Railway route schematics to assist with learning & refreshing routes" />
<meta name="description" content="Schematic route diagrams showing stations, junctions, crossings, bridges and more" />
<meta name="theme-color" content="#4fd1d1" />
<link rel="canonical" href="https://maps.owlboard.info" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://maps.owlboard.info" />
<title>OwlBoard Maps</title>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

21
src/lib/assets/global.css Normal file
View File

@@ -0,0 +1,21 @@
/* FONTS */
@font-face {
font-family: 'urwgothic';
src:
url('/fonts/urwgothic/urwgothic.woff2') format('woff2'),
url('/font/urwgothic/urwgothic.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'urwgothic';
src:
url('/fonts/urwgothic/urwgothicDemi.woff2') format('woff2'),
url('/font/urwgothic/urwgothicDemi.woff') format('woff');
font-weight: 900;
font-style: normal;
}
[id] {
scroll-margin-top: 100px;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" viewBox="0 0 562.09 562.09"><defs><radialGradient xlink:href="#a" id="b" cx="256" cy="256" r="254.04" fx="256" fy="256" gradientTransform="translate(27 27)" gradientUnits="userSpaceOnUse"/><linearGradient id="a"><stop offset="0" stop-color="#2b343c"/><stop offset="1" stop-color="#404c55"/></linearGradient></defs><path fill="url(#b)" d="M694.32 283a411.32 393.92 0 0 1-410.68 393.92 411.32 393.92 0 0 1-411.96-392.69 411.32 393.92 0 0 1 409.4-395.14A411.32 393.92 0 0 1 694.3 280.54" transform="translate(-1.96 -1.96)"/><path fill="#4fd1d1" d="M281.04 88.47c-70.95 0-128.48 57.53-128.48 128.48 0 14.11 2.33 28.12 6.88 41.47l-.01-.01 7.36 17.3a124.7 124.7 0 0 0 5.25 9.1l.97 1.69 108.03 187.1 108.03-187.1.98-1.7a128.5 128.5 0 0 0 5.25-9.08l7.35-17.28v-.02a128.48 128.48 0 0 0 6.87-41.47C409.52 146 352 88.47 281.04 88.47Zm-84.01 49.27 2.52 2.38c7.74 7.29 16.97 12.23 28.03 15.02 4.64 1.17 4.62 1.17 8.1-.34 25.92-11.2 64.62-11.22 91.01-.03 7.27 3.08 25.1-4.12 36.09-14.59a30.17 30.17 0 0 1 2.43-2.18c.35 0 .48 6.13.2 9.02a49.7 49.7 0 0 1-9.29 24.82l-1.47 2.06.7.94c38.07 51.55-7.46 115.42-60.6 85-1.3-.74-2.45-1.24-2.57-1.11-.2.2-5.5 10.18-9.4 17.68-1.76 3.39-1.4 3.4-3.26-.14-4.77-9.12-9.38-17.7-9.5-17.7-.08 0-1.18.58-2.45 1.31-52.6 30.1-99.03-34.13-60.98-84.33.78-1.02 1.1-1.66.95-1.87-7.68-10.44-11.39-21.86-10.7-32.92zm44.85 39.23a35.62 35.62 0 0 0-15.48 3.92c-35.26 17.47-16.98 71.79 21.65 64.33a35.5 35.5 0 0 0 22.5-14.71c1.25-1.78 1.67-2.22 1.88-1.93.14.2 2.14 3.61 4.44 7.59 4.87 8.43 4.1 7.82 5.9 4.67a497.41 497.41 0 0 1 7.32-12.44c.14-.08.96.9 1.86 2.22 16.09 23.62 52.73 18.48 61.43-8.6 8.52-26.58-16.98-51.8-43.57-43.1-12.13 3.98-20.13 12.66-27.95 30.34l-.7 1.56-1.38-3.13c-8.8-20-22.71-30.89-37.9-30.72zm76.41 18.05c4.34-.12 8.8 1.43 12.33 4.95 11.83 11.83.35 31.55-16.05 27.58-8.43-2.04-13.82-10.73-12.05-19.4 1.65-8.07 8.54-12.92 15.77-13.13zm-75.7.04c8.54-.48 16.99 5.68 17.6 15.6.66 11.02-10.1 19.75-20.85 16.88-12.5-3.33-16.72-18.35-7.72-27.46a16.6 16.6 0 0 1 10.97-5.02zm6.22 4.93c-1.1.07-2.17.56-3 1.54-3.25 3.86 1.28 9.24 5.7 6.74a4.52 4.52 0 0 0 2.21-3.84c0-2.82-2.5-4.61-4.9-4.44zm63.97.06c-3.15.2-5.02 3.3-3.63 6.36 1.05 2.31 4.26 3.26 6.3 1.87 3.77-2.56 2.44-7.95-2.03-8.23a5.19 5.19 0 0 0-.64 0z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" viewBox="0 0 508.09 508.09"><defs><radialGradient xlink:href="#a" id="b" cx="256" cy="256" r="254.04" fx="256" fy="256" gradientUnits="userSpaceOnUse"/><linearGradient id="a"><stop offset="0" stop-color="#2b343c"/><stop offset="1" stop-color="#404c55"/></linearGradient></defs><circle cx="256" cy="256" r="254.04" fill="url(#b)" transform="translate(-1.96 -1.96)"/><path fill="#4fd1d1" d="M254.04 61.47c-70.95 0-128.48 57.53-128.48 128.48 0 14.11 2.33 28.12 6.88 41.47l-.01-.01 7.36 17.3a124.7 124.7 0 0 0 5.25 9.1l.97 1.69 108.03 187.1 108.03-187.1.98-1.7a128.5 128.5 0 0 0 5.25-9.08l7.35-17.28v-.02a128.48 128.48 0 0 0 6.87-41.47C382.52 119 325 61.47 254.04 61.47Zm-84.01 49.27 2.52 2.38c7.74 7.29 16.97 12.23 28.03 15.02 4.64 1.17 4.62 1.17 8.1-.34 25.92-11.2 64.62-11.22 91.01-.03 7.27 3.08 25.1-4.12 36.09-14.59a30.17 30.17 0 0 1 2.43-2.18c.35 0 .48 6.13.2 9.02a49.7 49.7 0 0 1-9.29 24.82l-1.47 2.06.7.94c38.07 51.55-7.46 115.42-60.6 85-1.3-.74-2.45-1.24-2.57-1.11-.2.2-5.5 10.18-9.4 17.68-1.76 3.39-1.4 3.4-3.26-.14-4.77-9.12-9.38-17.7-9.5-17.7-.08 0-1.18.58-2.45 1.31-52.6 30.1-99.03-34.13-60.98-84.33.78-1.02 1.1-1.66.95-1.87-7.68-10.44-11.39-21.86-10.7-32.92zm44.85 39.23a35.62 35.62 0 0 0-15.48 3.92c-35.26 17.47-16.98 71.79 21.65 64.33a35.5 35.5 0 0 0 22.5-14.71c1.25-1.78 1.67-2.22 1.88-1.93.14.2 2.14 3.61 4.44 7.59 4.87 8.43 4.1 7.82 5.9 4.67a497.41 497.41 0 0 1 7.32-12.44c.14-.08.96.9 1.86 2.22 16.09 23.62 52.73 18.48 61.43-8.6 8.52-26.58-16.98-51.8-43.57-43.1-12.13 3.98-20.13 12.66-27.95 30.34l-.7 1.56-1.38-3.13c-8.8-20-22.71-30.89-37.9-30.72zm76.41 18.05c4.34-.12 8.8 1.43 12.33 4.95 11.83 11.83.35 31.55-16.05 27.58-8.43-2.04-13.82-10.73-12.05-19.4 1.65-8.07 8.54-12.92 15.77-13.13zm-75.7.04c8.54-.48 16.99 5.68 17.6 15.6.66 11.02-10.1 19.75-20.85 16.88-12.5-3.33-16.72-18.35-7.72-27.46a16.6 16.6 0 0 1 10.97-5.02zm6.22 4.93c-1.1.07-2.17.56-3 1.54-3.25 3.86 1.28 9.24 5.7 6.74a4.52 4.52 0 0 0 2.21-3.84c0-2.82-2.5-4.61-4.9-4.44zm63.97.06c-3.15.2-5.02 3.3-3.63 6.36 1.05 2.31 4.26 3.26 6.3 1.87 3.77-2.56 2.44-7.95-2.03-8.23a5.19 5.19 0 0 0-.64 0z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,23 +1,34 @@
<script lang="ts">
import { components } from '$lib/mapRegistry';
import type { ElecType } from '$lib/railStyles';
import { IconArrowNarrowRight } from '@tabler/icons-svelte';
export let feature: any; // Raw Object
export let activeElec: string; // Active Electrification Type
type featureType = "station" | "junction" | "crossovers" | "siteof" | "bridge" | "minorBridge" | "crossover" | "crossing" | "loop" | "loops" | "signallerChange" | "electrificationChange" | "default" | "tunnel";
export let feature: {name: string; type: featureType; goto?: string; entryPoint?: string; miles: number; chains: number; description?: string}; // Raw Object
export let activeElec: ElecType; // Active Electrification Type
export let reversed: boolean = false;
$: Icon = components[feature.type] || components.default;
// Linking Logic
$: isLinkable = !!(feature.goto && feature.entryPoint);
$: href = `/map/${feature.goto}#${feature.entryPoint}`;
const slugify = (str?: string) =>
str?.toLocaleLowerCase().trim().replace(/\s+/g, '-') ?? 'unknown';
</script>
<div class="row-container">
<div class="row-container" id={slugify(feature.name)}>
<div class="mileage-col">
<span class="miles">{feature.miles + 'm' || ''}</span>
<span class="chains">{feature.chains + 'ch' || ''}</span>
</div>
<div class="icon-col">
<svelte:component this={Icon} {feature} {activeElec} {reversed} />
<svelte:component this={Icon} feature={feature as any} {activeElec} {reversed} />
</div>
<svelte:element this={isLinkable ? 'a' : 'div'} {...(isLinkable ? { href } : {})} class="link-wrapper">
<div class="label-col">
{#if feature.name}
<div class="feature-name">{feature.name}</div>
@@ -26,18 +37,31 @@
<div class="feature-desc">{feature.description}</div>
{/if}
</div>
{#if isLinkable}
<div class="link-indicator">
<IconArrowNarrowRight />
</div>
{/if}
</svelte:element>
</div>
<style>
a {
cursor: pointer;
text-decoration: none;
}
.row-container {
display: grid;
/* Balanced columns: 1fr on both sides keeps the 64px icon in the dead center */
grid-template-columns: 3.5rem 64px 1fr;
width: 100%;
height: 64px;
max-height: 64px;
align-items: center;
margin: 0;
padding: 0;
overflow: hidden;
}
.mileage-col {
@@ -47,7 +71,7 @@
padding-right: 12px;
font-family: 'Courier New', Courier, monospace;
font-size: 0.85rem;
color: #64748b; /* Adjusted slightly for contrast */
color: #64748b;
}
.miles {
@@ -59,13 +83,46 @@
font-size: 0.7rem;
}
.icon-col {
width: 64px;
height: 64px;
/* Ensure the icon itself is centered if the SVG is smaller than 64px */
display: flex;
justify-content: center;
align-items: center;
overflow: visible;
}
.link-wrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
text-decoration: none;
color: inherit;
padding: 0;
box-sizing: border-box;
}
.link-indicator {
display: flex;
align-items: center;
margin-left: 5px;
margin-right: 8px;
flex-shrink: 0;
color: #e1ebeb;
background-color: #3c6f79;
padding: 2px 2px;
border-radius: 999px;
transition: all 0.3s ease;
}
.link-wrapper:hover .link-indicator {
background-color: #404c55;
transform: rotate(-45deg);
}
.label-col {
@@ -73,20 +130,16 @@
display: flex;
flex-direction: column;
justify-content: center;
/* FIX: Allow children to manage their own wrapping */
overflow: hidden;
/* min-width: 0 is critical for flex children to allow truncation */
min-width: 0;
}
.feature-name {
font-weight: 700;
font-family: sans-serif;
color: #1e293b;
font-size: 0.8rem;
text-transform: capitalize;
/* Allow the title to wrap naturally onto multiple lines */
white-space: normal;
line-height: 1.2;
margin-bottom: 2px;
@@ -95,12 +148,11 @@
.feature-desc {
display: -webkit-box;
-webkit-box-orient: vertical;
font-family: sans-serif;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
/* Firefox Fix: Ensure white-space is normal here too */
white-space: normal;
line-height: 1.2rem;
max-height: 2.4rem;
font-size: 0.75rem;
@@ -108,21 +160,26 @@
word-break: break-word;
}
/* Tablet and Desktop scaling */
@media (max-width: 320px) {
.feature-desc {
display: none;
}
}
@media (min-width: 480px) {
.feature-name {
font-size: 1rem; /* The larger title you requested */
font-size: 1rem;
margin-bottom: 4px;
}
.feature-desc {
font-size: 0.85rem; /* Slightly larger desc to match */
font-size: 0.85rem;
line-height: 1.3rem;
max-height: 2.6rem;
}
.label-col {
padding-left: 24px; /* More "breathing room" on big screens */
padding-left: 24px;
}
}
</style>

View File

@@ -9,10 +9,9 @@
$: type = feature.kind.toLowerCase();
$: isFoot = type === 'foot';
$: filterCategory = isFoot ? 'foot' : type === 'uwc' ? 'uwc' : 'level-crossing';
</script>
<svg viewBox="0 0 64 64" width="64" height="64" class={filterCategory}>
<svg viewBox="0 0 64 64" width="64" height="64">
<BaseTrack {activeElec} height={64} />
{#if type === 'foot'}

View File

@@ -37,13 +37,13 @@
<g font-family="sans-serif" font-size="10" font-weight="800" text-anchor="start">
{#if showFromEco}
<text x="75" y="24" fill="#b91c1c" style="text-transform: uppercase;">
ECO: {feature.from.eco}
ECO: {feature.from.eco} | Type: {feature.from.elec}
</text>
{/if}
{#if showToEco}
<text x="75" y="48" fill="#b91c1c" style="text-transform: uppercase;">
ECO: {feature.to.eco}
ECO: {feature.to.eco} | Type: {feature.from.elec}
</text>
{/if}
</g>

View File

@@ -6,6 +6,8 @@
direction: 'up' | 'down';
diverges: 'left' | 'right' | 'both';
elecBranch?: string;
goto?: string;
entryPoint?: string;
};
export let activeElec: any;
export let reversed: boolean = false;

View File

@@ -1,12 +1,14 @@
<script lang="ts">
import { IconArrowNarrowRight } from '@tabler/icons-svelte';
export let feature: {
routeName: string;
routeId: string;
entryPoint: string;
};
</script>
<div class="link-wrapper">
<a href="/map/{feature.routeId}" class="wide-button">
<a href="/map/{feature.routeId}#{feature.entryPoint}" class="wide-button">
<div class="content">
<div class="header-row">
<span class="sub-text">Go to</span>
@@ -16,16 +18,7 @@
</div>
<div class="icon-circle">
<svg viewBox="0 0 24 24" width="20" height="20">
<path
d="M5 12h14M12 5l7 7-7 7"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
<IconArrowNarrowRight />
</div>
</a>
</div>
@@ -68,12 +61,14 @@
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
font-family: "urwgothic";
color: #64748b;
}
.route-id-chip {
font-size: 0.6rem;
font-weight: 800;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
background: #f1f5f9;
color: #475569;
padding: 2px 6px;
@@ -82,6 +77,7 @@
}
.main-text {
font-family: "urwgothic";
font-size: 1rem;
font-weight: 800;
color: #0f172a;
@@ -91,16 +87,15 @@
}
.icon-circle {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: #f8fafc;
border-radius: 50%;
color: #94a3b8;
color: #e1ebeb;
background-color: #3c6f79;
padding: 4px 4px;
border-radius: 999px;
transition: all 0.3s ease;
margin-left: 12px;
}
.wide-button:hover {
@@ -110,8 +105,7 @@
}
.wide-button:hover .icon-circle {
background: #4f46e5;
color: #ffffff;
background-color: #404c55;
transform: rotate(-45deg);
}

View File

@@ -1,11 +1,18 @@
<script lang="ts">
import BaseTrack from '$lib/components/mapIcons/BaseTrack.svelte';
import type { ElecType } from '$lib/railStyles';
export let activeElec: string;
export let activeElec: ElecType;
export let feature: any;
export let reversed: boolean;
const pinSVG: string = `<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 256.96 385.14"><path fill="#4fd1d1" d="M128.48 0C57.52 0 0 57.52 0 128.48c0 14.1 2.32 28.12 6.88 41.47l-.01-.01 7.35 17.3c1.63 3.1 3.38 6.14 5.25 9.1l.98 1.68 108.03 187.12 108.03-187.12.98-1.69a128.5 128.5 0 0 0 5.25-9.09l7.34-17.28a128.48 128.48 0 0 0 6.88-41.48C256.96 57.52 199.44 0 128.48 0ZM44.47 49.27l2.52 2.37c7.74 7.3 16.96 12.23 28.02 15.02 4.65 1.17 4.62 1.17 8.11-.34 25.92-11.2 64.61-11.21 91-.03 7.28 3.09 25.1-4.12 36.1-14.58a30.17 30.17 0 0 1 2.43-2.18c.34 0 .47 6.13.19 9.02a49.7 49.7 0 0 1-9.28 24.82l-1.48 2.06.7.94c38.07 51.55-7.45 115.42-60.6 85-1.3-.74-2.45-1.24-2.56-1.12-.2.2-5.5 10.18-9.4 17.68-1.77 3.4-1.41 3.4-3.26-.13-4.77-9.13-9.38-17.71-9.5-17.71-.09 0-1.19.6-2.45 1.32-52.6 30.1-99.04-34.14-60.98-84.34.77-1.02 1.1-1.66.94-1.86-7.67-10.45-11.38-21.86-10.7-32.92zM89.3 88.5a35.62 35.62 0 0 0-15.47 3.91c-35.27 17.48-16.99 71.8 21.65 64.34a35.5 35.5 0 0 0 22.49-14.72c1.26-1.78 1.68-2.21 1.88-1.93.15.2 2.15 3.62 4.44 7.6 4.88 8.42 4.11 7.82 5.91 4.67a554.85 554.85 0 0 1 7.31-12.45c.15-.07.97.91 1.86 2.23 16.1 23.61 52.74 18.48 61.43-8.6 8.53-26.59-16.97-51.8-43.57-43.1-12.12 3.98-20.12 12.66-27.95 30.34l-.69 1.56-1.38-3.14c-8.81-20-22.72-30.88-37.9-30.71zm76.42 18.05c4.34-.12 8.8 1.42 12.33 4.95 11.83 11.83.35 31.55-16.05 27.58-8.43-2.05-13.82-10.73-12.05-19.41 1.64-8.06 8.54-12.91 15.77-13.12zm-75.7.03c8.54-.47 16.98 5.68 17.59 15.6.67 11.03-10.1 19.76-20.84 16.89-12.5-3.34-16.73-18.36-7.72-27.46a16.6 16.6 0 0 1 10.97-5.03zm6.22 4.93c-1.1.08-2.18.56-3 1.54-3.25 3.87 1.28 9.25 5.69 6.75a4.52 4.52 0 0 0 2.22-3.85c0-2.81-2.5-4.6-4.91-4.44zm63.97.07c-3.15.2-5.02 3.3-3.64 6.36 1.05 2.3 4.27 3.26 6.3 1.87 3.77-2.56 2.44-7.96-2.02-8.23a5.19 5.19 0 0 0-.64 0z"/></svg>`;
</script>
<!--
Check for 'feature.side' and place map pin on that side of the track.
Use 'reversed' to switch sides if the route is reversed.
-->
<svg viewBox="0 0 64 64" width="64" height="64" class="loops">
<BaseTrack {activeElec} height={64} />
</svg>

View File

@@ -2,7 +2,7 @@
import BaseTrack from './BaseTrack.svelte';
export let feature: {
tunnelType: 'start' | 'whole' | 'end';
tunnelType: 'start' | 'whole' | 'end' | 'mid';
length: string;
};
@@ -13,7 +13,7 @@
const portalColour = '#475569'; // Slate grey
$: effectiveType = (() => {
if (!reversed || feature.tunnelType === 'whole') return feature.tunnelType;
if (!reversed || feature.tunnelType === 'whole' || feature.tunnelType === 'mid') return feature.tunnelType;
return feature.tunnelType === 'start' ? 'end' : 'start';
})();
</script>
@@ -32,7 +32,7 @@
{/if}
</g>
{#if feature.tunnelType === 'whole' && feature.length}
{#if feature.length}
<rect x="12" y="26" width="40" height="12" fill="white" />
<text
x="32"

View File

@@ -16,6 +16,7 @@ export const components = {
crossovers: Crossover,
siteof: SiteOf,
bridge: Bridge,
minorBridge: Bridge,
crossover: Crossover,
crossing: Crossing,
loop: Loop,

View File

@@ -1,11 +1,14 @@
<script lang="ts">
import favicon from '$lib/assets/favicon.svg';
import favicon from '$lib/assets/round-logo.svg';
import appleLogo from '$lib/assets/apple-touch-icon.png';
import '$lib/assets/global.css';
let { children } = $props();
</script>
<svelte:head>
<link rel="icon" href={favicon} />
<link rel="apple-touch-icon" href={appleLogo} />
</svelte:head>
{@render children()}

View File

@@ -1,23 +1,27 @@
<script lang="ts">
import logo from '$lib/assets/round-logo-text.svg';
import type { PageData } from './$types';
import { base } from '$app/paths';
export let data: PageData;
import { resolve } from '$app/paths';
let searchTerm = '';
let { data }: { data: PageData } = $props();
let searchTerm = $state('');
const formatDate = (dateVal: string | null) => {
if (!dateVal) return '---';
const d = new Date(dateVal);
return d.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: '2-digit' });
};
let filteredMaps = $derived(
data.maps.filter((m) => {
const term = searchTerm.toLowerCase();
$: filteredMaps = data.maps.filter(
(m) =>
m.routeId.toString().includes(searchTerm) ||
m.routeStart.toLowerCase().includes(searchTerm.toLowerCase()) ||
m.routeEnd.toLowerCase().includes(searchTerm.toLowerCase())
if (m.routeId.toString().includes(term)) return true;
return m.contents.some((location) => location.toLowerCase().includes(term));
})
);
const vibrate = (pattern: number | number[] = 10) => {
if (typeof window !== 'undefined' && window.navigator.vibrate) {
window.navigator.vibrate(pattern);
}
};
const isVerifiedRecently = (dateVal: string | null) => {
if (!dateVal) return 'draft';
@@ -26,51 +30,40 @@
return checkedDate > oneYearAgo ? 'verified' : 'stale';
};
const formatDate = (dateVal: string | null) => {
if (!dateVal) return 'N/A';
const date = new Date(dateVal);
// Check for invalid dates to avoid "NaN/NaN/NaN"
if (isNaN(date.getTime())) return 'Invalid Date';
const d = date.getDate().toString().padStart(2, '0');
const m = (date.getMonth() + 1).toString().padStart(2, '0');
const y = date.getFullYear().toString().slice(-2);
return `${d}/${m}/${y}`;
};
</script>
<div class="page-wrapper">
<header class="main-header">
<div class="brand-container">
<img
src="https://owlboard.info/images/logo/wide_logo.svg"
alt="OwlBoard Logo"
class="main-logo"
/>
<div class="brand-text">
<h1>Routes</h1>
</div>
<div class="search-container">
<input
type="text"
bind:value={searchTerm}
placeholder="Search index..."
class="search-input"
/>
</div>
<img src={logo} alt="OwlBoard Logo" class="main-logo" />
</div>
</header>
<div class="list-container">
<a
href="https://owlboard.info"
class="card external-card"
target="_blank"
rel="noopener noreferrer"
>
<div class="card-top">
<span class="route-id system-tag">OWL</span>
<span class="status-badge external">External Tool</span>
</div>
<div class="card-body">
<div class="location">OwlBoard</div>
<p class="description">Live Departure Boards and PIS Codes</p>
</div>
<div class="card-footer">
<span>Visit owlboard.info ↗</span>
</div>
</a>
<a href="https://owlboard.info" class="button-link">Go to OwlBoard Live Departures & PIS</a>
<input type="text" bind:value={searchTerm} placeholder="Search" class="search-input" />
{#each filteredMaps as map (map.routeId)}
<a href="{base}/map/{map.routeId.toString().padStart(4, '0')}" class="card">
<a
href={resolve(`/map/${map.routeId.toString().padStart(4, '0')}`)}
class="card"
onclick={() => vibrate(10)}
>
<div class="card-top">
<span class="route-id">{map.routeId.toString().padStart(4, '0')}</span>
<span class="status-badge {isVerifiedRecently(map.checked)}">
@@ -91,7 +84,7 @@
</div>
<div class="card-footer">
<span>Created: {formatDate(map.created)}</span>
<span>Updated: {formatDate(map.updated)}</span>
{#if map.checked}
<span>• Checked: {formatDate(map.checked)}</span>
{/if}
@@ -107,7 +100,8 @@
/* Mobile-First Base Styles */
:global(body) {
margin: 0;
background-color: #f8fafc;
background-color: #404c55;
background-image: radial-gradient(#2b343c, #404c55);
color: #0f172a;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
@@ -120,94 +114,115 @@
.main-header {
margin-bottom: 1.5rem;
position: fixed;
height: 80px;
top: 0;
left: 0;
box-sizing: border-box;
width: 100%;
background: #3c6f79;
color: #ebebeb;
z-index: 100;
}
.brand-container {
padding: 15px;
display: flex;
width: 100%;
align-items: center;
box-sizing: border-box;
gap: 1rem;
margin-bottom: 1.5rem;
}
.main-logo {
/* Adjust height to fit your logo's aspect ratio */
height: 48px;
height: 52px;
width: auto;
display: block;
}
.brand-text h1 {
font-size: 1.5rem;
font-weight: 900;
margin: 0;
line-height: 1;
letter-spacing: -0.05em;
font-style: italic;
}
.sub-brand {
font-size: 0.65rem;
font-weight: 800;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.1em;
margin: 0.2rem 0 0 0;
}
/* Desktop layout adjustment */
@media (min-width: 640px) {
.main-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 3rem;
}
.brand-container {
margin-bottom: 0;
}
.main-logo {
height: 56px;
}
}
.search-input {
width: 100%;
max-width: 500px;
font-family: 'urwgothic';
margin: auto;
height: 40px;
padding: 0.8rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 12px;
text-align: center;
border-radius: 30px;
border: none;
font-size: 1rem;
transition: all 0.3s ease;
box-sizing: border-box; /* Ensures padding doesn't break width */
outline: none;
}
.search-input:focus {
border-color: #3b82f6;
.search-input:hover {
box-shadow: rgba(0, 0, 0, 0.46);
}
/* Card Layout (Mobile) */
.list-container {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 90px 1rem 1rem 1rem;
isolation: isolate;
z-index: 1;
}
@media (max-width: 600px) {
.list-container {
padding-top: 90px;
gap: 0.75rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.card {
padding: 10px 12px;
margin: 0;
border-radius: 4px;
}
.location {
font-size: 1rem;
margin-bottom: 0.1rem;
font-weight: 500;
}
.path-arrow {
color: #fff;
font-size: 0.8rem;
margin: 0.1rem 0;
line-height: 0.75;
}
.card-footer {
font-size: 0.6rem;
line-height: 0.8rem;
padding-bottom: 2px;
}
}
.card {
display: block;
background: white;
background: #3c6f79;
padding: 1.25rem;
border-radius: 16px;
border-radius: 35px;
text-decoration: none;
color: inherit;
border: 1px solid #e2e8f0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
color: #4fd1d1;
text-shadow: 2px 1px 10px rgba(0, 0, 0, 0.29);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.482);
transition: transform 0.1s ease;
cursor: pointer;
}
.card:hover {
transform: scale(1.01);
z-index: 5;
}
.card:active {
transform: scale(0.98); /* Tactile feedback for mobile */
transform: scale(0.99); /* Tactile feedback for mobile */
}
.card-top {
@@ -220,8 +235,9 @@
.route-id {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-weight: 800;
color: #3b82f6;
background: #eff6ff;
color: #fff;
background: #2d2d2d;
text-shadow: none;
padding: 0.2rem 0.5rem;
border-radius: 6px;
}
@@ -231,30 +247,31 @@
}
.location {
font-size: 1.125rem;
font-family: 'urwgothic';
font-size: 1.23rem;
font-weight: 700;
letter-spacing: -0.02em;
}
.path-arrow {
color: #acc7e8;
color: #fff;
font-family: 'urwgothic';
font-size: 0.9rem;
margin: 0.2rem 0;
}
.card-footer {
font-size: 0.7rem;
color: #64748b;
color: #e2ebeb;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.05em;
border-top: 1px solid #f1f5f9;
padding-top: 0.75rem;
padding-top: 0;
}
.status-badge {
font-size: 0.65rem;
font-weight: 800;
text-shadow: none;
text-transform: uppercase;
padding: 0.3rem 0.6rem;
border-radius: 999px;
@@ -277,56 +294,22 @@
color: #475569;
}
@media (min-width: 640px) {
.page-wrapper {
padding: 3rem 1.5rem;
.button-link {
text-decoration: none;
border: none;
background: #3c6f79;
max-width: 360px;
padding: 8px 25px;
margin: auto;
text-align: center;
border-radius: 25px;
width: auto;
color: #e2ebeb;
transition: 0.3s all ease;
font-weight: 620;
}
.main-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.search-input {
width: 300px;
}
.path-arrow {
display: inline-block;
margin: 0 0.5rem;
}
.card-body {
display: flex;
align-items: center;
}
}
.external-card {
border-left: 4px solid #6366f1;
background: linear-gradient(to right, #ffffff, #f5f3ff);
}
.system-tag {
background: #eef2ff !important;
color: #4338ca !important;
}
.status-badge.external {
background: #6366f1;
color: white;
}
.description {
margin: 0.25rem 0 0 0;
font-size: 0.9rem;
color: #64748b;
font-weight: 500;
}
.external-card:hover {
border-color: #4338ca;
background: #f5f3ff;
.button-link:hover {
background: #2b2b2b;
}
</style>

View File

@@ -1,5 +1,14 @@
import type { PageLoad } from './$types';
export interface RouteMapIndex {
routeId: string | number;
routeStart: string;
routeEnd: string;
updated: string;
checked: string;
contents: string[];
}
export const load: PageLoad = async ({ fetch }) => {
const response = await fetch('map-index.json');
@@ -12,6 +21,6 @@ export const load: PageLoad = async ({ fetch }) => {
return {
maps: maps.sort((a: any, b: any) => {
return Number(a.routeId) - Number(b.routeId);
})
}) as RouteMapIndex[]
};
};

View File

@@ -0,0 +1,45 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import logo from '$lib/assets/round-logo.svg';
import maskableLogo from '$lib/assets/maps-logo-maskable.svg';
import appleLogo from '$lib/assets/apple-touch-icon.png';
export const prerender = true;
export const GET: RequestHandler = ({ url }) => {
const manifest = {
name: 'OwlBoard Maps',
short_name: 'OB Maps',
start_url: '/',
display: 'standalone',
theme_color: '#4fd1d1',
background_color: '#3d4952',
icons: [
{
src: logo,
sizes: '48x48 72x72 96x96 128x128 256x256 512x512 any',
type: 'image/svg+xml',
purpose: 'any'
},
{
src: maskableLogo,
sizes: '48x48 72x72 96x96 128x128 256x256 512x512 any',
type: 'image/svg+xml',
purpose: 'maskable'
},
{
src: appleLogo,
sizes: '180x180',
type: 'image/png',
purpose: 'any'
}
]
};
return json(manifest, {
headers: {
'Content-Type': 'application/manifest+json',
'Cache-Control': 'public, max-age=3600'
}
});
};

View File

@@ -2,7 +2,10 @@
import RouteRow from '$lib/components/RouteRow.svelte';
import RouteEndLink from '$lib/components/mapIcons/RouteEndLink.svelte';
import { slide } from 'svelte/transition';
import { base } from '$app/paths';
import { resolve } from '$app/paths';
import logo from '$lib/assets/round-logo.svg';
import { IconArrowsExchange, IconSettings } from '@tabler/icons-svelte';
// data.route contains: routeStart, routeEnd, routeId, elecStart, elecEnd, routeDetail[]
export let data;
@@ -11,6 +14,7 @@
let visibleTypes = {
station: true,
minorBridge: false,
bridge: true,
crossovers: true,
loop: true,
@@ -18,7 +22,8 @@
electrificationChange: true,
siteof: true,
junction: true,
tunnel: true
tunnel: true,
crossing: true,
};
let showFilters = false;
@@ -61,8 +66,8 @@
<div class="map-layout">
<header class="top-nav">
<div class="nav-cluster">
<a href="{base}/" class="home-link" title="Back to Index">
<span class="home-icon"></span>
<a href={resolve('/')} aria-label="Home" class="home-link" title="Back to Index">
<img src={logo} alt="OwlBoard Logo" class="nav-logo" />
</a>
<div class="route-stack">
@@ -71,27 +76,34 @@
{reversed ? data.route.routeEnd : data.route.routeStart}
</h1>
<span class="secondary-station">
to {reversed ? data.route.routeStart : data.route.routeEnd}
<span class="route-stack-to">
to</span> {reversed ? data.route.routeStart : data.route.routeEnd}
</span>
{/if}
</div>
</div>
<div class="quick-actions">
<button class="icon-btn" on:click={() => (reversed = !reversed)}>
{reversed ? 'UP' : 'DN'}
<button class="icon-btn" onclick={() => (reversed = !reversed)}>
<IconArrowsExchange />
</button>
<button class="icon-btn" on:click={() => (showFilters = !showFilters)}> Settings </button>
<button class="icon-btn" onclick={() => (showFilters = !showFilters)}> <IconSettings /> </button>
</div>
</header>
{#if showFilters}
<div class="backdrop" on:click={() => (showFilters = false)}></div>
<div
class="backdrop"
role="button"
tabindex="0"
onclick={() => (showFilters = false)}
onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && (showFilters = false)}
></div>
<div class="filter-drawer" transition:slide>
<div class="drawer-header">
<h3>Visibility Filters</h3>
<button class="close-icon" on:click={() => (showFilters = false)} aria-label="Close">
<button class="close-icon" onclick={() => (showFilters = false)} aria-label="Close">
<svg
viewBox="0 0 24 24"
width="20"
@@ -111,7 +123,7 @@
<button
class="filter-chip"
class:active={visibleTypes[type]}
on:click={() => toggleFilter(type)}
onclick={() => toggleFilter(type)}
>
{formatLabel(type)}
</button>
@@ -145,13 +157,20 @@
}
.top-nav {
position: fixed;
width: 100%;
box-sizing: border-box;
top: 0;
height: 80px;
left: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
background: #0f172a;
color: white;
padding: 0 0;
background: #3c6f79;
color: #e1ebeb;
gap: 1rem;
z-index: 10000;
}
.nav-cluster {
@@ -165,27 +184,42 @@
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: #1e293b;
border: 1px solid #334155;
border-radius: 8px;
color: #94a3b8;
text-decoration: none;
font-size: 1.2rem;
flex-shrink: 0; /* Keeps button from squishing on mobile */
width: 52px;
height: 52px;
padding-left: 0;
margin-left: 15px;
margin-right: 0;
padding-right: 0;
flex-shrink: 0;
transition: all 0.3s ease;
}
@media (max-width: 350px) {
.home-link {
width: 42px;
height: 42px;
}
}
.home-link:hover {
background: #334155;
color: white;
border-color: #475569;
transform: translateY(-1px) scale(1.05);
filter: brightness(1.1);
}
.home-link:active {
transform: scale(0.95);
}
.route-stack {
display: flex;
font-family: "urwgothic";
flex-direction: column;
min-width: 0;
margin-left: 0;
}
.route-stack-to {
text-transform: lowercase;
}
.primary-station {
@@ -193,63 +227,38 @@
font-weight: 800;
margin: 0;
white-space: nowrap;
color: #cce9e9;
overflow: hidden;
text-overflow: ellipsis;
}
.secondary-station {
font-size: 0.7rem;
color: #94a3b8;
text-transform: uppercase;
color: #cce9e9;
text-transform: capitalize;
font-weight: 600;
}
.quick-actions {
display: flex;
gap: 0.5rem;
}
.icon-btn {
background: #1e293b;
border: 1px solid #334155;
color: white;
padding: 0.4rem 0.6rem;
border-radius: 6px;
font-size: 0.75rem;
font-weight: bold;
cursor: pointer;
white-space: nowrap;
margin-right: 15px;
}
.map-spine {
padding-top: 72px;
}
.secondary-station {
font-size: 0.85rem;
font-weight: 500;
color: #64748b;
letter-spacing: 0.025em;
margin-top: 2px;
}
@media (min-width: 768px) {
@media (min-width: 536px) {
.primary-station {
font-size: 1.5rem;
}
.secondary-station {
font-size: 1rem;
}
.top-nav {
padding: 0 2rem;
height: 90px;
}
.map-spine {
padding-top: 90px;
}
@media (min-width: 768px) {
h1 {
font-size: 1.5rem;
letter-spacing: -0.03em;
@@ -268,6 +277,7 @@
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 150;
transition: all 0.3s ease;
}
.filter-drawer {
@@ -275,7 +285,7 @@
bottom: 0;
left: 0;
right: 0;
background: white;
background: #3c6f79;
z-index: 200;
border-radius: 20px 20px 0 0;
box-shadow: 0 -8px 20px rgba(0, 0, 0, 0.15);
@@ -288,17 +298,17 @@
justify-content: space-between;
align-items: center;
padding: 1.25rem 1.5rem 0.75rem;
border-bottom: 1px solid #f1f5f9;
border-bottom: 1px solid #e1ebeb;
}
.drawer-header h3 {
margin: 0;
font-size: 1rem;
color: #1e293b;
color: #e1ebeb;
}
.close-icon {
background: #f1f5f9;
background: #404c55;
border: none;
border-radius: 50%;
width: 32px;
@@ -307,7 +317,12 @@
align-items: center;
justify-content: center;
cursor: pointer;
color: #64748b;
color: #e1ebeb;
transition: all 0.3s ease;
}
.close-icon:hover {
background: #2d2d2d;
}
.drawer-content {
@@ -324,14 +339,14 @@
padding: 0.5rem 1rem;
border-radius: 999px;
border: 1px solid #e2e8f0;
background: #f8fafc;
background: #ff6060;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
}
.filter-chip.active {
background: #1e293b;
background: #00725b;
color: white;
border-color: #1e293b;
}
@@ -346,23 +361,28 @@
display: inline-flex;
align-items: center;
gap: 0.4rem;
border: none;
padding: 0.5rem 0.8rem;
border-radius: 12px;
border: 1px solid #e2e8f0;
background: #f8fafc;
color: #475569;
background: #404c55;
color: #e1ebeb;
font-size: 0.75rem;
font-weight: 700;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
text-transform: uppercase;
letter-spacing: 0.02em;
transition: all 0.3s ease;
}
@media (max-width: 390px) {
.icon-btn {
padding: 0.3rem 0.3rem;
}
}
.icon-btn:hover {
background: #f1f5f9;
border-color: #cbd5e1;
color: #1e293b;
background: #2d2d2d;
}
.icon-btn:active {

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,8 +1,8 @@
routeStart: Paddington
routeEnd: Reading West Jn
routeEnd: Reading
routeId: 0001
created: 2026-02-04
checked: 2026-02-06
updated: 2026-02-09
checked: 2026-02-09
signallerStart: TVSC Paddington WS
signallerEnd: TVSC Reading WS
elecStart:
@@ -52,7 +52,7 @@ routeDetail:
chains: 52
- type: crossovers
name: Subway Junction
name: Subway Jn
miles: 0
chains: 61
@@ -85,7 +85,7 @@ routeDetail:
chains: 18
- type: junction
name: Westbourne Park Junction
name: Westbourne Park Jn
diverges: left
direction: up
description: to Crossrail Core Operating Section
@@ -93,7 +93,7 @@ routeDetail:
chains: 21
- type: crossovers
name: Portobello Junction
name: Portobello Jn
miles: 1
chains: 33
@@ -120,7 +120,7 @@ routeDetail:
chains: 73
- type: junction
name: Kensal Green East Junction
name: Kensal Green East Jn
diverges: both
direction: down
description: to Crossrail & North Pole Depots
@@ -154,11 +154,11 @@ routeDetail:
miles: 3
chains: 0
- type: junction
- type: siteof
name: Old Oak Common West
diverges: left
direction: up
description: Out of Use for HS2 Works
description: Junction has been removed
miles: 3
chains: 20
@@ -177,10 +177,11 @@ routeDetail:
chains: 45
- type: junction
name: Friars Junction
name: Friars Jn
diverges: left
direction: up
description: Out of Use for HS2 Works
elecBranch: none
description: Out of Use for HS2 Works. Track temporarily disconnected.
miles: 3
chains: 53
@@ -206,7 +207,7 @@ routeDetail:
chains: 80
- type: junction
name: Acton East Junction
name: Acton East Jn
diverges: left
direction: up
description: Up/Dn Poplar to Acton Wells Jn
@@ -311,9 +312,10 @@ routeDetail:
chains: 46
- type: junction
name: West Ealing Junction
name: West Ealing Jn
diverges: left
direction: down
elecBranch: none
description: Dn/Up Greenford to Greenford & West Ealing Sidings
miles: 6
chains: 54
@@ -326,9 +328,10 @@ routeDetail:
chains: 71
- type: junction
name: Hanwell Junction
name: Hanwell Jn
diverges: left
direction: up
elecBranch: none
description: Dn/Up Loop to Greenford & West Ealing Sidings
miles: 7
chains: 19
@@ -371,6 +374,8 @@ routeDetail:
- type: loop
position: left
name: Hanwell Up & Dn Goods Loops
description: Both on Up side
elecLoop: none
miles: 8
chains: 20
@@ -389,9 +394,10 @@ routeDetail:
chains: 46
- type: junction
name: Southall East Junction
name: Southall East Jn
diverges: right
direction: up
elecBranch: none
description: Brentford Waste Terminal & Southall Depot
miles: 8
chains: 62
@@ -417,7 +423,7 @@ routeDetail:
chains: 53
- type: crossovers
name: Southall West Junction
name: Southall West Jn
description: Southall Sidings Diverge
miles: 9
chains: 70
@@ -484,7 +490,7 @@ routeDetail:
chains: 77
- type: junction
name: Heathrow Airport Junction
name: Heathrow Airport Jn
diverges: both
direction: down
miles: 11
@@ -500,7 +506,7 @@ routeDetail:
chains: 28
- type: crossovers
name: Stockley Bridge Junction
name: Stockley Bridge Jn
miles: 12
chains: 9
@@ -513,7 +519,7 @@ routeDetail:
chains: 22
- type: crossovers
name: West Drayton East Junction
name: West Drayton East Jn
miles: 12
chains: 67
@@ -538,7 +544,7 @@ routeDetail:
chains: 56
- type: junction
name: West Drayton Junction
name: West Drayton Jn
diverges: left
direction: down
description: Colnbrook Freight (near Heathrow)
@@ -638,6 +644,7 @@ routeDetail:
name: Langley GF
diverges: left
description: Langley Sidings
elecBranch: none
direction: up
miles: 15
chains: 76
@@ -718,7 +725,7 @@ routeDetail:
chains: 42
- type: junction
name: Windsor Branch Junction
name: Windsor Branch Jn
diverges: right
direction: down
description: Windsor Branch from platforms 1 & 2 Only
@@ -1041,7 +1048,7 @@ routeDetail:
chains: 1
- type: junction
name: Henley Branch Junction
name: Henley Branch Jn
diverges: left
direction: down
description: Henley-on-Thames from Platform 4 only
@@ -1162,7 +1169,7 @@ routeDetail:
chains: 77
- type: crossovers
name: Kennet Bridge Junction
name: Kennet Bridge Jn
miles: 35
chains: 10
@@ -1175,11 +1182,11 @@ routeDetail:
chains: 28
- type: junction
name: Reading New Junction
name: Reading New Jn
diverges: right
direction: up
elecBranch: none
description: to Southern Lines (Waterloo/Guildford)
description: Up/Dn Reading Spur to Southern Lines (Waterloo/Guildford)
miles: 35
chains: 40
@@ -1187,11 +1194,12 @@ routeDetail:
position: under
category: rail
name: Low Level Tunnel
description: Reading Low Level line
miles: 35
chains: 40
- type: junction
name: Reading East Junction
name: Reading East Jn
diverges: left
direction: up
elecBranch: none
@@ -1211,43 +1219,7 @@ routeDetail:
miles: 35
chains: 78
- type: bridge
name: Caversham Road
position: under
category: minorRoad
miles: 35
chains: 11
- type: junction
diverges: right
direction: down
name: Westbury Line Junction
description: to Oxford Road Jn
miles: 36
chains: 17
- type: junction
diverges: right
direction: down
name: Caversham Road Junction
description: Reading Feeder Main/Relief diverge and pass under Reading Viaduct to Oxford Rd Jn
miles: 36
chains: 22
- type: crossovers
name: Reading High Level Junction
description: Down Reading Festival Connects to Down Main
miles: 36
chains: 47
- type: junction
diverges: right
direction: up
name: Reading West Junction
description: to Oxford Road Junction (From relief lines only)
miles: 37
chains: 17
- type: continues
routeName: Reading West Jn - Bristol TM
routeId: "0002"
routeName: Reading - Bristol TM
routeId: '0002'
entryPoint: reading

View File

@@ -1,7 +1,7 @@
routeStart: Reading West Jn
routeStart: Reading
routeEnd: Bristol TM
routeId: 0002
created: 2026-02-04
updated: 2026-02-04
checked:
signallerStart: TVSC Reading WS
signallerEnd: TVSC Temple Meads WS
@@ -12,10 +12,54 @@ elecEnd:
elec: none
routeDetail:
- type: continues
routeName: Paddington - Reading West Jn
routeId: "0001"
routeName: Paddington - Reading
routeId: '0001'
entryPoint: reading
- type: station
name: Reading
miles: 35
chains: 78
- type: bridge
name: Caversham Road
position: under
category: minorRoad
miles: 35
chains: 11
- type: junction
diverges: right
direction: down
name: Westbury Line Jn
description: to Oxford Road Jn
goto: "0201"
entryPoint: "oxford-road-jn"
miles: 36
chains: 17
- type: junction
diverges: right
direction: down
name: Caversham Road Jn
description: Reading Feeder Main/Relief diverge and pass under Reading Viaduct to Oxford Rd Jn
goto: "0201"
entryPoint: "oxford-road-jn"
miles: 36
chains: 22
- type: crossovers
name: Scours Lane Junction
description: Line diverges
miles: 38
chains: 90
name: Reading High Level Jn
description: Down Reading Festival Connects to Down Main
miles: 36
chains: 47
- type: junction
diverges: right
direction: up
name: Reading West Jn
description: to Oxford Road Junction (From relief lines only)
goto: "0201"
entryPoint: "oxford-road-jn"
miles: 37
chains: 17

View File

@@ -0,0 +1,368 @@
routeStart: Swindon Junction
routeEnd: Standish Junction
routeId: 0230
updated: 2026-02-11
checked:
signallerStart: TVSC Swindon WS
signallerEnd: Gloucester PSB
elecStart:
elec: 25kvac
eco: Didcot (TVSC)
elecEnd:
elec: none
routeDetail:
- type: continues
routeName: Reading - Bristol TM
routeId: '0002'
entryPoint: swindon
- type: junction
name: Swindon Jn
diverges: right
goto: "0002"
entryPoint: swindon-jn
miles: 77
chains: 36
- type: electrificationChange
miles: 77
chains: 58
from:
elec: 25kvac
eco: Didcot
to:
elec: none
- type: minorBridge
name: Bruce Street
position: under
category: road
miles: 78
chains: 7
- type: crossovers
name: Rodbourne Jn
description: Limit of bidirectional running from Swindon
miles: 78
chains: 30
- type: bridge
name: River Hay
position: under
category: waterway
miles: 78
chains: 71
- type: minorBridge
name: Former MSWJ Railway
position: under
category: foot
miles: 79
chains: 20
- type: minorBridge
name: Edward's Bridge
position: under
category: road
miles: 79
chains: 33
- type: minorBridge
name: Mill Brook
position: under
category: stream
miles: 79
chains: 34
- type: bridge
name: Purton Road
description: Double bridge
position: over
category: road
roadName: B4534
miles: 79
chains: 53
- type: minorBridge
position: under
category: road
miles: 80
chains: 0
- type: minorBridge
position: over
category: road
roadName: B4553
miles: 80
chains: 30
- type: crossing
kind: foot
name: Purton No.72 Foot Crossing
miles: 80
chains: 63
- type: crossing
kind: AHB
name: Purton Collins Lane LC
miles: 81
chains: 9
- type: crossing
name: Purton No.64 Foot Crossing
kind: foot
miles: 81
chains: 25
- type: crossing
name: Purton No.60 Foot Crossing
kind: foot
miles: 81
chains: 46
- type: crossing
name: Purton Common Crossing
kind: MSL
miles: 81
chains: 65
- type: crossing
name: Purton No.129 Foot Crossing
kind: foot
miles: 82
chains: 9
- type: minorBridge
name: Farm Bridge
position: over
category: road
miles: 82
chains: 13
- type: crossing
name: Purton No.34 Foot Crossing
kind: foot
miles: 82
chains: 30
- type: minorBridge
name: Carpenter's
position: under
category: road
miles: 82
chains: 39
- type: minorBridge
name: Braydon Brook
position: under
category: stream
miles: 82
chains: 53
- type: minorBridge
name: Oak Hill (Coxhill Farm)
position: under
category: road
miles: 82
chains: 69
- type: crossing
kind: uwc
name: Clover's Foot Crossing
miles: 83
chains: 11
- type: minorBridge
name: Farm Bridge
position: over
category: road
miles: 83
chains: 28
- type: crossing
name: Gambols Farm Crossing
kind: uwc
miles: 83
chains: 57
- type: minorBridge
name: Black Dog Road
roadName: B4696
category: road
position: over
miles: 84
chains: 25
- type: crossing
kind: uwc
name: Gryphon Lodge Crossing
miles: 84
chains: 66
- type: minorBridge
position: under
category: stream
miles: 84
chains: 22
- type: minorBridge
position: over
name: Station Road
roadName: B4040
category: road
miles: 85
chains: 36
- type: siteof
name: Minety & Ashton Keynes
description: Former station
miles: 85
chains: 37
- type: crossing
kind: foot
name: Minety No.44 Foot Crossing
miles: 85
chains: 62
- type: crossing
kind: foot
name: Minety No.6 Foot Crossing
miles: 86
chains: 06
- type: minorBridge
nam: Askew Bridge
category: road
position: under
miles: 86
chains: 18
- type: crossing
kind: foot
name: Minety No.3 Foot Crossing
miles: 86
chains: 23
- type: minorBridge
position: under
category: road
name: Minety Moor
miles: 86
chains: 36
- type: crossing
kind: foot
name: Minety No.5 Foot Crossing
miles: 86
chains: 42
- type: crossing
kind: CCTV
name: Minety LC
description: CCTV by TVSC (LC Workstation)
miles: 86
chains: 74
- type: crossing
kind: foot
name: Minety No.26 Foot Crossing
miles: 87
chains: 4
- type: crossing
kind: foot
name: Minety No.26 Foot Crossing
miles: 87
chains: 31
- type: minorBridge
position: under
category: road
name: Slurt Bridge
miles: 87
chains: 63
- type: minorBridge
position: under
category: stream
name: Swill Brook
miles: 88
chains: 5
- type: crossing
kind: foot
name: Oaksey No.5 Foot Crossing
miles: 88
chains: 31
- type: minorBridge
position: over
name: Oaksey Road
category: road
miles: 88
chains: 37
- type: crossing
name: Oaksey No.4 Foot Crossing
kind: foot
miles: 88
chains: 56
- type: minorBridge
position: under
category: road
miles: 88
chains: 77
- type: crossing
name: West End Farm Foot Crossing
kind: foot
miles: 89
chains: 29
- type: minorBridge
name: Poole Keynes
position: under
category: road
miles: 89
chains: 30
- type: crossing
name: Kemble Wick Foot Crossing
kind: foot
miles: 89
chains: 59
- type: minorBridge
name: Wick Bridge
position: over
category: road
miles: 90
chains: 3
- type: tunnel
tunnelType: whole
name: Kemble Tunnel
length: 0mi 409yd
miles: 90
chains: 50
- type: crossovers
name: Kemble Jn
miles: 90
chains: 65
- type: junction
diverges: left
direction: down
description: Up Kemble Siding (Former platform)
miles: 90
chains: 74
name: Kemble GF
- type: station
name: Kemble
miles: 90
chains: 79

View File

@@ -0,0 +1,654 @@
routeStart: Wootton Bassett Jn
routeEnd: Stoke Gifford Jn
routeId: 0240
updated: 2026-02-11
checked: 2026-02-11
signallerStart: TVSC Swindon WS
signallerEnd: TVSC Stoke Gifford WS
elecStart:
elec: 25kvac
eco: Didcot (TVSC)
elecEnd:
elec: 25kvac
eco: Didcot (TVSC)
routeDetail:
- type: continues
routeName: Reading - Bristol TM
entryPoint: wootton-bassett-jn
routeId: '0002'
- type: junction
diverges: right
direction: down
name: Wootton Basset Jn
description: to Chippenham & Bristol via Bath
goto: "0002"
entryPoint: wootton-bassett-jn
miles: 83
chains: 7
- type: loop
name: Up Wootton Basset Goods Line
description: Loop spans junction
position: left
miles: 83
chains: 10
- type: bridge
position: over
name: Bath Road
roadName: A3102
category: aroad
miles: 83
chains: 29
- type: crossovers
name: Wootton Basset West
miles: 84
chains: 7
- type: minorBridge
name: Whitehill Lane
position: under
category: road
miles: 84
chains: 13
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 84
chains: 39
- type: bridge
position: over
name: Motorway
roadName: M4
category: motorway
miles: 84
chains: 50
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 85
chains: 15
- type: minorBridge
position: over
name: Callow Hill
category: road
miles: 85
chains: 29
- type: minorBridge
name: Farm Bridge
position: over
category: road
miles: 85
chains: 58
- type: minorBridge
name: Brinkworth Brooklands
position: over
category: road
miles: 86
chains: 75
- type: crossing
kind: foot
name: Brinkworth Foot Crossing
miles: 87
chains: 17
- type: minorBridge
name: Farm Bridge
category: road
position: over
miles: 88
chains: 12
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 88
chains: 46
- type: minorBridge
name: School Hill
position: under
category: road
miles: 88
chains: 66
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 89
chains: 9
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 89
chains: 29
- type: minorBridge
name: Idover Lane
position: under
category: road
miles: 89
chains: 46
- type: minorBridge
name: The Street
position: under
category: road
miles: 89
chains: 70
- type: bridge
name: River Avon
position: under
category: waterway
miles: 90
chains: 26
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 90
chains: 60
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 91
chains: 36
- type: minorBridge
name: Startley Road
position: under
category: road
miles: 91
chains: 50
- type: minorBridge
name: Rodbourne Lane
description: or 'Pound Hill'
position: under
category: road
miles: 92
chains: 2
- type: minorBridge
name: Cabbage Lane
position: over
category: road
miles: 92
chains: 28
- type: crossing
name: Kingway Barn Foot Crossing
kind: foot
miles: 93
chains: 7
- type: bridge
name: Kingway
position: under
category: aroad
roadName: A429
miles: 93
chains: 30
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 93
chains: 70
- type: loop
position: right
name: Down Hullavington Goods Loop
miles: 94
chains: 5
- type: minorBridge
position: under
name: Court Farm
category: road
miles: 94
chains: 9
- type: minorBridge
position: under
name: Bradfield Cottages
category: road
miles: 94
chains: 27
- type: loop
name: Up Hullavington Goods Loop
position: left
miles: 94
chains: 42
- type: crossovers
name: Hullavington
miles: 94
chains: 45
- type: minorBridge
name: Gills Hunting
position: over
category: road
miles: 94
chains: 62
- type: bridge
name: Aqueduct
position: over
category: stream
miles: 95
chains: 22
- type: minorBridge
name: Happy Lands
position: over
category: road
miles: 95
chains: 40
- type: minorBridge
name: Pig Lane
position: over
category: road
miles: 95
chains: 69
- type: minorBridge
name: Fosse Way
category: road
position: over
miles: 96
chains: 47
- type: minorBridge
name: Pipeline
category: pipeline
position: over
miles: 96
chains: 48
- type: minorBridge
name: Footbridge
position: over
category: foot
miles: 96
chains: 64
- type: tunnel
name: Alterton Tunnel
tunnelType: whole
length: 0mi 506yd
miles: 97
chains: 46
- type: signallerChange
from: TVSC Swindon WS (SW)
to: TVSC Stoke Gifford WS (BL)
miles: 98
chains: 0
- type: minorBridge
name: Footbridge
category: foot
position: over
miles: 98
chains: 2
- type: minorBridge
name: Footbridge
category: foot
positon: over
miles: 98
chains: 17
- type: minorBridge
name: Alderton Road
position: over
category: road
miles: 98
chains: 40
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 98
chains: 76
- type: minorBridge
name: Farm Bridge
position: under
category: road
miles: 99
chains: 7
- type: minorBridge
name: Footbridge
category: foot
position: over
miles: 99
chains: 40
- type: bridge
name: Luckington Road
category: road
roadName: B4040
position: over
miles: 99
chains: 46
- type: minorBridge
name: Footbridge
category: foot
position: over
miles: 99
chains: 59
- type: siteof
name: Badminton
side: centre
miles: 100
chains: 1
- type: bridge
name: Station Road
position: over
category: road
miles: 100
chains: 4
- type: minorBridge
name: Footbridge
category: foot
position: over
miles: 100
chains: 33
- type: bridge
name: Aqueduct
position: over
category: stream
miles: 100
chains: 57
- type: minorBridge
name: Footbridge
category: foot
position: over
miles: 100
chains: 65
- type: tunnel
tunnelType: whole
length: 2mi 926yd
name: Chipping Sodbury Tunnel
miles: 102
chains: 21
description: 10 telephones on Up side
- type: minorBridge
position: over
name: Footbridge
category: foot
miles: 103
chains: 63
- type: bridge
name: Frome Bridge
position: over
category: stream
miles: 103
chains: 75
- type: minorBridge
name: Colt's Green Bridge
category: road
position: over
miles: 104
chains: 19
- type: loop
name: Chipping Sodbury Goods Loop
position: left
description: Bidirectional with access to Chipping Sodbury Sidings
miles: 104
chains: 31
- type: minorBridge
name: Farm Bridge
category: road
position: over
miles: 104
chains: 56
- type: minorBridge
name: Kingrove Lane
position: over
category: road
miles: 104
chains: 77
- type: bridge
name: Aqueduct
position: over
category: stream
miles: 105
chains: 18
- type: minorBridge
name: Dodington Road
category: road
position: over
miles: 105
chains: 19
- type: bridge
name: Aqueduct
category: stream
position: over
miles: 105
chains: 38
- type: minorBridge
name: Footbridge
category: foot
position: over
miles: 106
chains: 32
- type: bridge
name: Westerleigh Road
category: road
position: under
miles: 107
chains: 4
- type: junction
diverges: left
direction: up
name: Westerleigh Jn
miles: 107
chains: 14
description: Up/Dn Charfield towards Gloucester
- type: bridge
name: Westerleigh Branch
category: rail
position: under
miles: 107
chains: 15
- type: minorBridge
name: Dodmoor Farm
category: road
position: under
miles: 107
chains: 33
- type: minorBridge
name: Farm Bridge
category: road
position: under
miles: 107
chains: 70
- type: minorBridge
name: Boxhenge Farm Lane
category: road
position: under
miles: 108
chains: 10
- type: minorBridge
name: The Dramway
position: over
category: foot
miles: 108
chains: 25
- type: minorBridge
name: Henfield Road
position: under
category: road
miles: 107
chains: 52
- type: bridge
name: Bristol Road
category: aroad
position: under
roadName: A432
miles: 108
chains: 78
- type: minorBridge
name: Park Lane
position: under
category: road
miles: 109
chains: 27
- type: bridge
name: Upford Viaduct
positon: under
category: waterway
miles: 109
chains: 58
- type: minorBridge
name: Hicks Common Lane
position: over
category: road
miles: 109
chains: 71
- type: minorBridge
name: Down Road
category: road
position: over
miles: 110
chains: 15
- type: minorBridge
name: Mill Lane
category: road
position: under
miles: 110
chains: 24
- type: bridge
name: Winterbourne Viaduct
category: road
position: under
miles: 110
chains: 38
- type: minorBridge
name: Farm Bridge
category: road
position: under
miles: 110
chains: 48
- type: bridge
name: M4 Motorway
category: motorway
roadName: M4
position: under
miles: 110
chains: 63
- type: minorBridge
name: Old Gloucester Road
category: road
roadName: B4427
position: under
miles: 110
chains: 70
- type: minorBridge
name: Curtis Lane
position: over
category: road
miles: 111
chains: 14
- type: crossovers
name: Stoke Gifford East Jn
miles: 111
chains: 20
- type: minorBridge
name: Pearson's Brick Yard
category: road
position: over
miles: 111
chains: 32
- type: station
name: Bristol Parkway
description: Stoke Gifford Dn Sidings on Down side
miles: 111
chains: 62
- type: bridge
name: Bristol Road
category: road
position: under
miles: 111
chains: 77
- type: junction
name: Stoke Gifford No.1 Jn
diverges: right
direction: down
description: Up/Dn Filton & Bristol TM
miles: 111
chains: 79
- type: continues
routeName: Bristol TM - Cardiff West Shunt
entryPoint: patchway-junction
routeId: '9999'

View File

@@ -0,0 +1,12 @@
routeStart: Westerleigh Jn
routeEnd: Gloucester
routeId: 2420
updated: 2026-02-11
checked:
signallerStart: TVSC Swindon WS
signallerEnd: Gloucester PSB
elecStart:
elec: 25kvac
eco: Didcot (TVSC)
elecEnd:
elec: none

View File

@@ -2,5 +2,14 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit()],
build: {
assetsInlineLimit: 0,
cssCodeSplit: true,
sourcemap: false,
target: 'esnext',
modulePreload: {
polyfill: false
}
}
});