feat: add Progressive Web App (PWA) support with service worker registration, manifest, update notifications, and install prompts

This commit is contained in:
2026-03-24 23:55:15 -04:00
parent 611cd3ca02
commit f22a2abae8
25 changed files with 2522 additions and 1 deletions
+42
View File
@@ -0,0 +1,42 @@
# ObsiGate PWA Icons
## Generated Icons
This directory contains PWA icons in SVG format.
### Converting to PNG
For production, convert these SVG files to PNG:
**Using ImageMagick:**
```bash
for file in *.svg; do
size=$(echo $file | grep -oP '\d+x\d+' | head -1 | cut -d'x' -f1)
convert -background none -resize ${size}x${size} "$file" "${file%.svg}.png"
done
```
**Using Inkscape:**
```bash
for file in *.svg; do
size=$(echo $file | grep -oP '\d+x\d+' | head -1 | cut -d'x' -f1)
inkscape "$file" --export-filename="${file%.svg}.png" --export-width=$size
done
```
**Online tools:**
- https://cloudconvert.com/svg-to-png
- https://convertio.co/svg-png/
### Icon Types
- **Regular icons**: Standard app icons with rounded corners
- **Maskable icons**: Icons with safe zone padding for adaptive icons
- **Search icon**: Icon for the search shortcut
### Sizes
- 72x72, 96x96, 128x128, 144x144, 152x152: Mobile devices
- 192x192: Android home screen
- 384x384: High-res displays
- 512x512: Splash screens and high-DPI displays
+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="128" height="128" rx="19.2" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(32.0, 25.6)">
<!-- Book cover -->
<rect x="0" y="0" width="64.0" height="76.8" rx="2.56" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="6.4" y="0" width="2.56" height="76.8" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="12.8" y1="19.2" x2="57.6" y2="19.2" stroke="#1a1a1a" stroke-width="1.28" opacity="0.3"/>
<line x1="12.8" y1="32.0" x2="57.6" y2="32.0" stroke="#1a1a1a" stroke-width="1.28" opacity="0.3"/>
<line x1="12.8" y1="44.8" x2="57.6" y2="44.8" stroke="#1a1a1a" stroke-width="1.28" opacity="0.3"/>
<line x1="12.8" y1="57.6" x2="44.8" y2="57.6" stroke="#1a1a1a" stroke-width="1.28" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="144" height="144" rx="21.599999999999998" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(36.0, 28.8)">
<!-- Book cover -->
<rect x="0" y="0" width="72.0" height="86.39999999999999" rx="2.88" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="7.2" y="0" width="2.88" height="86.39999999999999" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="14.4" y1="21.599999999999998" x2="64.8" y2="21.599999999999998" stroke="#1a1a1a" stroke-width="1.44" opacity="0.3"/>
<line x1="14.4" y1="36.0" x2="64.8" y2="36.0" stroke="#1a1a1a" stroke-width="1.44" opacity="0.3"/>
<line x1="14.4" y1="50.4" x2="64.8" y2="50.4" stroke="#1a1a1a" stroke-width="1.44" opacity="0.3"/>
<line x1="14.4" y1="64.8" x2="50.4" y2="64.8" stroke="#1a1a1a" stroke-width="1.44" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="152" height="152" rx="22.8" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(38.0, 30.400000000000002)">
<!-- Book cover -->
<rect x="0" y="0" width="76.0" height="91.2" rx="3.04" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="7.6000000000000005" y="0" width="3.04" height="91.2" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="15.200000000000001" y1="22.8" x2="68.4" y2="22.8" stroke="#1a1a1a" stroke-width="1.52" opacity="0.3"/>
<line x1="15.200000000000001" y1="38.0" x2="68.4" y2="38.0" stroke="#1a1a1a" stroke-width="1.52" opacity="0.3"/>
<line x1="15.200000000000001" y1="53.199999999999996" x2="68.4" y2="53.199999999999996" stroke="#1a1a1a" stroke-width="1.52" opacity="0.3"/>
<line x1="15.200000000000001" y1="68.4" x2="53.199999999999996" y2="68.4" stroke="#1a1a1a" stroke-width="1.52" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Full background for maskable -->
<rect width="192" height="192" fill="url(#grad1)"/>
<!-- Book icon (centered with padding) -->
<g transform="translate(57.25, 49.6)">
<!-- Book cover -->
<rect x="0" y="0" width="76.5" height="91.8" rx="3.06" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="7.65" y="0" width="3.06" height="91.8" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="15.3" y1="22.95" x2="68.85000000000001" y2="22.95" stroke="#1a1a1a" stroke-width="1.53" opacity="0.3"/>
<line x1="15.3" y1="38.25" x2="68.85000000000001" y2="38.25" stroke="#1a1a1a" stroke-width="1.53" opacity="0.3"/>
<line x1="15.3" y1="53.55" x2="68.85000000000001" y2="53.55" stroke="#1a1a1a" stroke-width="1.53" opacity="0.3"/>
<line x1="15.3" y1="68.85000000000001" x2="53.55" y2="68.85000000000001" stroke="#1a1a1a" stroke-width="1.53" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="192" height="192" rx="28.799999999999997" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(48.0, 38.400000000000006)">
<!-- Book cover -->
<rect x="0" y="0" width="96.0" height="115.19999999999999" rx="3.84" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="9.600000000000001" y="0" width="3.84" height="115.19999999999999" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="19.200000000000003" y1="28.799999999999997" x2="86.4" y2="28.799999999999997" stroke="#1a1a1a" stroke-width="1.92" opacity="0.3"/>
<line x1="19.200000000000003" y1="48.0" x2="86.4" y2="48.0" stroke="#1a1a1a" stroke-width="1.92" opacity="0.3"/>
<line x1="19.200000000000003" y1="67.19999999999999" x2="86.4" y2="67.19999999999999" stroke="#1a1a1a" stroke-width="1.92" opacity="0.3"/>
<line x1="19.200000000000003" y1="86.4" x2="67.19999999999999" y2="86.4" stroke="#1a1a1a" stroke-width="1.92" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="384" height="384" rx="57.599999999999994" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(96.0, 76.80000000000001)">
<!-- Book cover -->
<rect x="0" y="0" width="192.0" height="230.39999999999998" rx="7.68" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="19.200000000000003" y="0" width="7.68" height="230.39999999999998" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="38.400000000000006" y1="57.599999999999994" x2="172.8" y2="57.599999999999994" stroke="#1a1a1a" stroke-width="3.84" opacity="0.3"/>
<line x1="38.400000000000006" y1="96.0" x2="172.8" y2="96.0" stroke="#1a1a1a" stroke-width="3.84" opacity="0.3"/>
<line x1="38.400000000000006" y1="134.39999999999998" x2="172.8" y2="134.39999999999998" stroke="#1a1a1a" stroke-width="3.84" opacity="0.3"/>
<line x1="38.400000000000006" y1="172.8" x2="134.39999999999998" y2="172.8" stroke="#1a1a1a" stroke-width="3.84" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Full background for maskable -->
<rect width="512" height="512" fill="url(#grad1)"/>
<!-- Book icon (centered with padding) -->
<g transform="translate(153.25, 132.8)">
<!-- Book cover -->
<rect x="0" y="0" width="204.5" height="245.39999999999998" rx="8.18" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="20.450000000000003" y="0" width="8.18" height="245.39999999999998" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="40.900000000000006" y1="61.349999999999994" x2="184.05" y2="61.349999999999994" stroke="#1a1a1a" stroke-width="4.09" opacity="0.3"/>
<line x1="40.900000000000006" y1="102.25" x2="184.05" y2="102.25" stroke="#1a1a1a" stroke-width="4.09" opacity="0.3"/>
<line x1="40.900000000000006" y1="143.14999999999998" x2="184.05" y2="143.14999999999998" stroke="#1a1a1a" stroke-width="4.09" opacity="0.3"/>
<line x1="40.900000000000006" y1="184.05" x2="143.14999999999998" y2="184.05" stroke="#1a1a1a" stroke-width="4.09" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="512" height="512" rx="76.8" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(128.0, 102.4)">
<!-- Book cover -->
<rect x="0" y="0" width="256.0" height="307.2" rx="10.24" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="25.6" y="0" width="10.24" height="307.2" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="51.2" y1="76.8" x2="230.4" y2="76.8" stroke="#1a1a1a" stroke-width="5.12" opacity="0.3"/>
<line x1="51.2" y1="128.0" x2="230.4" y2="128.0" stroke="#1a1a1a" stroke-width="5.12" opacity="0.3"/>
<line x1="51.2" y1="179.2" x2="230.4" y2="179.2" stroke="#1a1a1a" stroke-width="5.12" opacity="0.3"/>
<line x1="51.2" y1="230.4" x2="179.2" y2="230.4" stroke="#1a1a1a" stroke-width="5.12" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="72" height="72" rx="10.799999999999999" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(18.0, 14.4)">
<!-- Book cover -->
<rect x="0" y="0" width="36.0" height="43.199999999999996" rx="1.44" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="3.6" y="0" width="1.44" height="43.199999999999996" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="7.2" y1="10.799999999999999" x2="32.4" y2="10.799999999999999" stroke="#1a1a1a" stroke-width="0.72" opacity="0.3"/>
<line x1="7.2" y1="18.0" x2="32.4" y2="18.0" stroke="#1a1a1a" stroke-width="0.72" opacity="0.3"/>
<line x1="7.2" y1="25.2" x2="32.4" y2="25.2" stroke="#1a1a1a" stroke-width="0.72" opacity="0.3"/>
<line x1="7.2" y1="32.4" x2="25.2" y2="32.4" stroke="#1a1a1a" stroke-width="0.72" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+27
View File
@@ -0,0 +1,27 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="96" height="96" rx="14.399999999999999" fill="url(#grad1)"/>
<!-- Book icon -->
<g transform="translate(24.0, 19.200000000000003)">
<!-- Book cover -->
<rect x="0" y="0" width="48.0" height="57.599999999999994" rx="1.92" fill="white" opacity="0.95"/>
<!-- Book spine -->
<rect x="4.800000000000001" y="0" width="1.92" height="57.599999999999994" fill="#1a1a1a" opacity="0.2"/>
<!-- Book pages lines -->
<line x1="9.600000000000001" y1="14.399999999999999" x2="43.2" y2="14.399999999999999" stroke="#1a1a1a" stroke-width="0.96" opacity="0.3"/>
<line x1="9.600000000000001" y1="24.0" x2="43.2" y2="24.0" stroke="#1a1a1a" stroke-width="0.96" opacity="0.3"/>
<line x1="9.600000000000001" y1="33.599999999999994" x2="43.2" y2="33.599999999999994" stroke="#1a1a1a" stroke-width="0.96" opacity="0.3"/>
<line x1="9.600000000000001" y1="43.2" x2="33.599999999999994" y2="43.2" stroke="#1a1a1a" stroke-width="0.96" opacity="0.3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+18
View File
@@ -0,0 +1,18 @@
<?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="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2563eb;stop-opacity:1" />
<stop offset="100%" style="stop-color:#1d4ed8;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="96" height="96" rx="14.399999999999999" fill="url(#grad1)"/>
<!-- Search icon -->
<g transform="translate(19.200000000000003, 19.200000000000003)">
<circle cx="24.0" cy="24.0" r="17.28" fill="none" stroke="white" stroke-width="4.800000000000001" opacity="0.95"/>
<line x1="36.480000000000004" y1="36.480000000000004" x2="48.0" y2="48.0" stroke="white" stroke-width="4.800000000000001" stroke-linecap="round" opacity="0.95"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 865 B