.claude/settings.local.json
+5
-1
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 6bd2218..3daa45c 100644
@@ -6,7 +6,11 @@
"PowerShell(Get-ChildItem -Path \"d:\\\\BirdGO\" -Recurse -Directory | Where-Object { $_.Name -match \"\\(src|audio|lib\\)\" } | Select-Object -ExpandProperty FullName | head -30)",
"Bash(xargs ls -la)",
"WebFetch(domain:svelte.dev)",
"Bash(deno task *)"
"Bash(deno task *)",
"Bash(Get-ChildItem -Path \"d:\\\\BirdGO\\\\static\" -Recurse)",
"Bash(Select-Object FullName, PSIsContainer)",
"Bash(Format-Table -AutoSize)",
"PowerShell(Get-ChildItem -Path \"d:\\\\BirdGO\\\\static\" -Recurse | ForEach-Object { $_.FullName })"
]
}
}
src/lib/labels.ts
+1
-1
diff --git a/src/lib/labels.ts b/src/lib/labels.ts
index b02b679..172a69a 100644
@@ -33,7 +33,7 @@ const supportedLanguages = [
function resolveLocale(): Array<(typeof supportedLanguages)[number]> {
if (typeof navigator === "undefined") return ["en_us"];
const navigatorLanguages = navigator.languages.slice(1).map((l) =>
const navigatorLanguages = navigator.languages.map((l) =>
l.replace("-", "_").toLowerCase()
);
const foundLanguages = navigatorLanguages.flatMap(
src/routes/+layout.svelte
+2
-0
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 2149dd1..a27bbd7 100644
@@ -6,6 +6,8 @@
<svelte:head>
<link rel="icon" href={favicon} />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0e1a10" />
</svelte:head>
<main>
src/service-worker.ts
+80
-0
diff --git a/src/service-worker.ts b/src/service-worker.ts
new file mode 100644
index 0000000..b035d6e
@@ -0,0 +1,80 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
/// <reference types="@sveltejs/kit" />
import { build, files, version } from "$service-worker";
const sw = self as unknown as ServiceWorkerGlobalScope;
const SHELL_CACHE = `shell-${version}`;
const MODEL_CACHE = "birdnet-models";
// Precache the app shell and lightweight static files.
// Model binaries and label JSON are excluded — they're too large to cache at
// install time and are instead cached on first use via the fetch handler.
const SHELL_ASSETS = [
...build,
...files.filter((f) => !f.startsWith("/models/") && f !== "/robots.txt"),
"/",
];
sw.addEventListener("install", (event) => {
event.waitUntil(
caches.open(SHELL_CACHE).then((cache) => cache.addAll(SHELL_ASSETS)),
);
sw.skipWaiting();
});
sw.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys
.filter((k) => k !== SHELL_CACHE && k !== MODEL_CACHE)
.map((k) => caches.delete(k)),
),
),
);
sw.clients.claim();
});
sw.addEventListener("fetch", (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== "GET" || url.origin !== sw.location.origin) return;
// Model files: cache-first with a stable cache that outlives app updates.
if (url.pathname.startsWith("/models/")) {
event.respondWith(
caches.open(MODEL_CACHE).then(async (cache) => {
const cached = await cache.match(request);
if (cached) return cached;
const response = await fetch(request);
cache.put(request, response.clone());
return response;
}),
);
return;
}
// Everything else: serve from shell cache when available, fall back to
// network, and for navigation failures return the cached root as SPA shell.
event.respondWith(
caches.match(request).then(async (cached) => {
if (cached) return cached;
try {
return await fetch(request);
} catch {
if (request.mode === "navigate") {
return (
(await caches.match("/")) ??
new Response("Offline", { status: 503 })
);
}
return new Response("Offline", { status: 503 });
}
}),
);
});
static/favicon.svg
+16
-0
diff --git a/static/favicon.svg b/static/favicon.svg
new file mode 100644
index 0000000..77c50de
@@ -0,0 +1,16 @@
<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>
static/manifest.json
+18
-0
diff --git a/static/manifest.json b/static/manifest.json
new file mode 100644
index 0000000..9e897dc
@@ -0,0 +1,18 @@
{
"name": "BirdGO",
"short_name": "BirdGO",
"description": "Identify birds by sound, collect them all.",
"theme_color": "#0e1a10",
"background_color": "#0e1a10",
"display": "standalone",
"orientation": "portrait",
"start_url": "/",
"icons": [
{
"src": "/favicon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any maskable"
}
]
}