diff --git a/README.md b/README.md index fd76323..6a56db6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Currency and Weight Converter +# Currency Converter 1. **Currency Conversion**: - Convert prices from one currency to another. @@ -19,15 +19,9 @@ - Uses real API endpoint (`https://api.frankfurter.dev/v2/rates`) to get the latest exchange rates. - Displays the last updated time for the exchange rate. -1. **Weight-Based Price Calculations**: - - Calculate price per kilogram for a given weight. - - Calculate price for a specific unit weight (e.g., 250g). - - For example: compare prices in supermarket: - * enter price in PLN - * add weight for 200g in PLN - * converts to kilogram price in EUR - * add weight for 250g in EUR - * converts to 250g price in EUR +1. **Optional Price per Weight Calculations**: + - Calculate price per kilogram when weight is provided + - Compare products by weight (e.g., 200g vs 250g packages) 1. **Localization**: - Automatically detects browser language and switches between English and German. @@ -47,16 +41,13 @@ 1. Select the **current currency** and the **target currency**. 1. (Optional) Add a **bank markup percentage** to adjust the exchange rate (positive values reduce the rate, negative values increase it). 1. Enter the **price** in the current currency. -1. (Optional) Add the product's **weight** and a **custom unit weight** (e.g., 250g). -1. The app will calculate: - - Price in the target currency. - - Price per kilogram (if weight is provided). - - Price for the specified unit in **target currency** (if unit weight is provided). +1. (Optional) Enter **weight** to calculate price per kilogram +1. The app shows the price in the target currency 1. Click the ๐Ÿ”„ button to fetch the latest exchange rates. 1. Use the ๐Ÿงน button to reset all input fields while preserving your currency preferences. Access the app directly from GitHub Pages: -๐Ÿ‘‰ [Currency and Weight Converter](https://leneffets.github.io/currency/) +๐Ÿ‘‰ [Currency Converter](https://leneffets.github.io/currency/) ## Screenshot ![image](https://github.com/user-attachments/assets/a1d71a72-45cc-4436-a050-f4c4cbb8373b) diff --git a/currency_192.png b/currency_192.png new file mode 100644 index 0000000..e24bc1c Binary files /dev/null and b/currency_192.png differ diff --git a/currency_512.png b/currency_512.png new file mode 100644 index 0000000..db0bedc Binary files /dev/null and b/currency_512.png differ diff --git a/favicon.png b/favicon.png index 1a1f13b..e55c436 100644 Binary files a/favicon.png and b/favicon.png differ diff --git a/index.html b/index.html index 8c2c7b1..9e0b557 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,17 @@ - Currency and Weight Converter + + + + + + Currency Converter + + -
@@ -76,6 +82,7 @@
+
diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..e0ddb7d --- /dev/null +++ b/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Currency Converter", + "short_name": "Currency Converter", + "description": "Convert currencies and calculate prices per weight", + "start_url": "/", + "display": "standalone", + "background_color": "#f4f4f9", + "theme_color": "#2d2d2d", + "orientation": "portrait-primary", + "icons": [ + { + "src": "currency_192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "currency_512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "categories": ["finance", "utilities"] +} diff --git a/script.js b/script.js index bc12888..1bb2259 100644 --- a/script.js +++ b/script.js @@ -223,6 +223,29 @@ function resetInputs() { if (priceEl) priceEl.focus(); } +// Capture install prompt +let deferredPrompt; +window.addEventListener('beforeinstallprompt', (e) => { + e.preventDefault(); + deferredPrompt = e; + const installBtn = document.getElementById('installButton'); + if (installBtn) { + installBtn.style.display = 'block'; + } +}); + + + +window.addEventListener('appinstalled', () => { + deferredPrompt = null; +}); + +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js').catch((err) => console.error('SW registration failed:', err)); +} + + + // Initialize the app (wait for DOM ready before touching DOM) (async function initializeApp() { if (document.readyState === 'loading') { @@ -232,6 +255,19 @@ function resetInputs() { await loadLocale(userLanguage); loadCurrencySettings(); + // Setup install button + const installBtn = document.getElementById('installButton'); + if (installBtn) { + installBtn.addEventListener('click', async () => { + if (deferredPrompt) { + deferredPrompt.prompt(); + await deferredPrompt.userChoice; + deferredPrompt = null; + installBtn.style.display = 'none'; + } + }); + } + // initial exchange rate fetch (debounced wrapper) updateExchangeRate(); })(); diff --git a/styles.css b/styles.css index f5ac6c3..fedd5da 100644 --- a/styles.css +++ b/styles.css @@ -45,10 +45,35 @@ body { border-radius: 8px; box-shadow: 0 2px 10px var(--shadow-color); transition: background-color 0.3s ease, box-shadow 0.3s ease; + overflow: auto; } h1 { text-align: center; + margin: 0 0 20px 0; +} + +.install-badge { + background: none; + border: 1px solid #2d7ef2; + color: #2d7ef2; + border-radius: 4px; + padding: 6px 10px; + font-size: 0.85em; + cursor: pointer; + display: inline-block; + transition: all 0.3s ease; + margin-top: 15px; + float: right; +} + +.install-badge:hover { + background: #2d7ef2; + color: #ffffff; +} + +.install-badge:active { + transform: scale(0.95); } label { diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..7a36765 --- /dev/null +++ b/sw.js @@ -0,0 +1,41 @@ +const CACHE_NAME = 'currency-converter-v1'; +const ASSETS = [ + '/', + '/index.html', + '/styles.css', + '/script.js', + '/favicon.png', + '/manifest.json', + '/currency_192.png', + '/currency_512.png', + '/locales/en.json', + '/locales/de.json' +]; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS)) + ); + self.skipWaiting(); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))) + ) + ); + self.clients.claim(); +}); + +self.addEventListener('fetch', (event) => { + if (event.request.url.includes('api.frankfurter')) { + event.respondWith( + fetch(event.request).catch(() => caches.match(event.request)) + ); + return; + } + event.respondWith( + caches.match(event.request).then((response) => response || fetch(event.request)) + ); +});