koopa-admin-log/configs/bank-landing/index.html
Hernâni Marques 48f77139c5
landing: compact GOA alt display and all-account bank fallback
Wire goa-amount.js into bank/exchange/merchant intros, deploy it with the
landings, and deepen the in-container bank stats fallback to scan all
accounts (skip only exchange) when the host timer is down.
2026-07-17 08:12:00 +02:00

2082 lines
76 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>
<meta name="description" content="Get your GOA in one step from the shared bank account, try GOA shop samples, pay with GNU Taler — Intergalactic · GOA · no IBAN." />
<link rel="canonical" href="https://bank.hacktivism.ch/intro/" />
<!-- Link previews (chats / social): absolute image + no-IBAN copy matching the page badge -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="hacktivism.ch · GOA" />
<meta property="og:locale" content="en_US" />
<meta property="og:title" content="GOA Exploration Bank · no IBAN" />
<meta property="og:description" content="Intergalactic · GOA · no IBAN. Get your GOA in one step from the shared bank account, browse GOA shop samples, pay with GNU Taler." />
<meta property="og:url" content="https://bank.hacktivism.ch/intro/" />
<meta property="og:image" content="https://bank.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
<meta property="og:image:secure_url" content="https://bank.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="GOA Shop — Intergalactic · GOA · no IBAN — pay with GNU Taler" />
<link rel="image_src" href="https://bank.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="GOA Exploration Bank · no IBAN" />
<meta name="twitter:description" content="Intergalactic · GOA · no IBAN. Get your GOA in one step from the shared bank account, browse GOA shop samples, pay with GNU Taler." />
<meta name="twitter:image" content="https://bank.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
<meta name="twitter:image:alt" content="GOA Shop — Intergalactic · GOA · no IBAN — pay with GNU Taler" />
<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;
}
/* Technical stack strip (software + versions) — top of page */
.stack-bar {
margin: 0;
padding: 0.35rem 0.75rem;
background: #0042b3;
color: #d6e4ff;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.62rem;
font-weight: 500;
letter-spacing: 0.02em;
line-height: 1.35;
text-align: center;
border-bottom: 1px solid rgba(164, 201, 255, 0.35);
}
.stack-bar strong {
color: #fff;
font-weight: 650;
}
.stack-bar .sep { color: rgba(164, 201, 255, 0.55); margin: 0 0.2rem; }
.stack-bar a {
color: #a4c9ff;
text-decoration: none;
border-bottom: 1px solid rgba(164, 201, 255, 0.4);
}
.stack-bar a:hover { color: #fff; border-bottom-color: #fff; }
.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);
padding: 1rem 1.05rem 1.05rem;
margin-bottom: 1rem;
}
.step-card.step-money h2 {
font-size: 1.15rem;
margin: 0 0 0.15rem;
}
.step-card.step-money .step-num {
width: 1.75rem;
height: 1.75rem;
font-size: 1rem;
margin-right: 0.4rem;
}
.step-card.step-money .step-tagline {
margin: 0 0 0.65rem;
font-size: 0.88rem;
}
.step-card.step-money .step-body {
gap: 0.5rem;
}
.step-card.step-money .qr-taler-wrap {
max-width: 200px;
}
.step-card.step-money .qr-taler-wrap .goa-pay-taler-qr {
margin-bottom: 0.3rem;
max-width: 200px;
padding: 7px;
border-radius: 16px;
}
.step-card.step-money .goa-pay-taler-qr__inner {
padding: 7px;
border-radius: 14px;
min-height: 0;
}
.step-card.step-money .qr-label {
margin-top: 0;
font-size: 0.78rem;
}
/* Step-2 wallet open: chip centered under QR */
.step-card.step-money a.cta.wallet-open {
margin: 0.1rem auto 0 !important;
align-self: center !important;
text-align: center;
}
.step-card.step-money a.meta-uri {
margin: 0.1rem 0 0;
font-size: 0.68rem;
max-width: 20rem;
}
.step-card.step-money .balance-note {
margin: 0.35rem 0 0;
padding: 0.4rem 0.55rem;
font-size: 0.76rem;
line-height: 1.35;
}
.step-card.step-money button.gen-own-acct {
margin-top: 0.05rem;
font-size: 0.76rem;
}
.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);
}
/*
* Open in wallet — small chip under QR (must win over .cta fat padding/shadow).
* Not a full CTA slab; sits next to the blue meta-uri in scale.
*/
a.cta.wallet-open,
a.cta.teal.wallet-open {
display: inline-flex !important;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: auto !important;
min-width: 0 !important;
max-width: none !important;
margin: 0.2rem auto 0 !important;
padding: 0.28rem 0.7rem !important;
font-size: 0.78rem !important;
font-weight: 650;
line-height: 1.2;
letter-spacing: 0.01em;
border-radius: 999px !important;
box-shadow: 0 1px 4px rgba(20, 184, 166, 0.22) !important;
background: linear-gradient(135deg, #5eead4 0%, #14b8a6 55%, #0f766e 100%);
color: #042f2e;
white-space: nowrap;
flex: 0 0 auto;
align-self: center !important;
text-align: center;
}
a.cta.wallet-open:hover {
filter: brightness(1.05);
}
a.cta.wallet-open.disabled,
a.cta.wallet-open[href="#"] {
opacity: 0.45;
pointer-events: none;
}
#auto-account-open-withdraw.wallet-open {
margin-top: 0.35rem;
width: auto !important;
max-width: none !important;
}
.meta {
font-size: 0.78rem;
color: var(--muted);
word-break: break-all;
margin: 0.5rem 0 0;
text-align: center;
max-width: 100%;
}
/* Blue payload link under each QR (encoded content) */
a.meta-uri {
display: block;
font-size: 0.68rem;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
color: #7eb6ff;
word-break: break-all;
margin: 0.4rem auto 0;
text-align: center;
max-width: 22rem;
text-decoration: none;
border-bottom: 1px solid rgba(126, 182, 255, 0.4);
line-height: 1.35;
cursor: pointer;
}
a.meta-uri:hover {
color: #b8d6ff;
border-bottom-color: #7eb6ff;
}
.qr-taler-wrap a.meta-uri {
margin-top: 0.45rem;
}
a.meta-uri.disabled,
a.meta-uri[href="#"] {
color: var(--muted);
border-bottom-color: transparent;
cursor: default;
pointer-events: none;
}
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(232, 168, 56, 0.4);
color: #f0d090;
}
.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;
}
/* git branch: solid nodes, stroked edges */
.app-ico.branch-ico circle {
fill: currentColor;
stroke: none;
}
.app-ico.branch-ico path {
fill: none;
stroke: currentColor;
stroke-width: 1.7;
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: #f0c86a; }
.app-btn: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; }
/* command-line marks: gold penguin (deb) · gold terminal (posix) */
button.app-btn.cli-toggle > .app-ico.penguin-ico,
button.app-btn.cli-toggle > .app-ico.console-ico {
width: 1.2rem;
height: 1.2rem;
flex-shrink: 0;
border-radius: 0;
}
button.app-btn.cli-toggle > .app-ico.console-ico rect,
button.app-btn.cli-toggle > .app-ico.console-ico path {
fill: none;
}
.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: #f0c86a; }
.step-tagline .app-ico.android { color: #f0c86a; }
/* Two tech options side-by-side; both open the same panel below */
.cli-tech-row {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
width: 100%;
flex: 1 1 100%;
}
button.app-btn.cli-toggle {
flex: 1 1 calc(50% - 0.25rem);
min-width: 9.5rem;
max-width: 100%;
border-color: rgba(232, 168, 56, 0.4);
color: #f0d090;
font-size: 0.72rem;
cursor: pointer;
font-family: inherit;
font-weight: 650;
width: auto;
justify-content: flex-start;
align-items: center;
gap: 0.4rem;
padding: 0.55rem 0.65rem;
line-height: 1.25;
}
button.app-btn.cli-toggle .cli-label {
flex: 1 1 auto;
text-align: left;
min-width: 0;
}
button.app-btn.cli-toggle .cli-chevron {
margin-left: auto;
flex-shrink: 0;
color: #3ecfbf;
font-size: 1rem;
font-weight: 700;
line-height: 1;
transition: transform 0.15s ease;
}
button.app-btn.cli-toggle[aria-expanded="true"] .cli-chevron {
transform: rotate(90deg);
}
button.app-btn.cli-toggle .app-ico { color: #f0c86a; }
button.app-btn.cli-toggle[aria-expanded="true"] {
border-color: rgba(240, 200, 106, 0.75);
background: rgba(232, 168, 56, 0.12);
}
/* Shared expand area under both tech buttons (does not cover withdraw) */
.cli-panel {
display: none;
width: 100%;
max-width: 100%;
margin: 0.55rem 0 0.25rem;
text-align: left;
border: 1px solid rgba(232, 168, 56, 0.32);
border-radius: 12px;
background: rgba(0, 0, 0, 0.28);
padding: 0.85rem 0.9rem 1rem;
box-sizing: border-box;
position: relative;
z-index: 0;
clear: both;
}
.cli-panel.open { display: block; }
.cli-pane { display: none; }
.cli-pane.active { display: block; }
.step-card[aria-labelledby="step1-title"] .step-body {
align-items: stretch;
}
.step-card[aria-labelledby="step1-title"] .app-links {
width: 100%;
}
#withdraw {
position: relative;
z-index: 1;
}
.cli-panel .cli-lead {
margin: 0 0 0.75rem;
color: var(--muted);
font-size: 0.86rem;
line-height: 1.4;
}
.cli-panel a {
color: #e8c878;
text-decoration: none;
border-bottom: 1px solid rgba(232, 168, 56, 0.35);
}
.cli-panel a:hover {
color: #f0d090;
border-bottom-color: #e8c878;
}
.cli-panel .cli-h {
margin: 0.85rem 0 0.35rem;
font-size: 0.78rem;
font-weight: 750;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #f0c86a;
}
.cli-panel .cli-h:first-of-type { margin-top: 0.15rem; }
.cli-panel .cli-note {
margin: 0 0 0.45rem;
color: var(--muted);
font-size: 0.8rem;
line-height: 1.4;
}
/* Allow selecting/copying individual lines (not whole block at once) */
.cli-block {
margin: 0 0 0.35rem;
padding: 0.65rem 0.7rem;
border-radius: 10px;
background: rgba(0, 0, 0, 0.45);
border: 1px solid rgba(240, 200, 106, 0.22);
color: #e8e0d0;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.7rem;
line-height: 1.45;
overflow-x: auto;
white-space: pre;
-webkit-overflow-scrolling: touch;
-webkit-user-select: text;
user-select: text;
cursor: text;
}
.cli-block .cmt { color: #8a9a90; }
.cli-block .hi { color: #5eead4; }
/* 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);
}
/* Small distinct control under shared withdraw (not a second full CTA) */
button.gen-own-acct {
display: inline-block;
margin: 0.15rem auto 0;
padding: 0.2rem 0.35rem;
border: 0;
border-bottom: 1px solid rgba(240, 200, 106, 0.55);
border-radius: 0;
background: transparent;
color: #f0c86a;
font: inherit;
font-size: 0.8rem;
font-weight: 650;
letter-spacing: 0.01em;
cursor: pointer;
width: auto;
max-width: none;
line-height: 1.35;
text-decoration: none;
}
button.gen-own-acct:hover,
button.gen-own-acct:focus-visible {
color: #ffe6a8;
border-bottom-color: #f0c86a;
background: transparent;
outline: none;
}
button.gen-own-acct:active {
opacity: 0.85;
}
.more-qr {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 1rem 0 0.25rem;
text-align: center;
}
.more-qr .qr-taler-wrap {
transform: scale(0.92);
transform-origin: center top;
}
/* Wallet open under QR: always centered in column */
.more-qr > a.cta.wallet-open {
display: inline-flex !important;
margin-left: auto !important;
margin-right: auto !important;
align-self: center !important;
}
/* Bank login QR: plain box (not wallet / not taler://withdraw) */
.more-qr--browser .qr-browser-wrap {
text-align: center;
max-width: 220px;
}
.more-qr--browser .qr-browser {
display: inline-block;
padding: 0.45rem;
background: #fff;
border-radius: 8px;
border: 1px solid rgba(201, 184, 160, 0.45);
}
.more-qr--browser .qr-browser img {
display: block;
margin: 0 auto;
}
.more-qr--browser .qr-label {
color: #c9b8a0;
font-weight: 650;
max-width: 240px;
}
.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">
<p class="stack-bar" role="note" title="Taler software on this bank service (links → git.taler.net tags)">
<strong>stack</strong><span class="sep">·</span>
<a href="https://git.taler.net/taler/libeufin/commit/ab00480dd9b3be50704fb449b750c764fd4c1de0.html" target="_blank" rel="noopener noreferrer" title="libeufin tag v1.6.6">libeufin-bank&nbsp;1.6.6</a>
<span class="sep">·</span>
<a href="https://git.taler.net/taler/libeufin/commit/ab00480dd9b3be50704fb449b750c764fd4c1de0.html" target="_blank" rel="noopener noreferrer" title="libeufin tag v1.6.6">libeufin-common&nbsp;1.6.6</a>
</p>
<main>
<header class="hero">
<span class="badge">Intergalactic · GOA · no IBAN</span>
<h1>GOA Exploration Bank</h1>
<p class="sub">
Get your <strong>GOA in one step</strong> from the
<strong>shared bank account</strong> into your wallet — 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>
<!-- Linux (left) · POSIX (right) — same expand area below -->
<div class="cli-tech-row" role="group" aria-label="Command-line wallet paths">
<button type="button" class="app-btn hub cli-toggle" id="deb-cli-toggle"
aria-expanded="false" aria-controls="cli-shared-body"
data-cli-pane="cli-pane-deb"
aria-label="Debian/Ubuntu · Command line">
<svg class="app-ico browser-ico penguin-ico" viewBox="0 0 24 24" width="20" height="20" role="img" aria-label="Linux penguin">
<path fill="#f0c86a" d="M12 2.8c-2.2 0-3.8 1.7-3.8 3.6 0 .5.1 1 .3 1.4C6.4 8.8 5 11 5 13.6c0 2.6 1.3 4.5 3.2 5.6-.2.5-.3 1-.3 1.5 0 1.2.9 2.2 2.2 2.2.7 0 1.3-.3 1.7-.8.4.1.8.2 1.2.2s.8-.1 1.2-.2c.4.5 1 .8 1.7.8 1.3 0 2.2-1 2.2-2.2 0-.5-.1-1-.3-1.5 1.9-1.1 3.2-3 3.2-5.6 0-2.6-1.4-4.8-3.5-5.8.2-.4.3-.9.3-1.4C15.8 4.5 14.2 2.8 12 2.8z"/>
<ellipse cx="12" cy="14.5" rx="3.2" ry="4.2" fill="#1a1410" opacity="0.28"/>
<circle cx="10.4" cy="6.6" r="0.75" fill="#1a1410"/>
<circle cx="13.6" cy="6.6" r="0.75" fill="#1a1410"/>
<path fill="#e8a838" d="M11.2 8 12 9.4 12.8 8z"/>
<ellipse cx="9.2" cy="21.2" rx="1.8" ry="0.9" fill="#e8a838"/>
<ellipse cx="14.8" cy="21.2" rx="1.8" ry="0.9" fill="#e8a838"/>
</svg>
<span class="cli-label">Debian/Ubuntu · Command line</span>
<span class="cli-chevron" aria-hidden="true"></span>
</button>
<button type="button" class="app-btn hub cli-toggle" id="unix-cli-toggle"
aria-expanded="false" aria-controls="cli-shared-body"
data-cli-pane="cli-pane-unix"
aria-label="Unix / POSIX · from source">
<svg class="app-ico browser-ico console-ico" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
<rect x="2.5" y="4" width="19" height="15" rx="2.2" fill="none" stroke="#f0c86a" stroke-width="1.7"/>
<path d="M6.5 9.2 9.8 12 6.5 14.8" fill="none" stroke="#f0c86a" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5 14.8h5.2" fill="none" stroke="#f0c86a" stroke-width="1.7" stroke-linecap="round"/>
</svg>
<span class="cli-label">Unix / POSIX · from source</span>
<span class="cli-chevron" aria-hidden="true"></span>
</button>
</div>
</div>
<!-- Shared expand area under both tech buttons (sibling of app-links; above withdraw) -->
<div class="cli-panel" id="cli-shared-body" hidden>
<div class="cli-pane" id="cli-pane-deb">
<p class="cli-lead">
Debian/Ubuntu ·
<a href="https://deb.taler.net/" target="_blank" rel="noopener noreferrer">deb.taler.net</a>
·
<a href="https://docs.taler.net/manpages/taler-wallet-cli.1.html" target="_blank" rel="noopener noreferrer">taler-wallet-cli</a>
· mint
<a href="https://bank.hacktivism.ch/intro/demo-withdraw.json" target="_blank" rel="noopener noreferrer">demo-withdraw.json</a>
</p>
<pre class="cli-block"># install (deb.taler.net · trixie): keyring, apt source, taler-wallet-cli + wget
sudo mkdir -p /etc/apt/keyrings &amp;&amp; \
sudo wget -q -O /etc/apt/keyrings/taler-systems.gpg https://taler.net/taler-systems.gpg &amp;&amp; \
echo 'deb [signed-by=/etc/apt/keyrings/taler-systems.gpg] https://deb.taler.net/apt/debian trixie main' | sudo tee /etc/apt/sources.list.d/taler.list &amp;&amp; \
sudo apt-get update &amp;&amp; sudo apt-get install -y taler-wallet-cli wget
# private wallet DB under /tmp (not the default home DB)
alias tw='taler-wallet-cli --wallet-db="${TMPDIR:-/tmp}/taler-wallet-goa.sqlite3" --no-throttle' &amp;&amp; \
# mint from bank, register exchange, withdraw GOA:10
wget -q -O demo-withdraw.json https://bank.hacktivism.ch/intro/demo-withdraw.json &amp;&amp; \
tw exchanges add https://exchange.hacktivism.ch/ &amp;&amp; \
tw exchanges accept-tos https://exchange.hacktivism.ch/ &amp;&amp; \
tw withdraw accept-uri --exchange https://exchange.hacktivism.ch/ \
"$(grep -o 'taler://[^"]*' demo-withdraw.json)" &amp;&amp; \
tw run-until-done &amp;&amp; \
# pay orbit-sticker (GOA:2); balance ends at GOA:8
tw handle-uri --yes \
taler://pay-template/taler.hacktivism.ch/instances/goa-shop/orbit-sticker &amp;&amp; \
tw run-until-done &amp;&amp; tw balance</pre>
</div>
<div class="cli-pane" id="cli-pane-unix">
<p class="cli-lead">
Unix / macOS ·
<a href="https://git.taler.net/taler-typescript-core.git" target="_blank" rel="noopener noreferrer">taler-typescript-core</a>
· mint
<a href="https://bank.hacktivism.ch/intro/demo-withdraw.json" target="_blank" rel="noopener noreferrer">demo-withdraw.json</a>
</p>
<pre class="cli-block"># install: git + node (LTS) + build tools; then monorepo
git clone https://git.taler.net/taler-typescript-core.git &amp;&amp; cd taler-typescript-core &amp;&amp; ./bootstrap &amp;&amp; ./configure &amp;&amp; make
# monorepo CLI + private wallet DB (not system package / default home DB)
alias tw='node packages/taler-wallet-cli/bin/taler-wallet-cli.mjs --wallet-db="${TMPDIR:-/tmp}/taler-wallet-goa.sqlite3" --no-throttle' &amp;&amp; \
# mint from bank, register exchange, withdraw GOA:10
(wget -q -O demo-withdraw.json https://bank.hacktivism.ch/intro/demo-withdraw.json || curl -fsS -o demo-withdraw.json https://bank.hacktivism.ch/intro/demo-withdraw.json) &amp;&amp; \
tw exchanges add https://exchange.hacktivism.ch/ &amp;&amp; \
tw exchanges accept-tos https://exchange.hacktivism.ch/ &amp;&amp; \
tw withdraw accept-uri --exchange https://exchange.hacktivism.ch/ \
"$(grep -o 'taler://[^"]*' demo-withdraw.json)" &amp;&amp; \
tw run-until-done &amp;&amp; \
# pay orbit-sticker (GOA:2); balance ends at GOA:8
tw handle-uri --yes \
taler://pay-template/taler.hacktivism.ch/instances/goa-shop/orbit-sticker &amp;&amp; \
tw run-until-done &amp;&amp; tw balance</pre>
</div>
</div>
</div>
</section>
<!-- ========== STEP 2: one-click community pool withdraw (compact) ========== -->
<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 step · shared pool · 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>
<a class="meta-uri" id="withdraw-demo-meta" href="#">Preparing withdraw…</a>
</div>
<a class="cta teal wallet-open" id="open-withdraw-demo" href="#">
Open in wallet →
</a>
<button type="button" class="gen-own-acct" id="generate-own-account">
Generate own account
</button>
<p class="balance-note" role="note">
<strong>Community pool balance:</strong>
<strong id="shared-balance"></strong> sits in the shared bank account
(everyone draws from the same pool — not your personal wallet).
Need more? Withdraw again from this pool, or create an own account
and fund it separately.
</p>
</div>
</section>
<!-- ========== GOA shop (after withdraw, always visible) ========== -->
<aside class="shop-box" aria-labelledby="shop-title">
<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 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: own bank UI account (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 · own bank account</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.
After create, the <strong>wallet QR is a real <code>taler://withdraw/…</code></strong>
(same shared pool as step 2) — not payto, not the bank web UI.
</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 username &amp; password now — not stored for recovery.
<strong>Log in at the bank SPA:</strong>
<a class="meta-uri" href="https://bank.hacktivism.ch/webui/" id="auto-account-login-hint"
target="_blank" rel="noopener noreferrer" style="display:inline;margin:0;border-bottom:1px solid rgba(126,182,255,.5)">https://bank.hacktivism.ch/webui/</a>
(this intro page has no login form — use the bank UI link).
</p>
<dl class="own-acct-dl">
<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>
</dl>
<p class="meta" id="auto-account-status" style="margin:0.5rem 0 0"></p>
<!-- Wallet QR only: shared-pool taler://withdraw (same as step 2). Never payto / webui. -->
<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">Wallet only · taler://withdraw (shared pool)</div>
<a class="meta-uri" id="qr-auto-acct-payload" href="#"></a>
</div>
<a class="cta teal wallet-open" id="auto-account-open-withdraw" href="#">
Open in wallet →
</a>
</div>
</div>
<!-- Browser login only — plain QR, no Taler wallet ring/logo (must not look like withdraw) -->
<div class="more-qr more-qr--browser" id="webui-qr-block">
<div class="qr-browser-wrap">
<div class="qr-browser" id="qr-webui"></div>
<div class="qr-label">Browser only · bank login page (not a wallet / withdraw QR)</div>
<a class="meta-uri" id="qr-webui-payload" href="https://bank.hacktivism.ch/webui/" target="_blank" rel="noopener noreferrer">https://bank.hacktivism.ch/webui/</a>
</div>
</div>
<div class="more-actions">
<a class="cta-sec" href="https://bank.hacktivism.ch/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>
</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 (10)</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 src="/intro/goa-amount.js"></script>
<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) */
/* Prefer PNG: Android WebView often fails the official Penpot SVG
(root fill="none" + wordmark paths inherit black from a parent <g>
→ blue T only, missing "taler"). PNG matches taler-assets qr-logo.png. */
function logoSrc() {
try {
var b = document.querySelector("base");
if (b && b.href) return new URL("qr-logo.png", b.href).href;
} catch (e) {}
return "/intro/qr-logo.png";
}
function setQrLogo(imgEl) {
if (!imgEl) return;
imgEl.alt = "Taler";
imgEl.width = 100;
imgEl.height = 50;
imgEl.decoding = "async";
imgEl.src = logoSrc();
imgEl.onerror = function () {
/* fallback to fixed SVG if PNG missing */
try {
var b = document.querySelector("base");
imgEl.src = b && b.href
? new URL("qr-logo.svg", b.href).href
: "/intro/qr-logo.svg";
} catch (e2) {
imgEl.style.display = "none";
}
};
imgEl.style.display = "";
}
function makeQr(el, text, logoEl, sizeOpt) {
if (!el) return;
el.innerHTML = "";
var size = sizeOpt || 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) {
/* Strip default :443 — libeufin adds it; wallets prefer host without port */
uri = String(uri || "")
.trim()
.replace(/(taler:\/\/withdraw\/[^/:]+):443(?=\/|$)/g, "$1");
var open = document.getElementById("open-withdraw-demo");
var meta = document.getElementById("withdraw-demo-meta");
if (open) {
open.href = uri;
open.classList.remove("disabled");
}
if (meta) {
meta.href = uri;
meta.textContent = uri;
meta.classList.remove("disabled");
meta.setAttribute("title", "Open in wallet");
}
makeQr(
document.getElementById("qr-withdraw-demo"),
uri,
document.getElementById("qr-withdraw-demo-logo"),
176
);
}
function loadDemoWithdraw() {
var meta = document.getElementById("withdraw-demo-meta");
var open = document.getElementById("open-withdraw-demo");
if (meta) {
meta.textContent = "Preparing withdraw…";
meta.href = "#";
meta.classList.add("disabled");
}
if (open) {
open.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");
(function wireCliFolds() {
var panel = document.getElementById("cli-shared-body");
var btns = [
document.getElementById("deb-cli-toggle"),
document.getElementById("unix-cli-toggle")
].filter(Boolean);
if (!panel || !btns.length) return;
var panes = panel.querySelectorAll(".cli-pane");
function showPane(paneId) {
for (var i = 0; i < panes.length; i++) {
panes[i].classList.toggle("active", panes[i].id === paneId);
}
}
function setExpanded(activeBtn) {
for (var i = 0; i < btns.length; i++) {
btns[i].setAttribute(
"aria-expanded",
btns[i] === activeBtn ? "true" : "false"
);
}
}
function closeAll() {
panel.classList.remove("open");
panel.setAttribute("hidden", "");
for (var i = 0; i < panes.length; i++) panes[i].classList.remove("active");
setExpanded(null);
}
btns.forEach(function (btn) {
btn.addEventListener("click", function () {
var paneId = btn.getAttribute("data-cli-pane");
var already =
panel.classList.contains("open") &&
btn.getAttribute("aria-expanded") === "true";
if (already) {
closeAll();
return;
}
showPane(paneId);
panel.classList.add("open");
panel.removeAttribute("hidden");
setExpanded(btn);
});
});
})();
var paintWebuiQrOnce = (function () {
var webuiPainted = false;
return function () {
if (webuiPainted) return;
/* Plain QR (no Taler logo ring) — browser login only, not withdraw */
makeQr(document.getElementById("qr-webui"), WEBUI, null, 160);
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 u = document.getElementById("auto-account-user");
var p = document.getElementById("auto-account-pass");
var webuiA = document.getElementById("auto-account-webui");
if (u) u.textContent = d.username || d.name || d.display_name || "—";
if (p) p.textContent = d.password || "—";
/* Absolute HTTPS bank UI (login only — not for wallet QR) */
var webui = d.webui || d.account_url || WEBUI;
if (!/^https?:\/\//i.test(webui)) {
webui = WEBUI;
}
if (webuiA) {
webuiA.href = webui;
webuiA.setAttribute("target", "_blank");
webuiA.setAttribute("rel", "noopener noreferrer");
}
/* Wallet QR + blue link + Open in wallet: taler://withdraw only */
var withdrawUri = "";
if (d.taler_withdraw_uri && String(d.taler_withdraw_uri).indexOf("taler://withdraw/") === 0) {
withdrawUri = String(d.taler_withdraw_uri).trim();
} else if (d.qr_payload && String(d.qr_payload).indexOf("taler://withdraw/") === 0) {
withdrawUri = String(d.qr_payload).trim();
}
if (st) {
st.textContent = withdrawUri
? ("Account created · " + (d.username || "") +
" · save username & password · wallet QR = taler://withdraw (shared pool, like step 2)")
: ("Account created · " + (d.username || "") +
" · but withdraw URI missing — refresh / try again");
}
var qrBlock = document.getElementById("auto-account-qr-block");
var qrLabel = document.getElementById("qr-auto-acct-label");
var openW = document.getElementById("auto-account-open-withdraw");
var autoPayload = document.getElementById("qr-auto-acct-payload");
if (qrBlock) qrBlock.hidden = !withdrawUri;
if (qrLabel) {
qrLabel.textContent =
"Scan in wallet · shared pool" +
(d.withdraw_amount ? " · " + d.withdraw_amount : "") +
" · taler://withdraw";
}
if (autoPayload) {
if (withdrawUri) {
autoPayload.href = withdrawUri;
autoPayload.textContent = withdrawUri;
autoPayload.classList.remove("disabled");
} else {
autoPayload.href = "#";
autoPayload.textContent = "—";
autoPayload.classList.add("disabled");
}
autoPayload.removeAttribute("target");
}
if (openW) {
if (withdrawUri) {
openW.href = withdrawUri;
openW.classList.remove("disabled");
} else {
openW.href = "#";
openW.classList.add("disabled");
}
}
if (withdrawUri) {
makeQr(
document.getElementById("qr-auto-acct"),
withdrawUri,
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);
}
function setAmt(id, rowOrAmt) {
var el = document.getElementById(id);
if (!el) return;
if (window.GoaAmount) {
var f = GoaAmount.pick(rowOrAmt, d.alt_unit_names);
el.textContent = f.display;
if (f.title && f.title !== f.display) el.setAttribute("title", f.title);
else el.removeAttribute("title");
} else {
var raw = rowOrAmt && typeof rowOrAmt === "object"
? (rowOrAmt.amount_alt || rowOrAmt.amount || rowOrAmt.total_amount || "—")
: (rowOrAmt == null || rowOrAmt === "" ? "—" : String(rowOrAmt));
el.textContent = raw;
}
}
if (window.GoaAmount) GoaAmount.rememberAlt(d);
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 : "—");
setAmt("st-incoming", fin.amount_alt ? fin : (fin.amount || flow.total_in || "—"));
setAmt("st-withdraw", fwd.amount_alt ? fwd : (fwd.amount || w.total_amount || "—"));
setAmt("st-24h", h24.amount_alt ? h24 : (h24.amount || "GOA:0"));
setAmt("st-7d", h7.amount_alt ? h7 : (h7.amount || "GOA:0"));
setAmt("shared-balance", d.balance_explorer_alt
? { amount: d.balance_explorer, amount_alt: d.balance_explorer_alt, amount_full: d.balance_explorer_full }
: (d.balance_explorer || "—"));
var list = document.getElementById("st-recent");
if (list) {
list.innerHTML = "";
/* Full list from stats.json (generator emits 10) */
var rows = (d.recent_withdraws || []).slice(0, 10);
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";
if (window.GoaAmount) {
var af = GoaAmount.pick(row, d.alt_unit_names);
amt.textContent = af.display;
if (af.title && af.title !== af.display) amt.setAttribute("title", af.title);
} else {
amt.textContent = row.amount_alt || row.amount || "?";
}
var meta = document.createElement("span");
meta.className = "meta";
var when = fmtCest(row.at_unix, row.at || row.at_iso) || "";
var who = row.account ? " · " + row.account : "";
meta.textContent = when + who;
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>