Add PWA support (manifest, service worker, icons, safe-area CSS)
This commit is contained in:
1 parent
0a2616c5d6
commit
02f49e55f8
5 files changed
+145
-2
No files matched your search
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<!-- Solid background (fills corners for maskable safe zone) -->
|
||||||
|
<rect width="512" height="512" fill="#1a1a1a"/>
|
||||||
|
<!-- Sun -->
|
||||||
|
<circle cx="205" cy="205" r="70" fill="#e07848"/>
|
||||||
|
<!-- Cloud bumps (front, overlapping sun) -->
|
||||||
|
<circle cx="215" cy="325" r="72" fill="#ece7dc"/>
|
||||||
|
<circle cx="305" cy="297" r="88" fill="#ece7dc"/>
|
||||||
|
<circle cx="390" cy="328" r="62" fill="#ece7dc"/>
|
||||||
|
<!-- Cloud base (flat bottom) -->
|
||||||
|
<rect x="147" y="335" width="305" height="68" fill="#ece7dc"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 559 B |
+14
-1
@@ -2,8 +2,16 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||||
<title>Weather</title>
|
<title>Weather</title>
|
||||||
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
<link rel="apple-touch-icon" href="/icons/icon.svg">
|
||||||
|
<meta name="theme-color" content="#f5f2ec" media="(prefers-color-scheme: light)">
|
||||||
|
<meta name="theme-color" content="#191510" media="(prefers-color-scheme: dark)">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Weather">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Playfair+Display:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Playfair+Display:wght@400;500&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||||
@@ -134,5 +142,10 @@
|
|||||||
|
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register('/sw.js');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "Weather",
|
||||||
|
"short_name": "Weather",
|
||||||
|
"description": "Self-hosted weather app with animated radar. No API keys required.",
|
||||||
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#191510",
|
||||||
|
"theme_color": "#1a1a1a",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"categories": ["weather", "utilities"],
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icons/icon.svg",
|
||||||
|
"sizes": "any",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+33
-1
@@ -454,6 +454,38 @@ a:hover { text-decoration: underline; }
|
|||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── PWA: safe area insets (iPhone notch / home bar) ── */
|
||||||
|
@supports (padding: env(safe-area-inset-bottom)) {
|
||||||
|
.app {
|
||||||
|
padding-left: max(1.5rem, calc(1rem + env(safe-area-inset-left)));
|
||||||
|
padding-right: max(1.5rem, calc(1rem + env(safe-area-inset-right)));
|
||||||
|
padding-bottom: max(4rem, calc(2rem + env(safe-area-inset-bottom)));
|
||||||
|
}
|
||||||
|
.radar-map-full {
|
||||||
|
height: calc(100vh - 280px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── PWA: status bar area when running standalone ── */
|
||||||
|
@media (display-mode: standalone) {
|
||||||
|
.masthead { padding-top: env(safe-area-inset-top); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Touch-friendly tap targets (min 44 × 44 px) ── */
|
||||||
|
.tab {
|
||||||
|
min-height: 44px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.search-row input,
|
||||||
|
.search-row button { min-height: 44px; }
|
||||||
|
.radar-btn { width: 44px; height: 44px; font-size: 0.8rem; }
|
||||||
|
.tl-tick { min-height: 36px; }
|
||||||
|
|
||||||
|
/* iOS momentum scrolling */
|
||||||
|
.hourly-scroll { -webkit-overflow-scrolling: touch; }
|
||||||
|
|
||||||
/* ── Leaflet overrides ── */
|
/* ── Leaflet overrides ── */
|
||||||
.leaflet-container { background: #1a1f2e; }
|
.leaflet-container { background: #1a1f2e; }
|
||||||
.leaflet-control-zoom a {
|
.leaflet-control-zoom a {
|
||||||
@@ -464,7 +496,7 @@ a:hover { text-decoration: underline; }
|
|||||||
|
|
||||||
/* ── Responsive ── */
|
/* ── Responsive ── */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.app { padding: 1.25rem 1rem 3rem; }
|
.app { padding: 1.25rem 1rem max(3rem, calc(1.5rem + env(safe-area-inset-bottom, 0px))); }
|
||||||
.masthead { grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; }
|
.masthead { grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; }
|
||||||
.masthead h1 { font-size: 1.6rem; }
|
.masthead h1 { font-size: 1.6rem; }
|
||||||
.tagline { display: none; }
|
.tagline { display: none; }
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const CACHE = 'weather-v1';
|
||||||
|
|
||||||
|
// App shell — cached on install for offline use
|
||||||
|
const PRECACHE = [
|
||||||
|
'/',
|
||||||
|
'/app.js',
|
||||||
|
'/style.css',
|
||||||
|
'/manifest.json',
|
||||||
|
'/icons/icon.svg'
|
||||||
|
];
|
||||||
|
|
||||||
|
self.addEventListener('install', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE)
|
||||||
|
.then(cache => cache.addAll(PRECACHE))
|
||||||
|
.then(() => self.skipWaiting())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', event => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys()
|
||||||
|
.then(keys => Promise.all(
|
||||||
|
keys.filter(k => k !== CACHE).map(k => caches.delete(k))
|
||||||
|
))
|
||||||
|
.then(() => self.clients.claim())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
const { request } = event;
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
// Skip non-GET, API calls, and map/radar tile requests
|
||||||
|
if (
|
||||||
|
request.method !== 'GET' ||
|
||||||
|
url.pathname.startsWith('/api/') ||
|
||||||
|
url.hostname.includes('rainviewer') ||
|
||||||
|
url.hostname.includes('openstreetmap') ||
|
||||||
|
url.hostname.includes('cartodb') ||
|
||||||
|
url.hostname.includes('basemaps')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.respondWith(
|
||||||
|
caches.match(request).then(cached => {
|
||||||
|
if (cached) return cached;
|
||||||
|
return fetch(request).then(response => {
|
||||||
|
// Only cache successful same-origin and trusted CDN responses
|
||||||
|
if (response.ok && (
|
||||||
|
url.origin === self.location.origin ||
|
||||||
|
url.hostname.includes('googleapis') ||
|
||||||
|
url.hostname.includes('gstatic') ||
|
||||||
|
url.hostname.includes('unpkg.com')
|
||||||
|
)) {
|
||||||
|
const clone = response.clone();
|
||||||
|
caches.open(CACHE).then(cache => cache.put(request, clone));
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user