Add features:

- manifest.json with PNG icon set.
 - reset option against QuickLinks storage
 - 'Preferences' store, ready for preferences page - including location boolean... Important!
 - Minor service-worker improvements for better offline fallbacks
This commit is contained in:
2026-05-14 19:39:05 +01:00
parent bc56e66178
commit 25b64edabd
11 changed files with 154 additions and 16 deletions

View File

@@ -3,12 +3,14 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="title" content="OwlBoard | Your fastest route to live and reference data" />
<meta
name="description"
content="Live station departures, Live train tracking, PIS Codes & more"
/>
<!-- PWA Requirements -->
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
<link rel="apple-touch-icon" href="%sveltekit.assets%/icons/apple-touch-icon.png" />
<meta name="theme-color" content="#4fd1d1" />
<link rel="canonical" href="https://owlboard.info" />
<meta property="og:type" content="website" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 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 562.1 562.1"><defs><radialGradient xlink:href="#a" id="b" cx="256" cy="256" r="254" 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.3 283a411.3 394 0 0 1-410.7 394 411.3 394 0 0 1-412-392.8 411.3 394 0 0 1 409.5-395.1 411.3 394 0 0 1 413.2 391.4" transform="translate(-2 -2)"/><path fill="#4fd1d1" d="M281 81a200 200 0 0 0-153.4 71.8 120 120 0 0 0 50.5 25.8c9 2.3 8.9 2.3 15.6-.6 50-21.6 124.4-21.6 175.2-.1 13.3 5.6 44.6-6.5 65.9-24.8a200 200 0 0 0-153.8-72m-161.9 82.7a200 200 0 0 0-38 117.3 200 200 0 0 0 200 200 200 200 0 0 0 200-200 200 200 0 0 0-37.8-116.8 96 96 0 0 1-17.7 46.6l-2.8 4 1.3 1.8c73.3 99.2-14.3 222.2-116.7 163.6-2.5-1.4-4.7-2.4-5-2.1s-10.5 19.6-18 34c-3.4 6.5-2.7 6.6-6.3-.3-9.2-17.5-18-34-18.3-34-.1 0-2.3 1-4.7 2.5-101.3 58-190.7-65.7-117.4-162.4q2.3-3 1.8-3.6a102 102 0 0 1-20.4-50.6m86.5 57q-14.6 0-29.8 7.5C108 261.8 143.1 366.4 217.5 352a68 68 0 0 0 43.3-28.4c2.4-3.4 3.3-4.2 3.7-3.7l8.5 14.6c9.4 16.3 8 15 11.4 9 5.1-9 13.7-23.5 14-24 .3-.2 1.9 1.8 3.6 4.3 31 45.5 101.6 35.6 118.3-16.5a66.4 66.4 0 0 0-83.9-83c-23.3 7.7-38.7 24.4-53.8 58.4l-1.3 3-2.7-6c-17-38.5-43.7-59.5-73-59.2m147.2 34.7a32 32 0 0 1 23.7 9.5c22.8 22.8.7 60.8-30.9 53.1a32 32 0 0 1-23.2-37.3 31.5 31.5 0 0 1 30.4-25.3m-145.8 0c16.4-.8 32.7 11 33.9 30.1a32.2 32.2 0 0 1-40.1 32.5 31.5 31.5 0 0 1-15-52.9 32 32 0 0 1 21.2-9.6m12 9.6q-3.3 0-5.8 3c-6.3 7.4 2.5 17.7 11 13a9 9 0 0 0 4.2-7.5c0-5.4-4.8-8.9-9.4-8.5m123.1 0c-6 .5-9.6 6.5-7 12.3 2 4.5 8.3 6.3 12.2 3.6 7.2-4.9 4.7-15.3-4-15.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,43 @@
import { browser } from '$app/environment';
export interface Preferences {
locationEnabled: boolean;
}
const STORAGE_KEY = 'ob_pref';
const DEFAULT_PREFS: Preferences = {
locationEnabled: false,
};
function loadPrefs(): Preferences {
if (!browser) return DEFAULT_PREFS;
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) return DEFAULT_PREFS;
try {
return { ...DEFAULT_PREFS, ...JSON.parse(stored) };
} catch {
return DEFAULT_PREFS;
}
}
class PreferencesStore {
current = $state<Preferences>(loadPrefs());
constructor() {
$effect.root(() => {
$effect(() => {
if (browser) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(this.current));
}
});
});
}
toggleLocation(enabled: boolean) {
this.current.locationEnabled = enabled;
}
}
export const prefs = new PreferencesStore();

View File

@@ -15,7 +15,11 @@ class QuickLinksService {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('ql');
if (saved) {
try {
this.#links = JSON.parse(saved);
} catch {
this.#links = [];
}
}
}
}
@@ -53,8 +57,17 @@ class QuickLinksService {
this.#links = sorted.slice(0, MAX_ENTRIES);
if (typeof window !== 'undefined') {
localStorage.setItem('ql', JSON.stringify(this.#links));
}
}
reset() {
this.#links = [];
if (typeof window !== 'undefined') {
localStorage.removeItem('ql');
}
}
}
export const quickLinks = new QuickLinksService();

View File

@@ -6,9 +6,17 @@ import { build, files, version } from '$service-worker';
const sw = self as unknown as ServiceWorkerGlobalScope;
const CACHE_NAME = `owlboard-cache-${version}`;
const ASSETS = [...build, ...files, '/'];
const ASSETS = [
...build,
...files.filter(file =>
!file.endsWith('manifest.json') &&
!file.startsWith('/icons/')
),
'/',
];
sw.addEventListener('install', (event) => {
sw.skipWaiting();
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
);
@@ -16,11 +24,14 @@ sw.addEventListener('install', (event) => {
sw.addEventListener('activate', (event) => {
event.waitUntil(
Promise.all([
caches.keys().then((keys) => {
return Promise.all(
keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))
);
})
}),
sw.clients.claim()
])
);
});
@@ -30,6 +41,12 @@ sw.addEventListener('fetch', (event) => {
const url = new URL(request.url);
// Uncached static assets
if (url.pathname.endsWith('manifest.json') || url.pathname.includes('/icons/')) {
event.respondWith(fetch(request));
return;
}
// 1. Static Assets (Cache-First)
if (ASSETS.includes(url.pathname) || url.pathname.startsWith('/_app/')) {
event.respondWith(

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/icons/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
static/icons/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

62
static/manifest.json Normal file
View File

@@ -0,0 +1,62 @@
{
"name": "OwlBoard",
"short_name": "OwlBoard",
"description": "Real-time UK rail data and operational tools for staff and enthusiasts.",
"id": "/",
"start_url": "/",
"scope": "/",
"display": "standalone",
"display_override": [
"window-controls-overlay",
"standalone",
"minimal-ui"
],
"background_color": "#404c55",
"theme_color": "#4fd1d1",
"orientation": "portrait",
"categories": [
"transportation",
"utilities"
],
"dir": "ltr",
"lang": "en-GB",
"prefer_related_applications": false,
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"shortcuts": [
{
"name": "Find PIS",
"short_name": "PIS",
"description": "Find PIS Codes fast",
"url": "/pis",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192"
}
]
}
],
"handle_links": "preferred",
"launch_handler": {
"client_mode": "focus-existing"
}
}