forex/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Currency Converter</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Helvetica, Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
margin: 0 0 30px 0;
font-size: 24px;
color: #333;
text-align: center;
}
.input-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #555;
font-size: 14px;
font-weight: 600;
}
input, select {
font-size: 16px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
width: 100%;
font-family: Helvetica, Arial, sans-serif;
background: white;
}
.row {
display: flex;
gap: 10px;
align-items: flex-end;
margin-bottom: 20px;
}
.col {
flex: 1;
}
.col-swap {
flex: 0 0 auto;
}
.swap-btn {
padding: 10px 15px;
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
font-size: 20px;
line-height: 1;
margin-bottom: 0;
}
.swap-btn:hover {
background: #e0e0e0;
}
.convert-btn, .share-btn {
width: 100%;
padding: 12px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
font-family: Helvetica, Arial, sans-serif;
margin-bottom: 10px;
}
.convert-btn:hover, .share-btn:hover {
background: #0056b3;
}
.share-btn {
background: #28a745;
}
.share-btn:hover {
background: #218838;
}
.result {
margin-top: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 4px;
text-align: center;
display: none;
}
.result-amount {
font-size: 32px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
}
.result-rate {
font-size: 14px;
color: #666;
}
.meta {
margin-top: 20px;
font-size: 12px;
color: #999;
text-align: center;
}
.error {
color: #dc3545;
padding: 10px;
background: #f8d7da;
border-radius: 4px;
margin-top: 15px;
display: none;
font-size: 14px;
}
.success {
color: #155724;
padding: 10px;
background: #d4edda;
border-radius: 4px;
margin-top: 15px;
display: none;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1>Currency Converter</h1>
<div class="input-group">
<label for="amount">Amount</label>
<input type="number" id="amount" value="100" min="0" step="any">
</div>
<div class="row">
<div class="col">
<label for="from">From</label>
<select id="from"></select>
</div>
<div class="col-swap">
<button id="swap" class="swap-btn">⇄</button>
</div>
<div class="col">
<label for="to">To</label>
<select id="to"></select>
</div>
</div>
<button id="share" class="share-btn">Copy Share Link</button>
<div id="success" class="success"></div>
<div id="error" class="error"></div>
<div id="result" class="result">
<div class="result-amount" id="result-amount"></div>
<div class="result-rate" id="result-rate"></div>
</div>
<div class="meta">
<span id="last-updated">Loading rates...</span>
</div>
</div>
<script type="module">
const currencies = [
{ code: 'AUD', name: 'Australian Dollar', country: 'AU' },
{ code: 'USD', name: 'US Dollar', country: 'US' },
{ code: 'CAD', name: 'Canadian Dollar', country: 'CA' },
{ code: 'GBP', name: 'British Pound', country: 'GB' },
{ code: 'EUR', name: 'Euro', country: 'EU' },
{ code: 'JPY', name: 'Japanese Yen', country: 'JP' },
{ code: 'CNY', name: 'Chinese Yuan', country: 'CN' }
];
const STORAGE_KEY = 'currencyConverterPrefs';
const CACHE_KEY = 'exchangeRates';
const CACHE_TIME_KEY = 'exchangeRatesTime';
const CACHE_DURATION = 60 * 60 * 1000;
const amountInput = document.getElementById('amount');
const fromSelect = document.getElementById('from');
const toSelect = document.getElementById('to');
const swapBtn = document.getElementById('swap');
const shareBtn = document.getElementById('share');
const resultDiv = document.getElementById('result');
const resultAmount = document.getElementById('result-amount');
const resultRate = document.getElementById('result-rate');
const errorDiv = document.getElementById('error');
const successDiv = document.getElementById('success');
const lastUpdatedSpan = document.getElementById('last-updated');
function populateSelects() {
currencies.forEach(curr => {
const optionText = `${curr.code} - ${curr.name}`;
fromSelect.add(new Option(optionText, curr.code));
toSelect.add(new Option(optionText, curr.code));
});
}
function formatAmount(amount, currency) {
if (currency === 'JPY') {
return amount.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 });
}
return amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
async function detectUserCountry() {
try {
const response = await fetch('https://ipapi.co/json/', { timeout: 5000 });
if (response.ok) {
const data = await response.json();
return data.country_code;
}
} catch (e) {
console.log('IP detection failed, using timezone fallback');
}
try {
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const country = timeZone.split('/')[0];
if (country === 'Australia') return 'AU';
if (country === 'America' || country === 'US') return 'US';
if (country === 'Canada') return 'CA';
if (country === 'Europe' || country === 'London') return 'GB';
if (country === 'Japan' || country === 'Tokyo') return 'JP';
if (country === 'China' || country === 'Shanghai') return 'CN';
} catch (e) {
console.log('Timezone detection failed');
}
return null;
}
function getCurrencyFromCountry(countryCode) {
const mapping = {
'AU': 'AUD', 'US': 'USD', 'CA': 'CAD', 'GB': 'GBP',
'EU': 'EUR', 'JP': 'JPY', 'CN': 'CNY'
};
return mapping[countryCode] || null;
}
function loadFromURL() {
const params = new URLSearchParams(window.location.search);
const amount = params.get('amount');
const from = params.get('from');
const to = params.get('to');
if (amount && !isNaN(parseFloat(amount))) {
amountInput.value = amount;
}
if (from && currencies.find(c => c.code === from)) {
fromSelect.value = from;
}
if (to && currencies.find(c => c.code === to)) {
toSelect.value = to;
}
return !!(amount && from && to);
}
function loadFromStorage() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
const data = JSON.parse(stored);
if (data.amount) amountInput.value = data.amount;
if (data.from && currencies.find(c => c.code === data.from)) fromSelect.value = data.from;
if (data.to && currencies.find(c => c.code === data.to)) toSelect.value = data.to;
return true;
}
} catch (e) {
console.log('Error loading from storage');
}
return false;
}
function saveToStorage() {
try {
const data = {
amount: amountInput.value,
from: fromSelect.value,
to: toSelect.value,
timestamp: Date.now()
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) {
console.log('Error saving to storage');
}
}
async function initializeDefaults() {
populateSelects();
const loadedFromURL = loadFromURL();
if (!loadedFromURL) {
const loadedFromStorage = loadFromStorage();
if (!loadedFromStorage) {
const country = await detectUserCountry();
const defaultCurrency = getCurrencyFromCountry(country);
if (defaultCurrency && currencies.find(c => c.code === defaultCurrency)) {
fromSelect.value = defaultCurrency;
toSelect.value = defaultCurrency === 'USD' ? 'EUR' : 'USD';
} else {
fromSelect.value = 'USD';
toSelect.value = 'EUR';
}
}
}
}
async function fetchRates(base) {
const cache = localStorage.getItem(CACHE_KEY);
const cacheTime = localStorage.getItem(CACHE_TIME_KEY);
const now = Date.now();
if (cache && cacheTime && (now - parseInt(cacheTime)) < CACHE_DURATION) {
const parsed = JSON.parse(cache);
if (parsed.base === base) {
return parsed;
}
}
const response = await fetch(`https://api.exchangerate-api.com/v4/latest/${base}`);
if (!response.ok) throw new Error('Failed to fetch exchange rates');
const data = await response.json();
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
localStorage.setItem(CACHE_TIME_KEY, now.toString());
return data;
}
function showError(message) {
errorDiv.textContent = message;
errorDiv.style.display = 'block';
resultDiv.style.display = 'none';
setTimeout(() => { errorDiv.style.display = 'none'; }, 5000);
}
function showSuccess(message) {
successDiv.textContent = message;
successDiv.style.display = 'block';
setTimeout(() => { successDiv.style.display = 'none'; }, 3000);
}
function hideError() {
errorDiv.style.display = 'none';
}
function updateTimestamp(dateStr) {
if (!dateStr) {
lastUpdatedSpan.textContent = 'Rates loaded';
return;
}
const date = new Date(dateStr);
lastUpdatedSpan.textContent = `Last updated: ${date.toLocaleString()}`;
}
function displayResult(amount, from, converted, to, rate) {
resultAmount.textContent = `${formatAmount(converted, to)} ${to}`;
resultRate.innerHTML = `${formatAmount(amount, from)} ${from} = ${formatAmount(converted, to)} ${to}<br>1 ${from} = ${formatAmount(rate, to)} ${to}`;
resultDiv.style.display = 'block';
}
async function convert() {
const amount = parseFloat(amountInput.value);
const from = fromSelect.value;
const to = toSelect.value;
if (isNaN(amount) || amount < 0) {
showError('Please enter a valid amount');
return;
}
hideError();
if (from === to) {
displayResult(amount, from, amount, to, 1);
updateTimestamp(new Date());
saveToStorage();
return;
}
try {
const data = await fetchRates(from);
if (!data.rates || !(to in data.rates)) {
throw new Error('Exchange rate not available for selected currency pair');
}
const rate = data.rates[to];
const converted = amount * rate;
displayResult(amount, from, converted, to, rate);
updateTimestamp(data.date);
saveToStorage();
} catch (error) {
showError('Unable to fetch exchange rates. Please check your connection and try again.');
}
}
function swapCurrencies() {
const temp = fromSelect.value;
fromSelect.value = toSelect.value;
toSelect.value = temp;
convert();
}
function generateShareLink() {
const params = new URLSearchParams({
amount: amountInput.value,
from: fromSelect.value,
to: toSelect.value
});
const url = `${window.location.origin}${window.location.pathname}?${params.toString()}`;
navigator.clipboard.writeText(url).then(() => {
showSuccess('Link copied to clipboard!');
}).catch(() => {
showError('Failed to copy link. URL is: ' + url);
});
}
amountInput.addEventListener('input', convert);
fromSelect.addEventListener('change', convert);
toSelect.addEventListener('change', convert);
swapBtn.addEventListener('click', swapCurrencies);
shareBtn.addEventListener('click', generateShareLink);
document.addEventListener('DOMContentLoaded', async () => {
await initializeDefaults();
convert();
});
</script>
</body>
</html>