From eaf30baa15bd473f4cc4a39aa0f682a343154743 Mon Sep 17 00:00:00 2001 From: HikeMap User Date: Wed, 31 Dec 2025 08:16:34 -0600 Subject: [PATCH] Add PWA and push notification support for mobile app deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major features added: - Progressive Web App (PWA) manifest and service worker for offline support - Push notifications with VAPID authentication - Mobile-optimized UI with touch navigation fix - Admin panel with configurable settings - Geocache sound alerts - App icons in all required sizes Technical improvements: - Fixed mobile touch handling for navigation selection - Added remesh tool for track point standardization - Improved pathfinding algorithm for more efficient routes - WebSocket-based real-time multi-user tracking - Docker deployment with persistent data volumes Ready for APK generation via PWA2APK.com or Bubblewrap Full offline support with map tile caching Push notifications for geocache alerts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.local.json | 9 +- .gitignore | 6 + .well-known/assetlinks.json | 10 + BUILD_APK_INSTRUCTIONS.md | 121 ++ Dockerfile | 12 + generate-vapid-keys.js | 13 + generate_icons.py | 82 + geocaches.json | 60 + icon-128x128.png | Bin 0 -> 1245 bytes icon-144x144.png | Bin 0 -> 1388 bytes icon-152x152.png | Bin 0 -> 1471 bytes icon-192x192.png | Bin 0 -> 1835 bytes icon-384x384.png | Bin 0 -> 3794 bytes icon-512x512.png | Bin 0 -> 5264 bytes icon-72x72.png | Bin 0 -> 721 bytes icon-96x96.png | Bin 0 -> 947 bytes index.html | 694 +++++- manifest.json | 94 + package-lock.json | 4001 +++++++++++++++++++++++++++++++++++ package.json | 5 +- server.js | 148 +- service-worker.js | 214 ++ twa-manifest.json | 54 + 23 files changed, 5467 insertions(+), 56 deletions(-) create mode 100644 .gitignore create mode 100644 .well-known/assetlinks.json create mode 100644 BUILD_APK_INSTRUCTIONS.md create mode 100644 generate-vapid-keys.js create mode 100644 generate_icons.py create mode 100644 icon-128x128.png create mode 100644 icon-144x144.png create mode 100644 icon-152x152.png create mode 100644 icon-192x192.png create mode 100644 icon-384x384.png create mode 100644 icon-512x512.png create mode 100644 icon-72x72.png create mode 100644 icon-96x96.png create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 service-worker.js create mode 100644 twa-manifest.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ad4896a..b7b994c 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,14 @@ "Bash(git config:*)", "Bash(git push:*)", "Bash(docker-compose:*)", - "Bash(curl:*)" + "Bash(curl:*)", + "Bash(docker logs:*)", + "Bash(docker exec:*)", + "Bash(python3:*)", + "Bash(npm install)", + "Bash(node:*)", + "Bash(npm install:*)", + "Bash(keytool:*)" ] } } \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6794d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.env +.claude/ +push-subscriptions.json +*.backup.* +.DS_Store \ No newline at end of file diff --git a/.well-known/assetlinks.json b/.well-known/assetlinks.json new file mode 100644 index 0000000..eb91c74 --- /dev/null +++ b/.well-known/assetlinks.json @@ -0,0 +1,10 @@ +[{ + "relation": ["delegate_permission/common.handle_all_urls"], + "target": { + "namespace": "android_app", + "package_name": "org.duckdns.bibbit.hikemap", + "sha256_cert_fingerprints": [ + "4B:99:EB:12:8C:5C:7B:9B:3C:2F:E5:5C:7A:5D:22:16:7C:A4:8B:28:95:DF:B8:A3:F5:7C:06:92:F0:73:79:36" + ] + } +}] \ No newline at end of file diff --git a/BUILD_APK_INSTRUCTIONS.md b/BUILD_APK_INSTRUCTIONS.md new file mode 100644 index 0000000..6b08d8e --- /dev/null +++ b/BUILD_APK_INSTRUCTIONS.md @@ -0,0 +1,121 @@ +# HikeMap APK Build Instructions + +Your HikeMap PWA is ready to be converted to an APK! Here are three methods to create an installable Android app: + +## Method 1: Online Converter (Easiest - No coding required) + +### Using PWA2APK.com: +1. Visit https://pwa2apk.com +2. Enter your app URL: `https://maps.bibbit.duckdns.org` +3. Click "Start" +4. Fill in the form: + - App Name: HikeMap Trail Navigator + - Short Name: HikeMap + - Package ID: org.duckdns.bibbit.hikemap +5. Click "Generate APK" +6. Download the APK file +7. Share with users - they can install directly! + +### Using PWABuilder.com (Microsoft's Tool): +1. Visit https://www.pwabuilder.com +2. Enter URL: `https://maps.bibbit.duckdns.org` +3. Click "Start" +4. Review the PWA score (should be high!) +5. Click "Package for stores" +6. Select "Android" +7. Download the APK package + +## Method 2: Using Bubblewrap (Advanced - Full control) + +If you want to build locally with full customization: + +```bash +# Install required tools +sudo apt-get install openjdk-11-jdk android-sdk + +# Install Bubblewrap globally +npm install -g @bubblewrap/cli + +# Initialize your TWA project +bubblewrap init --manifest="https://maps.bibbit.duckdns.org/manifest.json" + +# Build the APK +bubblewrap build + +# The APK will be in: app-release-signed.apk +``` + +## Method 3: Android Studio (Most Control) + +1. Download Android Studio +2. Create new project → "Empty Activity" +3. Add TWA (Trusted Web Activity) support +4. Configure `AndroidManifest.xml` with your URL +5. Build → Generate Signed Bundle/APK + +## APK Features + +Your generated APK will have: +- ✅ Full offline support (Service Worker caching) +- ✅ Push notifications +- ✅ GPS location access +- ✅ Camera access (for future features) +- ✅ Install to home screen +- ✅ Runs in fullscreen (no browser UI) +- ✅ Auto-updates from your server + +## Sharing the APK + +Once you have the APK file: + +1. **Direct Install**: Users enable "Install from unknown sources" in Android settings +2. **Email/Message**: Send the APK file directly +3. **Download Link**: Host on your server at `https://maps.bibbit.duckdns.org/hikemap.apk` +4. **QR Code**: Generate QR code linking to the APK + +## Google Play Store (Optional) + +To publish on Play Store: +1. Create Google Play Developer account ($25 one-time fee) +2. Use the AAB (Android App Bundle) format instead of APK +3. Add the assetlinks.json file to your server +4. Submit for review + +## Testing the APK + +Before sharing: +1. Test on multiple Android versions (7.0+) +2. Verify GPS works properly +3. Test push notifications +4. Check offline functionality +5. Ensure all icons display correctly + +## Current Configuration + +Your app is configured with: +- Package ID: `org.duckdns.bibbit.hikemap` +- Version: 1.0.0 +- Min Android: 7.0 (API 24) +- Target Android: Latest +- Orientation: Portrait +- Theme Color: #4CAF50 + +## Troubleshooting + +**"App not installed" error**: +- Enable "Install unknown apps" for your browser +- Uninstall any previous version first + +**Notifications not working**: +- Ensure HTTPS is working +- Check notification permissions in Android settings + +**GPS not working**: +- Grant location permission when prompted +- Check location services are enabled + +--- + +## Quick Start (Recommended) + +For fastest results, use **PWA2APK.com** - it takes about 2 minutes and produces a ready-to-share APK file that your users can install immediately! \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b74f838..5e31742 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,18 @@ RUN npm install # Copy application files COPY server.js ./ COPY index.html ./ +COPY manifest.json ./ +COPY service-worker.js ./ + +# Copy .env file if it exists (for VAPID keys) +COPY .env* ./ + +# Copy PWA icons +COPY icon-*.png ./ + +# Copy .well-known directory for app verification +COPY .well-known ./.well-known + # Copy default.kml if it exists (optional) COPY default.kml* ./ diff --git a/generate-vapid-keys.js b/generate-vapid-keys.js new file mode 100644 index 0000000..7451f40 --- /dev/null +++ b/generate-vapid-keys.js @@ -0,0 +1,13 @@ +const webpush = require('web-push'); + +// Generate VAPID keys +const vapidKeys = webpush.generateVAPIDKeys(); + +console.log('Add these to your .env file:'); +console.log(''); +console.log(`VAPID_PUBLIC_KEY=${vapidKeys.publicKey}`); +console.log(`VAPID_PRIVATE_KEY=${vapidKeys.privateKey}`); +console.log(`VAPID_EMAIL=mailto:admin@maps.bibbit.duckdns.org`); +console.log(''); +console.log('Public key for client-side:'); +console.log(vapidKeys.publicKey); \ No newline at end of file diff --git a/generate_icons.py b/generate_icons.py new file mode 100644 index 0000000..32b8a46 --- /dev/null +++ b/generate_icons.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Generate PWA icons from SVG or create simple placeholder icons +""" + +from PIL import Image, ImageDraw, ImageFont +import os + +def create_icon(size): + """Create a simple app icon with the specified size.""" + # Create a new image with a green background + img = Image.new('RGBA', (size, size), color='#4CAF50') + draw = ImageDraw.Draw(img) + + # Draw a white mountain shape + mountain_color = 'white' + # Mountain 1 (left) + mountain1 = [ + (size * 0.1, size * 0.7), + (size * 0.35, size * 0.3), + (size * 0.6, size * 0.7) + ] + draw.polygon(mountain1, fill=mountain_color) + + # Mountain 2 (right, slightly overlapping) + mountain2 = [ + (size * 0.4, size * 0.7), + (size * 0.65, size * 0.4), + (size * 0.9, size * 0.7) + ] + draw.polygon(mountain2, fill='#E8F5E9') + + # Draw a location pin at the peak + pin_center = (int(size * 0.65), int(size * 0.4)) + pin_radius = int(size * 0.05) + + # Pin circle + draw.ellipse( + [pin_center[0] - pin_radius, pin_center[1] - pin_radius, + pin_center[0] + pin_radius, pin_center[1] + pin_radius], + fill='#FF5722' + ) + + # Pin point + draw.polygon([ + (pin_center[0] - pin_radius, pin_center[1]), + (pin_center[0] + pin_radius, pin_center[1]), + (pin_center[0], pin_center[1] + pin_radius * 2) + ], fill='#FF5722') + + # Add "HM" text at the bottom + try: + # Try to use a default font, fall back to PIL default if not available + font_size = int(size * 0.15) + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size) + except: + font = ImageFont.load_default() + + text = "HM" + bbox = draw.textbbox((0, 0), text, font=font) + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + text_x = (size - text_width) // 2 + text_y = int(size * 0.75) + + draw.text((text_x, text_y), text, fill='white', font=font) + + return img + +# Icon sizes needed for PWA +sizes = [72, 96, 128, 144, 152, 192, 384, 512] + +print("Generating HikeMap PWA icons...") + +for size in sizes: + icon = create_icon(size) + filename = f"icon-{size}x{size}.png" + icon.save(filename, "PNG") + print(f"Created {filename}") + +print("\nAll icons generated successfully!") +print("Icons feature a mountain landscape with location pin design.") \ No newline at end of file diff --git a/geocaches.json b/geocaches.json index cde33bc..b6366f5 100644 --- a/geocaches.json +++ b/geocaches.json @@ -17,5 +17,65 @@ ], "createdAt": 1767115055219, "alerted": true + }, + { + "id": "gc_1767127362829_hbnrs61u2", + "lat": 30.527765731982754, + "lng": -97.83747911453248, + "messages": [ + { + "author": "Dr. Poopinshitz", + "text": "Home is where you poop", + "timestamp": 1767127384562 + }, + { + "author": "Riker", + "text": "I poop here too", + "timestamp": 1767132954284 + }, + { + "author": "Sulu", + "text": "I poop outside weirdos.", + "timestamp": 1767132973928 + }, + { + "author": "Ibby Dibby", + "text": "CAN I EAT IT?", + "timestamp": 1767133040824 + }, + { + "author": "Riker", + "text": "Stupid little cousin ibby dibby...", + "timestamp": 1767133204544 + } + ], + "createdAt": 1767127362829, + "alerted": true + }, + { + "id": "gc_1767133043404_9zkbxphry", + "lat": 30.52230723615832, + "lng": -97.82822817564012, + "messages": [ + { + "author": "Georges Evil Twin.", + "text": "I'm going to lick all the raw chicken.", + "timestamp": 1767133083657 + } + ], + "createdAt": 1767133043404 + }, + { + "id": "gc_1767140463979_69hvt9x5v", + "lat": 30.489440035930812, + "lng": -97.80640304088594, + "messages": [ + { + "author": "Riker", + "text": "Why are their kids on my laser pointer field!?", + "timestamp": 1767140488863 + } + ], + "createdAt": 1767140463979 } ] \ No newline at end of file diff --git a/icon-128x128.png b/icon-128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..5ad25d3bd67dd618ea060a511dd663ca0426a915 GIT binary patch literal 1245 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU|Hhn;uumf=k46n*;B$Lj?I@a z`HIADs_QfY5qQS*Bps;A%WRn!$JZ`E76^uN=>l5nj&reoYKVM{&=ZI7KX zO={{T4XGmzuFP8}>pfXwJlQ9G-_LVTZSQ~nW0rU><{bC=_qFG1P|Q{ge7`6eM(Vm;ApR8yj8k$nS*86a=isyJ@bF3-n@0Ozg&IA5{qT( z4$JS(E#g`u^6&rJ=)-FmT%COt9d}PCo^W6d!?l+tF|UPYh%C6B*JsMw)s_{>RLnGm zH!z&_sDXMzN4F+pP3ih&4$o%@IUG6O!?16+*%J|ss>#Pc`6>!CwQ!woVwrZBy@z9h zcK`m}p|2{rRm2s(h;Iv+|Nm`c{+@k$qMocBWsi@2w)?tp+pAB%N>2-^%xBnnF?U9o zs)w4xo9t5g-&?shR&s5b#U-YCfki>RlVRn%D^qW7kNv)DUHOcaz8^pQT|`5-;ZT3SKc`kb zONeewyFRBEXTsj!tEKiGZfBV~mm!q@eqdPBI@S&M{w^}!C3K+bhtA#i8pfZ$5uI}0g*}EPBU&0Ty)B+_sl5ixrl)w3p%W~`3<9X-DR{eSUcD>=(7iEXD z-P-ydTzMz#-S7YZi{`&S*=0w+GauM_|J>u>FXgAb{d#iu^1_S{4u_-ura$dca}aK4 z=TADj%9J--`r5kV|JME%3KbCp24j7^yNYY9yosa`b~9}hK|sJzVNN#3=RGF z4$xq+EsL-}d;8+-B?7(RS{ld|cSlOp4* z9VP!yZPI*DXZQcN<+^``x}li|PyEtaFjN0qP28)$=f$2GT2?dv-u~|J@BB9hKTO+u zV_%^(L&Bqj{SiOX54z7>c7Y}1(V;UI4cF8E&-`x3%y304dq>6lqEnli0#|ZgI9c0J zc4zMVgL9W(200+RIzFE7UX4ZT&gsQBw&X3oUZuN$a|4rjnx2{6KJ$4iCVlW|5Z&=L zgUdhe3?oC`{Qcc8m+x0wry(=dim4_rddm0702W&ep00i_>zopr059$(Hvj+t literal 0 HcmV?d00001 diff --git a/icon-144x144.png b/icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..9ff42029d5a93a16531c1396fa9cb4d8c9c49db6 GIT binary patch literal 1388 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$farfz{a4#WAE}&fB~G88WF7$3Mo; z+99Io$Q-698ufT*)<%UWO)jY*UupiPd8}U*G(`7sC{>7^Z{m<+$^W~#>cXN9T{l+! zMeWzrB9<=A%u;rm)VgcUa?P75li!^){=DzK@#nvHjAE_)XXeklId@yii<^%(%Q9NN z?XYF2QC65CAmGEnp~k`@%+$omc(A1*!O6iuk+#AiY73sd`FKP5N{}kksYi;7K4=LF zJSvpGaBS^iF|O044$()QTpsS=$y(v{aM?%dtVTb12atNmJio@3l}sdC0jqYM!_?qC>C<>`_HkS59hMf8og6Dxygj&fO@$tl)2>PUD_*QUD5e#5iE(P_MrW5P z-w!U>&few8-tt9NI^brs(ox+fxxYO+*+gE|dUbr(sj~@x@j7U7ibLqfi?&TbJW+yXXbuQXzk}^ERpR zPo^k$$xSG))XSUK-nP_6gQWp>`zL6S9GfjTP+G= z{v^f!>$vV3_Vu^bg^ReSicPrw_Ob0epA{W99A1a0&hzQ$@Zvo2_}j`To0*%GwQLj~ zdHT;3EaYzXT*$bw_EP9IF)baSSiGd*)o)^9+bfJ*-9tPa?rrmreSA_xGVrZgz+E6i zbfTp~{%!W7MyyYC<+k~6SK1bko0oXx@@&&ZcNi;g*&W$z>+PafpQLyxaCWrBDMx$> zx#x)70-fYu!`iAJlV``r$Hrdrj^DfIWhwi+%82(ipT3=4zyI%R>%IR!vq%54n|Xa- zWvuxBzr~k7vX|Q~_%2tw{dL6ZyO%zn761IlaME$MQ!igU@j3r^^JI4>e<5+krzIKR z6?;vO?dp3gH}}H7-SS-~fif&pf|Gxk`M#g~%BrlS$jw;_ENHUe{&f8ncQd^w@x7mW z?L=Rq=cSC~5dV9>TU0XN?%z7^egCSC2DU9*wk_Ft+TLT*vGRK>kE`=}Oi>zbzO2cD}&>%=}9SJCCb)x~rv^Ji8QWTYP_u%F4?x zSM2<-v+r&F;t9n|`*^1u-ue1@Y2MF0Dlh+8t9^a7FWh8bam=JIkEhNrNli6;R^fOx zjrr?unHBNs`X*nmHJYn>23OB}WfiBMvtZ7?{Wi<9v#;;!JNtk0g82XcbT7FS3IzDQ z^@~2+s=upm?i_p3%vlv2Cmx)(_gj56&vVkc?bAx1UAlQfxHWwCcZbx7og0I0?tlDB qTpC>Z{L41>xq&ajP|K+&^?w)l&3v8xc`>jQW$<+Mb6Mw<&;$T3lxLs- literal 0 HcmV?d00001 diff --git a/icon-152x152.png b/icon-152x152.png new file mode 100644 index 0000000000000000000000000000000000000000..01c2b0f673533697d0f0ab9dcdaf4e234e3e04b7 GIT binary patch literal 1471 zcmcgseKgYx7?w<*;uhoNV{6h;KJq!VOSjrDIpwo3XJfy#DQ708DVDk8lH9nh+bGq_ z$3?=z|UK6^Ulp$ zT3UKO$IwBVdFyL!)YWv8Hux?rEl8#h`p~IMcNHTeq(P!t=chRfGaIBfR+1hm4Gi2= z2sum$@cWZj^JFcprCg1e(znh&eF5rY08MZ%=<7*C{7N-2Cyw;vho2HCI*)4DYypC`<_Jc{u#;rj* zx_h^!vkNoqBILxXT`~PUV ziv`exzi%QOmIiLD=x0JF1S=n)-h)D&NW+@Q}scogQ+hC_R zL3^MhSh(<`5Xns*t1qLs<4G=j?>sWtbtI4B+K>@Mew{vgFGviS+fe3_`nCW7r|mSG7YMG?L#nB)Q6O^KSXr(JdJnxr zn|xBzL1Ay-bX+91D?>tIaGEj4{&+qmBa0L_oqWukvT{kw!Jvk2w zJWhtk^fImVD{X`D@B6EpStSgahNw!eq{ItEk9V4G4SJz1B-0=>lPaET(r4Ui9-)s=PQg#Mk6yI zA(;vm7I&z-OAH2gyK>;cgbba$6;-z<2M!SCTe5`tMV1p;zua_j;N|-Rv5sM2hYTCY zj7P^7&Bj9j^qx(&lZpY`c*wMdp~3}=V>s1JF*I@;;QL!BxfNk`p${aHX3V2F2BPL1 zL)IY3B2|f*z@}^U-BwWqUmAM*kSY=wj05?x`wfftx|v8T4mk2KM;NktK~@Da=V+c% zKjTWOZxXu5{dnsq9?;rkI0jYIOTT1Z_N+RzF$Ttr%)f?(@3M z7SGSzx-D2y&=TjLExM(Qgj%G$SY9*_OBkCS5UR#IJ~hx`{L|@x!(X(1308`HGgezH zWJ?m!j%|QR_rsgj8LceBAh6Me66(bZ($NnBo|@yWA(gx&Assz$Q#witB=!}c!B0*)a+D~C9C+AXm-{AzGOA$vvoDAxKz)!w9mM6_U(uDQBg>- z8yIdLUSVPC@0z#8*A!C=dm1v)_GTzCdd>J*^-RPJzp9L)?ms^!{^!2);PB}1$436R zCq~DH7kTDR%%fLxo!d`LY+(SDMEPAid%KWWeAv=}SMw^+cJ(rxdkmTnB~#>=WvV6p zvoT3Tk9TM<7X{5BgZXd=(us;tnsB(SUo&l7UHY@3#@65a@Ha`fw(jITwwv<~xUnXf NwR`|SH0Lmw_AhKUy2bzi literal 0 HcmV?d00001 diff --git a/icon-192x192.png b/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..72a48b1c223a8f568a03d6f68632091893565555 GIT binary patch literal 1835 zcmdT_X;jm97X5{=$z~9XECQ+65>u(ARVW}y5Ks{dR6(X?b0t84KqCea$e-O9&>71z zAdo^b+{ciuhso%_B$dZ7Oi zV*@J#0D!UYQSTF)F8HJLbv1lqEKUyq-6dad&!Ch_`S=Uc-~|ieBI#bY&M7QUP&t!p zV(|Rn?Hr@f3k^3pIhbkI_o7U~Z#2}#j_j0edETcf<#qqGssErp=(Oia%0NY4v6m^0 zja1|0LXJmg$BX%lan+j*x69S>m+*-Xo7E$QBZK#ek~T!`>s%Z_Z9@>O{sK7uSzCO- zv_0#*_5w3*l}<4HbcF7eei< z<$43N(IUHF1=t;B*QYLL9nQyJZ)TJq2dT#7y{0VO=ZM?U$&AKvt!;?Ohun6ZzJH_x zALB-${?e%Q7@x3&032U8sFvR<3%xCL8a+?ZK{52tJ4w~_ar27<>b;p5W#CMpu8Px>pD!eiJ2y8 z_->Uob^FC#oj10C=L6QBKisX`O$P_##_5NuZw)v$T{85xgZMIW?w2848Jj!am=U%o zdm1S)PTk5rrv0_v%VafC>+7Xm*O zdw;Eq`gKLTJz3hqqlox~Vg#$brkj`fg#;v7t$?=8BT8e0){Du7FF9a%Ifu04uywF!POI1nVgga ztq=r;Ov{yFL2yI-=5ZrFbQ5S2{?)}?w9R4w3%E7)n!pEK4a&z)9LRo&z$DPPGI2^F zIvQN<+8UYt0>o6Z215|&N2`zxHx!g*K4y{H!R`7VjF}D;hF`UUTo8957G1pY6uJ(dv<=EB=Yc+HtHeH7x8 z-r>0dY2_lxnVr%XaeuSKS6mdf+7*FYjqSm^zf;gvZ*!QqN@X0GeYq>G)Z<&_IkH`< zu*1^Bd2T4jTEKSv#5q)5S+08i^BP}b?>AJIQoG+2I>~swuT|-1SG_oKGbFC!0II(L z?3|ldG`?Sd(C#ww<9#M}ZGX%zAiPJ8j8QpuP)FZcD@ITA$J0rWS$zShbfwOW)d@pG zl>X{Uc#rt24Xgg^8r5xSsxB*Yx>t|=uC!M)!KL20+oTg-B6R58i1wl}vC6_d9|B@i zL}!%RU(tLpfkfhTC>%?K8{?r48L+hGr@r3OU73Z?ay46duu0vAgf4NCUxZ%bLH{;5jOFgfHOp28yCpyT$%7KN7hv$w2|AaJX#XQ zk*G}WH=$E&*8>!sxrrgKolch8FZ%`0SVfY}PR<=#FjKi#5}2AkGy9SI!mmF82R}o^ zm&&DUv^g(Bc>gA&n*hG*pgVgjDjf0;LL8-E`=5_U}{9H-Z2F literal 0 HcmV?d00001 diff --git a/icon-384x384.png b/icon-384x384.png new file mode 100644 index 0000000000000000000000000000000000000000..87ba8b14ef3794fd3c60afb0741fa0413996c611 GIT binary patch literal 3794 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 literal 0 HcmV?d00001 diff --git a/icon-512x512.png b/icon-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..6be6afb8d4d54b7b052c0349957cae7897791343 GIT binary patch literal 5264 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 literal 0 HcmV?d00001 diff --git a/icon-72x72.png b/icon-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..e102e9944aff05ce0bfd88d1b3927a01fefcacfb GIT binary patch literal 721 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2V4C6S;uumf=k4sX*}{$z$L4RD zeTkpv(aJ9qrb%sx`^A36+jUKV(2YARAJyb+eoW_>=X`YYc3qvTaaAvuey`ZlJ5_1R zq9D<=?2i7MITdH8+ozSKz5Z>jI{WU;@;aOMZ&)UI8Grci)RB6qjB%ZE6NgfV0`WvE z2iM;P9k(S8?od!&zGl*rqa1FmyvvvMXnx8OpV0at)V%8MuKihG;*JT4OrN$#Q)-Iw ztG#(W97#f(yt*bQ22Ikr#PYUj(z3-m+}pw&Gu7vQ`?G`f<`bvbU1HYNl?LDT=566T zwdcdK9q*31sb4LWoucO0EEJN(b~)K=l9=Pp?`BVaEtzbtsJUGs)_?Ji!bfVd+rPb- z_N1gcb?zEJ&z&qU@15SU!~B%ai7P2rc2ov0(029PHe=n%mlEoe6(+AO`1X48wU3{? zinJVeJ=OT+;iY@xXzLY~l`KZMIxE1szN1^Pc$xh zY`T`T?TN-DuSHG&wlBYC5GIsjZQG@yDW!eFr7C9P;)F?BpQfMrXt9yMNK(*6z>x(+ zv^cR|U6#f1m*M>N`L4%jot_P3zHO7QmHWjgcdtfZ?&pfHfh9Fro1#)qzEgW~?^0}q zu-vk}nRiy+-xk(BdB4Hy+UowJQL@{`t{H^SZ4O`g=*Mq%ovUKp>yLIVICk}Qa{0@6 zy?u|qJl6yoJ!9>;g#VB2o~1wMUbCTps+hHny;Jt?-fLyjbKhM%QCwD)zoF|7HOXUCn literal 0 HcmV?d00001 diff --git a/icon-96x96.png b/icon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..b624e88f49dd83bf01048fc567947addbff6f76c GIT binary patch literal 947 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V6O6XaSW-L^LFk*?~p)|WBVo3 zye=Iy)L7IQxk5KGr4#TT6>-D0=CvheNR!F)liSx9<6z-_JT{NX?X8@3>%Mn!duz4hAEo zh8_+DWu_ApF@>2L9A^}+(~fQRa#OtB7^7INK){_6RL=_P}a3FGh3>Px#Cloo`Z zSf)Bfe*fC~jVCvVCroy0T%EjFczLcqJHtZeTWjAf=}}tE)YP^r*lBg)VJY__ahGGW z8a7;g^IOBqhkeVfl)d*_7Kd#-ADa-%?pLnDrqdf{PqkV>TkT-T6N#d zjrE0`Y~uOrc{_MG@0B|&4hX$!E@it-RYEu+>oHH!y$y-CrX;vDd~DBTGf%l)VcoPS zY~xzJ3wj5x*;a3U{etU!8_Qazoce^U65G2AXHI)?mBUDMnkVNimJM>^D_+l97ZUvZ z`zq!~9b$}c+Ecf^i|~n2-^5V%G z{?WBS=$4p5xouj(F6KvJf4dZ7FHB=_`+D58#&x$)&?ozp9Tv7tlNwMW@9~}cocHbO zxtrb3Cq4eow(eZ~dU^Ya?~m_YXwA^@vfNlhcY1lPJwwgkooy@A%>xhbw|?zo)$D$l zzi`K5c7_k1PHW$(Ex!M(|FY0}s|6QIWuEu(UW<>{iCNM9Uh5vmjh~yD86;jyua)0@ z$9~gmTk*m?me$m#$JI;H_#d8`$S6|m`h>r1@%G)~@Aua`gl`jWUB^`2$y*ra@XkT+ zg7FHetcp!G?JFClfo3@RyUQ^I{E9gCYl#MOtW$i{r>ppGk1)&X{Yy5#grh) z5HszHz1yB_VaWf+z;I(-UUFio;LQ@Tl_6YDr!HS{ZPlAp)1%sq3 - KML Track Editor + HikeMap Trail Navigator + + + + + + + + + + + + + + + + + + + + + + +