From 4efbf4f869dbf34461df40ac5bae4f2ec69882c9 Mon Sep 17 00:00:00 2001 From: Subhan Raj Date: Sat, 20 Dec 2025 16:56:15 +0000 Subject: [PATCH 01/18] feat: implement Progressive Web App (PWA) functionality with offline support, caching, and installation capabilities --- PWA.md | 240 ++++++++++++++++++++++++++++++++++++++++++ README.md | 11 ++ icons/icon-192.png | Bin 0 -> 1778 bytes icons/icon-192.svg | 35 ++++++ icons/icon-512.png | Bin 0 -> 4666 bytes icons/icon-512.svg | 35 ++++++ index.html | 148 ++++++++++++++++++++++++++ manifest.json | 59 +++++++++++ offline.html | 135 ++++++++++++++++++++++++ screenshots/README.md | 14 +++ sw.js | 220 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 897 insertions(+) create mode 100644 PWA.md create mode 100644 icons/icon-192.png create mode 100644 icons/icon-192.svg create mode 100644 icons/icon-512.png create mode 100644 icons/icon-512.svg create mode 100644 manifest.json create mode 100644 offline.html create mode 100644 screenshots/README.md create mode 100644 sw.js diff --git a/PWA.md b/PWA.md new file mode 100644 index 0000000..0f03bb5 --- /dev/null +++ b/PWA.md @@ -0,0 +1,240 @@ +# PWA Implementation Guide + +## Overview +SubGrid is now a fully functional Progressive Web App (PWA) with offline support, installation capabilities, and long-term device storage. + +## Features Implemented + +### ✅ Service Worker (sw.js) +- **Cache-first strategy** for static assets (HTML, CSS, JS, icons) +- **Network-first strategy** for exchange rate API calls +- **Offline fallback** to custom offline page when network unavailable +- **Automatic cache updates** with version management +- **Background sync** for exchange rates (when supported) +- **CDN resource caching** for Tailwind, Iconify, fonts + +### ✅ Web App Manifest (manifest.json) +- App metadata (name, description, icons) +- Standalone display mode (feels like native app) +- Theme colors matching app design +- App shortcuts for quick actions +- Share target integration +- Multiple icon sizes (192x192, 512x512) + +### ✅ Offline Support +- Custom offline page (offline.html) with: + - Connection status monitoring + - Auto-reload when online + - Clear messaging about offline capabilities + - Visual feedback +- All app functionality works offline (except exchange rate updates) + +### ✅ Installation +- Install prompt banner at top of page +- Detects PWA installation capability +- One-click installation +- Apple iOS support with meta tags +- Android support with manifest + +### ✅ Long-term Storage +- LocalStorage for subscription data (already implemented) +- Service worker caching for app shell +- Persistent across sessions and offline use +- Data survives browser restarts + +## How to Use + +### Testing Locally + +1. **Serve with HTTPS** (required for service workers): + ```bash + # Option 1: Using Python + python -m http.server 8000 + + # Option 2: Using npx + npx serve . + + # Option 3: Using local-web-server + npx local-web-server --https + ``` + +2. **Access via localhost**: + - Navigate to `http://localhost:8000` + - Open browser DevTools > Application tab + - Check "Service Workers" to see registration status + - Check "Manifest" to verify PWA configuration + +3. **Test Offline**: + - Load the page once (caches resources) + - Go to DevTools > Network tab + - Check "Offline" mode + - Refresh page - should still work! + - Try adding subscriptions offline + +4. **Test Installation**: + - Chrome: Look for install banner or + icon in address bar + - Edge: Similar to Chrome + - Safari iOS: Share > Add to Home Screen + - Android: Install banner appears automatically + +### Deployment (Cloudflare Pages) + +Already configured in `wrangler.jsonc`: + +```bash +# Deploy to Cloudflare Pages +npx wrangler pages deploy . + +# Or use Cloudflare dashboard +# - Connect your GitHub repo +# - Auto-deploys on push to main +``` + +### PWA Best Practices Implemented + +✅ **HTTPS only** - Service workers require secure context +✅ **Responsive design** - Works on all screen sizes +✅ **Fast loading** - Cached assets load instantly +✅ **Works offline** - Core features available without network +✅ **Installable** - Meets all PWA criteria +✅ **App-like experience** - Standalone display mode +✅ **Theme colors** - Matches system/browser theme +✅ **Proper icons** - Multiple sizes for different devices + +## File Structure + +``` +/ +├── manifest.json # PWA manifest +├── sw.js # Service worker +├── offline.html # Offline fallback page +├── index.html # Main app (with PWA meta tags) +├── icons/ +│ ├── icon-192.png # App icon (192x192) +│ ├── icon-512.png # App icon (512x512) +│ ├── icon-192.svg # Source SVG +│ └── icon-512.svg # Source SVG +└── screenshots/ + └── README.md # Placeholder for app screenshots +``` + +## Caching Strategy + +### Precached on Install +- `/` (home page) +- `/index.html` +- `/offline.html` +- `/styles.css` +- All JavaScript files +- App icons + +### Cached on First Use +- CDN resources (Tailwind, Iconify, fonts) +- Exchange rate API responses + +### Network First +- Exchange rate API (with cache fallback) + +## Browser Support + +| Feature | Chrome | Edge | Safari | Firefox | +|---------|--------|------|--------|---------| +| Service Worker | ✅ | ✅ | ✅ | ✅ | +| Web Manifest | ✅ | ✅ | ⚠️ * | ✅ | +| Installation | ✅ | ✅ | ⚠️ * | ❌ | +| Offline | ✅ | ✅ | ✅ | ✅ | +| Background Sync | ✅ | ✅ | ❌ | ❌ | + +\* Safari uses Add to Home Screen instead of install prompts + +## Debugging + +### Chrome DevTools +1. Open DevTools (F12) +2. Go to **Application** tab +3. Check sections: + - **Manifest** - Verify manifest.json + - **Service Workers** - See registration & status + - **Cache Storage** - Inspect cached files + - **Local Storage** - View subscription data + +### Common Issues + +**Service worker not registering?** +- Must be served over HTTPS (or localhost) +- Check browser console for errors +- Verify sw.js path is correct + +**Install prompt not showing?** +- Manifest must be valid +- Icons must be accessible +- App must be served over HTTPS +- User must visit site at least once + +**Offline not working?** +- Service worker must be active +- Page must be cached +- Check Network tab in DevTools (set to Offline) + +## Updating the PWA + +When you make changes: + +1. **Update cache version** in `sw.js`: + ```javascript + const CACHE_VERSION = 'subgrid-v1.0.1'; // Increment version + ``` + +2. **Service worker will auto-update**: + - Detects new version + - Shows reload prompt to user + - Cleans up old caches + +## Performance Metrics + +Expected Lighthouse scores (PWA audit): +- ✅ Progressive Web App: 100 +- ✅ Performance: 95+ +- ✅ Accessibility: 90+ +- ✅ Best Practices: 100 +- ✅ SEO: 100 + +## Future Enhancements + +Consider adding: +- [ ] Push notifications for subscription renewals +- [ ] Background sync for data backup +- [ ] Periodic background sync for exchange rates +- [ ] Web Share API for sharing stats +- [ ] Payment Request API integration +- [ ] Device contacts API for split subscriptions +- [ ] IndexedDB for larger data sets +- [ ] Advanced caching strategies per route + +## Security Considerations + +✅ **No sensitive data in cache** - Only app shell +✅ **LocalStorage encryption** - Consider implementing +✅ **HTTPS required** - Enforced by service workers +✅ **Content Security Policy** - Consider adding headers +✅ **No external tracking** - Privacy-first approach + +## Testing Checklist + +- [ ] Service worker registers successfully +- [ ] App works offline after first visit +- [ ] Install prompt appears on supported browsers +- [ ] Installed app opens in standalone mode +- [ ] Icons appear correctly on home screen +- [ ] Offline page shows when network unavailable +- [ ] Cache updates on new deployment +- [ ] LocalStorage persists data +- [ ] Exchange rates update when online +- [ ] All visualizations work offline + +## Resources + +- [MDN: Progressive Web Apps](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps) +- [Google: PWA Checklist](https://web.dev/pwa-checklist/) +- [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) +- [Web App Manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest) diff --git a/README.md b/README.md index f43b938..087f5e4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,17 @@ A simple tool to visualize your subscription costs. See where your money goes ea - Import subscriptions from bank statements (CSV) - Export your visualization as an image - Supports 38+ currencies +- **NEW: Progressive Web App (PWA)** - Install on your device, works offline! + +## PWA Features + +✨ **Install as an App** - Add to your home screen on mobile or desktop +📴 **Works Offline** - Access your subscription data without internet +⚡ **Fast Loading** - Cached assets load instantly +💾 **Long-term Storage** - Your data persists on your device +🔄 **Auto Updates** - Get the latest features automatically + +See [PWA.md](PWA.md) for detailed PWA implementation documentation. ## How to use diff --git a/icons/icon-192.png b/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..463971a59af763dcdaf18648f7717a9ff8225f24 GIT binary patch literal 1778 zcmZ{lc~BBq9LFDsNtl)8cqf`|Sz9WpWobaA(cz7inxMiqZ3<%MX66y3U0zVS(;=>= zW>K3RrUsr}I+bXtK!R7AXo4vbmZG>cyW8xx+aK?}nK$3}-uL(W{O0%0`uX4t5atK~ zfWeOK9s%05;md%}(ar%~6g>duaCdlY4m`>p?cPPR*lpUVARk`pY3nZ}JaywOxDY#O z*L|gQ)wuUP-OYFf71%t~J5wFGhp_brr?`3hBYZddu(_W@rR)R^=2`VmEp9_KzETp? z$;nndB5$oCi|C5!IK@Lsv!Zx`&*NfmIo880v$V;Elx8ywoY6^ev$W31;$J^yg&7Nu z*Hz7R4_d-crnOIXnPxFrBbpCpFb7DX!Z4@Rx|uKC*w<&mXWpeGwsMZ=FNzBeoaIfU z`<_?mE!e1Aq&Rn;heZCK-D5FTo$AZ@Ii)00lJ~}kVV@B>S{~j|b!C&-4LDnig|bP77F_aUkp4=kV{{jm#7GMrVLi)ub$rnir)lV}; z9;w`v(yb&S&&mfT%M)*tn3pa$`D%EWw?hIcWqA6*1GH|4@ouwYjuj_sUa%7U*yS(P zU2>IlrJrBAerIWGl3iUFQdN&NT$uo*R%nlZsrcKgJB2$P)f^=I@VhFa|0@gNQodvMw5V|uI z9MnqTpd|P)X6+JW)Jyxm=@{5Q_P;&wLV10FY#=_#MrQx-J>Tk~tilxs6dM1yku9yd zknKh`XPoq4))CKb&Qy%StPiEzoZ+^v6JlN2Bsl2}{BJjh3|Nc3y zRs!Ft;1VDI=?ZaJxb8T|xgM1l!V^kxwaJYNRlkv41ZH{1bVcRmB%wuA?_6wA07WCN z6L0CTAkT+mPXt73LdqLis#Cw|e~tL7Q~rk(Dk7GU8uZI)i__oD(Fx3=$&YfE-h=J^ z?>c2_E_$2!%^o@O>@t`d!|=Dr??fAG7a1cq?3%EaOw_@BFwE#8S_f@eX%8 z)GT%f-1W~0s-e2D*M0U2zr*k|u5)p`GBvq|QI(+$qLW>BioAM5tA9&{Xv=#Y+;_Pv zkH{CTWhpHOLlf8Zp<`gKFGbAu+4H^0E>gdE{P3~4^wiuu&aKK^S#-Mp3P?;Y$w|do z`IMJO65xRQcFv_!9p?kOy-~9M1mGJ$Ew$$gNA>^p9MfZ>!P46#p5Z>J;i{|FBsq;| zdzF!on1$r%`!L+c1x8ydbCef3ndEtp7-{0&DGCiiE?sK{iCGwVPF(4B6;=Jj^a8N7 z``9m+XpEZVi;gAzm*Tet{U_c=+jE;TU2x>gJQ8L+Y$*e)E%mS9sdaXv?_PTXa!~F> zu5AOO&R}}bTV08HimFbtDgdtV3RkKmzwIJ{l(`F7P7+JdlDtP+pX=8*HXC1^v1<&+ z4@TWRMOiU~!kZqDD9g32Ou#`k*0>XA4I}T2ZPUd5U_vg?vCW)frsU!aL-XuJyjQ(l z6m*pHzTAlcmd>Eiiz~>`RMw)M8k>uVu%Wg7{I! zgDEge`2K6Xw0!8Ao$4JmA@rjYs&%xq&0-ayP#Wbg_Ql;oj8i1-RmbwR{JYISHuPe8 zB{8~I|GwSmda~{4n0gl92JOCeHd^^!20_*y)d1H(2@~WkNfcB7_Fqslw6iAs-HhYXsF^L`H)(9 zoXK^Rqa#9O6<24l6Ygk?tQ{Impy#xuL}a4&Hp#+5t90WyYW~q-f{e#leuB<2&l<}T zfett{3GwQ4MIqDUx7>+cdonC0QjIUAr|9j^@^(f9C)XKi+Z%hB(x g883~i-_WLzO2QJ*MeJOq{mZ}(Palu6E#W8r0BQ&+eE + + + + + + + + + + + + + + + + + + + + + + + + + + $ + + + + + + + + diff --git a/icons/icon-512.png b/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..a1623fd1cf3d5d89f3592637a5f91c8b2c9a7f1d GIT binary patch literal 4666 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 + + + + + + + + + + + + + + + + + + + + + + + + + + $ + + + + + + + + diff --git a/index.html b/index.html index d7c523e..d3fc825 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,26 @@ name="description" content="Visualize your subscription costs with an interactive grid. See the true proportion of your monthly spending at a glance." /> + + + + + + + + + + + + + + + + + + + + @@ -59,6 +79,42 @@ + + +
@@ -794,5 +850,97 @@

Import Bank + + + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..4860d2b --- /dev/null +++ b/manifest.json @@ -0,0 +1,59 @@ +{ + "name": "SubGrid - Subscription Cost Visualizer", + "short_name": "SubGrid", + "description": "Visualize your subscription costs with an interactive grid. Track subscriptions, view costs proportionally, and manage your monthly spending.", + "start_url": "/", + "display": "standalone", + "background_color": "#F8F9FB", + "theme_color": "#6366F1", + "orientation": "portrait-primary", + "scope": "/", + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + } + ], + "categories": ["finance", "productivity", "utilities"], + "screenshots": [ + { + "src": "/screenshots/desktop.png", + "sizes": "1280x720", + "type": "image/png", + "form_factor": "wide" + }, + { + "src": "/screenshots/mobile.png", + "sizes": "750x1334", + "type": "image/png", + "form_factor": "narrow" + } + ], + "shortcuts": [ + { + "name": "Add Subscription", + "short_name": "Add", + "description": "Add a new subscription", + "url": "/?action=add", + "icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192" }] + } + ], + "share_target": { + "action": "/", + "method": "GET", + "enctype": "application/x-www-form-urlencoded", + "params": { + "title": "name", + "text": "description", + "url": "link" + } + } +} diff --git a/offline.html b/offline.html new file mode 100644 index 0000000..2996f4e --- /dev/null +++ b/offline.html @@ -0,0 +1,135 @@ + + + + + + Offline - SubGrid + + + + + + + +
+
+ +
+
+ +
+
+ + +

You're Offline

+ + +

+ It looks like you've lost your internet connection. Don't worry, your subscription data is safely stored on your device! +

+ + +
+
+ + What you can do offline: +
+
    +
  • + + View your saved subscriptions +
  • +
  • + + Add, edit, or delete subscriptions +
  • +
  • + + Generate visualizations from cached data +
  • +
  • + + Update exchange rates (requires internet) +
  • +
+
+ + +
+ + + +
+ + +
+

Checking connection status...

+
+
+
+ + + + diff --git a/screenshots/README.md b/screenshots/README.md new file mode 100644 index 0000000..efb5a7f --- /dev/null +++ b/screenshots/README.md @@ -0,0 +1,14 @@ +# Screenshots Placeholder + +This directory should contain screenshots for the PWA manifest: + +- `desktop.png` - 1280x720 desktop screenshot +- `mobile.png` - 750x1334 mobile screenshot + +These screenshots will be shown in app stores and install prompts on supported platforms. + +To generate these: +1. Run the app locally +2. Add some sample subscriptions +3. Take screenshots of the grid view +4. Resize and save them here diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..8acd2fb --- /dev/null +++ b/sw.js @@ -0,0 +1,220 @@ +const CACHE_VERSION = 'subgrid-v1.0.0'; +const CACHE_NAME = `${CACHE_VERSION}`; +const OFFLINE_URL = '/offline.html'; + +// Assets to cache immediately on install +const PRECACHE_ASSETS = [ + '/', + '/index.html', + '/offline.html', + '/styles.css', + '/manifest.json', + '/js/app.js', + '/js/storage.js', + '/js/rates.js', + '/js/treemap.js', + '/js/beeswarm.js', + '/js/circlepack.js', + '/js/modals.js', + '/js/presets.js', + '/js/bank-import.js', + '/icons/icon-192.png', + '/icons/icon-512.png' +]; + +// CDN resources to cache on first use +const CDN_RESOURCES = [ + 'https://cdn.tailwindcss.com', + 'https://code.iconify.design/3/3.1.1/iconify.min.js', + 'https://cdn.jsdelivr.net/npm/modern-screenshot@4.6.7/dist/index.js', + 'https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap' +]; + +// Install event - cache core assets +self.addEventListener('install', (event) => { + console.log('[ServiceWorker] Installing...'); + + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + console.log('[ServiceWorker] Precaching app shell'); + // Cache offline page first (critical) + return cache.add(OFFLINE_URL).then(() => { + // Then cache other assets, but don't fail if some are missing + return Promise.allSettled( + PRECACHE_ASSETS.map(url => + cache.add(url).catch(err => { + console.warn(`[ServiceWorker] Failed to cache ${url}:`, err); + }) + ) + ); + }); + }).then(() => { + console.log('[ServiceWorker] Skip waiting'); + return self.skipWaiting(); + }) + ); +}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => { + console.log('[ServiceWorker] Activating...'); + + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log('[ServiceWorker] Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }).then(() => { + console.log('[ServiceWorker] Claiming clients'); + return self.clients.claim(); + }) + ); +}); + +// Fetch event - serve from cache, fallback to network +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // Skip non-GET requests + if (request.method !== 'GET') { + return; + } + + // Handle API calls (exchange rates) - Network first, then cache + if (url.origin === 'https://open.er-api.com') { + event.respondWith( + networkFirstStrategy(request, CACHE_NAME) + ); + return; + } + + // Handle CDN resources - Cache first, then network + if (CDN_RESOURCES.some(cdn => url.href.startsWith(cdn))) { + event.respondWith( + cacheFirstStrategy(request, CACHE_NAME) + ); + return; + } + + // Handle navigation requests (HTML pages) + if (request.mode === 'navigate') { + event.respondWith( + cacheFirstStrategy(request, CACHE_NAME) + .catch(() => { + // If both cache and network fail, show offline page + return caches.match(OFFLINE_URL); + }) + ); + return; + } + + // Default: Cache first for all other requests + event.respondWith( + cacheFirstStrategy(request, CACHE_NAME) + ); +}); + +// Cache-first strategy: Try cache, fallback to network, then cache response +async function cacheFirstStrategy(request, cacheName) { + const cache = await caches.open(cacheName); + const cached = await cache.match(request); + + if (cached) { + console.log('[ServiceWorker] Serving from cache:', request.url); + return cached; + } + + try { + console.log('[ServiceWorker] Fetching from network:', request.url); + const response = await fetch(request); + + // Cache successful responses + if (response.ok) { + cache.put(request, response.clone()); + } + + return response; + } catch (error) { + console.error('[ServiceWorker] Fetch failed:', error); + throw error; + } +} + +// Network-first strategy: Try network, fallback to cache +async function networkFirstStrategy(request, cacheName) { + const cache = await caches.open(cacheName); + + try { + console.log('[ServiceWorker] Fetching from network (network-first):', request.url); + const response = await fetch(request); + + // Cache successful responses + if (response.ok) { + cache.put(request, response.clone()); + } + + return response; + } catch (error) { + console.log('[ServiceWorker] Network failed, trying cache:', request.url); + const cached = await cache.match(request); + + if (cached) { + return cached; + } + + throw error; + } +} + +// Handle messages from clients +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } + + if (event.data && event.data.type === 'CACHE_URLS') { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(event.data.urls); + }) + ); + } + + if (event.data && event.data.type === 'CLEAR_CACHE') { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => caches.delete(cacheName)) + ); + }) + ); + } +}); + +// Periodic background sync (if supported) +self.addEventListener('sync', (event) => { + if (event.tag === 'sync-rates') { + event.waitUntil(syncExchangeRates()); + } +}); + +async function syncExchangeRates() { + try { + const response = await fetch('https://open.er-api.com/v6/latest/USD'); + if (response.ok) { + const cache = await caches.open(CACHE_NAME); + cache.put('https://open.er-api.com/v6/latest/USD', response); + console.log('[ServiceWorker] Exchange rates synced'); + } + } catch (error) { + console.error('[ServiceWorker] Failed to sync rates:', error); + } +} + +console.log('[ServiceWorker] Service Worker loaded'); From 059be52f33e5861f40c3a5e53c35e356cb7ad324 Mon Sep 17 00:00:00 2001 From: Subhan Raj Date: Sat, 20 Dec 2025 17:27:02 +0000 Subject: [PATCH 02/18] feat: Implement dark mode support and theme management - Added dark mode configuration to Tailwind CSS. - Updated HTML structure to support dark mode styles. - Enhanced CSS with variables for theming and smooth transitions. - Created a theme management system in theme.js to handle light, dark, and auto themes. - Updated JavaScript files to reflect theme changes in UI components. - Improved accessibility and visual consistency across light and dark modes. --- index.html | 162 +++++++++++++++++++++++++++++++-------------------- js/app.js | 16 ++--- js/modals.js | 14 ++--- js/theme.js | 138 +++++++++++++++++++++++++++++++++++++++++++ styles.css | 103 +++++++++++++++++++++++++++++++- 5 files changed, 355 insertions(+), 78 deletions(-) create mode 100644 js/theme.js diff --git a/index.html b/index.html index d3fc825..ed00cd4 100644 --- a/index.html +++ b/index.html @@ -37,6 +37,7 @@ /> diff --git a/js/app.js b/js/app.js index d39a2b2..ffc3e8d 100644 --- a/js/app.js +++ b/js/app.js @@ -272,21 +272,21 @@ function renderList() { const sub = subs[i]; const color = getColor(sub.color); - html += '
'; + html += '
'; html += '
'; html += '
'; html += iconHtml(sub, "w-10 h-10"); html += '
'; - html += '
' + sub.name + '
'; - html += '
' + formatOriginalPrice(sub) + ' / ' + sub.cycle + '
'; + html += '
' + sub.name + '
'; + html += '
' + formatOriginalPrice(sub) + ' / ' + sub.cycle + '
'; html += '
'; html += '
'; - html += ''; - html += ''; + html += ''; + html += ''; html += '
'; } - html += ''; listContainer.innerHTML = html; @@ -379,9 +379,9 @@ function renderPresets() { const logo = "https://img.logo.dev/" + preset.domain + "?token=pk_KuI_oR-IQ1-fqpAfz3FPEw&size=100&retina=true&format=png"; html += ''; } grid.innerHTML = html; diff --git a/js/modals.js b/js/modals.js index 90f8dd5..5febdce 100644 --- a/js/modals.js +++ b/js/modals.js @@ -146,14 +146,14 @@ function renderCategoryFilters() { const cats = getCategories(); let html = ''; for (let i = 0; i < cats.length; i++) { const cat = cats[i]; const isActive = (selectedCategory === cat); html += ''; } @@ -197,7 +197,7 @@ function renderPresetsBrowserList(presetsToShow) { if (!container) return; if (presetsToShow.length === 0) { - container.innerHTML = '
No subscriptions found
'; + container.innerHTML = '
No subscriptions found
'; return; } @@ -218,7 +218,7 @@ function renderPresetsBrowserList(presetsToShow) { const items = byCategory[catName]; html += '
'; - html += '

' + catName + '

'; + html += '

' + catName + '

'; html += '
'; for (let i = 0; i < items.length; i++) { @@ -227,11 +227,11 @@ function renderPresetsBrowserList(presetsToShow) { const logo = "https://img.logo.dev/" + p.domain + "?token=pk_KuI_oR-IQ1-fqpAfz3FPEw&size=100&retina=true&format=png"; html += ''; } diff --git a/js/theme.js b/js/theme.js new file mode 100644 index 0000000..92c7917 --- /dev/null +++ b/js/theme.js @@ -0,0 +1,138 @@ +// Theme Management System +// Supports: auto (system), light, dark modes + +const THEME_KEY = 'vexly_theme_preference'; +const THEME_AUTO = 'auto'; +const THEME_LIGHT = 'light'; +const THEME_DARK = 'dark'; + +let currentTheme = THEME_AUTO; +let systemTheme = THEME_LIGHT; + +// Initialize theme system +function initTheme() { + // Load saved preference + const saved = localStorage.getItem(THEME_KEY); + currentTheme = saved || THEME_AUTO; + + // Detect system preference + updateSystemTheme(); + + // Apply theme + applyTheme(); + + // Listen for system theme changes + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + updateSystemTheme(); + if (currentTheme === THEME_AUTO) { + applyTheme(); + } + }); + } + + // Update UI + updateThemeUI(); +} + +// Detect system theme preference +function updateSystemTheme() { + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + systemTheme = THEME_DARK; + } else { + systemTheme = THEME_LIGHT; + } +} + +// Apply theme to document +function applyTheme() { + const effectiveTheme = currentTheme === THEME_AUTO ? systemTheme : currentTheme; + + if (effectiveTheme === THEME_DARK) { + document.documentElement.classList.add('dark'); + document.documentElement.setAttribute('data-theme', 'dark'); + updateMetaThemeColor('#0f172a'); // Dark background + } else { + document.documentElement.classList.remove('dark'); + document.documentElement.setAttribute('data-theme', 'light'); + updateMetaThemeColor('#6366F1'); // Indigo theme color + } +} + +// Update meta theme-color tag for mobile browsers +function updateMetaThemeColor(color) { + let meta = document.querySelector('meta[name="theme-color"]'); + if (meta) { + meta.setAttribute('content', color); + } +} + +// Set theme (light, dark, or auto) +function setTheme(theme) { + if (![THEME_AUTO, THEME_LIGHT, THEME_DARK].includes(theme)) { + return; + } + + currentTheme = theme; + localStorage.setItem(THEME_KEY, theme); + applyTheme(); + updateThemeUI(); +} + +// Get current theme setting +function getTheme() { + return currentTheme; +} + +// Get effective theme (resolves auto to light/dark) +function getEffectiveTheme() { + return currentTheme === THEME_AUTO ? systemTheme : currentTheme; +} + +// Update theme toggle UI +function updateThemeUI() { + const buttons = { + auto: document.getElementById('theme-auto'), + light: document.getElementById('theme-light'), + dark: document.getElementById('theme-dark') + }; + + // Update button states + Object.keys(buttons).forEach(key => { + const btn = buttons[key]; + if (btn) { + if (currentTheme === key) { + btn.classList.add('active-theme'); + btn.classList.remove('inactive-theme'); + } else { + btn.classList.remove('active-theme'); + btn.classList.add('inactive-theme'); + } + } + }); + + // Update theme icon in header (if exists) + const themeIcon = document.getElementById('theme-icon'); + if (themeIcon) { + const effectiveTheme = getEffectiveTheme(); + const icons = { + light: 'ph:sun-bold', + dark: 'ph:moon-bold' + }; + themeIcon.setAttribute('data-icon', icons[effectiveTheme] || icons.light); + } +} + +// Toggle between themes (for quick toggle button) +function toggleTheme() { + const effectiveTheme = getEffectiveTheme(); + const newTheme = effectiveTheme === THEME_LIGHT ? THEME_DARK : THEME_LIGHT; + setTheme(newTheme); +} + +// Initialize on load +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initTheme); +} else { + initTheme(); +} diff --git a/styles.css b/styles.css index fc1d4d9..99b5ec3 100644 --- a/styles.css +++ b/styles.css @@ -1,10 +1,76 @@ +/* ============================================ + CSS VARIABLES FOR THEMING + ============================================ */ +:root { + /* Light mode colors */ + --bg-primary: #F8F9FB; + --bg-secondary: #FFFFFF; + --bg-tertiary: #F1F5F9; + + --text-primary: #0f172a; + --text-secondary: #475569; + --text-tertiary: #94a3b8; + + --border-primary: #e2e8f0; + --border-secondary: #cbd5e1; + + --accent-indigo: #6366F1; + --accent-indigo-hover: #4f46e5; + + --shadow-sm: rgba(0, 0, 0, 0.05); + --shadow-md: rgba(0, 0, 0, 0.1); + --shadow-lg: rgba(0, 0, 0, 0.15); + + --scrollbar-thumb: #cbd5e1; + --scrollbar-thumb-hover: #94a3b8; +} + +/* Dark mode colors */ +.dark { + --bg-primary: #0f172a; + --bg-secondary: #1e293b; + --bg-tertiary: #334155; + + --text-primary: #f1f5f9; + --text-secondary: #cbd5e1; + --text-tertiary: #64748b; + + --border-primary: #334155; + --border-secondary: #475569; + + --accent-indigo: #818cf8; + --accent-indigo-hover: #a5b4fc; + + --shadow-sm: rgba(0, 0, 0, 0.3); + --shadow-md: rgba(0, 0, 0, 0.4); + --shadow-lg: rgba(0, 0, 0, 0.5); + + --scrollbar-thumb: #475569; + --scrollbar-thumb-hover: #64748b; +} + +/* Smooth theme transitions */ +* { + transition-property: background-color, border-color, color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +/* Exclude animations and transforms from theme transition */ +*:not(.step-panel):not(.treemap-cell):not(.treemap-cell-inner):not(.beeswarm-dot):not(.circlepack-bubble) { + transition-property: background-color, border-color, color, fill, stroke; +} + /* custom scrollbar - webkit only but whatever */ ::-webkit-scrollbar { width: 6px } ::-webkit-scrollbar-track { background: transparent } ::-webkit-scrollbar-thumb { - background-color: #cbd5e1; + background-color: var(--scrollbar-thumb); border-radius: 20px; } +::-webkit-scrollbar-thumb:hover { + background-color: var(--scrollbar-thumb-hover); +} /* hide number input spinners */ input[type="number"]::-webkit-inner-spin-button, @@ -119,3 +185,38 @@ input[type="number"]::-webkit-outer-spin-button { .circlepack-bubble.active .circlepack-tooltip { opacity: 1; } + +/* Theme toggle buttons */ +.active-theme { + border-color: #6366F1 !important; + background-color: #EEF2FF !important; +} + +.dark .active-theme { + border-color: #818cf8 !important; + background-color: #312e81 !important; +} + +.active-theme .iconify { + color: #6366F1 !important; +} + +.dark .active-theme .iconify { + color: #a5b4fc !important; +} + +.active-theme span:last-child { + color: #4f46e5 !important; +} + +.dark .active-theme span:last-child { + color: #c7d2fe !important; +} + +.inactive-theme { + opacity: 0.6; +} + +.inactive-theme:hover { + opacity: 1; +} From 49b953077304352abb0366b143b75350a6edfb91 Mon Sep 17 00:00:00 2001 From: Subhan Raj Date: Sat, 20 Dec 2025 17:36:12 +0000 Subject: [PATCH 03/18] feat: Enhance dark mode styling for buttons and containers --- index.html | 50 +++++++++++++++++++++++++------------------------- js/app.js | 8 +++++--- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/index.html b/index.html index ed00cd4..4294e5a 100644 --- a/index.html +++ b/index.html @@ -234,11 +234,11 @@

-
+
diff --git a/js/app.js b/js/app.js index ffc3e8d..f38f4ce 100644 --- a/js/app.js +++ b/js/app.js @@ -208,13 +208,15 @@ function setView(view) { // Update button styles const views = ["treemap", "beeswarm", "circlepack"]; - const activeClass = "bg-slate-900 text-white"; - const inactiveClass = "bg-white text-slate-600"; + const activeClass = "bg-slate-900 dark:bg-slate-100 text-white dark:text-slate-900"; + const inactiveClass = "bg-white dark:bg-slate-700 text-slate-600 dark:text-slate-300"; views.forEach(v => { const btn = document.getElementById("view-" + v); if (btn) { - btn.classList.remove(...activeClass.split(" "), ...inactiveClass.split(" ")); + // Remove all active and inactive classes + btn.classList.remove("bg-slate-900", "dark:bg-slate-100", "text-white", "dark:text-slate-900", + "bg-white", "dark:bg-slate-700", "text-slate-600", "dark:text-slate-300"); if (v === view) { btn.classList.add(...activeClass.split(" ")); } else { From 93cad77f9138112830472cf81bef9e457794cde3 Mon Sep 17 00:00:00 2001 From: Subhan Raj Date: Sat, 20 Dec 2025 18:18:17 +0000 Subject: [PATCH 04/18] feat: Add calendar with coin/payment SVG for subscription tracking --- logo-variant-3.svg | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 logo-variant-3.svg diff --git a/logo-variant-3.svg b/logo-variant-3.svg new file mode 100644 index 0000000..7ba5b7f --- /dev/null +++ b/logo-variant-3.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $ + + + + + + + + + + From f7f5dfb05370b2ac6d8584f664d25990c55bcf48 Mon Sep 17 00:00:00 2001 From: Subhan Raj Date: Sat, 20 Dec 2025 18:38:18 +0000 Subject: [PATCH 05/18] feat: Update icons and manifest for improved PWA support --- icons/apple-touch-icon.png | Bin 0 -> 7546 bytes icons/favicon-16x16.png | Bin 0 -> 477 bytes icons/favicon-32x32.png | Bin 0 -> 983 bytes icons/icon-192.png | Bin 1778 -> 0 bytes icons/icon-192.svg | 35 ------------------------ icons/icon-192x192.png | Bin 0 -> 7342 bytes icons/icon-512.png | Bin 4666 -> 0 bytes icons/icon-512.svg | 35 ------------------------ icons/icon-512x512.png | Bin 0 -> 20927 bytes index.html | 13 +++++---- logo-variant-3.svg | 53 ------------------------------------- logo.svg | 41 ++++++++++++++++++++++++++++ manifest.json | 6 ++--- 13 files changed, 52 insertions(+), 131 deletions(-) create mode 100644 icons/apple-touch-icon.png create mode 100644 icons/favicon-16x16.png create mode 100644 icons/favicon-32x32.png delete mode 100644 icons/icon-192.png delete mode 100644 icons/icon-192.svg create mode 100644 icons/icon-192x192.png delete mode 100644 icons/icon-512.png delete mode 100644 icons/icon-512.svg create mode 100644 icons/icon-512x512.png delete mode 100644 logo-variant-3.svg create mode 100644 logo.svg diff --git a/icons/apple-touch-icon.png b/icons/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b68853c031f825b50cb8792e7ddd21a01868f4eb GIT binary patch literal 7546 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 JTHYe`e*rks!S4V7 literal 0 HcmV?d00001 diff --git a/icons/favicon-16x16.png b/icons/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa159cffc8cf891a11999e6a7b7f805d6dd6289 GIT binary patch literal 477 zcmV<30V4j1P)7av)Lc4VBlFn|fiYPcb zNb?tTvp7i?=_vR`yA}E$6iO+ykOr!_Nc^~{(Wp1~p4Z_OOaLSGqL zbnfYOny9U2faliVvRQ}jeiz3TyxxW^!KqM}Vv7ZoK4 zVop68VhqZ}c!FvGC|qeshqxdh0&W66Y+bfD02q22Cs>8ut23S?)7Y=`NOXusN|(F= z9<;7N9E<{mN{L6w40}J8aP${qcW^w1+~Dg5ev`oshC8zWbX1CH=p+3zNc9Wpr@6yF<|Fwf_ Tp@?)100000NkvXXu0mjf?IhWG literal 0 HcmV?d00001 diff --git a/icons/favicon-32x32.png b/icons/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..55b9a0aaeb73a6e1c0398a4f504b2139782ea4bd GIT binary patch literal 983 zcmV;|11S87P)1!G#;$kMIBjUvkh+3YN*W0j1V{)WK^vgq z!X=0c7fett^gy_CK^#y;B~aBrY81+agKt$(QAsX{Lk~p+DYS(HG{Q(U4KdlZY3#LU zIM`0?fOn0oB%id>?t5?E_sx4VqglZgq$j-*w0aB8NgxI6*&6ddQ2_2Dm_x|R(--W; zMyII1jF0Q6lXTCbnFM5qq^(c^&bv8hDw7eV4FTihI%+4qYeMT6wuyKv=G~k#mdS{c z1aOk>*&QSYIMKb=Jp&-plimnQEda(23IM1`EgzFwsfk@62QZXSX({03E|AxtMIq}(YS$Q3-15!bMKEz%lN!Y$Apattbp%_@`b`L2m1pn(3gafVFNE8KJ)*Aoe(Ss z^gmQUtP7rh)I-ZH)exQQ0e~>+fPmN#qU!+Kh@kJyB7~;b`mJTYwklBcHy5H?Xts%HH%e(l_Y+{+P+CHlqX@G) zH(gN_{GJw>J6MF9wm3%OaflBhj5vDrE7ULFLZysMoyK4Ikj1}RUpWE=ltWIjjU-Qh zOlj^#B7>)p55Ea05GLh27g+lHRetrJY&li}UMuI#?|hb*RV(*qlD$J%FMUI0txRe0 zbNq#KlWcK+Yd-?Rp)A`1y(kfUy*uQ97S|<0UZ(oTveT$2DW_%a zU+w%(vuijvbJ4yf0oL% zG0m8m@Qx{=(x68{q-gWn9)LXPyJ&G;NjW`p(Z1E_{0D_2H>k%;-C6(u002ovPDHLk FV1nWtx!?c* literal 0 HcmV?d00001 diff --git a/icons/icon-192.png b/icons/icon-192.png deleted file mode 100644 index 463971a59af763dcdaf18648f7717a9ff8225f24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1778 zcmZ{lc~BBq9LFDsNtl)8cqf`|Sz9WpWobaA(cz7inxMiqZ3<%MX66y3U0zVS(;=>= zW>K3RrUsr}I+bXtK!R7AXo4vbmZG>cyW8xx+aK?}nK$3}-uL(W{O0%0`uX4t5atK~ zfWeOK9s%05;md%}(ar%~6g>duaCdlY4m`>p?cPPR*lpUVARk`pY3nZ}JaywOxDY#O z*L|gQ)wuUP-OYFf71%t~J5wFGhp_brr?`3hBYZddu(_W@rR)R^=2`VmEp9_KzETp? z$;nndB5$oCi|C5!IK@Lsv!Zx`&*NfmIo880v$V;Elx8ywoY6^ev$W31;$J^yg&7Nu z*Hz7R4_d-crnOIXnPxFrBbpCpFb7DX!Z4@Rx|uKC*w<&mXWpeGwsMZ=FNzBeoaIfU z`<_?mE!e1Aq&Rn;heZCK-D5FTo$AZ@Ii)00lJ~}kVV@B>S{~j|b!C&-4LDnig|bP77F_aUkp4=kV{{jm#7GMrVLi)ub$rnir)lV}; z9;w`v(yb&S&&mfT%M)*tn3pa$`D%EWw?hIcWqA6*1GH|4@ouwYjuj_sUa%7U*yS(P zU2>IlrJrBAerIWGl3iUFQdN&NT$uo*R%nlZsrcKgJB2$P)f^=I@VhFa|0@gNQodvMw5V|uI z9MnqTpd|P)X6+JW)Jyxm=@{5Q_P;&wLV10FY#=_#MrQx-J>Tk~tilxs6dM1yku9yd zknKh`XPoq4))CKb&Qy%StPiEzoZ+^v6JlN2Bsl2}{BJjh3|Nc3y zRs!Ft;1VDI=?ZaJxb8T|xgM1l!V^kxwaJYNRlkv41ZH{1bVcRmB%wuA?_6wA07WCN z6L0CTAkT+mPXt73LdqLis#Cw|e~tL7Q~rk(Dk7GU8uZI)i__oD(Fx3=$&YfE-h=J^ z?>c2_E_$2!%^o@O>@t`d!|=Dr??fAG7a1cq?3%EaOw_@BFwE#8S_f@eX%8 z)GT%f-1W~0s-e2D*M0U2zr*k|u5)p`GBvq|QI(+$qLW>BioAM5tA9&{Xv=#Y+;_Pv zkH{CTWhpHOLlf8Zp<`gKFGbAu+4H^0E>gdE{P3~4^wiuu&aKK^S#-Mp3P?;Y$w|do z`IMJO65xRQcFv_!9p?kOy-~9M1mGJ$Ew$$gNA>^p9MfZ>!P46#p5Z>J;i{|FBsq;| zdzF!on1$r%`!L+c1x8ydbCef3ndEtp7-{0&DGCiiE?sK{iCGwVPF(4B6;=Jj^a8N7 z``9m+XpEZVi;gAzm*Tet{U_c=+jE;TU2x>gJQ8L+Y$*e)E%mS9sdaXv?_PTXa!~F> zu5AOO&R}}bTV08HimFbtDgdtV3RkKmzwIJ{l(`F7P7+JdlDtP+pX=8*HXC1^v1<&+ z4@TWRMOiU~!kZqDD9g32Ou#`k*0>XA4I}T2ZPUd5U_vg?vCW)frsU!aL-XuJyjQ(l z6m*pHzTAlcmd>Eiiz~>`RMw)M8k>uVu%Wg7{I! zgDEge`2K6Xw0!8Ao$4JmA@rjYs&%xq&0-ayP#Wbg_Ql;oj8i1-RmbwR{JYISHuPe8 zB{8~I|GwSmda~{4n0gl92JOCeHd^^!20_*y)d1H(2@~WkNfcB7_Fqslw6iAs-HhYXsF^L`H)(9 zoXK^Rqa#9O6<24l6Ygk?tQ{Impy#xuL}a4&Hp#+5t90WyYW~q-f{e#leuB<2&l<}T zfett{3GwQ4MIqDUx7>+cdonC0QjIUAr|9j^@^(f9C)XKi+Z%hB(x g883~i-_WLzO2QJ*MeJOq{mZ}(Palu6E#W8r0BQ&+eE - - - - - - - - - - - - - - - - - - - - - - - - - - $ - - - - - - - - diff --git a/icons/icon-192x192.png b/icons/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..cf48a8a6071dcde3b487356bc53ac8a55e27ea90 GIT binary patch literal 7342 zcmZ{JWmFqa+;s>J1&Ujd0;NR?6e&<7XmM%q;!v!(TN5C-6e$pjyB0!miaQi2ZVlSv z(BKmM$^ZTEocF`~o;|yB&g^ey?)>)7?A^Q3+L}rvL<~d#0DweASpkeGyZ$=~@Gw{K zk`q3rz_U_QQUE;scjUE~CIbLZtW^|bb^WqX+5Rb}dfA7dPRLk$6Sw!ielho{0s+0O zOi8m$eK>8TPapgR37!aL)YP35MB2;281dF&quo|YaVPGDph|t3@;ac*UPweoraw-G z)wZGttk+Z`k$oB%v{Y2#GFw#?Au+d{+fe(ZZk!qV+&5^Pd2FTgWy%W!(^U$>XIbIs zT4jqGI+_YWu_xEf+q)x};t9}kYULGQ7g1!Uyd8v*CM>%vK^$vHU~I$|C}g2G z^MS{aL2w(BTZ}_Ou|*LMWQz8b-9QIq)Y*~WZWuu&3*hFNS6F@CQyQEA&CfJR(hl+&S1l) z(4@&yextlCH8DXQbo0A0+FdIHM>-+d^@ncBfIS_r; zQ02)qeoJTRz;{w-s2O$ii0dInBa^e^)kWpNP)*1|FHmP7BduQ$_`YvDlu>j}N(in# zFr#PG-hu+ZyZT&p3ul`si%^=cbO<^$6r^te=p&+`x}PN@;0_v(9N1N#=n3q>={16XmHFEXeQq z2lMPI!faTTZ6&Br@2;30GbcIFs9EkjHs}A{Tyr1(hm)=mAPsTeHyIQi2PP}oWb}gp zR2luJX{5d}UWO2KgQLhAddtjdvnfRks3WiyvN_^yD1jUK43Mz%t1H~PcGQZCs%J`B z{-!B{_MD^g{o*~=TK=ED218W>>ZAl06I%Zxg(OYG@_EE(&65e#Lbx;hWJ6hyHTLit zT4Ym3@cbXpA1!Ll(^>o}eS`DrBAV^dZ(wChBZNy@%NpckG|XtO$hXqQcjDEUX~c7R zvbncb=Tde0P%nN@KVf=;gGqNI)lf0OYk}$?tjBkJqTmEiy3f%VfB#9-*H5KFcJSG5Pp~~sUdFl zqR@{9ggd|`|TDW($JH2wSL$xVAru|e1)Z8(QOQCcClf( z)tLmAl~czFiAjQUpM4+wd7(b=RGunTS&>*q{eopRuJ<7|KgXf$Ayn1!$^8t?$kG4e zwp~*9<$eDouVXS_f^=C*)q}1saF(+6GDv~vL{mb-?sP3}SN$$&-4>p`%ew&5gz6tRV7BzI#-b@2T!AcKOX?dlEsrA$ z{~!_GeZGlXT)u{6lMG|0etzLd;}qmizS5U34t?!^6Imd=@XI}-24kI7HYpN9bv6aO z-(NvRxs0(!GZ^VSH|L)+#OL>nq<@lf$^%##kjbSi= zMh)@(f9XQh1)em-GUR690jMcj<64y4R|0scKVzu&e{t&n9i(A&K+t-`7(` zf;2<|XaKiXxtTYVNO-b>+ZD*kZ=tVeDm%>NfUHT;8`BK~SP|O0pgeK;fI6IVdIFse zxneDm39ju4%>1a=RRMOv_+wXrd@f&&!LJjFD?LitT4bu2(xFFs)0S77H=j)t&h??e z%=|w^eWc)*9!eQLyf)RRCConGP*ZzNN;QUo=8-=nTy?HMK7xjG*uF}Xl4{2Oa8 zL`+PoAqfxogS&`w(wuWr@_Gz3;vE4U$vy+^|3w4el?JXyzigV7~P&G9T7H4 zE3OR{;=G4yBtGpfS&b`(A1QE5}Q)FFlD%2(iel?V6PLQa1{ov5~ z!@_8bu8Z*K7w*W(d&I?)g8PQ^BA?Y*%0&rD`J44pf_f=@=W+7I^Pr@oCreibfBDQp zb+wrMq0izm8^4*)2&X6_Hcsr9UoP&{K`4iLNBRtcB_u6!nmdboDcpck>?VkeD{OXN z!PDF&r#y-VFqK;BBll-|tfInt^WW*I0phyI=RSDj)LfoY-av-1MfpWkGbx3etg
  • H)gCX%Gm#2yjD^982k>z+b2=8G+$LKI~ z?=b~J^8xj!lcl;NVU*e<4XP0e(nl!$@s6=kNo6XuiO84Hs;YuKN+4fHK-^G^=5#$i_lW-fk}<>1Y>I^VJ;}{pxJU8tsZu zl4vSk$9b~kK!Tp+11sG?#7`;vf;zt>UUvn0-h9XHBFc+9B9+wq8$E8T3w38&Dkn*I zy_@jLx-ysL>`VwTrA7TFHeHS}CMJGnAX^yIc>s9&1gaj^n*(rNfyM5s(? z7~Dfo3k5er#fUSf?rzo{7IT%zq@se*Jx5rvG`uFRu_t4bUtZOZ?(p9AK zRl}WPTNda|m+Z#8<9gJM3g}-KX~BHw}Mlt{REI=Qf7svpoRW!a?m=OC9ji z;oT3jFg+X%EmGzd0kQ3z%hVDp##dnJ+kDD1yGYheSp(kowysweZ&~j;2as1kLqK&R zExAAQ8Q5I(WJk`Mps_0W%qS9C_U38E>C>x00pfdlp+LRs9<8-bAL9G?YFVe%BjSHr zPAmayrEA{o=dRf9iPyzzbA?I|Zlk23<)r!95bBQCU(NRGg#wOuhF=O$zruSl`0Ur< zNH)3a*5nq8-I6E6z97%TzuB+pJX5sgFIIBr3Q+u6ZHdW$*AJJY-kSG_drCDod^IwV z|HN{?@@v9I#9GDu$e=K8m0 zGXo{!pdB7fYq5rA+ z!eq~RSvH}Fb^5Rl&c2y&A^dhvge+$kQE=yBr2U9%WZKn>N%n+%rKzf1oW1D~wzWa> zK?^n!`eXiVA0;!?Ph1SX=!V_ZT`_jkDHGJ$6&us;m#s;7;5rxWQB%$G-PCX%5$8E= z$pg9T!0PGpADgUn?3p_z+Y1|I3;W#M_L|Z*mh{`4(Jr**(Nw5~4(*sd?wAYunTQKd zvAW_T*f*Gf_0$er*nCMK&2AOCTm~0TC|)8)=CDszAM!WM(x(!41)*q0M|qAfmlvD7 zEEVXAEJbWJDIRv@?FDZl(#>jl>*fxiY$C9*Z0l01TDOCY@+55Eqh=P^_xe%dY5k7<hii*0xe9d-&MbYw2Bx`Wg1UZrQK zt<_!g;R#r@sp+JY+zq2FV-x8O*k=n@|5?dFE+Vq{Z0Fhp1CmeUsAUNMHaU+s!?oja zv?1cQbGy4ijuI^o#^_%+GUkJ;{!%N5!!?#qKn+fHC8%Y``T_CQr!k3i5OZFmfsq$#mH^~R9G&R5$a#Xn9H~Z@AL6KM z)A~Zutt@ZvLX2f9@-DK6M)@tq0_ZN=7?buUeOvL6#+xaK-2NDHnc>j*gR|Tjm5xg) zsj8pzDV9W1XxuitY7F}T$a@Aq?9U#ox9(Z}xufYvk}pm9Td7|W@WFj|-j#X_rAd}m zM$t$K|HClaR~{^b66ElsWnj6ou&^MWP9`EFuS4r9 z5Nq=uP84eJBR#?SfaD0z%jL+&}Yu+>n5~yebP25nU#Uiaw zBchBxamxYvPe`SZ^(8K{n8sjjA%zX{ci}S_es(@NnhFdnCRGq{h2T+&nML789x5dl zo~Gp;k48$*cp?`@$JnVSaT!KxH>`?8K5u_8xvA;?CQ$s5#&*swF(S5`sYJGhb}~M6 z@Y;qHhclEB2k6#zauYduXWqRia@~%5x6D;R6oh)?Ipe>X8Doz%P|N#+&cUX~OMMx5 zYi%AT0#_vqyQx}!Ww#VDljHtRb@HOhFX%mS`9;1^yvsnKr4Y_G`yZXB!r1B8M(Wv- z2kqFP7AB-Q^E8;?98zyy=UZm?baEr*-FBKQW}@1X|E1N@)&$7|(60Z6FP@XcDBGiU z-A_|=V8Y?yQ#}|#`DNiV^D>E;_BmmhT0!K2d~1Zn0!>kX5Ar8X`Z5P9&L-}*sj1WY zt3OR-%nmS>{h)1=e%=f;&rTuEvpz$E`W^Z(FEQCz%7tTltRd!lLt@(vB5Rpn)eNRJ z=HrRdC@i*mAMDYq?$1aePV%{g_KO2=$NDbcr0PYv@F*T}u%1Y~D7^Z%~>I9T^nP z9Pn7`d-1$hnbAbb?y(xCHWaK{*^w4pn{I|FET7PPQ zFD#c7b7eWz_uDV532zZ}Mabhu2%+ns!7In8hBR(NtF6^H?X=e=UCKcspk&b|`NtY`f|G_7j=C0NO=MtO(BVDn4xr^9#4 zYJPv$VwO^J?J$6t+zHIi9t8EHu9uHT?|qzDR@4m6uaD9O)ZF;dT19_c2lTzxTiwPF z)3lsGn0s=SuD$W|48O!g2Bra*?bks^BOjpsM0t+{lKENP4(5e=+AcKPE;=CC$UE-x zDuc7^9g2(y&$+i*O)JUw?>A!aBF-;vDp=tFWs@ zJhzb}N)PZ=rlG7K+}^_{VP6k}n&8?mBKOxvaH}I1szHJ~ACz@;!iCDEt;@sI#YjB& zK(RZv_7>je*PK9Ej_6RtQE29;zfR z`TA*Zit4PqJ-WU__pBB9E1nKe*rJoGCYWulbTn zUk|0bbLFcYS`SWRSsYc&f(LOaWOiMUqdlJE&;1Aci+4X(vo=!_+$FFif7-SE!vEkn zP@ZIFM`mX5sI-JgN8;(Uir!xW_ER@?`GK2*+us`gxm9~}xec4UiLBiSP$?s(s zt#i_6I>r2sQIn=F1wGP8wb)c@*w)O;_8ygXu z{&oOcGL!t}I)xe@qp~ z>sL9iVrPM!)ZPvksYFymiX+^bf%v`HD&9$-t|U0je~`4Hqt|1Ctsh%Yb;v>#AuN(0 zPi)WES=>OoPdQz`&GAIrgy=OFM}fmNp9`x4DpChY|=io`t462brtpTDRp`ju~&>r4S2 zR3k+yu^*9(?pqL`-GPlVt7q}<1rygni-bPH^?qIQ>9@JJEC5sJNptb9Fa3P9S%0+c(QO9vCNeNk=8>%?n9OixmzHTx~a~KI!=r%AXR3=fPdr4N5MNP)~P~3iATfn zR*Y6PRM>irjB)QV7769xs^wo<*XPzJc&;@m&!9sbzUluNgt)2?%C51`(g=)QJ0Ysu z$G9S$JQc5K)I!K-S9^|U^Vt6L<$C75vPA(IaUjwKaRs&F#2z(CtU;Xb{-+zHvbnqn z4U7@2odj}Pk^F&u?Hu)Pbh%&XC&oX;J2<#J6t2-e!IG_edE00>Kmxm<)3_Tv-Z#Tq zI&B{)V*`!nj9SsNpt(WD7^8na^3ykh7l(nFpUBwclqB1FTlZ!@x^edY@j`}gCNCVwdSq4K@Os}DQTczO3r#{z zIi0zJK7Eo-L!ro?yM1o9lwbDD%RIHin7;%cdIvsxk3aIh47^I2i2h%(m zn6A!w(L;#qHh;R6TL{3^${Jk%M3wrTxed`YidK3>6J zei8GZA%Bus<1pPP8h6;vXwTSyG2@}TN0XPU{(Y%Ag6DO0btc`Ez6pXV4UI7*Un|FLBi348??Tg$T>BfQz2GC_$OZYyQ%V>!MCSdps}w^^3`U` zJ~^%%v_Ko*p@`y2@r}Ym7Khv*R%_{x?&|~Bv@;sn30BJ$hD5L)&%gWO)>XHOg`Tj( zws!Z!f9XlSgr6uS#2Poq^w2I!jvIeJGwz#@D@KTe7_DbTCdu+!=)b1yx4oqPWw|Dl zIX5qrHKOhbMV^&-Cq=^I`E=2IyVh@c;Wc?(3AaV!?DMHCClkOkyYs!qQ{4@p4}IIG zhP(j&@*(fcdBvPD11x_Kv(uG__s4w+{iq#-;P5NHaY4QXEDxRfhKqxIACJu#Sa!nv zOp)zjNIY3E6nnExF4d9i`Pn$PhuYxekkl0dJ;>FAmBjrG5#3<`LA)+V{ zD%Gv1HEJHxIh=|LKB`f{I6istNXb#KQ4EaC$zX%9J7`pGrx1kh?)ze>Z!QR=8}gnP ziKL8PV|!v0RjX`CKJ28>lh+_4^%xJP__|?A1&}SnsEl8cO%RCd!}{+?*H^bSI=g9A hy0~s3!gl=u+aqJjQ0V*43(OBlfQq7~0z%FL_CKzia_9g6 literal 0 HcmV?d00001 diff --git a/icons/icon-512.png b/icons/icon-512.png deleted file mode 100644 index a1623fd1cf3d5d89f3592637a5f91c8b2c9a7f1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4666 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 - - - - - - - - - - - - - - - - - - - - - - - - - - $ - - - - - - - - diff --git a/icons/icon-512x512.png b/icons/icon-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..37ce951fff7ae5519b559abb5281cf3bf318bfb8 GIT binary patch literal 20927 zcmdSBbx>6C`!ITz?nb&pLQ1+*K|mTo=@yVikX|~ZOGIKpT1vW8K6Tb}@4@fy z_ulu;+&l04$DKQKhn?AF&*yxeem+l~jnq(6#Koe-0ssK_g_43603d-MkpK)d@a^ow zB03aUzV6BBo06-7CP>|L6kg>n$*+4b*S^D@$ls~cD(dn=kH`pbofk+7{ zg!!G)oK9gSRTSro*~&b)6@%oQC0Qb_scHtnI7a)s{xAZfU7~b8ns*%yYaTZ#jtK&8 zOUrasokI14+sc$LPVXe*=oa8v{HJg9roFawP&w=ec#=Li1h^JF;wR2!mC4cVtf7SI z%GSz^GGcLIMbMUxyC1{Zpy|*q*4*(ibb7zz5hL^fb;vdphO8_`GHM1}NQTMV&TYv# zslS?WY`(g&qfd)xr_&jQjBZbtCPaKoa!XDlM%`727)0SE?!WtaEbiLZ>#}pHxq#%Y8xvhq3SuI57=Q?vFXP~So0H??O#1e#>#VW`j z-e6{hXsMI0$R?F9G?)cb;)wBjqY^`*Wp7e4=u=-8C_F;`PzF>WYmmAdDkyku%B;E& z{w}0Pkc~|*&KPQY(r?4MqA_I>jQksC#o^4xUs0UGQ-x(KF>XZ(G(}q3I_MbwhNqW= z`Bb#M$q&L~Q=L5~>AWI?tAA&?!?T(+fE_;vYGwNr6m-mqptrak**%fBt6@}j7br9u z`4&hT^jiU?^*tVc9Dr2LP6J@}-%?f_6gYCjT#SDE{PA+_O%PrY5g+8|*Erw2X7+=% zJLo+?Zv#{Y#YRRH4K@OYLPdPhLSM zM+LO`{%2ejKbrUr**-Z1h0w=X0U@a>PM4bNA7%T9@e9=9tfroUb-=J&z7vv*g|*OGJH& zhzOg;XVE+J5b_nq89?Kdg6My*+p}c3_6Agf9%$|F3g#}IqALI9_jFqZt2l@l(B#yE z`%_~rBN1SWQ8F@ea8jW9`tAO?=Hz%U+!>_`aNoXl&E05_MmtW0S%4g^x~PwiH9VfM zFEPOqi}(H3fMPl|Uk;Dh-UDh-9cn1o?o8=@(-}?pBmayUAoHtPC(ryIfE;S|kE1CnkT28@OU)kvm+v~UP7Oxj#25A9dO^=S@w%S2x>v0+4O9;bC~*$ju8-QzO9KU zGSDmAX{3&?I(We|(DBKH^`yyitx);b%|BMi9q2hW-FM+qO`47dUg~;ac3jczIYe6i z2>@ESALJeZJC;${@icaOxflqOKlxfp1l=ENy)*&({z+rZ%$P)_^*oGj$_Z19prYRz zuVZ(rwXnPE&U9T}K_TBJ)pg^DbB1*og+LPgh|$ z;r3!QFgnkY7^-XUEybE!E{oZxq@W;xgSt{eYVRXW=uY3w{;T)>RQe$g)-iJO90w{R zV{SJa%U`;0)9wM>+cIHVuDJUpf|7J(EA2tC!JrbFpzQJs8|oo4Ve2A>FIWIjJ<6ep zdz$7hgoK1K3Bcck^BF;7DtG$uWL5cQJBJbJk1LPa#>_Cmo%x)({|rgf1{azaCBHJqqIzCI9H2{kd(Qe1}Urc39vX?_eLD**~qEX2~f9Xmbk!Vgz9 z^*3`j(PW~em`%=5OncI^MmSlHI4}GnBO;V1(O;Fn@NJqE=CbSk{@@W9c7=dfd`BuE zLTk~FTHY@IB5F^-f9mkfW3lL$aW5Nr4Bgh^i1GAay5@*p~9^F80y+g8KUB}1EJJHr6( zc0>0cmi|Z&3{vo~9u_N&|`0Ozzv zW~qWS3jLdiJz_$_Pz9_vJg);@E^1%3IWPGDsXSyVk!`f^K;?|+a+ZJfN zL=3&|{b2>9{<&!aq2=UNmbQI|C*GO}#c{VZ*oRr2B>{G)UPd74@q1({YAvgZF+7`l zL0sE)X{2-88)`+{!<-hJLVEoVTH(jU%$)~EB9c&+%S&8bTcAN;@6 z>0AJ+PrC*(GdCfB#k}+S9G540gVEv;;+)fl{*O~VM8suneA6GJi_uM|N)D@^eQS>q z#9(A({M#2Hwb!?|dpKNsPwovUP?WWO_VEKR^ryVqY=-{1rO$TzXQ9ocl>#%@Al?G3 zDz7TkTDWHVMOlDPCfsj+Tq~Cgvw*dv2Q9o;}gML#|8EtwAK{n}yc z;!%Ho`4NigmFss{N8y9M5YgvW{LClo@C*&xT*8N>?bSa}PWt0^(Z?tCrtFV6grM{B z-Sh)pU4&!V@MgviJ)}zWmvB))?Sz!{VQpGQi=FNW)$QbQ3c$uDbq7i}gM@fUp*d15xxg8=D@}hG27^goKWNxwnH+ttrnAJ01?*IB&Vo7p#A=Fr3<^6vaHA- zQ|A3r&;z_6t@yx*P z5qWX~;li<5_&Bd3MW>yV2$Kj?tl=)7%xv};h& ztY(FWSo`J;?0)ev#{b9ml-woVUlXzSYu|_fih^%Y5bOS=h?WwE09_;)Y)LD_`5%QO ze{>yg2;D%3p{z!5&}LT*fZ$RD`6wh;`=rb5#m_>Se1OtH`;W9l4|t?&)lu`|uSB58 zL|Y@tfbav8pDNazC|&?;ybyZO)hOhKsR5}btS$idM_N#Fgrr$riP${JCGr7&qL88^ zb#7DwTxK><92_-ix+(g;+dMpv0cA^D-hXn|B!BI(9S44cBr2Nr_5njS5;d{2zHU}X zNm3XG=sguuWPfR5c5Hn5i2D<>GZg$nA(XY__T85NUdU`)p%XV$AEpVy)y(PfI8viU zr&a}O8usr&H-UxBxv*(N4CHU3QsF*e7ECBeCz1wW{FycFey{-ZQLJjH-Ydu;1~{=P;zWKC?&d_FurY>2aZH2qzgbxVCx{ebIrB*aEsoK znmvHi?5UCY&yFfOob->}Ngkl;@RiLXgMuOv;^WHruMfB#SYlWY1ahdL(?A}e(h&pT zW(4LBA|;Vi1wb%z5tyJVXkzd^h!u;0^&dt*P#&-e`41F=|I1AtqY#3P+yXVTzh@Zf zeRD-oHtj+pfP0F?dY z(iDg}_4j_S{VmjDOA(u#Q1>YaWSkEItJ2Ue&&@he zp#5FRJA_5lQhF&!tm>x-S+&((I8`Ww7~sJ6Z!(XYmdN^O^pmb_nGGim(0E-`GTdr# zK3c`aSaIq9mDdzW#;UW*6(LjiF7{WVG>8oOKi&V|VAB7Rvmf4(#*}=2VK#q;u_lQ1 zoJ7mkl%B-v2ldA|G(%rsieD<+HuDEsYJmOW;I?VIN zdr+XUqvN@}K8_>@MluHKU<|BTJ}-_6?Fz9a9CSj-@)3$DS7SXs!y1Or1JLP?H7iYl zmr>?N5c(ucG4Lb{S4Ep$Kod}pGM}^MQ(%-;RLBp)1kBc$u^)3q=9C#QWa5GO0Y?b9 zYzFc~pQJk^^3~|72Bs+zfh)FJG_0=Yxs4}iHJW_x?vV2`p-nDRJ(*cP2*&r&k4#F| z0@>G*a;zz6*H0-wralkG3q|^YLZA1DD>9cU$4o>tjV)OLdO;f+`tdVL2q`g?;El66 z3JoTyJGp`ml2}qyH-w=Fh2D}?wI-ejch!!bQLgSWJr`vXtBbkmEH4CuZ~=|=Wp%<@ znlvE|YbJzMt=i1ZCQZW(iGUHCtWRViJ0fgy3^k)F&A>VwlNcKR)>d;{6^-WEp(Rcd zspwe3E!N&mSu_6IcYkhSl^{t()mzU8ygl1hS>Z3R)hH0n?Br&C?fDZO8D55@1dmh! zIar%oPAlg&TW>+TG#vlW{ryva=C3j#S9jB6Ns>6x9CQKw49>rQE4`M!LzZ#!JftgW zT{QeFa}Taa9R}RgR^D}hdPsouJOzeHEJo#~< z;p#3GiCBNj4I=&m84X#g$@=C7#lc}?kw&Ex^Yo^yOnY=z=N=zV;3+FW>exHjKewua zGFv_HtUmm4b(UJNWU~#JtX_chVk@Sn zr(^eUpd-*x^MPsDE-Uo3ko(8)`IEq2z1<3v(Gyg^fZXnrgXhl}klb0S==>E2L@IoF zr*)BEzK3c>A33wHuVEWFc84v-bwp5~v=03edZ$)rwpbqSJT}R4IiKN1F!f4vo%4K# zXZtd!O?pfNJP0}?0raLSb;++U&TOP{`h-c3?HK$KsZ}nPu8f@@bd1>TkU!dlAlXcgOBe zSvM-HNMg#x?f$ZTWM_AgVcjTDtX++n0+aBp5UjHQqI|CPmuD-Lhr^FeO9Kd_nAZPT z{=5rZ&hZJqT|N={>Z64SxV^fkllN3wJ6Sn~qwmfbl7f4lR={0QEYJ20aZCs%K$@Ig zKJpbe?=M-Ov$AAcvsnFxA$&NN?3Dc1=>Lf~5i6jh5uPfx(M2O~Q~|DAM3xktSrnYj z>{AMR^*?x>;7kf`h_up<-26dVs*P_DMUU-P;BR8U9dF$wa}w0uDU7+Uo7qz)QJew! zh9Q_nri3_g7bO~*&$dH!cG*c!euoX_An^?}CzDj;dylJy(Lw^d!UzG+(|3I5t#k#w-T5p(a6nPgtL-yfI)jq z;9I6rTc=>bQ^`u!Y}{?7Q9>P+TJnu7|e^fTcKZ8TAYb>N-?Jg;ayVqjN7S6 z!FVwo02miF6g0gPE}db~)YXI)w^j1dluxeTfM#;+Ya)Jl(ZGCMn)dVa6lCQ@;j3nuKO)Kx(XoolVatz2iv#NtU8iui~*!4`IR2?Pj9`)ydsL$?q;}a zXVlTeC`EwG1VK^yrUa(G{>y)!S&_g^5^t_Uz8b6X!n2n@HQGO zk4D6m&{}5fYWSQ!8Py6|R#^Px1<$GcRDfV3 zQye;|t=H|f+fKjpTUf0Gg<~G$=EnfRX9QnI3FYZIccD zv&s^fnVLR##&mj2w(5xB zGPn5+=$c&8FcwRIadmFumIQ*|lJ=H_IcabNH6fOx{&5XK-BCeZ z8GCslLM6P81FFFR=Rn(qh|cBqM+p;c=r^$IQ$&g(0PLO zjT=vwIDEWQ_?l_#gq$EwwjLu7%|(l1F&6nlzlYBj4e4MP9ggEjY!^2&VI!YVrWBSC`AnY(_&OdSD>JpY;E(n z2^7GhA7So}s1)0)T>s%IccImu``+L&gMUejF+w6>QF*``RK0znTmcmsfGku2bchkNw`A84garg>0W7FlK8-jVx66 z(TXsSf&^EMph?g!#>&EDsgQJXX9d89fiUtO?5J@OF#_b_KQj=#eI! zSTHKogk=;4q`9qEeEVh^V-Fqo&%P$r*`vxSEDJ4>W-0U`3o$~M+{lKqBT#nyNUXPN z<1fTl3iT9pL1ELN*SAs?j08BzSx=BcW9ePjtlIXpyg*N`5a=RQA%C4Sa#c}8Qrb(Y zkNaM#@0xV-vR+Od!KO2Ga(`r?P~y)}iaH~6UDxpr!H{<_a-5FAR%ROK6tr^?Jdtp0 zcoaX_TOspo5Ap;~Bb-McW5PM-z%D$+Rzey4QbRZ^8+Y5H$2_Hh64V+j_b$t0TpCh^@d4l zv3VZ;E`YSwO#OHcHitV}EsZaEB1t|m4kQJhIiX%>1)sHJkmHtZjX)#pc++%xNA2SBRfkJ8GPfed4(b92qvB!iXxy^uUVY7u5M zx~w~%Z7&a`y2rOHv^um8%-4JYI-C0T8Ms&g>Bp|D(LeU7?RI|rrRxZqh}csqR(kHd zEkdOkKlD#~l6d3+1}AMLPV$O5yLj9@@5Cf;6pr>nq);Vy{6>dtI{Xd?;$y{IrmCLI zCcRnR-#f0&bk@N)JwM?*UM##1DW4>vbI}sj;G8)lIn=Pj`h7n0=q)?3Ciibyocp6M zW@5DGx-a`ID*J>X#_*#V-O)Zj3h(h7KQy+vUVp@q**pTvQp|4}&OVN_c+CoGTZo+@ zR=XsL^$3rt@#UL3^e${dk(*{yG>KH{x1I>3+b!*O`T660sB#wytP9NSwG!{!MnyT@rbH0#c0pXWEDKe6MoBD=)9<&;H;8uVS{m z6F*RHevXqz^YAXEcb>nkAh^7_59s2l_v_Q6;c&=6QBrueDiQGYs24H%RC**PV8V9p zm#Z-qLREOM?x@>qMHQ3@PU#6cu(OO-eNN?kC{1jA!`v+6@!zum0T8vgLhlkssnQ+< zeu>NHdMb8QA47|a8d*BEY-i%snx(aGcqu{;Chwd$8#WkxFZ4k@BWlrP@* z#mcr}@im>c;VZXmt`O5{de8|o3iRS;J-MftBf2_2!X&?AYAi|Ro=tu<%{=B(=ikrY zB5c5K$K7n?A@h$8c#=VzM%IoDPwolW>b@9Wxs0iM;!nYU~ zAa$`m%28j9T~zaMDh2gR_>rnE$Y%|34)hDD(t5%tw0T0Z?ogw}0*7(BTAW#aD~=3s zuX7TQ!;yZ!q&pLtQTNMLU%iJf>?CDjPq=%h ziw<5MyftQ>Uv+cs^Dh1@8DDc}LXqto z%q9SnfNV4W%k$R1In^XjeZ*GZYZuhhoYF|lz2}}sSBwf)rqKg)bFJ5?^bcuN%#6}* zNnnB4NOzl8(o^oX$_=4+vv(NLICmypT3BQ+je;tMMA0=5JZ);fv-i!Y%O3Atn5>>9 z_s#79`wmy@S zvPGRg_GTz21_%x&wby|4R_MgD`_CNEQ{Md=2Yqy2{_8|XJMR2Lb6&WE(@wF5Y(litEk{1{uKGLsv=HOkUXPXN$UR9= z4gZrt_U^_1k-1+Z9|pCK^7}%7$V2-0YuTf&k*#m0VkytrWzB44X7s(F#`mK7Dt?|& zJ2Af>Ppuc=W*)}tSN9vk`)}~8?{*0Xih5^c)Z3YFwoPEC#d4@rVmq#&r4OHJwLeD*NA zSh_ZuT>vx$h*E@S5i$B9|0l=};bwup{K#Lulg}beqNBBf8oEni)+MFP#?q^gjhXU^ z{&9=4+x6=E6>;+31nUvV4YoMR7Wonyug{*w8Pe|=N~wDxPx8SOYe`6-;-jng z9ryNW8%LMjS;MOOx$1W05yi#L(@Gla8{MoUDliVg)HyH==w=9R4!cX8+VezeJxz>`?Mc zLTq!`VgDmC>pfu!lfvy>6<9%eh}7mvMKK+;grln@A>oyBv}H1iOrJ&8*1IQ;cQgV~ zmAEW16miG}xf)n+%SVJXeg*Ec3Wn3!P{mpGl?ICq3zcyB-h{Y)H{T`teL4QMp?bZC zDHd6(+3Jy$gq4|Q(_Oe$e?|7qxCuisx4KoCSO77xrK$Mfsh$iSJVmdmrYF?v9!qK2 z&0n4?*YuHd|B7RgQZ>Sx2?s)LKGnOL_w;M(x9{REzTJMazugLsM2Z?fpQrh3|J^4K zZGZ2;)5DN?m&-SV&G0*po|%!wdjlsh4Jzgy6YUmeINibiXO;(QyzQ{e0{``KN1Inis-`OpQdJ`AC zdHCR$K3ZM({ypa4VRo|rIzD|REws*H)$#sG{QO>{+QsjXF{=0Zr0%u#EP;;&7qQCZ zTG+Keo8kk68-q2HI4^t_h+dOf7%|FT+&vm@TSgjtRdk6{bSXeDC3)jBB~WzfPCpLh zU3Bd?pjpSkU-&c$;Y|o%TT2UikQGcx#2);5W^q-h5eT;1ax)s65o^U6(`WPv27D8zk9_$1IeIG zC?xa4^V_{S3hhdIJ-`W)sNrBcKVqGUr$tST8sD385Sb+ucGI|()(|MRs-que&C;(!U~GC1*_7~SsWfh<0R+e4j`aQW zZ3?&+#)*CWydRKx4P25_$LEC;2ZIp}MXq0lJ37}Z@Of?C+M;1X)` zJ_L|zs$1Gw{W~RC*< zd&wLi74<&T82hW7=ssvLG4`f5i5Q|n4ifeEWs`9Cz@duou8*F3;cn0FMHVisxVcD-}2FK}Kv=gTsV;3}8KMx{xjmCa+M*eb5d zpw5uX)7d#s*90UcX^^@Er^MOA;*6+S*@LJr(z_@{^9fQMsN1(=oH6YtPXXq`pclVU z_Zx_cOi%>385*$?k#v3p`ZI`L~ZExILDWEI(Gk$zWcJW8nX<@O0-tLB2FX7 z2>IV9@q@t2uQ;f)ZZKanc($90ui6ONM6Kj#KEANGW%q!UhGUXUP^t}<| zl1u%sH`-7DXiaC0Q;?(BkOeAsW!xKhd9UnHEAmB#u@a(MW{pp2L_U5vCf+?V8xWz6 z`(^a|;?g)L_kpJS&&lKis4H@Xw=u0Vr<~!f9LqA!%|F`JD7k1lN}) z9qFF5I--M_XP4ErV7FB3p{n&WNx*Wsa7F6+Sz1;d(jpU<;2~$F+6SAtGZc+0Fji8y zw~bT0-E1C^a7I)5jdQyFWkt$GRP}~mX2}QT3G7^@l2!HR>HE{c^^xfWWf8olr(oHV zRJRvGbk|`@>HK?t<{frX6w>=CXy4*93X_f#c1|Q0#q3sYE!nSt;&!b-P;eRPV*{1=?9N>vh!YHH1xbT67YrSQ>a_XV0 z)8iySo~{1XG0rER9N6H6NumwoDJ{gr*HKjm%)Hbn%MQ-E$@+drT{03{(u$Snd`e`! zMB%hWYMIv(D~3n>-&sYNV@qu6_DCw%rZ)cisd`U(3+axIWFQoe+$<~0l?xTMkrY-1Z;dm}u$+T#{}TCUpTMKY0j(8U>8QnLk#_bF2@4C@F!5{t;jt1@FLi)yAhoO3`0EPY1{9jH%>+qi6?ayrZ1|=WMBGA z>WqyD5_G(Hbd|N_mEH%yES%r`$0A@slq&d@1|i{!AYMk|i`t?UF$ymxs&i~A!q3x} zyVI{|i%NFK_CAbNzDq{;C3{*aE)C}ZPG*ru6aHQp|Fd#b-7cCmfZU{9XAwtzU;*@DGiJ3KXCE3c?ZOe*f);l`(8Br7( z!C?xA@3H9n{Zwbj1m`!xsv%pkrhj3aVg3Q23BREjNr>ayX$bmC_&kmh$i?j z(oWF+bH1XXoSHx`b#IAzd?G#*P^?x=WOf6;O z9`#`Pjf@_IXQ|)2zCYP|_xRr-^FPP8AelR<+7c;A5L)!TOyrsX(!*5gvc z_zgMpiAWyYg?{l+*gfEPpJ|qtbhIF0$<7d1?+vsw#36MW?tU)G&09%mjo@MMxi@?? zz?v_;VT%OgB-~f>-ebu%1@n~uhfvk=)5%6hd7z^dW`W;C(X$~Lta*DR)0r-@`zSr( zd%$!`Mlcg?>P-14`0QQC+}~$Ph>ylhBQeeRSxrr9}7uD1G((+ZbSCXBAtFPVsy5U|3;k&6u7S&g@YBAD?JuGTLl z`?N**dQX$SmW<>qhzQYocZuK5a3*URwF^Hlklwf=fY~$+bAtkV^$@q&G`+{vsTtFL zy?)}(s2kww*!hEX<5ipl)g8uqsZ=3__>JqWsMgp+b?dh+)%01t4~N~gHUWQs()U=o zso?>3k{6D{#l~jIukfK!BhV~w$k`+|Y>nTwDUAH&_s7!@eT@V{JS=h242+b-_Px!m zq$Gi7F}Enic3VhdyW(gsBp|l!L!PZ&?3pMs>QeaARnlOMis)>g#|j~FC9yn{@CUk; z6Qd}O7=xb|UxJa!Seky)NLZAh5650?RqWW!WxEWThsJ9N~ps&o5yxP!y?aU#cx-n6tQm?r9?bR`Ml!_>LyYQb5z zBi&@qH>Pza8#`|jm3@q_XOgla6vZUUF>(?+z;NPkcT}fTrwO#Iwk$0&~+_}v)J>r z3YX+&cv*r=f3B>4DmpGqI$@ZggK^heD3=sK6#20 zFI}VxQ=|=LLo6@5-E}5xNBc~6u9i}sb&B18(}+ESz zwbre_4`n(~7;~r6c%=4!b*9zjnSR++pCUhpJ#NMK?rs0jWXv)K*2>aF{u<+0_u(@{ z7D^b69H`1f*UJZ7XR!XR^^7U3(s}c5m*TgQ`>2TB#+Dan@@TCNPc7<2;By333-(BL=1Lhf$=P|tgamH%>At(d@e zZM?XTYTkQESWkEOT}*scGoRq+3C{BPzIF47Q}WJYw9nt;k6iJcbN1B?)2mlFiqU~S zu!YmR1@yx8tX9s48Z}rPjQFdrNNBY0Ct*xY=J=&A?C~NR{MSdt#aqRZYv!|z3cOZQ z$42c(XD$ddNw?&zNBkIIAs6Z%UO3R?AW1ND6ZWiq(M6Ox(u-R?KNOrUB@Gc=&#Yu^ z?{xFxj}KZ{cX%uV`Uv=(I^?NqMPSw$L?8Q}s*_`~Bp(Y!?cQjEXM;r~0~oG-RgQ&Wz#woN(Oozx~6^d-`tsB zqZGLj^NYE0I*=swJlguvuKZwsSSkwYSbf8BQe z&^gRUd|4D=n_rVGF#jkZGjc6Z+S49fBJFkdmlYS3Lf8IPWBuO8j=5#bU(s!RqYS~0 zH2!tx9OT6vktqxhqrg~dQ$sR;YL2c-n2H55kd4UCf8A?b)k$~znWL_peZcq`lyxgR zJK#9rNKImUOllzej55c@5bI`A99CEcIV0#}rQ@2w1wA?a4PxbhmeKjb(<{r#s{C?632j>Gt8|txz9JNpUUWcl!O2jQ=q~8*B&7I&qn;Xr%JK zAVa31)DQ&2(Ew4ZfoK(AVPU!{@dMEz)6qkf(u0Xm#9=W@{Yw1Lk{{Vh!iDE`iG2Rc zhv5*gFj>4^-ky5nrj6;MO@2Ee%6kxZSyHghr@#IMR`XY%0ZsDe<7krfA9019IgB}k zc-lof(vHXSxnE?RTzwm+T~{-V^3hGXqj3`Xg_jDBQ@^%M0vQ>h1+P-5_*RP zewW4%uv?3KT?d}9NX35f?ml&&IxGP!?BEI*1a*hnJ z#q8CWb`xiAU4K)w3Qhtz4LN#lW0jNM4>m?*T}WQ~af}=ik7Cw~uf7kIsWV<smaMT8?SoIgqz@3P<$PVO3pn2;Gy$j)akxSEy@f^M^UA z=OR^Ym7@F>7{~XYH+e?bVD#drDcW?ybCXk~yaOKR1;=nZQDX1;H(&%O^_3eBREO%* zH%fhr$0XVKj4JI>!zWwF$8D0lDRiLB%gcsLZ$EgPY3x*O`JsTLCDl7v z%kRPXw5m`toB;w3G@#b6a~GwHY9vVmlB=0npO#3YCy=WVp7E0HVP_7%c8EX+50~-8 zQ@?wi(1Qs38KmN}!X9vnT6Fs`bmRl47|f;vNB)^UX53N~y=5B5_a$n}_MjpA+)7gk z4pnfSYs{N;arzn1kC;HJ&kZzQR)pGlA-nH=`B<+4n?T-aATGGWQ`$26O6w*;&4=cw zM^rGg{G(rwP2rOt&Bbj11lV8La^SRnm>A;t{c*w72aBFV$mft!#z1Kqfw@eK4C#1@ z*Kvm5Wmghh4);e|9Z>REOBAi>3+!Up-Nv7_INB4m^*%q%{7%< zW=deS^S(9QEeV-7vz0)&@43P2h%A3n(BwZ{sTi32u~B?@RCzUPabE^SBLhvwsso%L zJ?dbmC;R()`?apmVc$jm4Z2SY1K< zS<(Ul-%brJ^1jKeu)Br$TZe>vBmJ??I<8Yr>&w=C2NdlWrLz}_!C%duN zpgG9@>uVJF?*YW9PFN{(6Fd-N=T5M~7cgoY2l3t+wF|1imL zjkfAJCHp-skmPrrqooHebYcwoMcf^tGHRO1V;W6Cy&Y>*9{=NSFHbZ-zx=Co4deWG z5UmWlS67bBlA-*@bh1x+vxQRVz$vb-bZ4=DLuCl~cM}bif&c>W1&)7B>5fkE6@9?r zfKgPNqdbl8KXe!s#^Ad$3M=g6lHVUZ2M684x%frc8tyK>!+AAcE4}{sC}33!kl-um z!o@@CMco`)Z&Bw^=a>h4F1%#&Xg53V=8!-9l|@M$?l{BkIykQX2`-~+{eZ>PeSEVg#+rAo!&9x-A|;rU#P$KT)NJHvk`Ebn~;$KjmOCuBxCao>ZL zYkhFWufHy^2-nI$;|=7EO#o!B9l2tVhZl{P^~Un3L1sQs1dn-L=BcAU)s>mP_K56R z&5`_+8eQ#vEDiU)2l|&_f){gu>w-sAf9xL8OgUA@r*{3|52WEpMZU#NpCj(Nk5&(_ zi9tX2C~@{x!XAS4YYseq3$w1}wJOOAqtQ)o_ z>fB*^hkF~RmeMJ>cA?*;az@l6r_{%j$U zNMZsgG~m-uJ?FFQc>Wiso#tmUM5I}y@_&A))3^D$2@ysJq0nfaJhLMZJs~dgUQPl> zxZGzc?iGn4j<<6FZ9uNCw=kPD%64T=-dA3J!=mIxHz&$iriSBN5#Ov#Ih}Mop`eQl zQfHxx;M4uu_&Z^r#bx1DvLR(LAk&#jv=hwbfc-M`K zgHhfdsD95dQ~s)a=MIN{8B2@^Yxwen(q6BxQ4GqFy5oyA>_)ZX*i7+MKkI8&NeSt6 z=|*K-J3$jWoWIR;-`QoT2~tz*GAKPrGFdnoRolv$^7C94UA}W1jOlPSTTbaL_HkkT zR@4nXM`6tej~c`g7%$$V(Tqjt=wgG@zu|(Xt(B~qm_@&n&UBQW7Y=N1ccw|Gr7pv> zBAS%S-YlHHBD@8EEOb)Z?9hk0O+B+GKCMYB1Ro<;R(ToI zRh3Ve;bDiuZ}SG@t*1`zn#=WSoitK4;)s_fuG?J-^Ti>MCu6jsEI%~_b$kOXx?+`L z7TTz9R)6dBwN2PhXv3FC>kP)X+PWs7QG4<}=neR=|K5%FO-DW;5ZxpeO1wFXXm9nw zjs4bgByxX~ox(M9aXA0hFuGIw?c<%~NAs#b^BG?9Bz>k-4sTHkjGg##qUnUCN&Zx)ZmHhQta&>Y)}QBipPl5{1p7O-#GJBqCLSxLRxyO>bgfo>rJ!Jc)DQ1t8xJ@d%@acYcqlBajK}e zb~ZZEybqT!-#_uUKkWa0huH3)N8VtrxirS9tS33)8CJI@t|u` zp5fNYx5Qz5Y778iKH89z@>!ki$q!}#e}@Gs=F-aiLK~I7lgt(wfQEMEMk3A>6D_Nd zWhcO^O%X=udBrrqso#J8<-IbtwL_978e60SS0 z$z+R8K+K{ptFGc(Sc)2&RB0j+f&xk?Qk9kf50EY(1W;O{F05r~0S#qo5=AVO&5FyKov9AK;*i&I*detEIRJ7>(z4QU^HOH@NIi0U_R9!K z01~yrB8Elw*7!aj5rADtrv!C4fu{y*R8*aT2p~;=bu%fC;35O$!Ej|hQF?Qi>j6s_ z+n5a*nyfdcM4Uj z=SNRBIJ0-tqE*}wvck4Cek;x7j(q@--I;l{werpxEKfCOzQjrYaZ{zE5JiUt-FXLH zU&I3w%X+HSsfQ3Lrm>T8lxKxRrvu<}nO9Iz%1J-K{l&a+N-ez{7?G>TZMQD);A4mO zYiz#}kQ85!#>)7l^M;{yQf6X#<<0OO^ZbE^c(K1(t+xwo{QuWaa)WFsjlnv;o$J6f5awP?S{3D>B>=GLSjFX{IpU1z?bO{! zHX_m8-viGyprAp0Czbz)bvOPx$tFw*7T^RJGt z&cj6Q360nLL~z#-v!iufj8q0E^G^U=c1J=20s^K>e41Xq;t{YsBNcZYEd+qpx#a9J ztzE0~2^%~JkFc#pmDU%9{tkeJV0Ep9UDV4V%U4Q(+?y`4^N33EJu3`AYMhuj`p!zc z!(TSZe1QXa48~R3|EGOG@}kFb2Rf`UDo@(2&oxmlqw)}2bwH#1_g~?pk;N4zOKk^y ziqjW>X^GW#KKZiGdB|6H;E2_bRU5+B0Z0cJZ@hMZpQB)PCMv9ar|ckY!a>k*>icSUN&A40j+faIB9&GUJV{Gw9jElBf7k#(;b>lo;SziP>Rxd_|3D;^ zZ|I~gTVv1}bygFA$1&JSRLO>UeCDxA+)Ot=xzN}j0+8Mxc5t&jb~Ac-oQ|ULtGpjE zQCuX)OP_;;o3V_H6*I^V+s_RSl57+TZ`;%2Q8Q4sjCbupo7<0U$$z7I_$0A0k<(~$ zhzyTa%W5=s?CDtr%{b^s3LmZ89M#SZc)rr!oQ&h2&$DU^CinOH zUIjp;#aEb8IJK1EpS zr>v;Uw79R!dO(jON*N5%(MglhM+!Ot>catnj7Gjj*4T|YBAjxJxX(+F-&Ri4@hMBV@)o3B0vtG6kYQ~ zI1R0jJf9)p3{Y|d=U0y~mY$R;5THCf+*XqPd%ho>vaB!legLOqaq<4ry3wArx7m-} zE)6#%?3c7k|uQ%-Wr;#^i%d%`7TfsOugfNh=H-(}sp zca8Q-48)2=9bLXnxY?q-mX;73@uAS7rOrwB$xak@ofoxRW!sOXZ;oDe+>J%<+D7FfY;jWB>RN@EQ=K{oJv}6@ zeGufx(WyicXoa-jgoy>%Q1GJd=5HgCw%OG3+)^mEe~zS0iF}#T4elX8(1(VvbcV^F zxKX7Ifzz8%Wqo`r%*&Ul#Sjuv8jWToPwP)vW0lePX?~A7LuTOY z1way*|D(NLi=e=9?5Nu?dF+C8sY)QueTXq>ofvkn(u9RH@O|a3r#!)Ac4e>E=jQdc zJv#%eHNy`1tAAW1_w!Kxi6NBHcy2wk$bnEq3E-LlHMuqS`YfjCk_zj3a`vH2B(QLn z#>iC;*QkfGTyfja4XgN7@DW=LhT`RshRo&cz=-k)!6{8jk&Xm!EXYZ@lH{ytr(E*^i`yDpn-5^GDn6ie7iSdiq){Z4= zRIJRPhGpyA0JtQowoXjSXD=+8)gRV2J)H~MKfW+)zFuB76vFQDMAcs)hyy!!r<6O+ zk!P;`Fch&DM=XI)PVLS{^@Qx4`sQV<{G}NLNm}E~pwhaQ^8!QnpYokK3*hszl=Iz> z;h^io9>8*Su!^~A2)+CAbXJm4=s4r~#P}|^zaqE$Zn}VgfwJMty(u($bLnp#Zz>Q}YGU}h0k5QQ zUy{N;T(4_)r`S_XTnD72@uyh#x;KG>u@dVz-HB#|bQ?g>mwZcRH{F_<81_4p5rmAo9mJj2pc@{Y-g|8EK1D?^jr{wf0AvXBtb4l>7_d=2x!mN>CHY#a@4a RZulAjSi`FZg_m6J{SV(55oZ7Z literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 4294e5a..1ea70d5 100644 --- a/index.html +++ b/index.html @@ -20,13 +20,16 @@ - - - + + + + - - + + + + diff --git a/logo-variant-3.svg b/logo-variant-3.svg deleted file mode 100644 index 7ba5b7f..0000000 --- a/logo-variant-3.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $ - - - - - - - - - - diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..eb8c441 --- /dev/null +++ b/logo.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $ + + + + + + + + + + diff --git a/manifest.json b/manifest.json index 4860d2b..b024899 100644 --- a/manifest.json +++ b/manifest.json @@ -10,13 +10,13 @@ "scope": "/", "icons": [ { - "src": "/icons/icon-192.png", + "src": "/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, { - "src": "/icons/icon-512.png", + "src": "/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" @@ -43,7 +43,7 @@ "short_name": "Add", "description": "Add a new subscription", "url": "/?action=add", - "icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192" }] + "icons": [{ "src": "/icons/icon-192x192.png", "sizes": "192x192" }] } ], "share_target": { From 153cafcd1d3f911848c3b18c70cf9d7378f7f4d9 Mon Sep 17 00:00:00 2001 From: Subhan Raj Date: Sat, 20 Dec 2025 18:45:30 +0000 Subject: [PATCH 06/18] feat: Revamp PWA install banner and enhance offline page styling for dark mode support --- index.html | 52 +++++++++++++++++++++++----------------------------- offline.html | 36 +++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/index.html b/index.html index 1ea70d5..65d80d7 100644 --- a/index.html +++ b/index.html @@ -83,40 +83,34 @@ - +