Function bodies 19 total
countryName function · javascript · L84-L86 (3 LOC)app.js
function countryName(code) {
return COUNTRY_NAMES[code] || code;
}loadCache function · javascript · L126-L136 (11 LOC)app.js
function loadCache(ignoreExpiry) {
try {
const raw = localStorage.getItem(CACHE_KEY);
if (!raw) return null;
const data = JSON.parse(raw);
if (!ignoreExpiry && Date.now() - data.timestamp > CACHE_DURATION) return null;
return data;
} catch {
return null;
}
}saveCache function · javascript · L138-L145 (8 LOC)app.js
function saveCache(ratesObj) {
try {
localStorage.setItem(CACHE_KEY, JSON.stringify({
rates: ratesObj,
timestamp: Date.now(),
}));
} catch { /* quota exceeded — ignore */ }
}buildAvailable function · javascript · L147-L162 (16 LOC)app.js
function buildAvailable() {
availableCurrencies = Object.keys(ALL_CURRENCIES).filter(
(c) => c === 'EUR' || rates[c] !== undefined,
);
// Keep only available currencies in selection
selectedCurrencies = selectedCurrencies.filter(
(c) => c !== 'EUR' && availableCurrencies.includes(c),
);
// Fill to 10 if needed
for (const c of availableCurrencies) {
if (selectedCurrencies.length >= 10) break;
if (c !== 'EUR' && !selectedCurrencies.includes(c)) {
selectedCurrencies.push(c);
}
}
}fetchRates function · javascript · L164-L192 (29 LOC)app.js
async function fetchRates() {
const cached = loadCache(false);
if (cached) {
rates = cached.rates;
rates.EUR = 1;
buildAvailable();
return;
}
try {
const res = await fetch('https://api.frankfurter.app/latest?from=EUR');
if (!res.ok) throw new Error('API ' + res.status);
const data = await res.json();
rates = data.rates;
rates.EUR = 1;
saveCache(rates);
buildAvailable();
} catch (err) {
// Fall back to stale cache
const stale = loadCache(true);
if (stale) {
rates = stale.rates;
rates.EUR = 1;
buildAvailable();
} else {
throw err;
}
}
}formatRate function · javascript · L195-L201 (7 LOC)app.js
function formatRate(value) {
if (!Number.isFinite(value)) return '—';
if (value >= 1000) return value.toLocaleString('bg-BG', { maximumFractionDigits: 0 });
if (value >= 100) return value.toLocaleString('bg-BG', { maximumFractionDigits: 1 });
if (value >= 1) return value.toLocaleString('bg-BG', { maximumFractionDigits: 2 });
return value.toLocaleString('bg-BG', { maximumFractionDigits: 4 });
}convert function · javascript · L203-L207 (5 LOC)app.js
function convert(amount, from, to) {
const f = from === 'EUR' ? 1 : (rates[from] || 1);
const t = to === 'EUR' ? 1 : (rates[to] || 1);
return amount * t / f;
}Repobility · code-quality intelligence · https://repobility.com
renderGrid function · javascript · L210-L233 (24 LOC)app.js
function renderGrid() {
const grid = $('currency-grid');
grid.innerHTML = '';
selectedCurrencies.forEach((code, i) => {
const cur = ALL_CURRENCIES[code];
if (!cur) return;
const card = document.createElement('div');
card.className = 'currency-card';
card.style.borderTopColor = CARD_COLORS[i % CARD_COLORS.length];
card.style.animationDelay = i * 0.05 + 's';
card.innerHTML =
'<span class="flag">' + cur.flag + '</span>' +
'<div class="code">' + countryName(code) + '</div>' +
'<div class="name">' + cur.name + '</div>' +
'<div class="rate">' + formatRate(rates[code]) + '</div>';
card.addEventListener('click', () => {
converterTo = code;
$('to-currency').value = code;
updateConverter();
$('converter-section').scrollIntoView({ behavior: 'smooth' });
});
grid.appendChild(card);
});
}populateConverterSelects function · javascript · L236-L250 (15 LOC)app.js
function populateConverterSelects() {
const sorted = ['EUR'].concat(
availableCurrencies.filter((c) => c !== 'EUR'),
);
const html = sorted.map((code) => {
const cur = ALL_CURRENCIES[code];
const label = cur ? cur.flag + ' ' + countryName(code) + ' \u2014 ' + cur.name : code;
return '<option value="' + code + '">' + label + '</option>';
}).join('');
$('from-currency').innerHTML = html;
$('to-currency').innerHTML = html;
$('from-currency').value = converterFrom;
$('to-currency').value = converterTo;
}updateConverter function · javascript · L252-L262 (11 LOC)app.js
function updateConverter() {
const amount = parseFloat($('from-amount').value) || 0;
converterFrom = $('from-currency').value;
converterTo = $('to-currency').value;
const result = convert(amount, converterFrom, converterTo);
const el = $('to-result');
el.textContent = formatRate(result);
el.classList.remove('pop-in');
void el.offsetWidth; // reflow to restart animation
el.classList.add('pop-in');
}renderItems function · javascript · L265-L295 (31 LOC)app.js
function renderItems() {
const grid = $('items-grid');
grid.innerHTML = '';
ITEMS.forEach((item, i) => {
const card = document.createElement('div');
card.className = 'item-card';
card.style.animationDelay = i * 0.04 + 's';
let pricesHtml = '';
selectedCurrencies.forEach((code) => {
const cur = ALL_CURRENCIES[code];
if (!cur) return;
const price = convert(item.price, 'EUR', code);
pricesHtml +=
'<div class="item-price">' +
'<span class="flag">' + cur.flag + '</span>' +
'<span class="val">' + formatRate(price) + '</span>' +
'<span class="cur">' + countryName(code) + '</span>' +
'</div>';
});
card.innerHTML =
'<div class="item-header">' +
'<span class="item-emoji">' + item.emoji + '</span>' +
'<span class="item-name">' + item.name + '</span>' +
'<span class="item-eur-price">' + item.price.toFixed(2) + ' \u20AC</span>' +
'</div>' +
'<div class=openModal function · javascript · L298-L370 (73 LOC)app.js
function openModal() {
const modal = $('modal');
const list = $('currency-list');
const tempSelected = new Set(selectedCurrencies);
const regionOrder = ['Европа', 'Америка', 'Азия и Океания', 'Африка и Близък изток'];
function renderList() {
list.innerHTML = '';
// Group available (non-EUR) currencies by region
const groups = {};
availableCurrencies.forEach((code) => {
if (code === 'EUR') return;
const cur = ALL_CURRENCIES[code];
const region = cur ? cur.region : 'Други';
if (!groups[region]) groups[region] = [];
groups[region].push(code);
});
regionOrder.forEach((region) => {
const codes = groups[region];
if (!codes || codes.length === 0) return;
const section = document.createElement('div');
section.className = 'region-group';
section.innerHTML = '<h3>' + region + '</h3>';
codes.forEach((code) => {
const cur = ALL_CURRENCIES[code];
const checked = tempSelected.hasrenderList function · javascript · L305-L352 (48 LOC)app.js
function renderList() {
list.innerHTML = '';
// Group available (non-EUR) currencies by region
const groups = {};
availableCurrencies.forEach((code) => {
if (code === 'EUR') return;
const cur = ALL_CURRENCIES[code];
const region = cur ? cur.region : 'Други';
if (!groups[region]) groups[region] = [];
groups[region].push(code);
});
regionOrder.forEach((region) => {
const codes = groups[region];
if (!codes || codes.length === 0) return;
const section = document.createElement('div');
section.className = 'region-group';
section.innerHTML = '<h3>' + region + '</h3>';
codes.forEach((code) => {
const cur = ALL_CURRENCIES[code];
const checked = tempSelected.has(code);
const disabled = !checked && tempSelected.size >= 10;
const row = document.createElement('div');
row.className = 'cur-option' + (disabled ? ' disabled' : '');
row.innerHTML =
'<saveSelection function · javascript · L373-L377 (5 LOC)app.js
function saveSelection() {
try {
localStorage.setItem(SELECTION_KEY, JSON.stringify(selectedCurrencies));
} catch { /* ignore */ }
}loadSelection function · javascript · L379-L386 (8 LOC)app.js
function loadSelection() {
try {
const raw = localStorage.getItem(SELECTION_KEY);
if (!raw) return;
const arr = JSON.parse(raw);
if (Array.isArray(arr) && arr.length > 0) selectedCurrencies = arr;
} catch { /* ignore */ }
}Repobility · severity-and-effort ranking · https://repobility.com
setupEvents function · javascript · L389-L403 (15 LOC)app.js
function setupEvents() {
$('customize-btn').addEventListener('click', openModal);
$('refresh-btn').addEventListener('click', refreshRates);
$('from-currency').addEventListener('change', updateConverter);
$('to-currency').addEventListener('change', updateConverter);
$('from-amount').addEventListener('input', updateConverter);
$('swap-btn').addEventListener('click', () => {
const tmp = $('from-currency').value;
$('from-currency').value = $('to-currency').value;
$('to-currency').value = tmp;
updateConverter();
});
}refreshRates function · javascript · L406-L426 (21 LOC)app.js
async function refreshRates() {
const btn = $('refresh-btn');
btn.disabled = true;
btn.textContent = '...';
try {
const res = await fetch('https://api.frankfurter.app/latest?from=EUR');
if (!res.ok) throw new Error('API ' + res.status);
const data = await res.json();
rates = data.rates;
rates.EUR = 1;
saveCache(rates);
buildAvailable();
renderGrid();
updateConverter();
renderItems();
$('last-updated').textContent =
'Последно обновяване: ' + new Date().toLocaleString('bg-BG');
} catch { /* silent */ }
btn.disabled = false;
btn.textContent = 'Обнови';
}startAutoRefresh function · javascript · L429-L446 (18 LOC)app.js
function startAutoRefresh() {
setInterval(async () => {
try {
const res = await fetch('https://api.frankfurter.app/latest?from=EUR');
if (!res.ok) return;
const data = await res.json();
rates = data.rates;
rates.EUR = 1;
saveCache(rates);
buildAvailable();
renderGrid();
updateConverter();
renderItems();
$('last-updated').textContent =
'Последно обновяване: ' + new Date().toLocaleString('bg-BG');
} catch { /* silent */ }
}, CACHE_DURATION);
}init function · javascript · L449-L479 (31 LOC)app.js
async function init() {
loadSelection();
try {
await fetchRates();
} catch {
$('loader').innerHTML =
'<p style="color:#FF6B6B;font-size:1.2rem;padding:2rem;text-align:center">' +
'Грешка при зареждане. Моля, опитай отново.' +
'</p>';
return;
}
populateConverterSelects();
renderGrid();
updateConverter();
renderItems();
setupEvents();
startAutoRefresh();
// Show last-updated timestamp
const cached = loadCache(true);
if (cached) {
$('last-updated').textContent =
'Последно обновяване: ' + new Date(cached.timestamp).toLocaleString('bg-BG');
}
// Hide loader, show app
$('loader').classList.add('hidden');
$('app').classList.remove('hidden');
}