Add Progressive Web App functionality
- Add web app manifest for home screen installation - Implement service worker with offline caching strategy - Create offline fallback page with auto-reconnect - Generate PWA icons in multiple sizes (72px-512px) - Add PWA meta tags and Apple Touch icons to all pages - Register service worker with graceful degradation - Update documentation with PWA installation instructions - Add browserconfig.xml for Windows tile support Features: - Installable on mobile and desktop - Offline functionality with cached resources - App-like experience in standalone mode - Automatic updates when online - Works seamlessly with existing progressive enhancement 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
|
@ -4,8 +4,24 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Great Lakes Ice Report Admin</title>
|
||||
<link rel="icon" type="image/svg+xml" href="https://iceymi.b-cdn.net/favicon.svg">
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="Admin panel for Great Lakes Ice Report - manage winter road condition reports">
|
||||
<meta name="theme-color" content="#2196F3">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Ice Report Admin">
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/favicon.svg">
|
||||
<link rel="icon" type="image/x-icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIj4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLnNub3dmbGFrZSB7IGZpbGw6ICMyMTk2RjM7IH0KICAgICAgLmNlbnRlciB7IGZpbGw6ICMxOTc2RDI7IH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIAogIDxnIGNsYXNzPSJzbm93Zmxha2UiPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiAvPgogICAgPHJlY3QgeD0iMiIgeT0iMTUiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyIiAvPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cmVjdCB4PSIxNSIgeT0iMiIgd2lkdGg9IjIiIGhlaWdodD0iMjgiIHRyYW5zZm9ybT0icm90YXRlKC00NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjE2LDIgMTQsNiAxOCw2IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSIxNiwzMCAxNCwyNiAxOCwyNiIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMiwxNiA2LDE0IDYsMTgiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjMwLDE2IDI2LDE0IDI2LDE4IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSI2LjMsNi4zIDguNiw0IDkuOSw3LjciIHRyYW5zZm9ybT0icm90YXRlKDQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNywyNS43IDIzLjQsMjggMjIuMSwyNC4zIiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjYuMywyNS43IDguNiwyOCA5LjksMjQuMyIgdHJhbnNmb3JtPSJyb3RhdGUoLTQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNyw2LjMgMjMuNCw0IDIyLjEsNy43IiB0cmFuc2Zvcm09InJvdGF0ZSgtNDUgMTYgMTYpIiAvPgogIDwvZz4KICA8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIzIiBjbGFzcz0iY2VudGVyIiAvPgo8L3N2Zz4K">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192.svg">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script>
|
||||
// Apply theme immediately to prevent flash
|
||||
|
|
10
public/browserconfig.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/icons/icon-152.svg"/>
|
||||
<square310x310logo src="/icons/icon-384.svg"/>
|
||||
<TileColor>#2196F3</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
32
public/icons/favicon.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="16" cy="16" r="16" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(16, 16)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-9.6" y1="0" x2="9.6" y2="0" stroke="white" stroke-width="2.56" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-9.6" x2="0" y2="9.6" stroke="white" stroke-width="2.56" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-6.72" y1="-6.72" x2="6.72" y2="6.72" stroke="white" stroke-width="1.92" stroke-linecap="round"/>
|
||||
<line x1="6.72" y1="-6.72" x2="-6.72" y2="6.72" stroke="white" stroke-width="1.92" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="1.28" fill="white"/>
|
||||
<circle cx="4.8" cy="0" r="0.64" fill="white"/>
|
||||
<circle cx="-4.8" cy="0" r="0.64" fill="white"/>
|
||||
<circle cx="0" cy="4.8" r="0.64" fill="white"/>
|
||||
<circle cx="0" cy="-4.8" r="0.64" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
32
public/icons/icon-128.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="64" cy="64" r="64" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(64, 64)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-38.4" y1="0" x2="38.4" y2="0" stroke="white" stroke-width="10.24" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-38.4" x2="0" y2="38.4" stroke="white" stroke-width="10.24" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-26.88" y1="-26.88" x2="26.88" y2="26.88" stroke="white" stroke-width="7.68" stroke-linecap="round"/>
|
||||
<line x1="26.88" y1="-26.88" x2="-26.88" y2="26.88" stroke="white" stroke-width="7.68" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="5.12" fill="white"/>
|
||||
<circle cx="19.2" cy="0" r="2.56" fill="white"/>
|
||||
<circle cx="-19.2" cy="0" r="2.56" fill="white"/>
|
||||
<circle cx="0" cy="19.2" r="2.56" fill="white"/>
|
||||
<circle cx="0" cy="-19.2" r="2.56" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
32
public/icons/icon-144.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="144" height="144" viewBox="0 0 144 144" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="72" cy="72" r="72" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(72, 72)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-43.199999999999996" y1="0" x2="43.199999999999996" y2="0" stroke="white" stroke-width="11.52" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-43.199999999999996" x2="0" y2="43.199999999999996" stroke="white" stroke-width="11.52" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-30.24" y1="-30.24" x2="30.24" y2="30.24" stroke="white" stroke-width="8.64" stroke-linecap="round"/>
|
||||
<line x1="30.24" y1="-30.24" x2="-30.24" y2="30.24" stroke="white" stroke-width="8.64" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="5.76" fill="white"/>
|
||||
<circle cx="21.599999999999998" cy="0" r="2.88" fill="white"/>
|
||||
<circle cx="-21.599999999999998" cy="0" r="2.88" fill="white"/>
|
||||
<circle cx="0" cy="21.599999999999998" r="2.88" fill="white"/>
|
||||
<circle cx="0" cy="-21.599999999999998" r="2.88" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
32
public/icons/icon-152.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="152" height="152" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="76" cy="76" r="76" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(76, 76)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-45.6" y1="0" x2="45.6" y2="0" stroke="white" stroke-width="12.16" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-45.6" x2="0" y2="45.6" stroke="white" stroke-width="12.16" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-31.919999999999998" y1="-31.919999999999998" x2="31.919999999999998" y2="31.919999999999998" stroke="white" stroke-width="9.12" stroke-linecap="round"/>
|
||||
<line x1="31.919999999999998" y1="-31.919999999999998" x2="-31.919999999999998" y2="31.919999999999998" stroke="white" stroke-width="9.12" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="6.08" fill="white"/>
|
||||
<circle cx="22.8" cy="0" r="3.04" fill="white"/>
|
||||
<circle cx="-22.8" cy="0" r="3.04" fill="white"/>
|
||||
<circle cx="0" cy="22.8" r="3.04" fill="white"/>
|
||||
<circle cx="0" cy="-22.8" r="3.04" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
33
public/icons/icon-192-maskable.svg
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="192" height="192" viewBox="0 0 192 192" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="96" cy="96" r="96" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(96, 96)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-46.08" y1="0" x2="46.08" y2="0" stroke="white" stroke-width="12.288" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-46.08" x2="0" y2="46.08" stroke="white" stroke-width="12.288" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-32.256" y1="-32.256" x2="32.256" y2="32.256" stroke="white" stroke-width="9.216" stroke-linecap="round"/>
|
||||
<line x1="32.256" y1="-32.256" x2="-32.256" y2="32.256" stroke="white" stroke-width="9.216" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="6.144" fill="white"/>
|
||||
<circle cx="23.04" cy="0" r="3.072" fill="white"/>
|
||||
<circle cx="-23.04" cy="0" r="3.072" fill="white"/>
|
||||
<circle cx="0" cy="23.04" r="3.072" fill="white"/>
|
||||
<circle cx="0" cy="-23.04" r="3.072" fill="white"/>
|
||||
</g>
|
||||
|
||||
<!-- Safe zone indicator (invisible) -->
|
||||
<circle cx="96" cy="96" r="76.80000000000001" fill="none" stroke="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
32
public/icons/icon-192.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="192" height="192" viewBox="0 0 192 192" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="96" cy="96" r="96" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(96, 96)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-57.599999999999994" y1="0" x2="57.599999999999994" y2="0" stroke="white" stroke-width="15.36" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-57.599999999999994" x2="0" y2="57.599999999999994" stroke="white" stroke-width="15.36" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-40.32" y1="-40.32" x2="40.32" y2="40.32" stroke="white" stroke-width="11.52" stroke-linecap="round"/>
|
||||
<line x1="40.32" y1="-40.32" x2="-40.32" y2="40.32" stroke="white" stroke-width="11.52" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="7.68" fill="white"/>
|
||||
<circle cx="28.799999999999997" cy="0" r="3.84" fill="white"/>
|
||||
<circle cx="-28.799999999999997" cy="0" r="3.84" fill="white"/>
|
||||
<circle cx="0" cy="28.799999999999997" r="3.84" fill="white"/>
|
||||
<circle cx="0" cy="-28.799999999999997" r="3.84" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
32
public/icons/icon-384.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="384" height="384" viewBox="0 0 384 384" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="192" cy="192" r="192" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(192, 192)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-115.19999999999999" y1="0" x2="115.19999999999999" y2="0" stroke="white" stroke-width="30.72" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-115.19999999999999" x2="0" y2="115.19999999999999" stroke="white" stroke-width="30.72" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-80.64" y1="-80.64" x2="80.64" y2="80.64" stroke="white" stroke-width="23.04" stroke-linecap="round"/>
|
||||
<line x1="80.64" y1="-80.64" x2="-80.64" y2="80.64" stroke="white" stroke-width="23.04" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="15.36" fill="white"/>
|
||||
<circle cx="57.599999999999994" cy="0" r="7.68" fill="white"/>
|
||||
<circle cx="-57.599999999999994" cy="0" r="7.68" fill="white"/>
|
||||
<circle cx="0" cy="57.599999999999994" r="7.68" fill="white"/>
|
||||
<circle cx="0" cy="-57.599999999999994" r="7.68" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
33
public/icons/icon-512-maskable.svg
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="256" cy="256" r="256" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(256, 256)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-122.88" y1="0" x2="122.88" y2="0" stroke="white" stroke-width="32.768" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-122.88" x2="0" y2="122.88" stroke="white" stroke-width="32.768" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-86.016" y1="-86.016" x2="86.016" y2="86.016" stroke="white" stroke-width="24.576" stroke-linecap="round"/>
|
||||
<line x1="86.016" y1="-86.016" x2="-86.016" y2="86.016" stroke="white" stroke-width="24.576" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="16.384" fill="white"/>
|
||||
<circle cx="61.44" cy="0" r="8.192" fill="white"/>
|
||||
<circle cx="-61.44" cy="0" r="8.192" fill="white"/>
|
||||
<circle cx="0" cy="61.44" r="8.192" fill="white"/>
|
||||
<circle cx="0" cy="-61.44" r="8.192" fill="white"/>
|
||||
</g>
|
||||
|
||||
<!-- Safe zone indicator (invisible) -->
|
||||
<circle cx="256" cy="256" r="204.8" fill="none" stroke="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
32
public/icons/icon-512.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="256" cy="256" r="256" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(256, 256)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-153.6" y1="0" x2="153.6" y2="0" stroke="white" stroke-width="40.96" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-153.6" x2="0" y2="153.6" stroke="white" stroke-width="40.96" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-107.52" y1="-107.52" x2="107.52" y2="107.52" stroke="white" stroke-width="30.72" stroke-linecap="round"/>
|
||||
<line x1="107.52" y1="-107.52" x2="-107.52" y2="107.52" stroke="white" stroke-width="30.72" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="20.48" fill="white"/>
|
||||
<circle cx="76.8" cy="0" r="10.24" fill="white"/>
|
||||
<circle cx="-76.8" cy="0" r="10.24" fill="white"/>
|
||||
<circle cx="0" cy="76.8" r="10.24" fill="white"/>
|
||||
<circle cx="0" cy="-76.8" r="10.24" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
32
public/icons/icon-72.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72" height="72" viewBox="0 0 72 72" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="36" cy="36" r="36" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(36, 36)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-21.599999999999998" y1="0" x2="21.599999999999998" y2="0" stroke="white" stroke-width="5.76" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-21.599999999999998" x2="0" y2="21.599999999999998" stroke="white" stroke-width="5.76" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-15.12" y1="-15.12" x2="15.12" y2="15.12" stroke="white" stroke-width="4.32" stroke-linecap="round"/>
|
||||
<line x1="15.12" y1="-15.12" x2="-15.12" y2="15.12" stroke="white" stroke-width="4.32" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="2.88" fill="white"/>
|
||||
<circle cx="10.799999999999999" cy="0" r="1.44" fill="white"/>
|
||||
<circle cx="-10.799999999999999" cy="0" r="1.44" fill="white"/>
|
||||
<circle cx="0" cy="10.799999999999999" r="1.44" fill="white"/>
|
||||
<circle cx="0" cy="-10.799999999999999" r="1.44" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
32
public/icons/icon-96.svg
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="96" height="96" viewBox="0 0 96 96" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#2196F3;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1976D2;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="48" cy="48" r="48" fill="url(#bg)"/>
|
||||
|
||||
<!-- Ice crystal/snowflake icon -->
|
||||
<g transform="translate(48, 48)">
|
||||
<!-- Main cross -->
|
||||
<line x1="-28.799999999999997" y1="0" x2="28.799999999999997" y2="0" stroke="white" stroke-width="7.68" stroke-linecap="round"/>
|
||||
<line x1="0" y1="-28.799999999999997" x2="0" y2="28.799999999999997" stroke="white" stroke-width="7.68" stroke-linecap="round"/>
|
||||
|
||||
<!-- Diagonal lines -->
|
||||
<line x1="-20.16" y1="-20.16" x2="20.16" y2="20.16" stroke="white" stroke-width="5.76" stroke-linecap="round"/>
|
||||
<line x1="20.16" y1="-20.16" x2="-20.16" y2="20.16" stroke="white" stroke-width="5.76" stroke-linecap="round"/>
|
||||
|
||||
<!-- Small decorative elements -->
|
||||
<circle cx="0" cy="0" r="3.84" fill="white"/>
|
||||
<circle cx="14.399999999999999" cy="0" r="1.92" fill="white"/>
|
||||
<circle cx="-14.399999999999999" cy="0" r="1.92" fill="white"/>
|
||||
<circle cx="0" cy="14.399999999999999" r="1.92" fill="white"/>
|
||||
<circle cx="0" cy="-14.399999999999999" r="1.92" fill="white"/>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -4,8 +4,26 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Great Lakes Ice Report</title>
|
||||
<link rel="icon" type="image/svg+xml" href="https://iceymi.b-cdn.net/favicon.svg">
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="Community-driven winter road conditions and icy hazards tracker for the Great Lakes region">
|
||||
<meta name="theme-color" content="#2196F3">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Ice Report">
|
||||
<meta name="msapplication-TileColor" content="#2196F3">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/favicon.svg">
|
||||
<link rel="icon" type="image/x-icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIj4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLnNub3dmbGFrZSB7IGZpbGw6ICMyMTk2RjM7IH0KICAgICAgLmNlbnRlciB7IGZpbGw6ICMxOTc2RDI7IH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIAogIDxnIGNsYXNzPSJzbm93Zmxha2UiPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiAvPgogICAgPHJlY3QgeD0iMiIgeT0iMTUiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyIiAvPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cmVjdCB4PSIxNSIgeT0iMiIgd2lkdGg9IjIiIGhlaWdodD0iMjgiIHRyYW5zZm9ybT0icm90YXRlKC00NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjE2LDIgMTQsNiAxOCw2IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSIxNiwzMCAxNCwyNiAxOCwyNiIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMiwxNiA2LDE0IDYsMTgiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjMwLDE2IDI2LDE0IDI2LDE4IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSI2LjMsNi4zIDguNiw0IDkuOSw3LjciIHRyYW5zZm9ybT0icm90YXRlKDQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNywyNS43IDIzLjQsMjggMjIuMSwyNC4zIiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjYuMywyNS43IDguNiwyOCA5LjksMjQuMyIgdHJhbnNmb3JtPSJyb3RhdGUoLTQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNyw2LjMgMjMuNCw0IDIyLjEsNy43IiB0cmFuc2Zvcm09InJvdGF0ZSgtNDUgMTYgMTYpIiAvPgogIDwvZz4KICA8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIzIiBjbGFzcz0iY2VudGVyIiAvPgo8L3N2Zz4K">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192.svg">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script>
|
||||
|
@ -154,5 +172,58 @@ placeholder="Enter address, intersection (e.g., Main St & Second St, City), or l
|
|||
<script src="theme-utils.js"></script>
|
||||
<script src="utils.js"></script>
|
||||
<script src="app-mapbox.js"></script>
|
||||
|
||||
<!-- PWA Service Worker Registration -->
|
||||
<script>
|
||||
// Register service worker for PWA functionality
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then((registration) => {
|
||||
console.log('✅ Service Worker registered successfully:', registration.scope);
|
||||
|
||||
// Check for updates
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration.installing;
|
||||
if (newWorker) {
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// New content available, notify user
|
||||
console.log('🔄 New content available! Please refresh.');
|
||||
// Could show a notification here
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('❌ Service Worker registration failed:', error);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log('Service Worker not supported in this browser');
|
||||
}
|
||||
|
||||
// PWA Install Prompt
|
||||
let deferredPrompt;
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
console.log('💾 PWA install prompt available');
|
||||
// Prevent Chrome 67 and earlier from automatically showing the prompt
|
||||
e.preventDefault();
|
||||
// Stash the event so it can be triggered later
|
||||
deferredPrompt = e;
|
||||
|
||||
// Could show custom install button here
|
||||
// For now, let the browser handle it naturally
|
||||
});
|
||||
|
||||
window.addEventListener('appinstalled', (evt) => {
|
||||
console.log('🎉 PWA was installed successfully!');
|
||||
deferredPrompt = null;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
75
public/manifest.json
Normal file
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"name": "Great Lakes Ice Report",
|
||||
"short_name": "Ice Report",
|
||||
"description": "Community-driven winter road conditions and icy hazards tracker for the Great Lakes region",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait-primary",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#2196F3",
|
||||
"lang": "en-US",
|
||||
"scope": "/",
|
||||
"categories": ["weather", "transportation", "utilities"],
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-72.svg",
|
||||
"sizes": "72x72",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-96.svg",
|
||||
"sizes": "96x96",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-128.svg",
|
||||
"sizes": "128x128",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-144.svg",
|
||||
"sizes": "144x144",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-152.svg",
|
||||
"sizes": "152x152",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-192.svg",
|
||||
"sizes": "192x192",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-384.svg",
|
||||
"sizes": "384x384",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512.svg",
|
||||
"sizes": "512x512",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-192-maskable.svg",
|
||||
"sizes": "192x192",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512-maskable.svg",
|
||||
"sizes": "512x512",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
89
public/offline.html
Normal file
|
@ -0,0 +1,89 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Offline - Great Lakes Ice Report</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
.offline-container {
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.offline-icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.offline-message {
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
padding: 2rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background-color: var(--primary-color, #2196F3);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="offline-container">
|
||||
<div class="offline-message">
|
||||
<div class="offline-icon">📡</div>
|
||||
<h1>You're Offline</h1>
|
||||
<p>It looks like you've lost your internet connection. The Great Lakes Ice Report needs an internet connection to show current road conditions and submit new reports.</p>
|
||||
|
||||
<p><strong>When you're back online, you'll be able to:</strong></p>
|
||||
<ul style="text-align: left; max-width: 400px; margin: 1rem auto;">
|
||||
<li>View current ice condition reports</li>
|
||||
<li>Submit new hazard reports</li>
|
||||
<li>Get real-time location updates</li>
|
||||
<li>Access the interactive map</li>
|
||||
</ul>
|
||||
|
||||
<button class="retry-btn" onclick="window.location.reload()">
|
||||
🔄 Try Again
|
||||
</button>
|
||||
|
||||
<p style="margin-top: 1rem; font-size: 0.9rem; opacity: 0.8;">
|
||||
This app works best with an internet connection for up-to-date safety information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-reload when connection is restored
|
||||
window.addEventListener('online', () => {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
// Apply theme from localStorage if available
|
||||
(function() {
|
||||
const savedTheme = localStorage.getItem('theme') || 'auto';
|
||||
if (savedTheme === 'auto') {
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -4,8 +4,24 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Privacy Policy - Great Lakes Ice Report</title>
|
||||
<link rel="icon" type="image/svg+xml" href="https://iceymi.b-cdn.net/favicon.svg">
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="description" content="Privacy policy for Great Lakes Ice Report - winter road conditions tracker">
|
||||
<meta name="theme-color" content="#2196F3">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Ice Report Privacy">
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/favicon.svg">
|
||||
<link rel="icon" type="image/x-icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIj4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLnNub3dmbGFrZSB7IGZpbGw6ICMyMTk2RjM7IH0KICAgICAgLmNlbnRlciB7IGZpbGw6ICMxOTc2RDI7IH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIAogIDxnIGNsYXNzPSJzbm93Zmxha2UiPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiAvPgogICAgPHJlY3QgeD0iMiIgeT0iMTUiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyIiAvPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cmVjdCB4PSIxNSIgeT0iMiIgd2lkdGg9IjIiIGhlaWdodD0iMjgiIHRyYW5zZm9ybT0icm90YXRlKC00NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjE2LDIgMTQsNiAxOCw2IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSIxNiwzMCAxNCwyNiAxOCwyNiIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMiwxNiA2LDE0IDYsMTgiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjMwLDE2IDI2LDE0IDI2LDE4IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSI2LjMsNi4zIDguNiw0IDkuOSw3LjciIHRyYW5zZm9ybT0icm90YXRlKDQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNywyNS43IDIzLjQsMjggMjIuMSwyNC4zIiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjYuMywyNS43IDguNiwyOCA5LjksMjQuMyIgdHJhbnNmb3JtPSJyb3RhdGUoLTQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNyw2LjMgMjMuNCw0IDIyLjEsNy43IiB0cmFuc2Zvcm09InJvdGF0ZSgtNDUgMTYgMTYpIiAvPgogIDwvZz4KICA8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIzIiBjbGFzcz0iY2VudGVyIiAvPgo8L3N2Zz4K">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192.svg">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script>
|
||||
// Apply theme immediately to prevent flash
|
||||
|
|
143
public/sw.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Service Worker for Great Lakes Ice Report PWA
|
||||
const CACHE_NAME = 'ice-report-v1';
|
||||
const OFFLINE_URL = '/offline.html';
|
||||
|
||||
// Files to cache for offline functionality
|
||||
const CACHE_FILES = [
|
||||
'/',
|
||||
'/index.html',
|
||||
'/admin.html',
|
||||
'/privacy.html',
|
||||
'/style.css',
|
||||
'/app.js',
|
||||
'/admin.js',
|
||||
'/utils.js',
|
||||
'/manifest.json',
|
||||
OFFLINE_URL
|
||||
];
|
||||
|
||||
// Install event - cache essential files
|
||||
self.addEventListener('install', (event) => {
|
||||
console.log('Service Worker: Installing...');
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
console.log('Service Worker: Caching files');
|
||||
return cache.addAll(CACHE_FILES);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Files cached successfully');
|
||||
return self.skipWaiting();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Service Worker: Cache failed:', error);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Activate event - clean up old caches
|
||||
self.addEventListener('activate', (event) => {
|
||||
console.log('Service Worker: Activating...');
|
||||
|
||||
event.waitUntil(
|
||||
caches.keys()
|
||||
.then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames
|
||||
.filter((cacheName) => cacheName !== CACHE_NAME)
|
||||
.map((cacheName) => {
|
||||
console.log('Service Worker: Deleting old cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Service Worker: Activated');
|
||||
return self.clients.claim();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Fetch event - serve cached content when offline
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// Only handle GET requests
|
||||
if (event.request.method !== 'GET') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip cross-origin requests
|
||||
if (!event.request.url.startsWith(self.location.origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request)
|
||||
.then((response) => {
|
||||
// Return cached version if available
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Try to fetch from network
|
||||
return fetch(event.request)
|
||||
.then((response) => {
|
||||
// Don't cache non-successful responses
|
||||
if (!response || response.status !== 200 || response.type !== 'basic') {
|
||||
return response;
|
||||
}
|
||||
|
||||
// Clone the response for caching
|
||||
const responseToCache = response.clone();
|
||||
|
||||
// Cache successful responses for static assets
|
||||
if (event.request.url.match(/\.(js|css|html|png|jpg|jpeg|gif|svg|ico|woff|woff2)$/)) {
|
||||
caches.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
cache.put(event.request, responseToCache);
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
})
|
||||
.catch(() => {
|
||||
// If network fails and we're requesting an HTML page, show offline page
|
||||
if (event.request.headers.get('accept').includes('text/html')) {
|
||||
return caches.match(OFFLINE_URL);
|
||||
}
|
||||
|
||||
// For other requests, just fail
|
||||
throw new Error('Network failed and no cache available');
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Background sync for when connection is restored
|
||||
self.addEventListener('sync', (event) => {
|
||||
if (event.tag === 'background-sync') {
|
||||
console.log('Service Worker: Background sync triggered');
|
||||
// Could implement queued location submissions here
|
||||
}
|
||||
});
|
||||
|
||||
// Push notifications (for future use)
|
||||
self.addEventListener('push', (event) => {
|
||||
if (event.data) {
|
||||
const data = event.data.json();
|
||||
console.log('Service Worker: Push message received:', data);
|
||||
|
||||
// Could show notifications about severe weather warnings
|
||||
const options = {
|
||||
body: data.body,
|
||||
icon: '/icons/icon-192.png',
|
||||
badge: '/icons/icon-72.png',
|
||||
tag: 'ice-report',
|
||||
renotify: true
|
||||
};
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title, options)
|
||||
);
|
||||
}
|
||||
});
|