Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 33 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
164 changes: 164 additions & 0 deletions css/styles.css
Original file line number Diff line number Diff line change
@@ -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);
}
Binary file added img/currency.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/map.avif
Binary file not shown.
35 changes: 35 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@600;700;800&family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/styles.css" />
</head>

<body>
<h1>Map My Money</h1>
<p class="tagline">Type a country. Watch your money move.</p>

<form id="countryForm">
<label for="countryInput">Country</label>
<input id="countryInput" name="country" autocomplete="off" />
<button type="submit">Find</button>
</form>

<p id="statusMsg"></p>

<article id="result" hidden>
<h2 id="name"></h2>
<img id="flag" alt="" />
<p>Capital: <span id="capital"></span></p>
<p>Currency: <span id="currencyCode"></span> — <span id="currencyName"></span></p>
<p>Rate: <span id="rate"></span></p>
</article>

<script src="js/main.js"></script>
</body>
</html>
93 changes: 93 additions & 0 deletions js/main.js
Original file line number Diff line number Diff line change
@@ -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}`;
}
}