Country Detect
by sbhomra
HTML
<div class="container">
<header>
<h1><span class="globe">π</span> Country Detect</h1>
<p>Three ways to find a user's country β no location prompt required</p>
</header>
<!-- Method 1: IP Geolocation -->
<div class="card" id="card-ip">
<div class="card-header">
<span class="method-name">β IP Geolocation API</span>
<span class="badge net">network</span>
</div>
<div class="result-grid" id="ip-results">
<div class="result-item">
<div class="result-label">Country</div>
<div class="result-value big"><span class="skeleton"></span></div>
</div>
<div class="result-item">
<div class="result-label">Code</div>
<div class="result-value big"><span class="skeleton"></span></div>
</div>
<div class="result-item">
<div class="result-label">Region</div>
<div class="result-value"><span class="skeleton"></span></div>
</div>
<div class="result-item">
<div class="result-label">City</div>
<div class="result-value"><span class="skeleton"></span></div>
</div>
<div class="result-item full">
<div class="result-label">IP Address</div>
<div class="result-value"><span class="skeleton wide"></span></div>
</div>
</div>
</div>
<!-- Method 2: Intl API -->
<div class="card" id="card-intl">
<div class="card-header">
<span class="method-name">β‘ Intl / Timezone API</span>
<span class="badge local">local</span>
</div>
<div class="result-grid" id="intl-results">
<div class="result-item full">
<div class="result-label">Timezone</div>
<div class="result-value big"><span class="skeleton wide"></span></div>
</div>
<div class="result-item">
<div class="result-label">Locale</div>
<div class="result-value"><span class="skeleton"></span></div>
</div>
<div class="result-item">
...
CSS
:root {
--bg: #0c0c0f;
--surface: #16161a;
--border: #2a2a32;
--text: #e8e6e1;
--muted: #7a7880;
--accent: #c4f06a;
--accent-dim: rgba(196, 240, 106, 0.08);
--error: #f06a6a;
--radius: 14px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--text);
font-family: 'DM Mono', monospace;
min-height: 100vh;
padding: 40px 20px;
line-height: 1.6;
}
.container {
max-width: 720px;
margin: 0 auto;
}
header {
margin-bottom: 56px;
text-align: center;
}
h1 {
font-family: 'Instrument Serif', serif;
font-weight: 400;
font-size: 3rem;
letter-spacing: -0.02em;
margin-bottom: 8px;
}
h1 .globe { display: inline-block; animation: spin 8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
header p {
color: var(--muted);
font-size: 0.8rem;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 28px 28px 24px;
margin-bottom: 20px;
position: relative;
overflow: hidden;
animation: fadeUp 0.5s ease both;
}
.card:nth-child(1) { animation-delay: 0.1s; }
.card:nth-child(2) { animation-delay: 0.25s; }
.card:nth-child(3) { animation-delay: 0.4s; }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
opacity: 0;
transition: opacity 0.4s;
}
.card.loaded::before { opacity: 1; }
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.method-name {
...
JavaScript
function fill(containerId, data) {
const card = document.getElementById(containerId);
const grid = document.getElementById(containerId.replace('card-', '') + '-results');
const items = grid.querySelectorAll('.result-item');
data.forEach((val, i) => {
if (items[i]) {
const el = items[i].querySelector('.result-value');
el.textContent = val ?? 'β';
}
});
card.classList.add('loaded');
}
// β IP Geolocation
fetch('https://ipapi.co/json/')
.then(r => r.json())
.then(d => {
fill('card-ip', [
d.country_name,
d.country_code,
d.region,
d.city,
d.ip
]);
})
.catch(() => {
fill('card-ip', ['Error', 'β', 'β', 'β', 'Could not reach API']);
});
// β‘ Intl / Timezone
(function () {
const opts = Intl.DateTimeFormat().resolvedOptions();
const timezone = opts.timeZone;
const locale = navigator.language;
const parts = locale.split('-');
const countryHint = parts[1] || '(none)';
let currency = 'β';
try {
const sample = new Intl.NumberFormat(locale, { style: 'currency', currency: 'USD' });
// Detect the locale's own default currency via a known mapping trick
const resolved = new Intl.NumberFormat(locale, { style: 'currency', currencyDisplay: 'code', currency: 'USD' }).resolvedOptions();
currency = resolved.currency || 'β';
} catch (e) { /* ignore */ }
// Try to get the actual default currency for the locale
const localeCurrencyMap = {
US: 'USD', GB: 'GBP', EU: 'EUR', DE: 'EUR', FR: 'EUR', JP: 'JPY',
CN: 'CNY', IN: 'INR', BR: 'BRL', CA: 'CAD', AU: 'AUD', KR: 'KRW',
MX: 'MXN', RU: 'RUB', ZA: 'ZAR', SE: 'SEK', NO: 'NOK', DK: 'DKK',
CH: 'CHF', NZ: 'NZD', SG: 'SGD', HK: 'HKD', TW: 'TWD', PL: 'PLN',
};
const guessedCurrency = localeCurrencyMap[countryHint.toUpperCase()] || 'β';
...