From ef40149ed2f2f0604ed2e7be8fbcce6989f38365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Thu, 9 Jul 2026 14:15:26 +0200 Subject: [PATCH] shared: goa-stats snippet assets for landings --- configs/shared/goa-stats-snippet.html | 39 ++++++++ configs/shared/goa-stats.css | 86 +++++++++++++++++ configs/shared/goa-stats.js | 130 ++++++++++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 configs/shared/goa-stats-snippet.html create mode 100644 configs/shared/goa-stats.css create mode 100644 configs/shared/goa-stats.js diff --git a/configs/shared/goa-stats-snippet.html b/configs/shared/goa-stats-snippet.html new file mode 100644 index 0000000..7070aee --- /dev/null +++ b/configs/shared/goa-stats-snippet.html @@ -0,0 +1,39 @@ + + diff --git a/configs/shared/goa-stats.css b/configs/shared/goa-stats.css new file mode 100644 index 0000000..9f3f45f --- /dev/null +++ b/configs/shared/goa-stats.css @@ -0,0 +1,86 @@ +/* Shared GOA flow stats (used on bank / exchange / merchant landings) */ +.stats { + background: rgba(18, 28, 28, 0.55); + border: 1px solid rgba(62, 207, 191, 0.28); + border-radius: 16px; + padding: 1.1rem 1.15rem 1.2rem; + margin: 0 0 1.5rem; +} +.stats h2 { + margin: 0 0 0.85rem; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--muted, #c9b8a0); + text-align: center; +} +.stats-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.65rem 0.85rem; +} +@media (min-width: 420px) { + .stats-grid { grid-template-columns: 1fr 1fr 1fr; } +} +.stat { + background: rgba(0, 0, 0, 0.22); + border-radius: 10px; + padding: 0.55rem 0.65rem; + min-width: 0; +} +.stat .k { + display: block; + font-size: 0.68rem; + color: var(--muted, #c9b8a0); + letter-spacing: 0.04em; + text-transform: uppercase; + margin-bottom: 0.2rem; + text-align: center; +} +.stat .v { + display: block; + font-size: 1.05rem; + font-weight: 750; + color: #f0d090; + word-break: break-word; + font-variant-numeric: tabular-nums; + text-align: center; +} +.stat .v.teal { color: #5eead4; } +.stat.wide { grid-column: 1 / -1; } +.wd-list { + list-style: none; + margin: 0.15rem 0 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.35rem; +} +.wd-list li { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 0.25rem 0.75rem; + font-size: 0.88rem; + font-weight: 650; + color: #f0d090; + font-variant-numeric: tabular-nums; + border-bottom: 1px solid rgba(201, 184, 160, 0.12); + padding-bottom: 0.3rem; +} +.wd-list li:last-child { border-bottom: none; padding-bottom: 0; } +.wd-list .amt { color: #5eead4; font-weight: 750; } +.wd-list .meta { + color: var(--muted, #c9b8a0); + font-size: 0.78rem; + font-weight: 600; +} +.stats-foot { + margin: 0.75rem 0 0; + font-size: 0.72rem; + color: var(--muted, #c9b8a0); + text-align: center; +} +.stats-foot a { color: #e8c878; } +.stats-foot.err { color: #f0a090; } diff --git a/configs/shared/goa-stats.js b/configs/shared/goa-stats.js new file mode 100644 index 0000000..8689216 --- /dev/null +++ b/configs/shared/goa-stats.js @@ -0,0 +1,130 @@ +/* Load GOA flow stats from bank landing stats.json (CORS on bank). */ +(function () { + var STATS_URL = "https://bank.hacktivism.ch/intro/stats.json"; + var BANK_INTRO = "https://bank.hacktivism.ch/intro/"; + + function fmtCest(unix, fallback) { + var base = ""; + if (fallback) { + base = String(fallback).replace(/\s*(CEST|CET|UTC|Z)\s*$/i, "").trim(); + } else if (unix != null && unix !== "") { + try { + base = new Intl.DateTimeFormat("de-CH", { + timeZone: "Europe/Zurich", + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hour12: false + }).format(new Date(Number(unix) * 1000)); + } catch (e) { + base = ""; + } + } + return base ? base + " Europe/Zurich" : ""; + } + + function set(id, v) { + var el = document.getElementById(id); + if (el) el.textContent = v == null || v === "" ? "—" : String(v); + } + + function showFallback(msg) { + var box = document.getElementById("stats"); + var foot = document.getElementById("st-foot"); + if (box) box.hidden = false; + if (foot) { + foot.hidden = false; + foot.className = "stats-foot err"; + foot.innerHTML = + (msg || "Stats offline") + + ' · bank.hacktivism.ch'; + } + } + + function loadStats() { + var box = document.getElementById("stats"); + var foot = document.getElementById("st-foot"); + if (!box) return; + box.hidden = false; + + // Same-origin first (bank landing), else public bank URL + var urls = []; + if (location.hostname.indexOf("bank.hacktivism") !== -1) { + urls.push("stats.json"); + } + urls.push(STATS_URL); + + function tryNext(i) { + if (i >= urls.length) { + showFallback("No stats"); + return; + } + fetch(urls[i], { cache: "no-store", mode: "cors" }) + .then(function (r) { + if (!r.ok) throw new Error("HTTP " + r.status); + return r.json(); + }) + .then(function (d) { + if (!d || !d.ok) throw new Error("bad stats"); + var w = d.withdraws || {}; + var h24 = w.last_24h || {}; + var h7 = w.last_7d || {}; + var ba = d.bank_accounts || {}; + var wl = d.wallets || {}; + var flow = d.flow || {}; + var fin = flow.incoming || {}; + var fwd = flow.withdraw || {}; + set("st-accounts", ba.total != null ? ba.total : "—"); + set("st-wallets", wl.unique_reserves != null ? wl.unique_reserves : "—"); + set("st-incoming", fin.amount || flow.total_in || "—"); + set("st-withdraw", fwd.amount || w.total_amount || "—"); + set("st-24h", h24.amount || "GOA:0"); + set("st-7d", h7.amount || "GOA:0"); + + var list = document.getElementById("st-recent"); + if (list) { + list.innerHTML = ""; + var rows = (d.recent_withdraws || []).slice(0, 3); + if (!rows.length) { + var empty = document.createElement("li"); + empty.className = "meta"; + empty.textContent = "—"; + list.appendChild(empty); + } else { + rows.forEach(function (row) { + var li = document.createElement("li"); + var amt = document.createElement("span"); + amt.className = "amt"; + amt.textContent = row.amount || "?"; + var meta = document.createElement("span"); + meta.className = "meta"; + meta.textContent = fmtCest(row.at_unix, row.at || row.at_iso) || ""; + li.appendChild(amt); + li.appendChild(meta); + list.appendChild(li); + }); + } + } + if (foot) { + foot.hidden = false; + foot.className = "stats-foot"; + foot.innerHTML = + 'Source: bank.hacktivism.ch' + + (d.generated_at_human ? " · " + d.generated_at_human : ""); + } + }) + .catch(function () { + tryNext(i + 1); + }); + } + tryNext(0); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", loadStats); + } else { + loadStats(); + } +})();