46 Commits

Author SHA1 Message Date
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
6c10cb628e Fix linter warnings 2026-02-06 23:54:50 +00:00
46c897efcf Add OwlBoard logo 2026-02-06 23:30:44 +00:00
16dec07270 NPM Update 2026-02-06 23:30:39 +00:00
d53748f8ae Add homepage and make bridges look nice 2026-02-06 23:09:56 +00:00
d406db9d18 Add name for Henley Branch Junction 2026-02-06 12:04:32 +00:00
07e5e4023c Correct name of West Drayton East 2026-02-06 12:00:46 +00:00
cf0b1396ea Add Hayes East 2026-02-06 11:59:17 +00:00
39ca73b015 Correct Old Oak Common Junctions & add siteof Old Oak Cpmmmon 2026-02-06 11:53:50 +00:00
f38ba5a971 Update static/mapFiles/yaml/0001.yaml 2026-02-06 11:49:24 +00:00
379180df7d Hijack requests to self and redirect to local URL 2026-02-05 23:46:38 +00:00
7ae9951fda Update junction electrification states 2026-02-05 23:39:03 +00:00
7dfba5e240 Fix stupid syntax error 2026-02-05 22:29:28 +00:00
74572b3ede Ensure 'static' directory is built into final container 2026-02-05 22:28:05 +00:00
41 changed files with 3142 additions and 1695 deletions

View File

@@ -3,7 +3,7 @@ run-name: ${{ gitea.actor }} is building and pushing
on:
create:
tags: "*"
tags: '*'
env:
GITEA_DOMAIN: git.fjla.uk
@@ -36,4 +36,4 @@ jobs:
push: true
tags: |
${{ env.GITEA_DOMAIN }}/${{ env.RESULT_IMAGE_NAME }}:${{ gitea.ref_name }}
${{ env.GITEA_DOMAIN }}/${{ env.RESULT_IMAGE_NAME }}:latest
${{ env.GITEA_DOMAIN }}/${{ env.RESULT_IMAGE_NAME }}:latest

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ node_modules
# Transpiled JSON
/static/mapFiles/json/
/static/map-index.json
# Output
.output

View File

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

View File

@@ -16,6 +16,7 @@ WORKDIR /app
COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/static ./static
USER node

261
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",
@@ -29,9 +30,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
"integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
"cpu": [
"ppc64"
],
@@ -46,9 +47,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
"integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
"cpu": [
"arm"
],
@@ -63,9 +64,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
"integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
"cpu": [
"arm64"
],
@@ -80,9 +81,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
"integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
"cpu": [
"x64"
],
@@ -97,9 +98,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
"integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
"cpu": [
"arm64"
],
@@ -114,9 +115,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
"integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
"cpu": [
"x64"
],
@@ -131,9 +132,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
"integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
"cpu": [
"arm64"
],
@@ -148,9 +149,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
"integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
"cpu": [
"x64"
],
@@ -165,9 +166,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
"integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
"cpu": [
"arm"
],
@@ -182,9 +183,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
"integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
"cpu": [
"arm64"
],
@@ -199,9 +200,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
"integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
"cpu": [
"ia32"
],
@@ -216,9 +217,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
"integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
"cpu": [
"loong64"
],
@@ -233,9 +234,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
"integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
"cpu": [
"mips64el"
],
@@ -250,9 +251,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
"integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
"cpu": [
"ppc64"
],
@@ -267,9 +268,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
"integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
"cpu": [
"riscv64"
],
@@ -284,9 +285,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
"integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
"cpu": [
"s390x"
],
@@ -301,9 +302,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
"integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
"cpu": [
"x64"
],
@@ -318,9 +319,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
"integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
"cpu": [
"arm64"
],
@@ -335,9 +336,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
"integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
"cpu": [
"x64"
],
@@ -352,9 +353,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
"integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
"cpu": [
"arm64"
],
@@ -369,9 +370,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
"integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
"cpu": [
"x64"
],
@@ -386,9 +387,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
"integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
"cpu": [
"arm64"
],
@@ -403,9 +404,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
"integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
"cpu": [
"x64"
],
@@ -420,9 +421,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
"integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
"cpu": [
"arm64"
],
@@ -437,9 +438,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
"integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
"cpu": [
"ia32"
],
@@ -454,9 +455,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
"integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
"cpu": [
"x64"
],
@@ -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",
@@ -1366,9 +1395,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.19.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz",
"integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==",
"version": "22.19.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.9.tgz",
"integrity": "sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1907,9 +1936,9 @@
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1920,32 +1949,32 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.2",
"@esbuild/android-arm": "0.27.2",
"@esbuild/android-arm64": "0.27.2",
"@esbuild/android-x64": "0.27.2",
"@esbuild/darwin-arm64": "0.27.2",
"@esbuild/darwin-x64": "0.27.2",
"@esbuild/freebsd-arm64": "0.27.2",
"@esbuild/freebsd-x64": "0.27.2",
"@esbuild/linux-arm": "0.27.2",
"@esbuild/linux-arm64": "0.27.2",
"@esbuild/linux-ia32": "0.27.2",
"@esbuild/linux-loong64": "0.27.2",
"@esbuild/linux-mips64el": "0.27.2",
"@esbuild/linux-ppc64": "0.27.2",
"@esbuild/linux-riscv64": "0.27.2",
"@esbuild/linux-s390x": "0.27.2",
"@esbuild/linux-x64": "0.27.2",
"@esbuild/netbsd-arm64": "0.27.2",
"@esbuild/netbsd-x64": "0.27.2",
"@esbuild/openbsd-arm64": "0.27.2",
"@esbuild/openbsd-x64": "0.27.2",
"@esbuild/openharmony-arm64": "0.27.2",
"@esbuild/sunos-x64": "0.27.2",
"@esbuild/win32-arm64": "0.27.2",
"@esbuild/win32-ia32": "0.27.2",
"@esbuild/win32-x64": "0.27.2"
"@esbuild/aix-ppc64": "0.27.3",
"@esbuild/android-arm": "0.27.3",
"@esbuild/android-arm64": "0.27.3",
"@esbuild/android-x64": "0.27.3",
"@esbuild/darwin-arm64": "0.27.3",
"@esbuild/darwin-x64": "0.27.3",
"@esbuild/freebsd-arm64": "0.27.3",
"@esbuild/freebsd-x64": "0.27.3",
"@esbuild/linux-arm": "0.27.3",
"@esbuild/linux-arm64": "0.27.3",
"@esbuild/linux-ia32": "0.27.3",
"@esbuild/linux-loong64": "0.27.3",
"@esbuild/linux-mips64el": "0.27.3",
"@esbuild/linux-ppc64": "0.27.3",
"@esbuild/linux-riscv64": "0.27.3",
"@esbuild/linux-s390x": "0.27.3",
"@esbuild/linux-x64": "0.27.3",
"@esbuild/netbsd-arm64": "0.27.3",
"@esbuild/netbsd-x64": "0.27.3",
"@esbuild/openbsd-arm64": "0.27.3",
"@esbuild/openbsd-x64": "0.27.3",
"@esbuild/openharmony-arm64": "0.27.3",
"@esbuild/sunos-x64": "0.27.3",
"@esbuild/win32-arm64": "0.27.3",
"@esbuild/win32-ia32": "0.27.3",
"@esbuild/win32-x64": "0.27.3"
}
},
"node_modules/escape-string-regexp": {
@@ -3079,9 +3108,9 @@
}
},
"node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -3186,9 +3215,9 @@
}
},
"node_modules/svelte": {
"version": "5.49.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.49.2.tgz",
"integrity": "sha512-PYLwnngYzyhKzqDlGVlCH4z+NVI8mC0/bTv15vw25CcdOhxENsOHIbQ36oj5DIf3oBazM+STbCAvaskpxtBmWA==",
"version": "5.50.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.50.0.tgz",
"integrity": "sha512-FR9kTLmX5i0oyeQ5j/+w8DuagIkQ7MWMuPpPVioW2zx9Dw77q+1ufLzF1IqNtcTXPRnIIio4PlasliVn43OnbQ==",
"dev": true,
"license": "MIT",
"dependencies": {

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

@@ -4,13 +4,57 @@ import path from 'path';
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 = [];
fs.readdirSync(inputDir).forEach((file) => {
if (file.endsWith('.yaml')) {
const content = yaml.load(fs.readFileSync(path.join(inputDir, file), 'utf8'));
const fullPath = path.join(inputDir, file);
const content = yaml.load(fs.readFileSync(fullPath, 'utf8'));
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,
updated: content.updated || null,
checked: content.checked || null,
contents: Array.from(contentSet)
});
}
});
fs.writeFileSync(indexFile, JSON.stringify(mapList));
console.log(`Generated ${mapList.length} map files and index.`);

View File

@@ -6,6 +6,7 @@
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"
/>
<link rel="manifest" href="/manifest.webmanifest" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">

20
src/hooks.server.ts Normal file
View File

@@ -0,0 +1,20 @@
import { type HandleFetch } from '@sveltejs/kit';
export const handleFetch: HandleFetch = async ({ request, fetch }) => {
if (request.url.startsWith('https://maps.owlboard.info')) {
const newUrl = request.url.replace('https://maps.owlboard.info', 'http://localhost:3000');
const headers = new Headers(request.headers);
headers.set('host', 'maps.owlboard.info');
request = new Request(newUrl, {
method: request.method,
headers: headers,
body: request.body,
// @ts-expect-error - 'duplex' is needed for node fetch with bodies
duplex: 'half'
});
}
return fetch(request);
};

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

@@ -6,9 +6,12 @@
export let reversed: boolean = false;
$: Icon = components[feature.type] || components.default;
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>

View File

@@ -1,80 +1,207 @@
<script lang="ts">
import BaseTrack from '$lib/components/mapIcons/BaseTrack.svelte';
import type { ElecType } from '$lib/railStyles';
export let feature: {
position: 'over' | 'under';
category: 'rail' | 'footbridge' | 'aroad' | 'minorRoad' | 'motorway' | 'waterway';
category:
| 'rail'
| 'stream'
| 'foot'
| 'aroad'
| 'minorRoad'
| 'motorway'
| 'waterway'
| 'pipeline';
roadName?: string;
};
export let activeElec: string;
export let activeElec: ElecType;
const bridgeStyles = {
rail: { color: '#ffffff', stroke: '#000000', width: 20, text: '#000000' },
footbridge: { color: '#ffffff', stroke: '#475569', width: 10, text: '#475569' },
aroad: { color: '#00703c', stroke: '#004d29', width: 24, text: '#FFEB3B' },
minorRoad: { color: '#ffffff', stroke: '#64748b', width: 20, text: '#334155' },
motorway: { color: '#005da1', stroke: '#003e6b', width: 32, text: '#ffffff' },
waterway: { color: '#bae6fd', stroke: '#0369a1', width: 24, text: '#075985' },
pipeline: { color: '#334155', stroke: '#1e293b', width: 4, text: '#334155' }
rail: {
stroke: '#0f172a',
width: 22,
bg: '#334155',
text: '#ffffff',
textBg: '#000000',
textBorder: '',
type: 'rail'
},
foot: {
stroke: '#475569',
width: 10,
bg: '#94a3b8',
text: '#ffffff',
textBg: '#000000',
textBorder: '',
type: 'path'
},
aroad: {
stroke: '#065f46',
width: 32,
bg: '#059669',
text: '#ffd200',
textBg: '#00703c',
textBorder: '#ffd200',
type: 'road'
},
minorRoad: {
stroke: '#334155',
width: 24,
bg: '#475569',
text: '#000000',
textBg: '#ffffff',
textBorder: '#000000',
type: 'road'
},
motorway: {
stroke: '#1e40af',
width: 40,
bg: '#3b82f6',
text: '#ffffff',
textBg: '#007ac1',
textBorder: '#ffffff',
type: 'road'
},
waterway: {
stroke: '#0369a1',
width: 32,
bg: '#bae6fd',
text: '#075985',
textBg: '#000000',
textBorder: '',
type: 'water'
},
stream: {
stroke: '#0369a1',
width: 4,
bg: '#bae6fd',
text: '#075985',
textBg: '#000000',
textBorder: '',
type: 'water'
},
pipeline: {
stroke: '#1e293b',
width: 4,
bg: '#1e293b',
text: '#ffffff',
textBg: '#000000',
textBorder: '',
type: 'path'
}
};
$: s = bridgeStyles[feature.category] || bridgeStyles.minorRoad;
$: isOver = feature.position === 'over';
$: topY = 32 - s.width / 2;
$: bottomY = 32 + s.width / 2;
$: xLeft = 6;
$: xRight = 58;
$: topPath = `M 0 ${topY} Q 8 ${topY} 8 ${topY - 4} M 56 ${topY - 4} Q 56 ${topY} 64 ${topY}`;
$: bottomPath = `M 0 ${bottomY} Q 8 ${bottomY} 8 ${bottomY + 4} M 56 ${bottomY + 4} Q 56 ${bottomY} 64 ${bottomY}`;
$: bodyPath = `M 0 ${topY} L 64 ${topY} L 64 ${bottomY} L 0 ${bottomY} Z`;
const yTop = 16;
const yBottom = 48;
</script>
<svg viewBox="0 0 64 64" width="64" height="64" class="bridge">
{#if !isOver}
<path d={bodyPath} fill={s.color} />
<path d={`M 0 ${topY} L 64 ${topY}`} stroke={s.stroke} stroke-width="2" />
<path d={`M 0 ${bottomY} L 64 ${bottomY}`} stroke={s.stroke} stroke-width="2" />
{#if isOver}
<BaseTrack {activeElec} height={64} />
<!-- White edge, to provide separation between abutment line and bridge content -->
<rect x="0" y={32 - s.width / 2 - 4} width="64" height={s.width + 8} fill="white" />
<rect x="6" y={32 - s.width / 2} width="52" height={s.width} fill={s.bg} />
{#if s.type === 'road'}
<line
x1={xLeft}
y1="32"
x2={xRight}
y2="32"
stroke="white"
stroke-width="1.5"
stroke-dasharray="4 3"
opacity="0.6"
/>
{:else if s.type === 'rail'}
<line x1={xLeft} y1="28" x2={xRight} y2="28" stroke="#cbd5e1" stroke-width="1.5" />
<line x1={xLeft} y1="36" x2={xRight} y2="36" stroke="#cbd5e1" stroke-width="1.5" />
{/if}
<g fill="none" stroke={s.stroke} stroke-width="2.5" stroke-linecap="square">
<path
d={`M 20 ${32 - s.width / 2 - 6} L 20 ${32 - s.width / 2 - 4} L 44 ${32 - s.width / 2 - 4} L 44 ${32 - s.width / 2 - 6}`}
/>
<path
d={`M 20 ${32 + s.width / 2 + 6} L 20 ${32 + s.width / 2 + 4} L 44 ${32 + s.width / 2 + 4} L 44 ${32 + s.width / 2 + 6}`}
/>
</g>
{:else}
<BaseTrack {activeElec} height={64} />
<rect x="6" y={32 - s.width / 2} width="52" height={s.width} fill={s.bg} />
<path d={bodyPath} fill="white" /> <path d={bodyPath} fill={s.color} />
{#if s.type === 'road'}
<line
x1={xLeft}
y1="32"
x2={xRight}
y2="32"
stroke="white"
stroke-width="1.5"
stroke-dasharray="4 3"
opacity="0.6"
/>
{:else if s.type === 'rail'}
<line x1={xLeft} y1="28" x2={xRight} y2="28" stroke="#cbd5e1" stroke-width="1.5" />
<line x1={xLeft} y1="36" x2={xRight} y2="36" stroke="#cbd5e1" stroke-width="1.5" />
{/if}
<g fill="none" stroke={s.stroke} stroke-width="2.5" stroke-linecap="round">
<path d={`M 0 ${topY} L 64 ${topY}`} />
<path d={`M 0 ${bottomY} L 64 ${bottomY}`} />
<path d={`M 0 ${topY} Q 4 ${topY} 4 ${topY - 6}`} />
<path d={`M 64 ${topY} Q 60 ${topY} 60 ${topY - 6}`} />
<path d={`M 0 ${bottomY} Q 4 ${bottomY} 4 ${bottomY + 6}`} />
<path d={`M 64 ${bottomY} Q 60 ${bottomY} 60 ${bottomY + 6}`} />
<rect x="26" y={yTop} width="12" height="64" fill="white" />
<g stroke={s.stroke} stroke-width="2.5" fill="none" stroke-linecap="square">
<path
d={`M 24 ${32 - s.width / 2 - 5} L 25 ${32 - s.width / 2 - 5} L 25 ${32 - s.width / 2 + s.width + 5} L 24 ${32 - s.width / 2 + s.width + 5}`}
/>
<path
d={`M 40 ${32 - s.width / 2 - 5} L 39 ${32 - s.width / 2 - 5} L 39 ${32 - s.width / 2 + s.width + 5} L 40 ${32 - s.width / 2 + s.width + 5}`}
/>
</g>
{#if feature.roadName}
<BaseTrack {activeElec} height={64} />
{/if}
{#if feature.roadName}
<g class="label-group">
<rect
x={32 - feature.roadName.length * 3 - 4}
y="27"
width={feature.roadName.length * 6 + 8}
height="10"
stroke={s.textBorder || s.textBg}
stroke-width="1"
fill={s.textBg}
rx="3"
ry="4"
/>
<text
x="32"
y="32"
text-anchor="middle"
dominant-baseline="central"
fill={s.text}
font-family="sans-serif"
font-weight="bold"
font-size={s.width > 20 ? '10' : '8'}
style="pointer-events: none; text-transform: uppercase; letter-spacing: 0.5px;"
font-family="ui-monospace, monospace"
font-weight="900"
font-size="8"
>
{feature.roadName}
</text>
{/if}
</g>
{/if}
</svg>
<style>
svg {
display: block;
overflow: visible;
}
text {
user-select: none;
text-transform: uppercase;
letter-spacing: 0.5px;
pointer-events: none;
}
</style>

View File

@@ -1,13 +1,14 @@
<script lang="ts">
import { getElecColour } from '$lib/railStyles';
import type { ElecType } from '$lib/railStyles';
export let feature: {
from: {
elec: string;
elec: ElecType;
eco?: string;
};
to: {
elec: string;
elec: ElecType;
eco?: string;
};
};

View File

@@ -1,43 +1,48 @@
<script lang="ts">
import { getElecColour } from '$lib/railStyles';
import BaseTrack from '$lib/components/mapIcons/BaseTrack.svelte';
import { getElecColour } from '$lib/railStyles';
import BaseTrack from '$lib/components/mapIcons/BaseTrack.svelte';
export let feature: {
direction: 'up' | 'down';
diverges: 'left' | 'right' | 'both';
elecBranch?: string;
};
export let activeElec: any;
export let reversed: boolean = false;
export let feature: {
direction: 'up' | 'down';
diverges: 'left' | 'right' | 'both';
elecBranch?: string;
};
export let activeElec: any;
export let reversed: boolean = false;
$: isUp = feature.direction === 'up';
$: visualUp = reversed ? !isUp : isUp;
$: isUp = feature.direction === 'up';
$: visualUp = reversed ? !isUp : isUp;
$: visualSide = (side: 'left' | 'right') => {
if (!reversed) return side;
return side === 'left' ? 'right' : 'left';
};
const getPath = (side: 'left' | 'right') => {
const yStart = visualUp ? 64 : 0;
const yEnd = visualUp ? 8 : 56;
const xEnd = side === 'right' ? 56 : 8;
return `M 32 ${yStart} Q 32 32 ${xEnd} ${yEnd}`;
};
const getPath = (side: 'left' | 'right') => {
const yStart = visualUp ? 64 : 8;
const yEnd = visualUp ? 8 : 56;
const xEnd = side === 'right' ? 56 : 8;
return `M 32 ${yStart} Q 32 32 ${xEnd} ${yEnd}`;
};
$: paths = (() => {
if (feature.diverges === 'both') return [getPath('left'), getPath('right')];
return [getPath(feature.diverges)];
})();
$: paths = (() => {
if (feature.diverges === 'both') return [getPath('left'), getPath('right')];
const effectiveSide = visualSide(feature.diverges as 'left' | 'right');
return [getPath(effectiveSide)];
})();
$: branchColour = getElecColour(feature.elecBranch || activeElec);
$: branchColour = getElecColour(feature.elecBranch || activeElec);
</script>
<svg viewBox="0 0 64 64" width="64" height="64" class="junction">
{#each paths as d}
<path {d} fill="none" stroke={branchColour} stroke-width="5" stroke-linecap="round" />
{/each}
<BaseTrack {activeElec} height={64} />
{#each paths as d (d)}
<path {d} fill="none" stroke={branchColour} stroke-width="5" stroke-linecap="round" />
{/each}
<BaseTrack {activeElec} height={64} />
</svg>
<style>
svg {
display: block;
overflow: visible;
}
svg {
display: block;
overflow: visible;
}
</style>

View File

@@ -1,93 +1,122 @@
<script lang="ts">
export let feature: {
routeName: string;
routeId: string;
};
export let feature: {
routeName: string;
routeId: string;
entryPoint: string;
};
</script>
<div class="link-wrapper">
<a href="/map/{feature.routeId}" class="wide-button">
<div class="accent-bar"></div>
<div class="content">
<span class="sub-text">Continue to next map</span>
<span class="main-text">{feature.routeName}</span>
</div>
<div class="icon">
<svg viewBox="0 0 24 24" width="20" height="20">
<path d="M5 12h14M12 5l7 7-7 7" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</a>
<a href="/map/{feature.routeId}#{feature.entryPoint}" class="wide-button">
<div class="content">
<div class="header-row">
<span class="sub-text">Go to</span>
<span class="route-id-chip">{feature.routeId}</span>
</div>
<span class="main-text">{feature.routeName}</span>
</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>
</div>
</a>
</div>
<style>
.link-wrapper {
position: relative;
z-index: 100;
padding: 12px;
width: 100%;
box-sizing: border-box;
}
.link-wrapper {
padding: 16px;
width: 100%;
box-sizing: border-box;
}
.wide-button {
display: flex;
align-items: center;
background: #ffffff;
border: 2px solid #e2e8f0;
border-radius: 12px;
text-decoration: none;
overflow: hidden;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
}
.wide-button {
display: flex;
align-items: center;
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 16px;
text-decoration: none;
padding: 12px 16px;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.accent-bar {
width: 6px;
align-self: stretch;
background: #475569;
}
.content {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.content {
flex: 1;
padding: 12px 16px;
display: flex;
flex-direction: column;
gap: 2px;
}
.header-row {
display: flex;
align-items: center;
gap: 8px;
}
.sub-text {
font-size: 10px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #94a3b8;
}
.sub-text {
font-size: 0.65rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #64748b;
}
.main-text {
font-size: 16px;
font-weight: 800;
color: #1e293b;
}
.route-id-chip {
font-size: 0.6rem;
font-weight: 800;
background: #f1f5f9;
color: #475569;
padding: 2px 6px;
border-radius: 6px;
text-transform: uppercase;
}
.icon {
padding-right: 20px;
color: #cbd5e1;
transition: transform 0.2s ease;
}
.main-text {
font-size: 1rem;
font-weight: 800;
color: #0f172a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.icon-circle {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: #f8fafc;
border-radius: 50%;
color: #94a3b8;
transition: all 0.3s ease;
margin-left: 12px;
}
.wide-button:hover {
border-color: #94a3b8;
background: #f8fafc;
}
.wide-button:hover {
border-color: #cbd5e1;
background: #fdfdfd;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
}
.wide-button:hover .icon {
color: #475569;
transform: translateX(4px);
}
.wide-button:hover .icon-circle {
background: #4f46e5;
color: #ffffff;
transform: rotate(-45deg);
}
.wide-button:active {
transform: scale(0.98);
background: #f1f5f9;
}
</style>
.wide-button:active {
transform: scale(0.98);
}
</style>

View File

@@ -6,7 +6,14 @@
to: string;
};
export let reversed: boolean = false;
export let activeElec: any;
$: effectiveFeature = {
from: reversed ? feature.to : feature.from,
to: reversed ? feature.from : feature.to
};
</script>
<svg viewBox="0 0 64 64" width="64" height="64" style="overflow: visible;">
@@ -24,10 +31,10 @@
<g font-family="sans-serif" font-weight="800" font-size="11">
<text x="70" y="24" fill="#4338ca" style="text-transform: uppercase;">
{feature.from}
{effectiveFeature.from}
</text>
<text x="70" y="46" fill="#4338ca" style="text-transform: uppercase;">
{feature.to}
{effectiveFeature.to}
</text>
</g>
</svg>

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

@@ -1,56 +1,59 @@
<script lang="ts">
import BaseTrack from "./BaseTrack.svelte";
import BaseTrack from './BaseTrack.svelte';
export let feature: {
tunnelType: "start" | "whole" | "end";
length: string;
};
export let feature: {
tunnelType: 'start' | 'whole' | 'end';
length: string;
};
export let activeElec: any;
export let activeElec: any;
export let reversed: boolean = false;
export let reversed: boolean = false;
const portalColour = "#475569"; // Slate grey
const portalColour = '#475569'; // Slate grey
$: effectiveType = (() => {
if (!reversed || feature.tunnelType === 'whole') return feature.tunnelType;
return feature.tunnelType === 'start' ? 'end' : 'start';
})();
$: effectiveType = (() => {
if (!reversed || feature.tunnelType === 'whole') return feature.tunnelType;
return feature.tunnelType === 'start' ? 'end' : 'start';
})();
</script>
<svg viewBox="0 0 64 64" width="64" height="64" class="tunnel">
<BaseTrack {activeElec} height={64} />
<BaseTrack {activeElec} height={64} />
<g fill="none" stroke={portalColour} stroke-width="3" stroke-linecap="round">
{#if effectiveType === 'whole'}
<path d="M 16 12 Q 32 24 48 12" />
<path d="M 16 52 Q 32 40 48 52" />
<g fill="none" stroke={portalColour} stroke-width="3" stroke-linecap="round">
{#if effectiveType === 'whole'}
<path d="M 16 12 Q 32 24 48 12" />
<path d="M 16 52 Q 32 40 48 52" />
{:else if effectiveType === 'start'}
<path d="M 16 12 Q 32 24 48 12" />
{:else if effectiveType === 'end'}
<path d="M 16 52 Q 32 40 48 52" />
{/if}
</g>
{:else if effectiveType === 'start'}
<path d="M 16 12 Q 32 24 48 12" />
{:else if effectiveType === 'end'}
<path d="M 16 52 Q 32 40 48 52" />
{/if}
</g>
{#if feature.tunnelType === 'whole' && feature.length}
<rect x="12" y="26" width="40" height="12" fill="white" />
<text
x="32"
y="35"
text-anchor="middle"
font-size="8.5"
font-weight="bold"
fill={portalColour}
class="t-text"
>
{feature.length}
</text>
{/if}
{#if feature.tunnelType === 'whole' && feature.length}
<rect x="12" y="26" width="40" height="12" fill="white" />
<text
x="32"
y="35"
text-anchor="middle"
font-size="8.5"
font-weight="bold"
fill={portalColour}
class="t-text"
>
{feature.length}
</text>
{/if}
</svg>
<style>
svg { display: block; overflow: visible; }
.t-text { font-family: ui-monospace, monospace; }
</style>
svg {
display: block;
overflow: visible;
}
.t-text {
font-family: ui-monospace, monospace;
}
</style>

View File

@@ -16,6 +16,7 @@ export const components = {
crossovers: Crossover,
siteof: SiteOf,
bridge: Bridge,
minorBridge: Bridge,
crossover: Crossover,
crossing: Crossing,
loop: Loop,
@@ -23,5 +24,5 @@ export const components = {
signallerChange: SignallerChange,
electrificationChange: ElectrificationChange,
default: BaseTrack,
tunnel: Tunnel,
tunnel: Tunnel
};

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,116 +1,315 @@
<script lang="ts">
import RouteRow from '$lib/components/RouteRow.svelte';
import logo from '$lib/assets/round-logo-text.svg';
import type { PageData } from './$types';
import { resolve } from '$app/paths';
const dummyFeatures = [
{
type: 'station',
miles: 0,
chains: 0,
name: 'Testington',
description: 'Terminus',
terminus: true,
elec: '25kvac'
},
{
type: 'station',
miles: 0,
chains: 76,
name: 'Closeby',
description: 'Temporarily closed',
elec: '25kvac'
},
{
type: 'junction',
name: 'Test Junction',
diverges: 'right',
direction: 'down',
elec: '25kvac',
elecBranch: 'none',
miles: 1,
chains: 13
},
{
type: 'bridge',
position: 'over',
category: 'pipeline',
name: 'Waterway Bridge',
roadName: 'TfL (LU)',
elec: '25kvac',
miles: 1,
chains: 41
},
{
type: 'crossover',
name: 'Dolphin junction',
elec: '25kvac',
miles: 1,
chains: 42
},
{
type: 'junction',
name: 'Test Junction South',
description: 'To: Banbury West Junction & Birmingham',
diverges: 'left',
direction: 'down',
elec: '25kvac',
elecBranch: '650vdc',
miles: 1,
chains: 42
},
{
type: 'electrificationChange',
from: {
elec: '25kvac',
eco: 'Didcot'
},
to: {
elec: 'none'
}
},
{
type: 'crossing',
kind: 'ahb',
elec: '25kvac',
name: 'Swindon Lane',
description: 'Controlled by TVSC (Level Crossing WS)'
},
{
type: 'loop',
side: 'both',
elec: '25kvac',
elecLoop: 'none'
},
{
type: 'signallerChange',
from: 'TVSC - Didcot WS (SB)',
to: 'TVSC - Swindon WS (SN)',
elec: '25kvac'
},
{
type: 'station',
miles: 4,
chains: 13,
name: 'Powerless',
description: "Doesn't exist",
elec: '750vdc'
let { data }: { data: PageData } = $props();
let searchTerm = $state('');
let filteredMaps = $derived(
data.maps.filter((m) => {
const term = 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';
const checkedDate = new Date(dateVal).getTime();
const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000;
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>
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
<div class="page-wrapper">
<header class="main-header">
<div class="brand-container">
<img src={logo} alt="OwlBoard Logo" class="main-logo" />
</div>
</header>
<div class="map-container">
{#each dummyFeatures as feature}
<RouteRow {feature} activeElec={feature.elec} />
{/each}
<div class="list-container">
<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={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)}">
{#if isVerifiedRecently(map.checked) === 'verified'}
Reviewed
{:else if isVerifiedRecently(map.checked) === 'stale'}
Needs review
{:else}
Draft
{/if}
</span>
</div>
<div class="card-body">
<div class="location origin">{map.routeStart}</div>
<div class="path-arrow">to</div>
<div class="location destination">{map.routeEnd}</div>
</div>
<div class="card-footer">
<span>Updated: {formatDate(map.updated)}</span>
{#if map.checked}
<span>• Checked: {formatDate(map.checked)}</span>
{/if}
</div>
</a>
{:else}
<div class="empty-state">No maps found.</div>
{/each}
</div>
</div>
<style>
.map-container {
width: 100%;
max-width: 600px;
/* Mobile-First Base Styles */
:global(body) {
margin: 0;
background-color: #404c55;
background-image: radial-gradient(#2b343c, #404c55);
color: #0f172a;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.page-wrapper {
padding: 1rem;
max-width: 800px;
margin: 0 auto;
font-family: sans-serif;
}
.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;
}
.main-logo {
height: 52px;
width: auto;
display: block;
}
.search-input {
width: 100%;
max-width: 500px;
font-family: 'urwgothic';
margin: auto;
height: 40px;
padding: 0.8rem 1rem;
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:hover {
box-shadow: rgba(0, 0, 0, 0.46);
}
.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: #3c6f79;
padding: 1.25rem;
border-radius: 35px;
text-decoration: none;
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.99); /* Tactile feedback for mobile */
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.route-id {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-weight: 800;
color: #fff;
background: #2d2d2d;
text-shadow: none;
padding: 0.2rem 0.5rem;
border-radius: 6px;
}
.card-body {
margin-bottom: 1rem;
}
.location {
font-family: 'urwgothic';
font-size: 1.23rem;
font-weight: 700;
}
.path-arrow {
color: #fff;
font-family: 'urwgothic';
font-size: 0.9rem;
margin: 0.2rem 0;
}
.card-footer {
font-size: 0.7rem;
color: #e2ebeb;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.05em;
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;
white-space: nowrap;
}
.verified {
background: #dcfce7;
color: #166534;
}
.stale {
background: #fef3c7;
color: #92400e;
border: 1px solid #fcd34d;
}
.draft {
background: #f1f5f9;
color: #475569;
}
.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;
}
.button-link:hover {
background: #2b2b2b;
}
</style>

26
src/routes/+page.ts Normal file
View File

@@ -0,0 +1,26 @@
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');
if (!response.ok) {
return { maps: [] };
}
const maps = await response.json();
return {
maps: maps.sort((a: any, b: any) => {
return Number(a.routeId) - Number(b.routeId);
}) as RouteMapIndex[]
};
};

View File

@@ -2,7 +2,10 @@ import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = () => {
return json({ status: 'ok', uptime: process.uptime() }, {
status: 200
});
};
return json(
{ status: 'ok', uptime: process.uptime() },
{
status: 200
}
);
};

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

@@ -1,15 +1,20 @@
<script lang="ts">
import RouteRow from '$lib/components/RouteRow.svelte';
import RouteEndLink from '$lib/components/mapIcons/RouteEndLink.svelte';
import RouteEndLink from '$lib/components/mapIcons/RouteEndLink.svelte';
import { slide } from 'svelte/transition';
import { resolve } from '$app/paths';
// data.route contains: routeName, routeId, elecStart, elecEnd, routeDetail[]
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;
let reversed = false; // Reverses Array, and passes value down to children
let visibleTypes = {
station: true,
minorBridge: false,
bridge: true,
crossovers: true,
loop: true,
@@ -17,7 +22,7 @@
electrificationChange: true,
siteof: true,
junction: true,
tunnel: true,
tunnel: true
};
let showFilters = false;
@@ -59,23 +64,44 @@
<div class="map-layout">
<header class="top-nav">
<h1>{data.route.routeName}</h1>
<span class="route-code">{data.route.routeId}</span>
<div class="nav-cluster">
<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">
{#if data?.route}
<h1 class="primary-station">
{reversed ? data.route.routeEnd : data.route.routeStart}
</h1>
<span class="secondary-station">
to {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"
@@ -91,11 +117,11 @@
<div class="drawer-content">
<div class="filter-flex">
{#each Object.keys(visibleTypes) as type}
{#each Object.keys(visibleTypes) as type (type)}
<button
class="filter-chip"
class:active={visibleTypes[type]}
on:click={() => toggleFilter(type)}
onclick={() => toggleFilter(type)}
>
{formatLabel(type)}
</button>
@@ -108,55 +134,121 @@
<main class="map-spine">
<div class="container">
{#each filteredFeatures as f, i (`${f.type}-${f.miles}-${f.chains}-${i}`)}
{#if (f.type === 'continues')}
<RouteEndLink feature={f} />
{:else}
<RouteRow feature={f} activeElec={f.activeElec} {reversed} />
{/if}
{#if f.type === 'continues'}
<RouteEndLink feature={f} />
{:else}
<RouteRow feature={f} activeElec={f.activeElec} {reversed} />
{/if}
{/each}
</div>
</main>
</div>
<style>
.map-layout {
display: flex;
flex-direction: column;
background: #ffffff;
min-height: 100vh;
margin-top: 0;
padding-top: 0;
}
.top-nav {
position: sticky;
position: fixed;
width: 100%;
box-sizing: border-box;
top: 0;
z-index: 100;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-bottom: 1px solid #e2e8f0;
padding: 0.75rem 1rem;
height: 80px;
left: 0;
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
padding: 0 0;
background: #3c6f79;
color: #e1ebeb;
gap: 1rem;
z-index: 10000;
}
h1 {
flex: 1;
.nav-cluster {
display: flex;
align-items: center;
gap: 1rem;
min-width: 0; /* Prevents flex children from overflowing */
}
.home-link {
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
padding-left: 0;
margin-left: 15px;
flex-shrink: 0;
transition: all 0.3s ease;
}
@media (max-width: 350px) {
.home-link {
width: 42px;
height: 42px;
}
}
.home-link:hover {
transform: translateY(-1px) scale(1.05);
filter: brightness(1.1);
}
.home-link:active {
transform: scale(0.95);
}
.route-stack {
display: flex;
flex-direction: column;
min-width: 0;
margin: 0;
font-size: 1.1rem;
}
.primary-station {
font-size: 1rem;
font-weight: 800;
line-height: 1.2;
color: #0f172a;
letter-spacing: -0.02em;
white-space: normal;
overflow-wrap: break-word;
margin: 0;
white-space: nowrap;
color: #cce9e9;
overflow: hidden;
text-overflow: ellipsis;
}
.secondary-station {
font-size: 0.7rem;
color: #cce9e9;
text-transform: uppercase;
font-weight: 600;
}
.quick-actions {
display: flex;
gap: 0.5rem;
margin-right: 15px;
}
.map-spine {
padding-top: 72px;
}
@media (min-width: 536px) {
.primary-station {
font-size: 1.5rem;
}
.secondary-station {
font-size: 1rem;
}
}
@media (min-width: 768px) {
.top-nav {
padding: 1rem 2rem;
}
h1 {
font-size: 1.5rem;
letter-spacing: -0.03em;
@@ -175,6 +267,7 @@
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 150;
transition: all 0.3s ease;
}
.filter-drawer {
@@ -182,7 +275,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);
@@ -195,17 +288,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;
@@ -214,7 +307,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 {
@@ -231,15 +329,53 @@
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;
}
.quick-actions {
display: flex;
gap: 0.5rem;
flex-shrink: 0;
}
.icon-btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
border: none;
padding: 0.5rem 0.8rem;
border-radius: 12px;
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: 350px) {
.icon-btn {
padding: 0.3rem 0.3rem;
}
}
.icon-btn:hover {
background: #2d2d2d;
}
.icon-btn:active {
transform: scale(0.96);
}
</style>

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
routeStart: Reading
routeEnd: Bristol TM
routeId: 0002
updated: 2026-02-04
checked:
signallerStart: TVSC Reading WS
signallerEnd: TVSC Temple Meads WS
elecStart:
elec: 25kvac
eco: Didcot (TVSC)
elecEnd:
elec: none
routeDetail:
- type: continues
routeName: Paddington - Reading
routeId: '0001'
- 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 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

View File

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

View File

@@ -0,0 +1,652 @@
routeStart: Wootton Bassett Junction
routeEnd: Stoke Gifford Junction
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-junction
routeId: '0002'
- type: junction
diverges: right
direction: down
name: Wootton Basset Junction
description: to Chippenham & Bristol via Bath
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 Junction
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 Junction
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 Junction
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 Junction
routeEnd: Gloucester
routeId: 2420
updated: 2026-02-11
checked: 2026-02-11
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
}
}
});