diff --git a/README.md b/README.md index d8d6388..a5f7afd 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,33 @@ -# πŸ“Š Project: Complex API - -### Goal: Use data returned from one api to make a request to another api and display the data returned - -### How to submit your code for review: - -- Fork and clone this repo -- Create a new branch called answer -- Checkout answer branch -- Push to your fork -- Issue a pull request -- Your pull request description should contain the following: - - (1 to 5 no 3) I completed the challenge - - (1 to 5 no 3) I feel good about my code - - Anything specific on which you want feedback! - -Example: -``` -I completed the challenge: 5 -I feel good about my code: 4 -I'm not sure if my constructors are setup cleanly... -``` +# πŸ’Έ Map My Money β€” Country Currency Mapper + +A sleek front-end app where you can type any **country** and instantly see its **flag**, **capital**, **currency**, and **exchange rate to USD** β€” all displayed over a vintage map backdrop. + +[Link to project](https://complex-country-currency-api.vercel.app/) + +![screenshot](img/currency.png "Map My Money β€” Country Currency Mapper") + +--- + +## How It’s Made: +**Tech used:** HTML, CSS, JavaScript + +Map My Money fetches country and currency data from public APIs and displays it in a glassy, modern layout. +The background features a textured map image for a global, travel-inspired aesthetic. +When you click the rate, it inverts the conversion (USD ⇄ Local Currency) for quick comparisons. + +--- + +## Optimizations +- Improve color contrast for better readability on bright map areas. +- Add a dropdown of suggested countries for faster searches. +- Include a β€œTop 5 Currencies Today” section for global trends. +- Animate a glowing marker effect behind the selected country flag. + +--- + +## Lessons Learned +- Handling **multiple API calls** smoothly and efficiently. +- Designing readable text and contrast over detailed backgrounds. +- Creating glass-style UI elements with **blur and transparency**. +- Using **fetch** to manage real-time data updates. +- Keeping layouts minimal while still visually engaging. diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..beba319 --- /dev/null +++ b/css/styles.css @@ -0,0 +1,164 @@ +* { + box-sizing: border-box; +} + +body { + min-height: 100vh; + margin: 0; + background: url("../img/map.avif") center/cover no-repeat fixed; + color: #f9f9f8; + font-family: "DM Sans", sans-serif; + display: flex; + flex-direction: column; + align-items: center; + padding: 56px 16px 40px; + overflow-x: hidden; + font-size: 20px; +} + +/* darker veil for readability on map */ +body::before { + content: ""; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, .1); + z-index: -1; +} + +h1 { + font-family: "Unbounded", cursive; + font-weight: 800; + font-size: clamp(32px, 5vw, 60px); + text-align: center; + letter-spacing: .5px; + color: #2f2f04; + text-shadow: 0 0 18px rgba(255, 255, 255, .38); + margin: 0 0 18px; +} + +.tagline { + font-family: "DM Sans", sans-serif; + font-size: 1.5rem; + opacity: .9; + margin: -4px 0 22px; + text-align: center; + letter-spacing: .3px; + color: #2f2f04; +} + + +#countryForm { + width: 400px; + background: rgba(22, 23, 25, .72); + border: 1px solid rgba(255, 255, 255, .12); + border-radius: 14px; + padding: 14px 16px; + display: flex; + align-items: center; + gap: 10px; + box-shadow: 0 10px 28px rgba(0, 0, 0, .55); +} + +label { + font-size: .95rem; + opacity: .85; +} + +#countryInput { + flex: 1; + background: rgba(255, 255, 255, .08); + color: #f9f9f8; + border: 1px solid rgba(255, 255, 255, .18); + border-radius: 10px; + padding: 10px 12px; + outline: none; + transition: border-color .2s ease, box-shadow .2s ease; +} + +#countryInput::placeholder { + color: rgba(255, 255, 255, .65); +} + +#countryInput:focus { + border-color: #00b8a9; + box-shadow: 0 0 0 2px rgba(0, 184, 169, .28); +} + +button { + background: linear-gradient(90deg, #00b8a9, #f8b500); + color: #111; + border: none; + border-radius: 10px; + padding: 10px 16px; + font-weight: 700; + cursor: pointer; + transition: transform .2s ease, box-shadow .2s ease, filter .2s ease; +} + +button:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(248, 181, 0, .35); + filter: brightness(1.03); +} + +button:active { + transform: translateY(0); +} + +#statusMsg { + margin: 10px 0 0; + opacity: .95; +} + +#result { + width: min(520px, 92vw); + margin-top: 28px; + padding: 20px; + background: rgba(22, 23, 25, .75); + /* a bit more transparent to show map */ + border: 1px solid rgba(255, 255, 255, .12); + border-radius: 16px; + backdrop-filter: blur(8px) saturate(1.05); + box-shadow: 0 12px 30px rgba(0, 0, 0, .6); + text-align: center; + transition: transform .25s ease, opacity .25s ease, background .4s ease; + opacity: 0; + transform: translateY(6px); +} + +#result:not([hidden]) { + opacity: 1; + transform: translateY(0); +} + +#result h2 { + font-family: "Unbounded", cursive; + margin-bottom: 8px; + font-size: 20px; +} + +#flag { + width: 100%; + height: 300px; + object-fit: cover; + border-radius: 12px; + margin: 12px 0; + border: 1px solid rgba(255, 255, 255, .12); +} + +#rate { + display: inline-block; + padding: 8px 10px; + border-radius: 10px; + background: rgba(255, 255, 255, .14); + border: 1px solid rgba(255, 255, 255, .18); + cursor: pointer; + font-size: 20px; + color: #ffd56b; + transition: background .2s ease, transform .2s ease; +} + +#rate:hover { + background: rgba(255, 255, 255, .2); + transform: translateY(-1px); +} \ No newline at end of file diff --git a/img/currency.png b/img/currency.png new file mode 100644 index 0000000..7bdb2d7 Binary files /dev/null and b/img/currency.png differ diff --git a/img/map.avif b/img/map.avif new file mode 100644 index 0000000..d878bd3 Binary files /dev/null and b/img/map.avif differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..ddbb54c --- /dev/null +++ b/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + +

Map My Money

+

Type a country. Watch your money move.

+ +
+ + + +
+ +

+ + + + + + \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..f4c84f4 --- /dev/null +++ b/js/main.js @@ -0,0 +1,93 @@ +// create variables and set values +let currencyCode = ""; +let lastUsdRate = null; +let showInverted = false; + +const statusMsg = document.querySelector("#statusMsg"); +const result = document.querySelector("#result"); +const countryName = document.querySelector("#name"); +const flag = document.querySelector("#flag"); +const capital = document.querySelector("#capital"); +const currencyCodeText = document.querySelector("#currencyCode"); +const currencyName = document.querySelector("#currencyName"); +const rate = document.querySelector("#rate"); + +// listen for when form is submitted to fetch data +document.querySelector("#countryForm").addEventListener("submit", function (e) { + e.preventDefault(); + + const input = document.querySelector("#countryInput").value; + if (!input) { + statusMsg.textContent = "Please enter a country."; + result.hidden = true; + return; + } + + statusMsg.textContent = "Fetching country info..."; + result.hidden = true; + + const url1 = `https://restcountries.com/v3.1/name/${input}?fields=name,flags,capital,currencies`; + + fetch(url1) + .then(res => res.json()) + .then(data => { + const country = data[0]; + const name = country.name.common; + const cap = country.capital ? country.capital[0] : "β€”"; + const flagUrl = country.flags.svg || country.flags.png; + const code = Object.keys(country.currencies)[0]; + const currency = country.currencies[code]; + + // paint UI + countryName.textContent = name; + capital.textContent = cap; + flag.src = flagUrl; + flag.alt = name + " flag"; + currencyCodeText.textContent = code; + currencyName.textContent = currency.name; + currencyCode = code; + + // fetch rate + statusMsg.textContent = "Fetching exchange rate..."; + getRate(); + + function getRate() { + const url2 = `https://open.er-api.com/v6/latest/${currencyCode}`; + fetch(url2) + .then(res => res.json()) + .then(data => { + const usd = data.rates.USD; + lastUsdRate = usd; + updateRate(); + result.hidden = false; + statusMsg.textContent = ""; + }) + .catch(() => { + statusMsg.textContent = "Could not fetch exchange rate."; + }); + } + }) + .catch(() => { + statusMsg.textContent = "Country not found."; + result.hidden = true; + }); +}); + +// click to invert: 1 CODE = USD ↔ 1 USD = CODE +rate.style.cursor = "pointer"; +rate.title = "Click to toggle conversion"; +rate.addEventListener("click", () => { + if (lastUsdRate === null) return; + showInverted = !showInverted; + updateRate(); +}); + +function updateRate() { + if (lastUsdRate == null || !currencyCode) return; + if (!showInverted) { + rate.textContent = `1 ${currencyCode} = ${lastUsdRate} USD`; + } else { + const inverse = 1 / lastUsdRate; + rate.textContent = `1 USD = ${inverse.toFixed(6)} ${currencyCode}`; + } +}