initial commit
This commit is contained in:
Executable
+176
@@ -0,0 +1,176 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>ESCAPE FROM TARKOV COMPANION — Goons Status</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- Global site styles -->
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
<!-- Page-scoped styles for Goons page -->
|
||||
<link rel="stylesheet" href="goons.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- ======= TOP BAR ======= -->
|
||||
<header class="topbar">
|
||||
<a href="index.html" class="logo">ESCAPE FROM TARKOV COMPANION</a>
|
||||
<!-- Hamburger -->
|
||||
<button class="nav-toggle" type="button" aria-controls="primary-nav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="nav-toggle__bar" aria-hidden="true"></span>
|
||||
<span class="nav-toggle__bar" aria-hidden="true"></span>
|
||||
<span class="nav-toggle__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
<!-- Primary nav -->
|
||||
<nav class="main-nav" id="primary-nav" aria-label="Primary navigation">
|
||||
<ul>
|
||||
<li><a href="index.html">Home</a></li>
|
||||
<!-- This page -->
|
||||
<li><a href="goons.html" aria-current="page">Goons</a></li>
|
||||
<li><a href="maps.html">Maps</a></li>
|
||||
<li><a href="quests.html">Quests</a></li>
|
||||
<li><a href="ammo.html">Ammo</a></li>
|
||||
<li><a href="traders.html">Traders</a></li>
|
||||
<li><a href="pricewatch.html">Price Watch</a></li>
|
||||
<li><a href="crafts.html">Craft Calculator</a></li>
|
||||
<li><a href="https://tarkovgunsmith.com/ballistics_simulator" target="_blank" rel="noopener">Ballistics Simulator</a></li>
|
||||
<li><a href="https://tarkov-market.com/" target="_blank" rel="noopener">Flea Market</a></li>
|
||||
<li><a href="https://escapefromtarkov.fandom.com/wiki/Escape_from_Tarkov_Wiki" target="_blank" rel="noopener">Wiki</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- ======= PAGE CONTENT ======= -->
|
||||
<main class="content">
|
||||
<h1 class="hero">Goons Locations</h1>
|
||||
<p class="page-title">Locations are gathered from public trackers at <a href="https://www.goon-tracker.com" target="_blank" rel="noopener">PVP</a> and <a href="https://www.goon-tracker.com/pvetracker" target="_blank" rel="noopener">PVE</a> and refreshed every 5 minutes.</p>
|
||||
|
||||
<!-- Page meta row: stale badge + last updated -->
|
||||
<div class="page-meta" style="margin-bottom:.5rem;">
|
||||
<span id="gs-stale" class="chip" title="Data health badge" aria-live="polite">Checking freshness…</span>
|
||||
<span class="spacer"></span>
|
||||
<span id="gs-updated" class="badge" title="From local /goons-status.json">Last updated: —</span>
|
||||
</div>
|
||||
|
||||
<!-- PvP + PvE cards -->
|
||||
<section class="gs-section" aria-labelledby="gs-h2">
|
||||
<h2 id="gs-h2">Live Snapshot</h2>
|
||||
<div class="gs-grid">
|
||||
<div class="gs-card" id="card-pvp">
|
||||
<h3>Last Seen (PvP)</h3>
|
||||
<div id="pvp-body" class="muted">Loading PvP last-seen…</div>
|
||||
</div>
|
||||
<div class="gs-card" id="card-pve">
|
||||
<h3>Last Seen (PvE)</h3>
|
||||
<div id="pve-body" class="muted">Loading PvE last-seen…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="muted" style="margin-top:.5rem;">
|
||||
Sources: community tracker pages. Verify in-game; this is for convenience only.
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- HAMBURGER MENU SCRIPT -->
|
||||
<script>
|
||||
const btn = document.querySelector('.nav-toggle');
|
||||
const nav = document.getElementById('primary-nav');
|
||||
btn.addEventListener('click', () => {
|
||||
const open = btn.getAttribute('aria-expanded') === 'true';
|
||||
btn.setAttribute('aria-expanded', !open);
|
||||
nav.classList.toggle('is-open', !open);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- PAGE LOGIC -->
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const UPDATED = document.getElementById('gs-updated');
|
||||
const STALE = document.getElementById('gs-stale');
|
||||
const PVP = document.getElementById('pvp-body');
|
||||
const PVE = document.getElementById('pve-body');
|
||||
|
||||
// Consider data stale after N minutes
|
||||
const STALE_MINUTES = 30;
|
||||
|
||||
function fmtDateLocal(iso) {
|
||||
try {
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return '—';
|
||||
return d.toLocaleString();
|
||||
} catch { return '—'; }
|
||||
}
|
||||
|
||||
function minutesDiffFromNow(iso) {
|
||||
try {
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return Infinity;
|
||||
return (Date.now() - d.getTime()) / 60000;
|
||||
} catch { return Infinity; }
|
||||
}
|
||||
|
||||
function setStaleBadge(fetchedAtIso) {
|
||||
const mins = minutesDiffFromNow(fetchedAtIso);
|
||||
if (!isFinite(mins)) {
|
||||
STALE.textContent = 'STALE (invalid time)';
|
||||
STALE.classList.add('stale');
|
||||
return;
|
||||
}
|
||||
if (mins > STALE_MINUTES) {
|
||||
STALE.textContent = `STALE (${Math.round(mins)} min old)`;
|
||||
STALE.classList.add('stale');
|
||||
} else {
|
||||
STALE.textContent = 'Fresh';
|
||||
STALE.classList.remove('stale');
|
||||
}
|
||||
}
|
||||
|
||||
function mapPill(map) {
|
||||
if (!map) return '<span class="muted">—</span>';
|
||||
return `<span class="map-pill">${map}</span>`;
|
||||
}
|
||||
|
||||
function renderBlock(el, node, label) {
|
||||
if (!node || !node.map) {
|
||||
el.innerHTML = `<div class="muted">No recent ${label} data.</div>`;
|
||||
return;
|
||||
}
|
||||
el.innerHTML = `
|
||||
<div>
|
||||
<div class="pill-row">${mapPill(node.map)}</div>
|
||||
${node.timeText ? `<div>Time: ${node.timeText}</div>` : ''}
|
||||
${node.lastSeenText ? `<div class="muted" style="margin-top:.15rem;">Last seen: ${node.lastSeenText}</div>` : ''}
|
||||
<div class="muted" style="margin-top:.35rem;">Source: ${node.source ? node.source.replace('https://','') : '—'}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
const res = await fetch('/goons-status.json', { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
const data = await res.json();
|
||||
|
||||
// Last updated + stale badge
|
||||
UPDATED.textContent = 'Last updated: ' + fmtDateLocal(data.fetchedAt || '');
|
||||
setStaleBadge(data.fetchedAt || '');
|
||||
|
||||
// Cards
|
||||
renderBlock(PVP, data.pvp, 'PvP');
|
||||
renderBlock(PVE, data.pve, 'PvE');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
UPDATED.textContent = 'Last updated: —';
|
||||
STALE.textContent = 'STALE (fetch failed)';
|
||||
STALE.classList.add('stale');
|
||||
PVP.textContent = 'Failed to load PvP last-seen.';
|
||||
PVE.textContent = 'Failed to load PvE last-seen.';
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user