6 Commits

Author SHA1 Message Date
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
14 changed files with 198 additions and 87 deletions

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

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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,12 +1,19 @@
<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>
@@ -18,9 +25,10 @@
</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>
@@ -29,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 {
@@ -50,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 {
@@ -62,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: hidden;
}
.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 {
@@ -76,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;
@@ -98,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;
@@ -111,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

@@ -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,4 +1,5 @@
<script lang="ts">
import { IconArrowNarrowRight } from '@tabler/icons-svelte';
export let feature: {
routeName: string;
routeId: string;
@@ -17,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>
@@ -69,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;
@@ -83,6 +77,7 @@
}
.main-text {
font-family: "urwgothic";
font-size: 1rem;
font-weight: 800;
color: #0f172a;
@@ -92,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 {
@@ -111,8 +105,7 @@
}
.wide-button:hover .icon-circle {
background: #4f46e5;
color: #ffffff;
background-color: #404c55;
transform: rotate(-45deg);
}

View File

@@ -9,21 +9,21 @@ export const prerender = true;
export const GET: RequestHandler = ({ url }) => {
const manifest = {
name: 'OwlBoard Maps',
short_name: 'OwlBoard Maps',
short_name: 'OB Maps',
start_url: '/',
display: 'standalone',
theme_color: '#3c6f79',
theme_color: '#4fd1d1',
background_color: '#3d4952',
icons: [
{
src: logo,
sizes: 'any',
sizes: '48x48 72x72 96x96 128x128 256x256 512x512 any',
type: 'image/svg+xml',
purpose: 'any'
},
{
src: maskableLogo,
sizes: 'any',
sizes: '48x48 72x72 96x96 128x128 256x256 512x512 any',
type: 'image/svg+xml',
purpose: 'maskable'
},

View File

@@ -5,6 +5,7 @@
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;
@@ -21,7 +22,8 @@
electrificationChange: true,
siteof: true,
junction: true,
tunnel: true
tunnel: true,
crossing: true,
};
let showFilters = false;
@@ -74,7 +76,8 @@
{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>
@@ -82,9 +85,9 @@
<div class="quick-actions">
<button class="icon-btn" onclick={() => (reversed = !reversed)}>
{reversed ? 'UP' : 'DN'}
<IconArrowsExchange />
</button>
<button class="icon-btn" onclick={() => (showFilters = !showFilters)}> Settings </button>
<button class="icon-btn" onclick={() => (showFilters = !showFilters)}> <IconSettings /> </button>
</div>
</header>
@@ -184,11 +187,20 @@
width: 52px;
height: 52px;
padding-left: 0;
margin-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 {
transform: translateY(-1px) scale(1.05);
filter: brightness(1.1);
@@ -200,8 +212,14 @@
.route-stack {
display: flex;
font-family: "urwgothic";
flex-direction: column;
min-width: 0;
margin-left: 0;
}
.route-stack-to {
text-transform: lowercase;
}
.primary-station {
@@ -217,32 +235,30 @@
.secondary-station {
font-size: 0.7rem;
color: #cce9e9;
text-transform: uppercase;
text-transform: capitalize;
font-weight: 600;
}
.quick-actions {
display: flex;
gap: 0.5rem;
margin-right: 15px;
}
.map-spine {
padding-top: 72px;
}
@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: 80px;
}
@media (min-width: 768px) {
h1 {
font-size: 1.5rem;
letter-spacing: -0.03em;
@@ -359,6 +375,12 @@
transition: all 0.3s ease;
}
@media (max-width: 390px) {
.icon-btn {
padding: 0.3rem 0.3rem;
}
}
.icon-btn:hover {
background: #2d2d2d;
}

View File

@@ -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
@@ -177,7 +177,7 @@ routeDetail:
chains: 45
- type: junction
name: Friars Junction
name: Friars Jn
diverges: left
direction: up
elecBranch: none
@@ -207,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
@@ -312,7 +312,7 @@ routeDetail:
chains: 46
- type: junction
name: West Ealing Junction
name: West Ealing Jn
diverges: left
direction: down
elecBranch: none
@@ -328,7 +328,7 @@ routeDetail:
chains: 71
- type: junction
name: Hanwell Junction
name: Hanwell Jn
diverges: left
direction: up
elecBranch: none
@@ -394,7 +394,7 @@ routeDetail:
chains: 46
- type: junction
name: Southall East Junction
name: Southall East Jn
diverges: right
direction: up
elecBranch: none
@@ -423,7 +423,7 @@ routeDetail:
chains: 53
- type: crossovers
name: Southall West Junction
name: Southall West Jn
description: Southall Sidings Diverge
miles: 9
chains: 70
@@ -490,7 +490,7 @@ routeDetail:
chains: 77
- type: junction
name: Heathrow Airport Junction
name: Heathrow Airport Jn
diverges: both
direction: down
miles: 11
@@ -506,7 +506,7 @@ routeDetail:
chains: 28
- type: crossovers
name: Stockley Bridge Junction
name: Stockley Bridge Jn
miles: 12
chains: 9
@@ -519,7 +519,7 @@ routeDetail:
chains: 22
- type: crossovers
name: West Drayton East Junction
name: West Drayton East Jn
miles: 12
chains: 67
@@ -544,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)
@@ -725,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
@@ -1048,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
@@ -1169,7 +1169,7 @@ routeDetail:
chains: 77
- type: crossovers
name: Kennet Bridge Junction
name: Kennet Bridge Jn
miles: 35
chains: 10
@@ -1182,7 +1182,7 @@ routeDetail:
chains: 28
- type: junction
name: Reading New Junction
name: Reading New Jn
diverges: right
direction: up
elecBranch: none
@@ -1199,7 +1199,7 @@ routeDetail:
chains: 40
- type: junction
name: Reading East Junction
name: Reading East Jn
diverges: left
direction: up
elecBranch: none
@@ -1222,3 +1222,4 @@ routeDetail:
- type: continues
routeName: Reading - Bristol TM
routeId: '0002'
entryPoint: reading

View File

@@ -14,6 +14,7 @@ routeDetail:
- type: continues
routeName: Paddington - Reading
routeId: '0001'
entryPoint: reading
- type: station
name: Reading
@@ -30,21 +31,25 @@ routeDetail:
- type: junction
diverges: right
direction: down
name: Westbury Line Junction
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 Junction
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: Reading High Level Junction
name: Reading High Level Jn
description: Down Reading Festival Connects to Down Main
miles: 36
chains: 47
@@ -52,7 +57,9 @@ routeDetail:
- type: junction
diverges: right
direction: up
name: Reading West Junction
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

@@ -1,5 +1,5 @@
routeStart: Wootton Bassett Junction
routeEnd: Stoke Gifford Junction
routeStart: Wootton Bassett Jn
routeEnd: Stoke Gifford Jn
routeId: 0240
updated: 2026-02-11
checked: 2026-02-11
@@ -14,14 +14,16 @@ elecEnd:
routeDetail:
- type: continues
routeName: Reading - Bristol TM
entryPoint: wootton-bassett-junction
entryPoint: wootton-bassett-jn
routeId: '0002'
- type: junction
diverges: right
direction: down
name: Wootton Basset Junction
name: Wootton Basset Jn
description: to Chippenham & Bristol via Bath
goto: "0002"
entryPoint: wootton-bassett-jn
miles: 83
chains: 7
@@ -486,7 +488,7 @@ routeDetail:
- type: junction
diverges: left
direction: up
name: Westerleigh Junction
name: Westerleigh Jn
miles: 107
chains: 14
description: Up/Dn Charfield towards Gloucester
@@ -614,7 +616,7 @@ routeDetail:
chains: 14
- type: crossovers
name: Stoke Gifford East Junction
name: Stoke Gifford East Jn
miles: 111
chains: 20
@@ -639,7 +641,7 @@ routeDetail:
chains: 77
- type: junction
name: Stoke Gifford No.1 Junction
name: Stoke Gifford No.1 Jn
diverges: right
direction: down
description: Up/Dn Filton & Bristol TM

View File

@@ -1,4 +1,4 @@
routeStart: Westerleigh Junction
routeStart: Westerleigh Jn
routeEnd: Gloucester
routeId: 2420
updated: 2026-02-11