Initial commit: Lageplan v1.0 - Next.js 15.5, React 19
0
public/.gitkeep
Normal file
BIN
public/0.5x/Element 1@0.5x.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
public/0.75x/Element 1@0.75x.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/1.5x/Element 1@1.5x.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/1x/Element 1.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
public/2x/Element 1@2x.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/3x/Element 1@3x.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
public/4x/Element 1@4x.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
public/Front_Pepe.gif
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
16
public/SVG/Element 1.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 751.13 754.43">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #e41313;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Ebene_1-2" data-name="Ebene 1">
|
||||
<g>
|
||||
<path class="cls-1" d="M750.46,590.12c-.14,84.87-80.94,164.13-164.84,164.16l-417.85.15c-41.23.02-80.97-17.81-110.92-45.41C22.8,677.64.27,633.05.24,586.16l-.24-422.7C5.62,75.03,77.43,4.25,166.02,0h420.61c62.96,3.69,119.27,41.03,146.88,97.42,12.43,24.66,17.66,51.06,17.62,78.93l-.67,413.76ZM370.18,625.88c68.31,1.54,132.18-29.97,171.03-85.28,19.04-27.11,29.77-59.07,31.22-92.31,2.32-53.09-17.94-99.1-49.2-140.66-16.25-21.6-34.32-40.41-53.32-59.63l-43.23-43.74c-12.68-12.83-21.88-27.8-28.68-44.35-10.42-25.91-11.61-52.65-2.26-80.45-22.44,9.87-41.94,24.28-58.9,41.82-32.89,34-50.93,79.43-49.03,126.79.86,21.44,6.18,40.62,13.88,60.14,3.95,10.01,6.17,20.15,5.8,30.69-.57,16.13-12.7,28.03-28.8,28.13-9.24.06-17.69-3.81-23.17-10.35-5.88-7.01-7.61-16.24-6.35-25.41,2.35-17.02-3.73-32.8-18.26-42-69.49,73.43-78.46,184.2-13.01,262.78,37.6,45.14,92.49,72.48,152.3,73.83Z"/>
|
||||
<path class="cls-1" d="M490.63,521.64c-17.79,22.43-41.28,37.71-67.84,46.68-59.05,19.93-124.13,1.45-163.53-46.85-25.25-30.95-37.66-72.35-28.81-112.76,30.31,30.19,79.51,27.67,110.75,2.18,36.69-29.94,30.9-75.91,14.6-116.82-6.76-16.96-10.43-34.03-11.76-52.32s2.8-36.52,9.95-52.81c11.14,34.99,32.99,58.35,58.82,82.14,7.59,6.99,15.55,12.82,22.13,20.89,20.88,25.59,29.46,58.1,25.52,90.79-3.27,27.11-12.78,51.87-30.01,73.44,41.77-13.36,65.73-42.83,67.49-87.7,28.98,50.16,29.42,106.82-7.32,153.13Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/logo-icon.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
13
public/logo.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 751.13 754.43">
|
||||
<!-- Generator: Adobe Illustrator 30.3.0, SVG Export Plug-In . SVG Version: 2.1.3 Build 157) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #e41313;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="st0" d="M750.46,590.12c-.14,84.87-80.94,164.13-164.84,164.16l-417.85.15c-41.23.02-80.97-17.81-110.92-45.41C22.8,677.64.27,633.05.24,586.16l-.24-422.7C5.62,75.03,77.43,4.25,166.02,0h420.61c62.96,3.69,119.27,41.03,146.88,97.42,12.43,24.66,17.66,51.06,17.62,78.93l-.67,413.76ZM370.18,625.88c68.31,1.54,132.18-29.97,171.03-85.28,19.04-27.11,29.77-59.07,31.22-92.31,2.32-53.09-17.94-99.1-49.2-140.66-16.25-21.6-34.32-40.41-53.32-59.63l-43.23-43.74c-12.68-12.83-21.88-27.8-28.68-44.35-10.42-25.91-11.61-52.65-2.26-80.45-22.44,9.87-41.94,24.28-58.9,41.82-32.89,34-50.93,79.43-49.03,126.79.86,21.44,6.18,40.62,13.88,60.14,3.95,10.01,6.17,20.15,5.8,30.69-.57,16.13-12.7,28.03-28.8,28.13-9.24.06-17.69-3.81-23.17-10.35-5.88-7.01-7.61-16.24-6.35-25.41,2.35-17.02-3.73-32.8-18.26-42-69.49,73.43-78.46,184.2-13.01,262.78,37.6,45.14,92.49,72.48,152.3,73.83Z"/>
|
||||
<path class="st0" d="M490.63,521.64c-17.79,22.43-41.28,37.71-67.84,46.68-59.05,19.93-124.13,1.45-163.53-46.85-25.25-30.95-37.66-72.35-28.81-112.76,30.31,30.19,79.51,27.67,110.75,2.18,36.69-29.94,30.9-75.91,14.6-116.82-6.76-16.96-10.43-34.03-11.76-52.32s2.8-36.52,9.95-52.81c11.14,34.99,32.99,58.35,58.82,82.14,7.59,6.99,15.55,12.82,22.13,20.89,20.88,25.59,29.46,58.1,25.52,90.79-3.27,27.11-12.78,51.87-30.01,73.44,41.77-13.36,65.73-42.83,67.49-87.7,28.98,50.16,29.42,106.82-7.32,153.13Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
28
public/manifest.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Lageplan – Feuerwehr Krokier-App",
|
||||
"short_name": "Lageplan",
|
||||
"description": "Digitale Einsatzdokumentation für Schweizer Feuerwehren. Lagepläne erstellen, Journal führen, Rapporte generieren.",
|
||||
"start_url": "/app",
|
||||
"display": "standalone",
|
||||
"orientation": "any",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#dc2626",
|
||||
"lang": "de-CH",
|
||||
"categories": ["productivity", "utilities"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/logo-icon.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
],
|
||||
"screenshots": [],
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
94
public/sw.js
Normal file
@@ -0,0 +1,94 @@
|
||||
const TILE_CACHE = 'lageplan-tiles-v2'
|
||||
const STATIC_CACHE = 'lageplan-static-v2'
|
||||
const APP_CACHE = 'lageplan-app-v2'
|
||||
|
||||
// Pre-cache essential app shell on install
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(
|
||||
caches.open(APP_CACHE).then((cache) =>
|
||||
cache.addAll([
|
||||
'/app',
|
||||
'/logo.svg',
|
||||
'/logo-icon.png',
|
||||
'/manifest.json',
|
||||
]).catch(() => {})
|
||||
)
|
||||
)
|
||||
self.skipWaiting()
|
||||
})
|
||||
|
||||
// Cache strategy: Network First for API, Cache First for tiles, Stale While Revalidate for static assets
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const url = event.request.url
|
||||
const { pathname } = new URL(url)
|
||||
|
||||
// Skip non-GET requests
|
||||
if (event.request.method !== 'GET') return
|
||||
|
||||
// API requests: network only (don't cache dynamic data)
|
||||
if (pathname.startsWith('/api/')) return
|
||||
|
||||
// Cache map tiles from OpenStreetMap (Cache First)
|
||||
if (url.includes('tile.openstreetmap.org') || url.includes('api.maptiler.com')) {
|
||||
event.respondWith(
|
||||
caches.open(TILE_CACHE).then((cache) =>
|
||||
cache.match(event.request).then((cached) => {
|
||||
if (cached) return cached
|
||||
return fetch(event.request).then((response) => {
|
||||
if (response.ok) {
|
||||
cache.put(event.request, response.clone())
|
||||
}
|
||||
return response
|
||||
}).catch(() => new Response('', { status: 503 }))
|
||||
})
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Static assets (JS, CSS, images): Stale While Revalidate
|
||||
if (pathname.match(/\.(js|css|png|jpg|jpeg|svg|ico|woff2?)$/)) {
|
||||
event.respondWith(
|
||||
caches.open(STATIC_CACHE).then((cache) =>
|
||||
cache.match(event.request).then((cached) => {
|
||||
const fetchPromise = fetch(event.request).then((response) => {
|
||||
if (response.ok) cache.put(event.request, response.clone())
|
||||
return response
|
||||
}).catch(() => cached || new Response('', { status: 503 }))
|
||||
return cached || fetchPromise
|
||||
})
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// App pages: Network First with cache fallback
|
||||
if (pathname === '/app' || pathname === '/' || pathname.startsWith('/app')) {
|
||||
event.respondWith(
|
||||
fetch(event.request).then((response) => {
|
||||
if (response.ok) {
|
||||
const clone = response.clone()
|
||||
caches.open(APP_CACHE).then((cache) => cache.put(event.request, clone))
|
||||
}
|
||||
return response
|
||||
}).catch(() =>
|
||||
caches.match(event.request).then((cached) => cached || caches.match('/app'))
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Clean old caches on activation
|
||||
self.addEventListener('activate', (event) => {
|
||||
const currentCaches = [TILE_CACHE, STATIC_CACHE, APP_CACHE]
|
||||
event.waitUntil(
|
||||
caches.keys().then((keys) =>
|
||||
Promise.all(
|
||||
keys
|
||||
.filter((k) => !currentCaches.includes(k))
|
||||
.map((k) => caches.delete(k))
|
||||
)
|
||||
).then(() => self.clients.claim())
|
||||
)
|
||||
})
|
||||