*<!DOCTYPE html>
:root { --bg: #020508; --surface: #060d14; --accent: #00ffe0; --accent2: #0077ff; --accent3: #ff4d6d; --text: #c8e8ff; --muted: #3a5570; --glow: 0 0 30px rgba(0,255,224,0.25); } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body { background: var(--bg); color: var(--text); font-family: 'Space Mono', monospace; overflow-x: hidden; } .bg-layer { position: fixed; inset: 0; z-index: 0; pointer-events: none; background: radial-gradient(ellipse 80% 60% at 20% 10%, rgba(0,119,255,0.07) 0%, transparent 70%), radial-gradient(ellipse 60% 50% at 80% 80%, rgba(0,255,224,0.06) 0%, transparent 70%); } .grid-overlay { position: fixed; inset: 0; z-index: 0; pointer-events: none; background-image: linear-gradient(rgba(0,255,224,0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(0,255,224,0.04) 1px, transparent 1px); background-size: 48px 48px; animation: gridDrift 30s linear infinite; } @keyframes gridDrift { to { background-position: 48px 48px; } } .shell { position: relative; z-index: 2; } nav { display: flex; align-items: center; justify-content: space-between; padding: 18px 40px; border-bottom: 1px solid rgba(0,255,224,0.1); backdrop-filter: blur(12px); position: sticky; top: 0; z-index: 100; background: rgba(2,5,8,0.85); } .logo { font-family: 'Bebas Neue'; font-size: 1.9rem; letter-spacing: 0.25em; color: var(--accent); text-shadow: 0 0 24px rgba(0,255,224,0.5); position: relative; } .logo::after { content: '●'; font-size: 0.35em; color: var(--accent3); position: absolute; top: 4px; right: -14px; animation: blink 1.4s step-end infinite; } @keyframes blink { 50% { opacity: 0; } } .nav-links { display: flex; gap: 28px; list-style: none; } .nav-links a { color: var(--muted); text-decoration: none; font-size: 0.7rem; letter-spacing: 0.15em; text-transform: uppercase; transition: color 0.2s; } .nav-links a:hover { color: var(--accent); } .status-pill { display: flex; align-items: center; gap: 8px; font-size: 0.65rem; color: var(--accent); letter-spacing: 0.12em; } .pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 8px var(--accent); animation: blink 1.4s step-end infinite; } /* HERO */ .hero { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: center; padding: 80px 40px 60px; max-width: 1300px; margin: 0 auto; } .hero-tag { font-size: 0.65rem; letter-spacing: 0.25em; text-transform: uppercase; color: var(--accent2); margin-bottom: 14px; display: flex; align-items: center; gap: 10px; } .hero-tag::before { content: ''; width: 30px; height: 1px; background: var(--accent2); } h1 { font-family: 'Bebas Neue'; font-size: clamp(3.5rem, 7vw, 6.5rem); line-height: 0.92; letter-spacing: 0.05em; color: #fff; margin-bottom: 22px; } h1 .t { color: var(--accent); text-shadow: 0 0 25px rgba(0,255,224,0.4); } h1 .r { color: var(--accent3); } .hero-desc { font-size: 0.82rem; line-height: 1.9; color: var(--muted); max-width: 420px; margin-bottom: 36px; } .hero-actions { display: flex; gap: 14px; flex-wrap: wrap; } .btn { border: none; padding: 13px 28px; font-family: 'Space Mono'; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; cursor: pointer; transition: all 0.25s; } .btn-primary { background: var(--accent); color: #000; clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 0 100%); } .btn-primary:hover { box-shadow: var(--glow); transform: translateY(-2px); } .btn-secondary { background: transparent; color: var(--text); border: 1px solid var(--muted); } .btn-secondary:hover { border-color: var(--accent); color: var(--accent); } /* GLOBE */ .globe-wrap { position: relative; display: flex; align-items: center; justify-content: center; height: 440px; } .globe { width: 320px; height: 320px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, rgba(0,119,255,0.25), transparent 60%), radial-gradient(circle at 50% 50%, #020d1a 40%, #010608 100%); border: 1px solid rgba(0,255,224,0.15); box-shadow: 0 0 80px rgba(0,119,255,0.2), inset 0 0 60px rgba(0,119,255,0.1); animation: globePulse 6s ease-in-out infinite; } @keyframes globePulse { 50% { box-shadow: 0 0 120px rgba(0,119,255,0.35), inset 0 0 80px rgba(0,119,255,0.15); } } .orbit { position: absolute; border-radius: 50%; border: 1px solid rgba(0,255,224,0.12); } .orbit-1 { width: 400px; height: 400px; animation: spin 12s linear infinite; } .orbit-2 { width: 480px; height: 480px; border-color: rgba(0,119,255,0.08); animation: spin 20s linear infinite reverse; } @keyframes spin { to { transform: rotate(360deg); } } .node { position: absolute; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 12px var(--accent); top: -4px; left: 50%; transform: translateX(-50%); } .orbit-2 .node { background: var(--accent2); box-shadow: 0 0 12px var(--accent2); } .signal { position: absolute; top: 50%; left: 50%; height: 1px; background: linear-gradient(90deg, rgba(0,255,224,0.5), transparent); transform-origin: left; animation: signalPulse 3s ease-in-out infinite; } @keyframes signalPulse { 0%{opacity:0;width:0} 40%{opacity:1} 100%{opacity:0;width:180px} } .s1{transform:rotate(35deg)} .s2{transform:rotate(135deg);animation-delay:.8s} .s3{transform:rotate(220deg);animation-delay:1.6s} .s4{transform:rotate(310deg);animation-delay:2.4s} .dbadge { position: absolute; background: rgba(6,13,20,0.9); border: 1px solid rgba(0,255,224,0.2); padding: 7px 12px; font-size: 0.6rem; color: var(--accent); letter-spacing: 0.1em; backdrop-filter: blur(8px); animation: floatUp 4s ease-in-out infinite; } .dbadge.b1{top:60px;left:10px} .dbadge.b2{bottom:80px;right:0;animation-delay:1.5s;color:var(--accent2);border-color:rgba(0,119,255,.3)} .dbadge.b3{top:180px;right:-10px;animation-delay:.8s;color:var(--accent3);border-color:rgba(255,77,109,.3)} @keyframes floatUp { 50%{transform:translateY(-8px)} } /* TICKER */ .ticker-wrap { overflow: hidden; padding: 11px 0; border-top: 1px solid rgba(0,255,224,0.07); border-bottom: 1px solid rgba(0,255,224,0.07); background: rgba(0,255,224,0.02); } .ticker-track { display: flex; gap: 60px; white-space: nowrap; animation: tickerScroll 30s linear infinite; } @keyframes tickerScroll { to { transform: translateX(-50%); } } .ticker-item { display: flex; align-items: center; gap: 10px; font-size: 0.65rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(--muted); } .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); } /* STATS */ .stats-row { max-width: 1300px; margin: 60px auto; padding: 0 40px; display: grid; grid-template-columns: repeat(4,1fr); gap: 2px; } .stat-card { background: var(--surface); border: 1px solid rgba(0,255,224,0.08); padding: 30px 26px; position: relative; overflow: hidden; transition: border-color 0.3s; cursor: default; } .stat-card:hover { border-color: rgba(0,255,224,0.22); } .stat-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.3s; } .stat-card:hover::before { opacity:1; } .stat-num { font-family:'Bebas Neue'; font-size:3rem; color:var(--accent); line-height:1; margin-bottom:4px; } .stat-num.b { color:var(--accent2); } .stat-num.r { color:var(--accent3); } .stat-label { font-size:0.6rem; color:var(--muted); letter-spacing:0.18em; text-transform:uppercase; } /* SECTION */ .section { max-width:1300px; margin:0 auto; padding:50px 40px; } .section-label { font-size:0.6rem; color:var(--accent); letter-spacing:0.3em; text-transform:uppercase; margin-bottom:8px; } .section-title { font-family:'Bebas Neue'; font-size:2.8rem; color:#fff; letter-spacing:0.05em; margin-bottom:36px; } /* REGISTER FORM */ .reg-panel { background:var(--surface); border:1px solid rgba(0,255,224,0.12); padding:36px; max-width:560px; } .field-group { display:flex; flex-direction:column; gap:10px; margin-bottom:16px; } .field-label { font-size:0.6rem; color:var(--muted); letter-spacing:0.2em; text-transform:uppercase; } .field-input { background:rgba(0,255,224,0.04); border:1px solid rgba(0,255,224,0.15); color:var(--text); padding:12px 16px; font-family:'Space Mono'; font-size:0.75rem; outline:none; width:100%; transition:border-color 0.2s; } .field-input:focus { border-color:var(--accent); } .field-input::placeholder { color:var(--muted); } select.field-input option { background:#020508; } .reg-result { margin-top:20px; background:rgba(0,255,224,0.04); border:1px solid rgba(0,255,224,0.2); padding:20px; font-size:0.7rem; line-height:2; display:none; } .reg-result.show { display:block; } .reg-result .key { color:var(--muted); } .reg-result .val { color:var(--accent); } /* NODES TABLE */ .nodes-table { width:100%; border-collapse:collapse; font-size:0.68rem; } .nodes-table th { color:var(--muted); letter-spacing:0.15em; text-transform:uppercase; font-size:0.58rem; padding:10px 14px; text-align:left; border-bottom:1px solid rgba(0,255,224,0.08); } .nodes-table td { padding:11px 14px; border-bottom:1px solid rgba(255,255,255,0.03); color:var(--text); } .nodes-table tr:hover td { background:rgba(0,255,224,0.02); } .status-badge { display:inline-block; padding:2px 8px; font-size:0.55rem; letter-spacing:0.12em; text-transform:uppercase; border-radius:2px; } .status-online { background:rgba(0,255,224,0.1); color:var(--accent); border:1px solid rgba(0,255,224,0.2); } .load-bar { width:80px; height:5px; background:rgba(0,255,224,0.08); border-radius:2px; overflow:hidden; display:inline-block; vertical-align:middle; margin-left:8px; } .load-fill { height:100%; border-radius:2px; background:linear-gradient(90deg,var(--accent2),var(--accent)); } .load-fill.high { background:linear-gradient(90deg,var(--accent3),#ff9a6c); } /* ROUTE TESTER */ .route-panel { display:grid; grid-template-columns:1fr 1fr; gap:2px; } .panel-box { background:var(--surface); border:1px solid rgba(0,255,224,0.08); padding:30px; } .panel-title { font-family:'Syne'; font-weight:800; font-size:0.85rem; color:#fff; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:20px; } .path-display { font-size:0.7rem; line-height:2.2; color:var(--muted); } .path-hop { display:flex; align-items:center; gap:10px; margin-bottom:4px; } .hop-city { color:var(--text); } .hop-arrow { color:var(--accent); } .info-row { display:flex; justify-content:space-between; padding:8px 0; border-bottom:1px solid rgba(255,255,255,0.03); font-size:0.68rem; } .info-key { color:var(--muted); } .info-val { color:var(--accent); } /* LOG FEED */ .log-feed { background:var(--surface); border:1px solid rgba(0,255,224,0.08); padding:24px; } .log-entry { display:flex; gap:14px; padding:9px 0; border-bottom:1px solid rgba(255,255,255,0.03); animation:fadeIn 0.4s ease; } @keyframes fadeIn { from{opacity:0;transform:translateX(-8px)} to{opacity:1;transform:none} } .ldot { width:6px; height:6px; border-radius:50%; flex-shrink:0; margin-top:5px; } .lg { background:var(--accent); box-shadow:0 0 8px var(--accent); } .lb { background:var(--accent2); box-shadow:0 0 8px var(--accent2); } .lr { background:var(--accent3); box-shadow:0 0 8px var(--accent3); } .ltext { font-size:0.65rem; line-height:1.7; color:var(--muted); } .ltext strong { color:var(--text); font-weight:400; } .ltime { font-size:0.57rem; color:rgba(58,85,112,0.6); display:block; margin-top:1px; } /* FOOTER */ footer { border-top:1px solid rgba(0,255,224,0.06); padding:36px 40px; display:flex; align-items:center; justify-content:space-between; max-width:1300px; margin:0 auto; } .footer-logo { font-family:'Bebas Neue'; font-size:1.3rem; color:var(--muted); letter-spacing:0.25em; } .footer-copy { font-size:0.58rem; color:rgba(58,85,112,0.5); letter-spacing:0.1em; } .footer-status { display:flex; align-items:center; gap:8px; font-size:0.6rem; color:var(--accent); letter-spacing:0.15em; } @media(max-width:900px){ .hero{grid-template-columns:1fr} .stats-row{grid-template-columns:repeat(2,1fr)} .route-panel{grid-template-columns:1fr} nav{padding:14px 20px} .nav-links{display:none} }
AIRTIMES
CONNECTING...
Global Data Mesh — v3.1.0
<h1>FREE<br><span>DATA</span> &<br><span>SERVICE</span><br>NETWORK</h1>
<p>AIRTIMES is a decentralized global infrastructure delivering free data, connectivity, and digital services to every node on Earth. No paywalls. Universal access.</p>
GET FREE ACCESS
VIEW NODES
▲ -- TB/s THROUGHPUT
◉ -- ACTIVE NODES
✦ 99.98% UPTIME
<span></span>AIRDATA FREE TIER ACTIVE
<span></span>NAIROBI NODE — ONLINE
<span></span>2.1M USERS CONNECTED
<span></span>SAO PAULO RELAY — OPTIMAL
<span></span>ZERO-COST SMS ROUTING ENABLED
<span></span>BANDWIDTH POOL REPLENISHED
<span></span>JAKARTA HUB — 340 Gbps
<span></span>AIRTIMES PROTOCOL v3.1 LIVE
<span></span>AIRDATA FREE TIER ACTIVE
<span></span>NAIROBI NODE — ONLINE
<span></span>2.1M USERS CONNECTED
<span></span>SAO PAULO RELAY — OPTIMAL
<span></span>ZERO-COST SMS ROUTING ENABLED
<span></span>BANDWIDTH POOL REPLENISHED
<span></span>JAKARTA HUB — 340 Gbps
<span></span>AIRTIMES PROTOCOL v3.1 LIVE
—Active Nodes Worldwide
—Daily Data Delivered (TB)
—Users Served — Free Tier
—Countries Covered
// Activate Free Access
JOIN THE NETWORK
Phone or Email
Identifier Type
Email
Phone
ACTIVATE FREE ACCESS →
// Live Infrastructure
GLOBAL NODES
| Node ID | City | Country | Operator | Capacity (Gbps) | Load | Status |
|---|---|---|---|---|---|---|
| Loading nodes... | ||||||
// Routing Engine
TEST A ROUTE
⚡ Route Parameters
Your Token (from registration)
Destination
Service
AirDataAirSMSAirWeb
AirAPIAirVaultAirRelay
COMPUTE ROUTE →
📡 Route Path
Run a route test to see the mesh path...
// Live Event Log
Connecting to event stream...
// Free API Test
🌐 AirAPI Gateway
City (Weather)
GET FREE WEATHER →
Currency (Exchange)
GET RATE
<span>Results will appear here — all free, no key needed.</span>
AIRTIMES
© 2025 AIRTIMES GLOBAL NETWORK · ALL SERVICES FREE · FOREVER
ALL SYSTEMS OPERATIONAL
// ── CONFIG ──
const API = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
? 'http://localhost:3001'
: '/api-placeholder'; // Replace with your deployed backend URL
// ── HELPERS ──
function scrollTo(id) { document.getElementById(id)?.scrollIntoView({behavior:'smooth'}); }
function fmt(n) { return Number(n).toLocaleString(); }
function animateCount(el, target, suffix='', dur=2000) {
let v=0; const step=target/(dur/16);
const t=setInterval(()=>{
v+=step; if(v>=target){v=target;clearInterval(t);}
el.textContent=Math.floor(v).toLocaleString()+suffix;
},16);
}
// ── LOAD STATS ──
async function loadStats() {
try {
const r = await fetch(API+'/stats'); // try backend
const d = await r.json();
document.getElementById('net-status').textContent = 'ALL SYSTEMS OPERATIONAL';
animateCount(document.getElementById('s1'), d.activeNodes||142);
animateCount(document.getElementById('s2'), d.dataTB||4700, ' TB');
animateCount(document.getElementById('s3'), d.totalUsers||2100000);
animateCount(document.getElementById('s4'), d.countriesCovered||193);
document.getElementById('badge-throughput').textContent = ▲ ${d.throughputGbps||4700} Gbps THROUGHPUT;
document.getElementById('badge-nodes').textContent = ◉ ${d.activeNodes||142} ACTIVE NODES;
} catch {
// Fallback to demo data when backend not running
animateCount(document.getElementById('s1'), 142);
animateCount(document.getElementById('s2'), 4700, ' TB');
animateCount(document.getElementById('s3'), 2100000);
animateCount(document.getElementById('s4'), 193);
document.getElementById('net-status').textContent = 'DEMO MODE';
document.getElementById('badge-throughput').textContent = '▲ 4,700 Gbps THROUGHPUT';
document.getElementById('badge-nodes').textContent = '◉ 142 ACTIVE NODES';
}
}
// ── LOAD NODES ──
async function loadNodes() {
const tbody = document.getElementById('nodes-body');
try {
const r = await fetch(API+'/nodes');
const {nodes} = await r.json();
renderNodes(nodes);
} catch {
// Demo nodes
renderNodes([
{id:'NODE-US-NYC01',city:'New York',country:'US',operator:'AT&T Surplus',capacity_gbps:2100,load_percent:52,status:'online'},
{id:'NODE-GB-LDN01',city:'London',country:'GB',operator:'BT Wholesale',capacity_gbps:3400,load_percent:45,status:'online'},
{id:'NODE-KE-NBI01',city:'Nairobi',country:'KE',operator:'Safaricom',capacity_gbps:880,load_percent:78,status:'online'},
{id:'NODE-AE-DXB01',city:'Dubai',country:'AE',operator:'Etisalat',capacity_gbps:1200,load_percent:60,status:'online'},
{id:'NODE-ID-JKT01',city:'Jakarta',country:'ID',operator:'Telkom ID',capacity_gbps:340,load_percent:88,status:'online'},
{id:'NODE-JP-TYO01',city:'Tokyo',country:'JP',operator:'NTT Com',capacity_gbps:2900,load_percent:41,status:'online'},
{id:'NODE-AU-SYD01',city:'Sydney',country:'AU',operator:'Telstra',capacity_gbps:760,load_percent:33,status:'online'},
{id:'NODE-BR-GRU01',city:'São Paulo',country:'BR',operator:'Vivo',capacity_gbps:640,load_percent:55,status:'online'},
{id:'NODE-ZA-CPT01',city:'Cape Town',country:'ZA',operator:'MTN SA',capacity_gbps:420,load_percent:29,status:'online'},
{id:'NODE-IN-BOM01',city:'Mumbai',country:'IN',operator:'Airtel',capacity_gbps:980,load_percent:71,status:'online'},
{id:'NODE-SG-SIN01',city:'Singapore',country:'SG',operator:'Singtel',capacity_gbps:1800,load_percent:66,status:'online'},
{id:'NODE-NG-LOS01',city:'Lagos',country:'NG',operator:'MTN NG',capacity_gbps:320,load_percent:82,status:'online'},
{id:'NODE-DE-BER01',city:'Berlin',country:'DE',operator:'Deutsche Telekom',capacity_gbps:1600,load_percent:38,status:'online'},
{id:'NODE-CN-SHA01',city:'Shanghai',country:'CN',operator:'China Telecom',capacity_gbps:2200,load_percent:59,status:'online'},
{id:'NODE-MX-MEX01',city:'Mexico City',country:'MX',operator:'Telmex',capacity_gbps:560,load_percent:44,status:'online'},
]);
}
}
function renderNodes(nodes) {
const tbody = document.getElementById('nodes-body');
tbody.innerHTML = nodes.map(n => {
const high = n.load_percent > 80;
return <tr>;
<td style="color:var(--accent);font-size:0.62rem">${n.id}</td>
<td>${n.city}</td>
<td>${n.country}</td>
<td style="color:var(--muted)">${n.operator}</td>
<td>${fmt(n.capacity_gbps)}</td>
<td>
${n.load_percent}%
<span class="load-bar"><span class="load-fill ${high?'high':''}" style="width:${n.load_percent}%"></span></span>
</td>
<td><span class="status-badge status-online">ONLINE</span></td>
</tr>
}).join('');
}
// ── REGISTER ──
async function register() {
const id = document.getElementById('reg-id').value.trim();
const type = document.getElementById('reg-type').value;
const btn = document.getElementById('reg-btn');
const res = document.getElementById('reg-result');
if (!id) { alert('Please enter your phone or email.'); return; }
btn.textContent = 'ACTIVATING...'; btn.disabled = true;
try {
const r = await fetch(API+'/register', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({identifier:id, type})
});
const d = await r.json();
if (d.success) {
res.className = 'reg-result show';
res.innerHTML = ;
<div><span class="key">STATUS: </span><span class="val">✓ FREE ACCESS ACTIVATED</span></div>
<div><span class="key">USER ID: </span><span class="val">${d.userId}</span></div>
<div><span class="key">TOKEN: </span><span class="val" style="font-size:0.6rem;word-break:break-all">${d.token}</span></div>
<div><span class="key">NODE: </span><span class="val">${d.assignedNode?.city}, ${d.assignedNode?.country} (${d.assignedNode?.latencyMs}ms)</span></div>
<div><span class="key">TIER: </span><span class="val">FREE — FOREVER</span></div>
<div><span class="key">DAILY DATA: </span><span class="val">${fmt(d.dailyDataMB)} MB</span></div>
<div><span class="key">SERVICES: </span><span class="val">${d.services?.join(' · ')}</span></div>
<div style="margin-top:10px;color:var(--muted);font-size:0.6rem">Save your token — paste it in the Route Test to compute your mesh path.</div>
// auto-fill token
document.getElementById('rt-token').value = d.token;
} else {
res.className = 'reg-result show'; res.innerHTML = <span style="color:var(--accent3)">Error: ${d.error}</span>;
}
} catch {
// Demo mode
const fakeToken = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,c=>{const r=Math.random()*16|0;return(c==='x'?r:(r&0x3|0x8)).toString(16);});
res.className='reg-result show';
res.innerHTML=;
<div><span class="key">STATUS: </span><span class="val">✓ FREE ACCESS ACTIVATED (Demo Mode)</span></div>
<div><span class="key">IDENTIFIER: </span><span class="val">${id}</span></div>
<div><span class="key">TOKEN: </span><span class="val" style="font-size:0.6rem">${fakeToken}</span></div>
<div><span class="key">NODE: </span><span class="val">London, GB — 34ms</span></div>
<div><span class="key">TIER: </span><span class="val">FREE — FOREVER</span></div>
<div style="margin-top:10px;color:var(--muted);font-size:0.6rem">Deploy the backend to get a real token and live routing.</div>
document.getElementById('rt-token').value = fakeToken;
}
btn.textContent = 'ACTIVATE FREE ACCESS →'; btn.disabled = false;
}
// ── ROUTE TEST ──
async function testRoute() {
const token = document.getElementById('rt-token').value.trim();
const dest = document.getElementById('rt-dest').value.trim() || 'google.com';
const service = document.getElementById('rt-service').value;
const display = document.getElementById('path-display');
display.innerHTML = '<span style="color:var(--muted)">Computing route...</span>';
try {
const r = await fetch(API+'/route', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({token, destination:dest, service})
});
const d = await r.json();
renderRoute(d, dest, service);
} catch {
// Demo route
renderRoute({
path:[
{city:'Your Device',country:'--'},{city:'London',country:'GB'},
{city:'Dubai',country:'AE'},{city:dest,country:'🌐'}
],
hops:3, estimatedLatencyMs:112, totalDistanceKm:9420,
compressed:true, encrypted:true, protocol:'AIRTIMES/3.1', free:true
}, dest, service);
}
}
function renderRoute(d, dest, service) {
const display = document.getElementById('path-display');
const hops = d.path || [];
const pathHTML = hops.map((h,i) => <span style="color:var(--muted);font-size:0.58rem">[${h.load}% load]</span>
<div class="path-hop">
<span style="color:var(--accent);font-size:0.6rem">${String(i).padStart(2,'0')}</span>
<span class="hop-arrow">→</span>
<span class="hop-city">${h.city}${h.country&&h.country!=='--'?' ('+h.country+')':''}</span>
${h.load!==undefined?:''}).join('');
</div>
display.innerHTML = ;
${pathHTML}
<div style="margin-top:16px;border-top:1px solid rgba(0,255,224,0.08);padding-top:16px">
<div class="info-row"><span class="info-key">HOPS</span><span class="info-val">${d.hops}</span></div>
<div class="info-row"><span class="info-key">LATENCY</span><span class="info-val">${d.estimatedLatencyMs} ms</span></div>
<div class="info-row"><span class="info-key">DISTANCE</span><span class="info-val">${fmt(d.totalDistanceKm)} km</span></div>
<div class="info-row"><span class="info-key">PROTOCOL</span><span class="info-val">${d.protocol||'AIRTIMES/3.1'}</span></div>
<div class="info-row"><span class="info-key">COMPRESSED</span><span class="info-val">${d.compressed?'YES':'NO'}</span></div>
<div class="info-row"><span class="info-key">ENCRYPTED</span><span class="info-val">${d.encrypted?'YES':'NO'}</span></div>
<div class="info-row"><span class="info-key">COST</span><span class="info-val" style="color:#00ffe0">FREE</span></div>
</div>
}
// ── FREE API ──
async function testWeather() {
const city = document.getElementById('api-city').value.trim() || 'Lagos';
const res = document.getElementById('api-result');
res.innerHTML = '<span style="color:var(--muted)">Fetching...</span>';
try {
const r = await fetch(${API}/airapi/weather?city=${encodeURIComponent(city)});
const d = await r.json();
res.innerHTML = ;
<div><span class="key">CITY: </span><span class="val">${city}</span></div>
<div><span class="key">TEMP: </span><span class="val">${d.data.temp_c}°C</span></div>
<div><span class="key">CONDITION: </span><span class="val">${d.data.condition}</span></div>
<div><span class="key">HUMIDITY: </span><span class="val">${d.data.humidity}%</span></div>
<div><span class="key">WIND: </span><span class="val">${d.data.wind_kmh} km/h</span></div>
<div><span class="key">COST: </span><span class="val">FREE</span></div>
} catch {
const temps = {Lagos:31,Nairobi:22,Jakarta:29,London:12,Tokyo:18,Sydney:24,Dubai:38};
const t = temps[city] || Math.round(15+Math.random()*20);
res.innerHTML = ;
<div><span class="key">CITY: </span><span class="val">${city}</span></div>
<div><span class="key">TEMP: </span><span class="val">${t}°C</span></div>
<div><span class="key">CONDITION: </span><span class="val">${['Clear','Sunny','Cloudy'][Math.floor(Math.random()*3)]}</span></div>
<div><span class="key">HUMIDITY: </span><span class="val">${Math.round(40+Math.random()*50)}%</span></div>
<div><span class="key">COST: </span><span class="val">FREE (Demo)</span></div>
}
}
async function testExchange() {
const from = document.getElementById('api-from').value.trim()||'USD';
const to = document.getElementById('api-to').value.trim()||'NGN';
const res = document.getElementById('api-result');
res.innerHTML = '<span style="color:var(--muted)">Fetching rate...</span>';
try {
const r = await fetch(${API}/airapi/exchange?from=${from}&to=${to});
const d = await r.json();
res.innerHTML = <div><span class="key">RATE: </span><span class="val">1 ${from} = ${d.rate} ${to}</span></div>;
<div><span class="key">COST: </span><span class="val">FREE</span></div>
} catch {
const rates = {NGN:1580,KES:130,GHS:15,ZAR:18,EUR:0.92,GBP:0.79,JPY:149};
const rate = rates[to.toUpperCase()] || (0.5+Math.random()*100).toFixed(4);
res.innerHTML = <div><span class="key">RATE: </span><span class="val">1 ${from} = ${rate} ${to}</span></div>;
<div><span class="key">COST: </span><span class="val">FREE (Demo)</span></div>
}
}
// ── LIVE EVENT LOG ──
const logEvents = [
{c:'lg', msg:'<strong>Nairobi Node</strong> — 12,440 users allocated free data'},
{c:'lb', msg:'<strong>AirAPI Gateway</strong> — 50K requests served to Lagos cluster'},
{c:'lg', msg:'<strong>Jakarta Relay</strong> — auto-scaled to 340 Gbps'},
{c:'lr', msg:'<strong>São Paulo</strong> — failover triggered, rerouting via satellite'},
{c:'lb', msg:'<strong>AirVault</strong> — 80K files synced across 8 geo-regions'},
{c:'lg', msg:'<strong>London Hub</strong> — 230K SMS messages routed free'},
{c:'lg', msg:'<strong>AirData</strong> — daily quota reset for 2.1M users'},
{c:'lb', msg:'<strong>Dubai Node</strong> — 99.99% uptime milestone reached'},
{c:'lg', msg:'<strong>Mumbai Cluster</strong> — 4 new nodes joined the mesh grid'},
{c:'lr', msg:'<strong>AirRelay</strong> — LEO satellite handoff completed in 0.3ms'},
];
let logIdx = 0;
function addLogEntry(msg, cls) {
const feed = document.getElementById('log-feed');
const el = document.createElement('div');
el.className = 'log-entry';
el.innerHTML = <div class="ldot ${cls}"></div><div class="ltext">${msg}<span class="ltime">just now</span></div>;
feed.insertBefore(el, feed.firstChild);
while (feed.children.length > 8) feed.removeChild(feed.lastChild);
}
function initLog() {
logEvents.slice(0,5).forEach((e,i) => {
setTimeout(() => addLogEntry(e.msg, e.c), i*100);
});
logIdx = 5;
}
// Try SSE first, fall back to polling
function startEventStream() {
try {
const es = new EventSource(API+'/events');
es.onmessage = (ev) => {
const d = JSON.parse(ev.data);
const cls = d.type==='warning'?'lr':d.type==='info'?'lb':'lg';
addLogEntry(<strong>LIVE</strong> — ${d.message}, cls);
};
es.onerror = () => { es.close(); startFallbackLog(); };
} catch { startFallbackLog(); }
}
function startFallbackLog() {
setInterval(() => {
const e = logEvents[logIdx % logEvents.length]; logIdx++;
addLogEntry(e.msg, e.c);
}, 3500);
}
// ── INIT ──
window.addEventListener('load', () => {
loadStats();
loadNodes();
initLog();
startEventStream();
});
is a submission for the DEV April Fools Challenge*
Top comments (0)