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()] || 'β€”';
 
   ...