koopa-admin-log/configs/bank-landing/index.html
Hernâni Marques eacf4f91c8
bank landing: show auto-account created datetime
API returns created; landing displays it once.
2026-07-10 16:46:00 +02:00

1461 lines
51 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GOA Exploration Bank</title>
<style>
:root {
--bg: #1a1410;
--card: rgba(42, 32, 24, 0.82);
--text: #fff6e8;
--muted: #c9b8a0;
--accent: #e8a838;
--accent2: #3ecfbf;
--border: rgba(232, 168, 56, 0.28);
--glow-gold: rgba(232, 168, 56, 0.35);
--glow-teal: rgba(62, 207, 191, 0.22);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: var(--text);
line-height: 1.55;
background-color: var(--bg);
background-image:
radial-gradient(ellipse 90% 55% at 50% 108%, rgba(26, 107, 110, 0.55) 0%, transparent 55%),
radial-gradient(circle 420px at 12% 18%, var(--glow-gold) 0%, transparent 62%),
radial-gradient(circle 380px at 88% 12%, var(--glow-teal) 0%, transparent 58%),
radial-gradient(circle 280px at 70% 70%, rgba(196, 122, 24, 0.18) 0%, transparent 60%),
linear-gradient(165deg, #2c1e14 0%, #1a1410 38%, #12181a 72%, #0e1c1e 100%);
background-attachment: fixed;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
opacity: 0.07;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
main {
position: relative;
z-index: 1;
max-width: 38rem;
margin: 0 auto;
padding: 2.25rem 1.25rem 3.5rem;
}
.hero {
text-align: center;
margin: 0 0 1.75rem;
}
h1 {
font-size: 1.85rem;
font-weight: 750;
margin: 0 0 0.45rem;
letter-spacing: -0.02em;
line-height: 1.2;
background: linear-gradient(120deg, #fff6e8 0%, #f0c86a 45%, #3ecfbf 120%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.sub {
color: var(--muted);
margin: 0 auto;
max-width: 30rem;
font-size: 1.05rem;
line-height: 1.45;
}
.badge {
display: inline-block;
background: linear-gradient(135deg, rgba(232, 168, 56, 0.2), rgba(62, 207, 191, 0.15));
color: #f0d090;
font-size: 0.75rem;
font-weight: 600;
padding: 0.2rem 0.7rem;
border-radius: 999px;
margin: 0 0 0.85rem;
border: 1px solid rgba(232, 168, 56, 0.4);
letter-spacing: 0.04em;
}
.balance-note {
margin: 0.75rem 0 0;
padding: 0.55rem 0.7rem;
border-radius: 10px;
background: rgba(232, 168, 56, 0.08);
border: 1px solid rgba(232, 168, 56, 0.32);
color: #f5e6c8;
font-size: 0.82rem;
line-height: 1.4;
text-align: left;
}
.balance-note strong { color: #f0c86a; font-weight: 750; }
/* How shared account works: collapsed until click */
.how-fold {
margin: 0 0 1.15rem;
border: 1px solid rgba(232, 168, 56, 0.4);
border-radius: 14px;
overflow: hidden;
background: rgba(232, 168, 56, 0.08);
color: #f5e6c8;
font-size: 0.88rem;
line-height: 1.45;
}
.how-fold-toggle {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
margin: 0;
padding: 0.75rem 0.9rem;
border: 0;
background: transparent;
color: #f0c86a;
font: inherit;
font-size: 0.9rem;
font-weight: 750;
text-align: left;
cursor: pointer;
line-height: 1.3;
}
.how-fold-toggle:hover {
background: rgba(232, 168, 56, 0.12);
color: #f5d78a;
}
.how-fold-toggle-text { flex: 1; }
.how-fold-chevron {
flex-shrink: 0;
transition: transform 0.15s ease;
color: #e8a838;
font-size: 0.95rem;
}
.how-fold.open .how-fold-chevron { transform: rotate(90deg); }
.how-fold-body {
display: none;
padding: 0 0.95rem 0.9rem;
border-top: 1px solid rgba(232, 168, 56, 0.28);
}
.how-fold.open .how-fold-body { display: block; }
.how-fold-body ol {
margin: 0.55rem 0 0;
padding-left: 1.2rem;
}
.how-fold-body li { margin: 0.3rem 0; }
.how-fold-body strong { color: #f0c86a; font-weight: 750; }
.step-card {
background: var(--card);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--border);
border-radius: 18px;
padding: 1.4rem 1.35rem 1.5rem;
margin-bottom: 1.35rem;
box-shadow:
0 14px 42px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 246, 232, 0.06);
}
.step-card.step-money {
border-color: rgba(62, 207, 191, 0.4);
}
.step-num {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.1rem;
height: 2.1rem;
border-radius: 50%;
font-weight: 800;
font-size: 1.15rem;
margin-right: 0.55rem;
vertical-align: middle;
color: #1a1208;
background: linear-gradient(135deg, #f0c86a, #c47a18);
box-shadow: 0 4px 14px var(--glow-gold);
}
.step-card.step-money .step-num {
background: linear-gradient(135deg, #5eead4, #0d9488);
color: #042f2e;
box-shadow: 0 4px 14px var(--glow-teal);
}
.step-card h2 {
font-size: 1.35rem;
font-weight: 800;
margin: 0 0 0.4rem;
letter-spacing: -0.01em;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.15rem 0;
text-align: center;
}
.step-tagline {
margin: 0 0 1.15rem;
color: var(--muted);
font-size: 0.98rem;
text-align: center;
}
.step-body {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.1rem;
}
.step-body ol {
margin: 0;
padding-left: 1.25rem;
width: 100%;
align-self: stretch;
}
.step-body li { margin: 0.4rem 0; }
/* Page QRs: QR_Taler blue ring (same classes as shop-pay / merchant webui) */
.qr-taler-wrap {
text-align: center;
width: 100%;
max-width: 280px;
}
.qr-taler-wrap .goa-pay-taler-qr {
margin-bottom: 0.55rem;
}
.qr-label {
margin-top: 0.15rem;
font-size: 0.85rem;
color: var(--muted);
font-weight: 700;
max-width: 260px;
margin-left: auto;
margin-right: auto;
}
.more-card .qr-label { color: #c9b8a0; }
.cta {
display: inline-block;
text-align: center;
text-decoration: none;
background: linear-gradient(135deg, #f0c86a 0%, #e8a838 40%, #c47a18 100%);
color: #1a1208;
font-weight: 750;
font-size: 1rem;
padding: 0.75rem 1.25rem;
border-radius: 12px;
box-shadow: 0 8px 22px var(--glow-gold);
}
.cta:hover { filter: brightness(1.06); }
.cta.teal {
background: linear-gradient(135deg, #5eead4 0%, #14b8a6 50%, #0d9488 100%);
color: #042f2e;
box-shadow: 0 8px 22px var(--glow-teal);
}
.meta {
font-size: 0.78rem;
color: var(--muted);
word-break: break-all;
margin: 0.5rem 0 0;
text-align: center;
max-width: 100%;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.86em;
background: rgba(0, 0, 0, 0.35);
padding: 0.1em 0.35em;
border-radius: 4px;
color: #f0d090;
}
/* App store buttons */
.app-links {
display: flex;
flex-wrap: wrap;
gap: 0.55rem;
justify-content: center;
width: 100%;
}
.app-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.45rem;
min-width: 9.5rem;
text-decoration: none;
font-weight: 700;
font-size: 0.92rem;
padding: 0.65rem 0.95rem;
border-radius: 11px;
border: 1px solid rgba(232, 168, 56, 0.4);
background: rgba(0, 0, 0, 0.28);
color: #f0d090;
}
.app-btn:hover { filter: brightness(1.08); border-color: rgba(240, 200, 106, 0.7); }
.app-btn.ios {
border-color: rgba(62, 207, 191, 0.4);
color: #b8f5ec;
}
.app-btn.hub {
flex: 1 1 100%;
border-color: rgba(201, 184, 160, 0.35);
color: var(--muted);
font-weight: 600;
font-size: 0.85rem;
gap: 0.5rem;
}
.app-ico {
width: 1.15rem;
height: 1.15rem;
flex-shrink: 0;
display: block;
}
.app-ico:not(.browser-ico) path,
.app-ico:not(.browser-ico) circle,
.app-ico:not(.browser-ico) rect { fill: currentColor; }
/* hub browser marks use explicit gold/blue fills in SVG */
.app-ico.stroke-ico path, .app-ico.stroke-ico circle, .app-ico.stroke-ico rect {
fill: none;
stroke: currentColor;
stroke-width: 1.6;
stroke-linecap: round;
stroke-linejoin: round;
}
.app-ico.gold { color: #f0c86a; }
.app-ico.blue { color: #3ecfbf; }
.app-btn .app-ico.gold { color: #f0c86a; }
.app-btn .app-ico.blue { color: #5eead4; }
.app-btn.ios .app-ico { color: #5eead4; }
.app-btn:not(.ios):not(.hub) .app-ico { color: #f0c86a; }
.hub-icons {
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.hub-icons .app-ico { width: 1.25rem; height: 1.25rem; }
.hub-icons .browser-ico { border-radius: 3px; flex-shrink: 0; }
.step-tagline .app-ico {
width: 1rem;
height: 1rem;
display: inline-block;
vertical-align: -0.15em;
margin: 0 0.12em;
}
.step-tagline .app-ico.ios { color: #5eead4; }
.step-tagline .app-ico.android { color: #f0c86a; }
/* Optional block: collapsed by default, expand like settlement / wallet fold */
.more-section {
margin-top: 1.5rem;
margin-bottom: 1.35rem;
border: 1px solid rgba(201, 184, 160, 0.4);
border-radius: 14px;
overflow: hidden;
background: rgba(18, 28, 28, 0.55);
}
.more-section > .more-label {
display: none; /* replaced by toggle */
}
.more-fold-toggle {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
margin: 0;
padding: 0.8rem 0.95rem;
border: 0;
background: transparent;
color: #e8c878;
font: inherit;
font-size: 0.88rem;
font-weight: 750;
letter-spacing: 0.04em;
text-align: left;
cursor: pointer;
line-height: 1.3;
}
.more-fold-toggle:hover {
background: rgba(232, 168, 56, 0.1);
color: #f0d090;
}
.more-fold-toggle-text { flex: 1; }
.more-fold-chevron {
flex-shrink: 0;
transition: transform 0.15s ease;
color: #3ecfbf;
font-size: 0.95rem;
}
.more-section.open .more-fold-chevron { transform: rotate(90deg); }
.more-fold-body {
display: none;
padding: 0 0.95rem 1.1rem;
border-top: 1px solid rgba(201, 184, 160, 0.3);
}
.more-section.open .more-fold-body { display: block; }
.more-fold-body > .more-card { margin-top: 0.85rem; }
.more-card {
background: rgba(18, 28, 28, 0.65);
border: 1px dashed rgba(62, 207, 191, 0.35);
border-radius: 16px;
padding: 1.25rem 1.2rem 1.4rem;
}
.more-card h2 {
font-size: 1.05rem;
font-weight: 700;
margin: 0 0 0.5rem;
color: #d8ebe8;
}
.more-card p {
margin: 0 0 0.85rem;
color: var(--muted);
font-size: 0.95rem;
}
.more-card ol {
margin: 0 0 1rem;
padding-left: 1.2rem;
color: var(--muted);
font-size: 0.92rem;
}
.more-card li { margin: 0.35rem 0; }
.more-actions {
display: flex;
flex-direction: column;
gap: 0.55rem;
align-items: stretch;
}
.cta-sec {
display: block;
text-align: center;
text-decoration: none;
color: var(--accent2);
border: 1px solid rgba(62, 207, 191, 0.4);
padding: 0.6rem 0.9rem;
border-radius: 10px;
font-weight: 600;
font-size: 0.92rem;
background: transparent;
font: inherit;
cursor: pointer;
width: 100%;
max-width: 22rem;
box-sizing: border-box;
}
button.cta-sec:hover {
background: rgba(62, 207, 191, 0.1);
}
.cta-sec.merchant {
color: #f0d090;
border-color: rgba(232, 168, 56, 0.35);
}
.more-qr {
display: flex;
justify-content: center;
margin: 1rem 0 0.25rem;
}
.more-qr .qr-taler-wrap {
transform: scale(0.92);
transform-origin: center top;
}
.own-acct-lead {
margin: 0 0 0.85rem;
color: var(--muted);
font-size: 0.95rem;
line-height: 1.45;
}
.own-acct-lead strong { color: #e8c878; }
.own-acct-creds {
margin: 0 0 0.85rem;
padding: 0.75rem 0.85rem;
border-radius: 12px;
background: rgba(94, 234, 212, 0.08);
border: 1px solid rgba(94, 234, 212, 0.35);
text-align: left;
}
.own-acct-warn {
margin: 0 0 0.65rem;
font-size: 0.86rem;
color: #ccfbf1;
line-height: 1.4;
}
.own-acct-warn strong { color: #5eead4; }
.own-acct-dl {
margin: 0;
display: grid;
gap: 0.45rem;
}
.own-acct-dl div {
display: grid;
grid-template-columns: 5.5rem 1fr;
gap: 0.35rem;
align-items: baseline;
}
.own-acct-dl dt {
margin: 0;
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--muted);
}
.own-acct-dl dd {
margin: 0;
}
.own-acct-dl code {
font-size: 0.95rem;
font-weight: 700;
color: #f0d090;
word-break: break-all;
user-select: all;
}
#auto-account-btn {
border: 0;
cursor: pointer;
width: 100%;
max-width: 22rem;
}
#auto-account-btn:disabled {
opacity: 0.55;
cursor: wait;
}
.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);
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);
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);
font-size: 0.78rem;
font-weight: 600;
}
.stats-foot {
margin: 0.75rem 0 0;
font-size: 0.72rem;
color: var(--muted);
text-align: center;
}
.stats-foot.err { color: #f0a090; }
.stats-run-note {
margin: 0.35rem 0 0;
font-size: 0.7rem;
color: #f0a090;
text-align: center;
}
footer {
margin-top: 2rem;
color: var(--muted);
font-size: 0.85rem;
text-align: center;
}
footer a { color: #e8c878; }
/* Compact GOA shop teaser */
.shop-box {
background: rgba(18, 28, 28, 0.55);
border: 1px solid rgba(232, 168, 56, 0.32);
border-radius: 16px;
padding: 1rem 1.05rem 1.1rem;
margin: 0 0 1.35rem;
}
.shop-box h2 {
margin: 0 0 0.25rem;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
text-align: center;
}
.shop-box .shop-lead {
margin: 0 0 0.75rem;
text-align: center;
font-size: 0.88rem;
color: var(--muted);
}
.shop-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
max-width: 22rem;
margin: 0 auto;
}
.shop-box.shop-mini {
margin: 0 0 1rem;
padding: 0.85rem 0.9rem 0.95rem;
}
.shop-item {
background: rgba(0, 0, 0, 0.28);
border: 1px solid rgba(232, 168, 56, 0.18);
border-radius: 12px;
padding: 0.55rem 0.5rem 0.6rem;
text-align: center;
min-width: 0;
}
.shop-item .emoji {
display: block;
font-size: 1.35rem;
line-height: 1.2;
margin-bottom: 0.2rem;
}
.shop-item .name {
display: block;
font-size: 0.78rem;
font-weight: 700;
color: #fff6e8;
line-height: 1.25;
}
.shop-item .price {
display: block;
margin-top: 0.2rem;
font-size: 0.8rem;
font-weight: 750;
color: #5eead4;
font-variant-numeric: tabular-nums;
}
.shop-foot {
margin: 0.75rem 0 0;
text-align: center;
}
.shop-foot a {
display: inline-block;
text-decoration: none;
font-size: 0.85rem;
font-weight: 700;
color: #1a1208;
background: linear-gradient(135deg, #f0c86a, #c47a18);
padding: 0.45rem 0.9rem;
border-radius: 10px;
}
.shop-foot a:hover { filter: brightness(1.06); }
</style>
<base href="/intro/" />
<link rel="stylesheet" href="/intro/shop-pay.css" />
<script src="/intro/qrcode.min.js"></script>
</head>
<body data-shop-theme="bank">
<main>
<header class="hero">
<span class="badge">Intergalactic · GOA · no IBAN</span>
<h1>GOA Exploration Bank</h1>
<p class="sub">
Install the wallet, then <strong>one QR</strong> withdraws GOA from a
<strong>shared bank account</strong> — no registration.
</p>
</header>
<!-- ========== STEP 1: install apps ========== -->
<section class="step-card" aria-labelledby="step1-title">
<h2 id="step1-title"><span class="step-num">1</span> Get the wallet</h2>
<div class="step-body">
<div class="app-links">
<a class="app-btn"
href="https://play.google.com/store/apps/details?id=net.taler.wallet"
target="_blank" rel="noopener noreferrer"
aria-label="Android Play Store">
<svg class="app-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.2 2.4l-.9-1.6c-.1-.2 0-.4.2-.5.2-.1.4 0 .5.2l.9 1.6c.8-.3 1.7-.5 2.6-.5s1.8.2 2.6.5l.9-1.6c.1-.2.3-.3.5-.2.2.1.3.3.2.5l-.9 1.6c1.8.8 3.1 2.5 3.3 4.6H4.9c.2-2.1 1.5-3.8 3.3-4.6zM7 8.5c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zm10 0c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zM5 10.5h14c.6 0 1 .4 1 1v6.2c0 1.3-1 2.3-2.3 2.3h-.5v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-4.8v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-.5C5.9 20 5 19 5 17.7v-6.2c0-.6.4-1 1-1z"/></svg>
Play Store
</a>
<a class="app-btn"
href="https://f-droid.org/en/packages/net.taler.wallet.fdroid/"
target="_blank" rel="noopener noreferrer"
aria-label="Android F-Droid">
<svg class="app-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.2 2.4l-.9-1.6c-.1-.2 0-.4.2-.5.2-.1.4 0 .5.2l.9 1.6c.8-.3 1.7-.5 2.6-.5s1.8.2 2.6.5l.9-1.6c.1-.2.3-.3.5-.2.2.1.3.3.2.5l-.9 1.6c1.8.8 3.1 2.5 3.3 4.6H4.9c.2-2.1 1.5-3.8 3.3-4.6zM7 8.5c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zm10 0c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zM5 10.5h14c.6 0 1 .4 1 1v6.2c0 1.3-1 2.3-2.3 2.3h-.5v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-4.8v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-.5C5.9 20 5 19 5 17.7v-6.2c0-.6.4-1 1-1z"/></svg>
F-Droid
</a>
<a class="app-btn ios"
href="https://apps.apple.com/app/taler-wallet/id6463440117"
target="_blank" rel="noopener noreferrer"
aria-label="iOS App Store">
<svg class="app-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M16.7 12.6c0-2.1 1.7-3.1 1.8-3.2-1-1.4-2.5-1.6-3-1.7-1.3-.1-2.5.8-3.1.8s-1.6-.7-2.7-.7c-1.4 0-2.7.8-3.4 2.1-1.5 2.5-.4 6.3 1 8.3.7 1 1.5 2.1 2.6 2 1-.1 1.5-.7 2.8-.7s1.7.7 2.8.7 1.9-1 2.6-2c.8-1.1 1.1-2.2 1.1-2.3-.1 0-2.2-.8-2.2-3.3zm-2-6.2c.6-.7 1-1.7.9-2.7-1 .1-2.1.6-2.8 1.4-.6.7-1.1 1.7-.9 2.6 1 .1 2-.5 2.8-1.3z"/></svg>
App Store
</a>
<a class="app-btn hub"
href="https://wallet.taler.net/"
target="_blank" rel="noopener noreferrer"
aria-label="Web browsers (experimental): Chrome, Firefox, Opera, Ubuntu Touch">
<span class="hub-icons" aria-hidden="true" title="Chrome · Firefox · Opera · Ubuntu Touch">
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Chrome">
<circle cx="12" cy="12" r="10" fill="#3ecfbf" opacity="0.22"/>
<path fill="#3ecfbf" d="M12 2a10 10 0 0 1 8.66 5H12a4 4 0 0 0-3.46 2L5.1 4.1A10 10 0 0 1 12 2z"/>
<path fill="#3ecfbf" opacity="0.8" d="M20.66 7A10 10 0 0 1 12 22l3.46-6A4 4 0 0 0 12 8h8.66z"/>
<path fill="#3ecfbf" opacity="0.55" d="M5.1 4.1 8.54 9A4 4 0 0 0 12 16l-3.46 6A10 10 0 0 1 5.1 4.1z"/>
<circle cx="12" cy="12" r="3.2" fill="#3ecfbf"/>
</svg>
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Firefox">
<circle cx="12" cy="12.2" r="8.2" fill="#3ecfbf" opacity="0.28"/>
<path fill="#3ecfbf" d="M20.2 9.2c-.2-1.4-1.1-2.6-2.2-3.4.5 1.1.6 2.3.3 3.4-.9-1.6-2.5-2.6-4.4-2.6-1.3 0-2.5.4-3.4 1.1C9.3 5.4 10.9 3.8 13 3.2c-2.5.1-4.7 1.4-6.1 3.4C5.3 8.2 4.6 10.2 4.8 12.3c.3 3.9 3.5 7 7.4 7.1 3.6.1 6.7-2.2 7.7-5.5.4-1.4.4-2.9.3-4.7z"/>
<circle cx="12.2" cy="13.2" r="2.4" fill="#3ecfbf"/>
</svg>
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Opera">
<circle cx="12" cy="12" r="9.2" fill="none" stroke="#3ecfbf" stroke-width="2"/>
<ellipse cx="12" cy="12" rx="4.15" ry="7.1" fill="none" stroke="#3ecfbf" stroke-width="1.85"/>
</svg>
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Ubuntu Touch">
<circle cx="12" cy="12" r="2.25" fill="#3ecfbf"/>
<circle cx="12" cy="4.85" r="1.7" fill="#3ecfbf"/>
<circle cx="18.15" cy="15.55" r="1.7" fill="#3ecfbf"/>
<circle cx="5.85" cy="15.55" r="1.7" fill="#3ecfbf"/>
</svg>
</span>
Web browsers (experimental)
</a>
</div>
</div>
</section>
<!-- ========== STEP 2: one-click community pool withdraw ========== -->
<section class="step-card step-money" id="withdraw" aria-labelledby="step2-title">
<h2 id="step2-title"><span class="step-num">2</span> Withdraw GOA</h2>
<p class="step-tagline">One QR · shared account · no registration</p>
<div class="step-body">
<div class="qr-taler-wrap">
<div class="goa-pay-taler-qr" id="qr-withdraw-demo-wrap">
<div class="goa-pay-taler-qr__inner" id="qr-withdraw-demo"></div>
<img class="goa-pay-taler-qr__logo" id="qr-withdraw-demo-logo" alt="" />
</div>
<div class="qr-label">Scan in wallet</div>
</div>
<a class="cta teal" id="open-withdraw-demo" href="#">
Open in wallet →
</a>
<p class="meta" id="withdraw-demo-meta">Preparing withdraw…</p>
<p class="meta" id="withdraw-demo-amount" hidden></p>
<button type="button" class="cta-sec" id="generate-own-account" style="margin-top:0.5rem">
Generate own account
</button>
<p class="balance-note" role="note">
<strong>Surprising balance?</strong>
Pool holds <strong id="shared-balance"></strong> (shared, but also yours).
You only get the small withdraw amount.
</p>
</div>
</section>
<div class="how-fold" id="how-fold">
<button type="button" class="how-fold-toggle" id="how-fold-toggle"
aria-expanded="false" aria-controls="how-fold-body">
<span class="how-fold-toggle-text" id="how-title">How the shared account works</span>
<span class="how-fold-chevron" aria-hidden="true"></span>
</button>
<div class="how-fold-body" id="how-fold-body" hidden>
<ol>
<li><strong>Now live:</strong> one <strong>shared bank account</strong> (community pool) already holds GOA for everyone.</li>
<li>Each “Open shared withdraw” / QR is a <strong>one-shot withdraw from that shared account</strong> — you do <em>not</em> log into the bank for this step.</li>
<li>Your GNU Taler app receives coins after the bank confirms (automatic on this demo).</li>
<li>Optional below: register your <em>own</em> bank UI account only if you want a private balance separate from the pool.</li>
</ol>
</div>
</div>
<!-- ========== Optional bank UI + mini GOA shop (collapsed) ========== -->
<div class="more-section" id="more-fold">
<button type="button" class="more-fold-toggle" id="more-fold-toggle"
aria-expanded="false" aria-controls="more-fold-body">
<span class="more-fold-toggle-text">Optional · bank UI &amp; GOA shop</span>
<span class="more-fold-chevron" aria-hidden="true"></span>
</button>
<div class="more-fold-body" id="more-fold-body" hidden>
<div class="more-card">
<h2>Own bank account?</h2>
<p class="own-acct-lead">
We can <strong>create a personal bank account for you</strong> automatically
(you do not pick a username or password) — separate from the shared pool.
</p>
<div class="more-actions" style="margin-bottom:0.75rem">
<button type="button" class="cta teal" id="auto-account-btn">
Create my bank account →
</button>
</div>
<div class="own-acct-creds" id="auto-account-box" hidden>
<p class="own-acct-warn" role="status">
<strong>Created for you.</strong>
Copy these credentials now — they are not stored for recovery.
Login at the bank UI.
</p>
<dl class="own-acct-dl">
<div><dt>Name</dt><dd><code id="auto-account-name"></code></dd></div>
<div><dt>Username</dt><dd><code id="auto-account-user"></code></dd></div>
<div><dt>Password</dt><dd><code id="auto-account-pass"></code></dd></div>
<div><dt>Created</dt><dd><code id="auto-account-created"></code></dd></div>
</dl>
<p class="meta" id="auto-account-status" style="margin:0.5rem 0 0"></p>
<div class="more-qr" id="auto-account-qr-block" style="margin-top:0.85rem" hidden>
<div class="qr-taler-wrap">
<div class="goa-pay-taler-qr" id="qr-auto-acct-wrap">
<div class="goa-pay-taler-qr__inner" id="qr-auto-acct"></div>
<img class="goa-pay-taler-qr__logo" id="qr-auto-acct-logo" alt="" />
</div>
<div class="qr-label" id="qr-auto-acct-label">Your account · bank UI login</div>
</div>
</div>
</div>
<div class="more-qr">
<div class="qr-taler-wrap">
<div class="goa-pay-taler-qr" id="qr-webui-wrap">
<div class="goa-pay-taler-qr__inner" id="qr-webui"></div>
<img class="goa-pay-taler-qr__logo" id="qr-webui-logo" alt="" />
</div>
<div class="qr-label">Bank UI · open site</div>
</div>
</div>
<div class="more-actions">
<a class="cta-sec" href="/webui/" id="auto-account-webui">Open bank UI →</a>
<button type="button" class="cta-sec" id="auto-account-again" hidden style="margin-top:0.45rem">
Generate another account
</button>
</div>
</div>
<aside class="shop-box shop-mini" aria-labelledby="shop-title" style="margin-top:1rem">
<h2 id="shop-title">GOA shop · samples</h2>
<p class="shop-lead">Tap → pay link · more at merchant</p>
<div class="shop-grid">
<button type="button" class="shop-item" data-product="orbit-sticker">
<span class="emoji" aria-hidden="true">🪐</span>
<span class="name">Orbit sticker pack</span>
<span class="price">GOA:2</span>
</button>
<button type="button" class="shop-item" data-product="nebula-coffee">
<span class="emoji" aria-hidden="true"></span>
<span class="name">Nebula coffee</span>
<span class="price">GOA:5</span>
</button>
</div>
<p class="shop-foot">
<a href="https://taler.hacktivism.ch/intro/">For more → taler.hacktivism.ch</a>
</p>
</aside>
</div>
</div>
<!-- ========== LIVE STATS (before performance) ========== -->
<section class="stats" id="stats" aria-labelledby="stats-title" hidden>
<h2 id="stats-title">GOA flow</h2>
<div class="stats-grid">
<div class="stat">
<span class="k">Accounts</span>
<span class="v teal" id="st-accounts"></span>
</div>
<div class="stat">
<span class="k">Bank credits</span>
<span class="v teal" id="st-incoming"></span>
</div>
<div class="stat">
<span class="k">Withdraws</span>
<span class="v" id="st-withdraw"></span>
</div>
<div class="stat">
<span class="k">Unique wallets</span>
<span class="v teal" id="st-wallets"></span>
</div>
<div class="stat">
<span class="k">24h</span>
<span class="v" id="st-24h"></span>
</div>
<div class="stat">
<span class="k">7d</span>
<span class="v" id="st-7d"></span>
</div>
<div class="stat wide">
<span class="k">Recent Withdraws</span>
<ul class="wd-list" id="st-recent" aria-live="polite">
<li class="meta"></li>
</ul>
</div>
</div>
<p class="stats-foot" id="st-foot" hidden></p>
<p class="stats-run-note" id="st-run-note" hidden></p>
</section>
<section class="stats" id="stats-perf" aria-labelledby="perf-title">
<h2 id="perf-title">Performance</h2>
<p class="note" style="text-align:center;font-size:0.82rem;color:var(--muted);margin:0 0 0.85rem">
Live HTTP probes + container memory (RSS)
</p>
<div class="stats-grid">
<div class="stat">
<span class="k">/config</span>
<span class="v teal" id="st-config-ms"></span>
</div>
<div class="stat">
<span class="k">/taler-integration/config</span>
<span class="v" id="st-int-ms"></span>
</div>
<div class="stat">
<span class="k">/webui/</span>
<span class="v" id="st-webui-ms"></span>
</div>
<div class="stat">
<span class="k">Loadavg</span>
<span class="v" id="st-load"></span>
</div>
<div class="stat">
<span class="k">Container</span>
<span class="v teal" id="st-mem-ctr"></span>
</div>
<div class="stat">
<span class="k">PostgreSQL</span>
<span class="v" id="st-mem-pg"></span>
</div>
<div class="stat">
<span class="k">Java / libeufin</span>
<span class="v" id="st-mem-java"></span>
</div>
<div class="stat">
<span class="k">Nginx</span>
<span class="v" id="st-mem-nginx"></span>
</div>
</div>
<div style="margin-top:0.85rem;overflow-x:auto">
<p class="note" style="text-align:center;margin-bottom:0.4rem">Top processes (RSS)</p>
<div id="st-mem-top"></div>
</div>
<p class="stats-foot" id="st-perf-foot">Memory from /proc + cgroup inside podman container.</p>
</section>
<footer>
hacktivism.ch ·
<a href="https://bank.hacktivism.ch/intro/">Bank</a> ·
<a href="https://exchange.hacktivism.ch/intro/">Exchange</a> ·
<a href="https://taler.hacktivism.ch/intro/">Merchant</a> ·
<a href="https://bank.hacktivism.ch/terms">Terms</a> ·
<a href="https://bank.hacktivism.ch/privacy">Privacy</a> ·
<a href="/config">/config</a> ·
<a href="https://git.hacktivism.ch/hernani/koopa-admin-log/src/branch/main/configs/bank-landing">Source</a>
<p class="privacy-note" style="margin:0.65rem 0 0;font-size:0.78rem;color:var(--muted);line-height:1.4">
Privacy: processing under Swiss FADP (revDSG). What is retained is listed on
<a href="https://bank.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
</p>
</footer>
</main>
<script>
(function () {
var WEBUI = "https://bank.hacktivism.ch/webui/";
/* Secondary: own-account path (register first) */
var WITHDRAW_EXCHANGE = "taler://withdraw-exchange/exchange.hacktivism.ch/";
/* QR_Taler: off-DOM encode → PNG img + center logo (same as shop-pay / merchant) */
function logoSrc() {
try {
var b = document.querySelector("base");
if (b && b.href) return new URL("qr-logo.svg", b.href).href;
} catch (e) {}
return "/intro/qr-logo.svg";
}
function setQrLogo(imgEl) {
if (!imgEl) return;
imgEl.src = logoSrc();
imgEl.onerror = function () {
imgEl.style.display = "none";
};
imgEl.style.display = "";
}
function makeQr(el, text, logoEl) {
if (!el) return;
el.innerHTML = "";
var size = 220;
if (!text) {
el.innerHTML = '<p style="padding:1rem;color:#666;font-size:0.85rem">…</p>';
return;
}
if (typeof QRCode === "undefined") {
el.innerHTML = '<p style="padding:1rem;color:#b91c1c;font-size:0.85rem">QR lib missing</p>';
return;
}
var level =
QRCode.CorrectLevel && QRCode.CorrectLevel.M != null
? QRCode.CorrectLevel.M
: 1;
var scratch = document.createElement("div");
scratch.setAttribute("aria-hidden", "true");
scratch.style.cssText =
"position:fixed;left:-9999px;top:0;width:" +
size +
"px;height:" +
size +
"px;overflow:hidden;opacity:0;pointer-events:none";
document.body.appendChild(scratch);
var dataUrl = "";
try {
new QRCode(scratch, {
text: String(text),
width: size,
height: size,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: level
});
var canvas = scratch.querySelector("canvas");
var libImg = scratch.querySelector("img");
if (canvas && canvas.width > 0) {
try {
dataUrl = canvas.toDataURL("image/png");
} catch (e1) {}
}
if (!dataUrl && libImg && libImg.src && libImg.src.indexOf("data:") === 0) {
dataUrl = libImg.src;
}
} catch (err) {
dataUrl = "";
}
if (scratch.parentNode) scratch.parentNode.removeChild(scratch);
if (!dataUrl) {
el.innerHTML = '<p style="padding:1rem;color:#b91c1c;font-size:0.85rem">QR encode failed</p>';
return;
}
var img = document.createElement("img");
img.alt = "QR code";
img.width = size;
img.height = size;
img.src = dataUrl;
img.style.display = "block";
img.style.width = size + "px";
img.style.height = size + "px";
img.style.maxWidth = "100%";
img.style.margin = "0 auto";
img.style.background = "#fff";
el.appendChild(img);
setQrLogo(logoEl);
}
function setDemoWithdraw(uri, amount) {
uri = String(uri || "").replace(/:443\//g, "/").replace(/:443\?/g, "?");
var open = document.getElementById("open-withdraw-demo");
var meta = document.getElementById("withdraw-demo-meta");
var amtEl = document.getElementById("withdraw-demo-amount");
if (open) {
open.href = uri;
open.classList.remove("disabled");
}
if (meta) meta.textContent = uri;
if (amtEl) {
if (amount) {
amtEl.hidden = false;
amtEl.textContent = "Amount: " + amount + " · shared pool";
} else {
amtEl.hidden = true;
}
}
makeQr(
document.getElementById("qr-withdraw-demo"),
uri,
document.getElementById("qr-withdraw-demo-logo")
);
}
function loadDemoWithdraw() {
var meta = document.getElementById("withdraw-demo-meta");
var open = document.getElementById("open-withdraw-demo");
if (meta) meta.textContent = "Preparing community withdraw…";
if (open) {
open.removeAttribute("href");
open.classList.add("disabled");
}
// Prefer live mint (fresh op each time); fall back to static withdraw.uri
fetch("/intro/demo-withdraw.json", { cache: "no-store" })
.then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then(function (d) {
if (!d || !d.taler_withdraw_uri) throw new Error(d && d.error ? d.error : "no uri");
setDemoWithdraw(d.taler_withdraw_uri, d.amount || "");
})
.catch(function () {
return fetch("/intro/withdraw.uri", { cache: "no-store" })
.then(function (r) {
if (!r.ok) throw new Error("no fallback");
return r.text();
})
.then(function (t) {
var uri = String(t || "").trim();
if (!uri) throw new Error("empty");
return fetch("/intro/withdraw.amount", { cache: "no-store" })
.then(function (r) {
return r.ok ? r.text() : Promise.resolve("");
})
.then(function (a) {
setDemoWithdraw(uri, String(a || "").trim());
});
});
})
.catch(function (err) {
if (meta) {
meta.textContent =
"Could not prepare withdraw — try again or use bank UI. " +
(err && err.message ? err.message : "");
}
});
}
loadDemoWithdraw();
/* Collapsible folds (like settlement QR fold) */
function openFold(foldId, btnId, bodyId, onOpen) {
var fold = document.getElementById(foldId);
var btn = document.getElementById(btnId);
var body = document.getElementById(bodyId);
if (!fold || !body) return;
fold.classList.add("open");
if (btn) btn.setAttribute("aria-expanded", "true");
body.removeAttribute("hidden");
if (onOpen) onOpen();
}
function wireFold(foldId, btnId, bodyId, onOpen) {
var fold = document.getElementById(foldId);
var btn = document.getElementById(btnId);
var body = document.getElementById(bodyId);
if (!fold || !btn || !body) return;
btn.addEventListener("click", function () {
var open = !fold.classList.contains("open");
fold.classList.toggle("open", open);
btn.setAttribute("aria-expanded", open ? "true" : "false");
if (open) {
body.removeAttribute("hidden");
if (onOpen) onOpen();
} else {
body.setAttribute("hidden", "");
}
});
}
wireFold("how-fold", "how-fold-toggle", "how-fold-body");
var paintWebuiQrOnce = (function () {
var webuiPainted = false;
return function () {
if (webuiPainted) return;
makeQr(
document.getElementById("qr-webui"),
WEBUI,
document.getElementById("qr-webui-logo")
);
webuiPainted = true;
};
})();
wireFold("more-fold", "more-fold-toggle", "more-fold-body", paintWebuiQrOnce);
(function () {
function createAutoAccount() {
var btn = document.getElementById("auto-account-btn");
var box = document.getElementById("auto-account-box");
var st = document.getElementById("auto-account-status");
var again = document.getElementById("auto-account-again");
if (btn) {
btn.disabled = true;
btn.textContent = "Creating account…";
}
if (st) st.textContent = "";
fetch("/intro/auto-account.json", { cache: "no-store" })
.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((d && d.error) || "create failed");
if (box) box.hidden = false;
var n = document.getElementById("auto-account-name");
var u = document.getElementById("auto-account-user");
var p = document.getElementById("auto-account-pass");
if (n) n.textContent = d.name || d.display_name || "—";
if (u) u.textContent = d.username || "—";
if (p) p.textContent = d.password || "—";
var cEl = document.getElementById("auto-account-created");
if (cEl) {
cEl.textContent =
d.created_human || d.created || "—";
}
if (st) {
st.textContent =
"Account created for you · " +
(d.name || d.username || "") +
" · save username & password";
}
/* Fancy QR_Taler: encode bank UI URL (login with credentials above) */
var qrBlock = document.getElementById("auto-account-qr-block");
var qrLabel = document.getElementById("qr-auto-acct-label");
var webui = d.webui || WEBUI;
if (qrBlock) qrBlock.hidden = false;
if (qrLabel) {
qrLabel.textContent =
"QR_Taler · open bank UI · login as " + (d.username || "you");
}
makeQr(
document.getElementById("qr-auto-acct"),
webui,
document.getElementById("qr-auto-acct-logo")
);
if (btn) {
btn.hidden = true;
btn.disabled = false;
btn.textContent = "Create my bank account →";
}
if (again) again.hidden = false;
})
.catch(function (err) {
if (box) box.hidden = false;
if (st) {
st.textContent =
"Could not create account — try again. " +
(err && err.message ? err.message : "");
}
if (btn) {
btn.disabled = false;
btn.hidden = false;
btn.textContent = "Create my bank account →";
}
});
}
var btn = document.getElementById("auto-account-btn");
var again = document.getElementById("auto-account-again");
if (btn) btn.addEventListener("click", createAutoAccount);
if (again) {
again.addEventListener("click", function () {
var b = document.getElementById("auto-account-btn");
if (b) {
b.hidden = false;
b.disabled = false;
}
again.hidden = true;
createAutoAccount();
});
}
/* Withdraw step: open optional fold + create personal account */
var genOwn = document.getElementById("generate-own-account");
if (genOwn) {
genOwn.addEventListener("click", function () {
openFold("more-fold", "more-fold-toggle", "more-fold-body", paintWebuiQrOnce);
var fold = document.getElementById("more-fold");
if (fold && fold.scrollIntoView) {
fold.scrollIntoView({ behavior: "smooth", block: "start" });
}
createAutoAccount();
});
}
})();
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 loadStats() {
var box = document.getElementById("stats");
var foot = document.getElementById("st-foot");
if (!box) return;
box.hidden = false;
fetch("stats.json", { cache: "no-store" })
.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((d && d.error) || "stats not ok");
var w = d.withdraws || {};
var h24 = w.last_24h || {};
var h7 = w.last_7d || {};
var ba = d.bank_accounts || {};
var wl = d.wallets || {};
function set(id, v) {
var el = document.getElementById(id);
if (el) el.textContent = v == null || v === "" ? "—" : String(v);
}
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");
set("shared-balance", d.balance_explorer || "—");
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.textContent = d.generated_at_human || d.generated_at || "";
}
function msLabel(ms, http) {
if (ms == null || ms === "") return "—";
var s = ms + " ms";
if (http && http !== "200") s += " (" + http + ")";
return s;
}
var p = d.performance || {};
set("st-config-ms", msLabel(p.config_ms, p.config_http));
set("st-int-ms", msLabel(p.integration_ms, p.integration_http));
set("st-webui-ms", msLabel(p.webui_ms, p.webui_http));
set("st-load", p.loadavg || "—");
var mem = p.memory || {};
set("st-mem-ctr", mem.container_rss_human || "—");
set("st-mem-pg", mem.postgres_rss_human
? mem.postgres_rss_human + (mem.postgres_n ? " · " + mem.postgres_n + "p" : "")
: "—");
set("st-mem-java", mem.java_rss_human
? mem.java_rss_human + (mem.java_n ? " · " + mem.java_n + "p" : "")
: "—");
set("st-mem-nginx", mem.nginx_rss_human || "—");
var topBox = document.getElementById("st-mem-top");
if (topBox) {
var tops = mem.top || [];
if (!tops.length) {
topBox.innerHTML = '<p class="meta">—</p>';
} else {
var th = '<table style="width:100%;font-size:0.78rem;border-collapse:collapse"><thead><tr>' +
'<th style="text-align:left;padding:0.25rem;border-bottom:1px solid var(--border)">RSS</th>' +
'<th style="text-align:left;padding:0.25rem;border-bottom:1px solid var(--border)">Comm</th>' +
'<th style="text-align:left;padding:0.25rem;border-bottom:1px solid var(--border)">Command</th></tr></thead><tbody>';
tops.forEach(function (t) {
th += '<tr><td style="padding:0.25rem;color:#5eead4">' + (t.rss_human || "?") +
'</td><td style="padding:0.25rem">' + (t.comm || "") +
'</td><td style="padding:0.25rem;word-break:break-all;color:var(--muted)">' +
(t.cmd || "") + "</td></tr>";
});
th += "</tbody></table>";
topBox.innerHTML = th;
}
}
var pf = document.getElementById("st-perf-foot");
if (pf) {
pf.textContent = "Container RSS + process groups · " +
(d.generated_at_human || d.generated_at || "");
}
return fetch("stats-run.json", { cache: "no-store" })
.then(function (r) { return r.ok ? r.json() : null; })
.catch(function () { return null; });
})
.then(function (run) {
var note = document.getElementById("st-run-note");
if (!note) return;
if (run && run.ok === false) {
note.hidden = false;
note.textContent = "Last refresh failed" +
(run.at_human ? " · " + run.at_human : "") +
" · showing last good data";
} else {
note.hidden = true;
note.textContent = "";
}
})
.catch(function () {
if (foot) {
foot.hidden = false;
foot.className = "stats-foot err";
foot.textContent = "Stats offline";
}
});
}
loadStats();
})();
</script>
<script src="/intro/shop-pay.js"></script>
</body>
</html>