koopa-admin-log/configs/merchant-landing/index.html
Hernâni Marques fb1a0b0b8e
landing: kGOA units, full money stats, rootless resource display
Public bank/exchange/merchant landing stats and performance UI only.
Secret goa-ui overlay stays on local/merchant-secret-ui (never push).
2026-07-17 18:13:13 +02:00

1129 lines
53 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 Merchant · dual currency</title>
<meta name="description" content="GOA merchant dual currency · sample shop products, pay with GNU Taler (GOA / CHF demo)." />
<link rel="canonical" href="https://taler.hacktivism.ch/intro/" />
<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 Merchant · dual currency" />
<meta property="og:description" content="Sample GOA shop products and dual-currency merchant flow — pay with GNU Taler." />
<meta property="og:url" content="https://taler.hacktivism.ch/intro/" />
<meta property="og:image" content="https://taler.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
<meta property="og:image:secure_url" content="https://taler.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://taler.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="GOA Merchant · dual currency" />
<meta name="twitter:description" content="Sample GOA shop products and dual-currency merchant flow — pay with GNU Taler (Intergalactic · GOA · no IBAN)." />
<meta name="twitter:image" content="https://taler.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" />
<base href="/intro/" />
<link rel="stylesheet" href="/intro/shop-pay.css" />
<script src="/intro/qrcode.min.js"></script>
<style>
:root {
--bg: #120f18;
--card: rgba(36, 28, 48, 0.88);
--text: #f4eefc;
--muted: #b8a8c9;
--accent: #c084fc;
--accent2: #a78bfa;
--border: rgba(167, 139, 250, 0.32);
--glow-violet: rgba(168, 85, 247, 0.38);
--glow-indigo: rgba(99, 102, 241, 0.28);
--chf: #93c5fd;
--goa: #c4b5fd;
--val: #e9d5ff;
--pay: #86efac;
--ref: #fca5a5;
}
* { 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(76, 29, 149, 0.45) 0%, transparent 55%),
radial-gradient(circle 420px at 12% 18%, var(--glow-violet) 0%, transparent 62%),
radial-gradient(circle 380px at 88% 14%, var(--glow-indigo) 0%, transparent 58%),
linear-gradient(165deg, #1e1530 0%, #120f18 40%, #0f1220 72%, #0c0e18 100%);
background-attachment: fixed;
}
main { position: relative; z-index: 1; max-width: 42rem; margin: 0 auto; padding: 2.25rem 1.25rem 3.5rem; }
.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.25rem; }
h1 {
font-size: 1.85rem; font-weight: 750; margin: 0 0 0.45rem; letter-spacing: -0.02em;
background: linear-gradient(120deg, #faf5ff 0%, #d8b4fe 40%, #818cf8 100%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.sub { color: var(--muted); margin: 0 auto; max-width: 30rem; font-size: 1.02rem; }
.sub a {
color: #5eead4; text-decoration: none; border-bottom: 1px solid rgba(94, 234, 212, 0.35);
font-weight: 650;
}
.sub a:hover { border-bottom-color: #5eead4; }
.badge {
display: inline-block;
background: linear-gradient(135deg, rgba(168, 85, 247, 0.22), rgba(99, 102, 241, 0.18));
color: #e9d5ff; font-size: 0.75rem; font-weight: 700;
padding: 0.2rem 0.7rem; border-radius: 999px; margin: 0 0 0.75rem;
border: 1px solid rgba(167, 139, 250, 0.45); letter-spacing: 0.04em;
}
.money-hint {
margin: 0 0 1.15rem;
padding: 0.85rem 0.95rem 0.95rem;
border-radius: 12px;
background: rgba(94, 234, 212, 0.1);
border: 1px solid rgba(94, 234, 212, 0.4);
color: #ccfbf1;
font-size: 0.9rem;
line-height: 1.4;
text-align: center;
}
.money-hint strong { color: #5eead4; font-weight: 750; }
.money-hint a {
color: #5eead4; font-weight: 700; text-decoration: none;
border-bottom: 1px solid rgba(94, 234, 212, 0.4);
}
.money-hint a:hover { border-bottom-color: #5eead4; }
.money-hint .money-hint-detail {
display: inline-block;
margin-top: 0.35rem;
font-size: 0.84rem;
color: #99f6e4;
opacity: 0.92;
}
.money-hint .money-hint-cta {
display: inline-block;
margin-top: 0.55rem;
padding: 0.45rem 0.9rem;
border-radius: 10px;
border: 0;
border-bottom: 0;
background: linear-gradient(135deg, #5eead4, #0d9488);
color: #042f2e;
font-weight: 750;
font-size: 0.88rem;
text-decoration: none;
}
.money-hint .money-hint-cta:hover {
filter: brightness(1.06);
border-bottom: 0;
}
.stats {
background: rgba(24, 18, 40, 0.65);
border: 1px solid rgba(167, 139, 250, 0.32);
border-radius: 16px; padding: 1.1rem 1.15rem 1.2rem; margin: 0 0 1.15rem;
}
.stats h2 {
margin: 0 0 0.35rem; font-size: 0.78rem; font-weight: 700;
letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); text-align: center;
}
.stats .note {
text-align: center; font-size: 0.82rem; color: var(--muted); margin: 0 0 0.85rem;
}
.stats-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 0.55rem 0.7rem;
}
@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.5rem 0.6rem;
}
.stat .k {
display: block; font-size: 0.68rem; color: var(--muted);
letter-spacing: 0.04em; text-transform: uppercase; margin-bottom: 0.15rem; text-align: center;
}
.stat .v {
display: block; font-size: 1.05rem; font-weight: 750; color: var(--val);
text-align: center; font-variant-numeric: tabular-nums;
}
.stat .v.teal { color: var(--accent2); }
/* Side-by-side currencies — static panels, never links */
.cur-pair {
display: grid; grid-template-columns: 1fr 1fr; gap: 0.65rem; margin-top: 0.85rem;
}
@media (max-width: 380px) { .cur-pair { grid-template-columns: 1fr; } }
.cur-panel {
border-radius: 14px; padding: 0.85rem 0.75rem 0.9rem;
background: rgba(0, 0, 0, 0.28);
border: 1px solid rgba(167, 139, 250, 0.25);
pointer-events: none; user-select: text;
}
.cur-panel.chf { border-color: rgba(147, 197, 253, 0.45); }
.cur-panel.goa { border-color: rgba(196, 181, 253, 0.5); }
.cur-panel .cur-name {
text-align: center; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em;
text-transform: uppercase; margin: 0 0 0.15rem;
}
.cur-panel.chf .cur-name { color: var(--chf); }
.cur-panel.goa .cur-name { color: var(--goa); }
.cur-panel .cur-sub {
text-align: center; font-size: 0.68rem; color: var(--muted); margin: 0 0 0.55rem;
}
.cur-panel .amt-big {
display: block; text-align: center; font-size: 1.28rem; font-weight: 800;
font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
color: var(--val); margin: 0 0 0.15rem; word-break: break-word;
}
.cur-panel.chf .amt-big { color: #bfdbfe; }
.cur-panel.goa .amt-big { color: #ddd6fe; }
.cur-panel .amt-label {
display: block; text-align: center; font-size: 0.68rem; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.55rem;
}
.cur-meta {
display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem 0.4rem;
font-size: 0.8rem; text-align: center;
}
.cur-meta .mk { display: block; font-size: 0.64rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.cur-meta .mv { display: block; font-weight: 700; color: var(--val); font-variant-numeric: tabular-nums; }
.cur-panel .amt-secondary {
margin-top: 0.55rem; text-align: center; font-size: 0.78rem; color: var(--muted);
}
.cur-panel .amt-secondary strong { color: var(--val); font-weight: 700; }
/* Recent activity — split by currency */
.act-pair {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
margin-top: 0.35rem;
}
@media (max-width: 480px) {
.act-pair { grid-template-columns: 1fr; }
}
.act-col {
background: rgba(24, 18, 40, 0.45);
border: 1px solid rgba(167, 139, 250, 0.22);
border-radius: 12px;
padding: 0.55rem 0.55rem 0.35rem;
min-width: 0;
}
.act-col-h {
margin: 0 0 0.35rem;
font-size: 0.78rem;
font-weight: 750;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--goa);
text-align: center;
}
.act-col.chf .act-col-h { color: var(--chf); }
.act-list { list-style: none; margin: 0; padding: 0; }
.act-item {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 0.25rem 0.45rem;
align-items: center;
padding: 0.4rem 0.2rem;
border-bottom: 1px solid rgba(255,255,255,0.06);
font-size: 0.82rem;
}
.act-item:last-child { border-bottom: 0; }
.act-kind {
font-size: 0.58rem; font-weight: 800; letter-spacing: 0.05em;
text-transform: uppercase; padding: 0.14rem 0.32rem; border-radius: 6px;
align-self: center;
}
.act-kind.payment { color: #14532d; background: rgba(134, 239, 172, 0.85); }
/* violet — refund is not an error */
.act-kind.refund { color: #f5e8ff; background: rgba(167, 139, 250, 0.92); }
.act-kind.other { color: #1e1b4b; background: rgba(196, 181, 253, 0.85); }
.act-main { min-width: 0; }
.act-amt {
font-weight: 800; font-variant-numeric: tabular-nums; color: var(--val);
font-size: 0.88rem;
}
.act-sum {
color: var(--muted); font-size: 0.72rem; white-space: nowrap;
overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.act-meta {
text-align: right; font-size: 0.62rem; color: var(--muted);
font-variant-numeric: tabular-nums; white-space: nowrap;
}
.empty { text-align: center; color: var(--muted); font-size: 0.8rem; padding: 0.45rem 0.2rem; }
.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;
}
.unit-legend {
margin: 0.45rem auto 0; border-collapse: collapse; font-size: 0.68rem; color: var(--muted);
display: inline-table;
}
.unit-legend th, .unit-legend td {
padding: 0.1rem 0.4rem; border-bottom: 1px solid rgba(167, 139, 250, 0.15); text-align: left;
}
.unit-legend th { font-weight: 700; font-size: 0.6rem; text-transform: uppercase; }
.unit-legend strong { color: var(--goa); }
.step-card {
background: var(--card); border: 1px solid var(--border); border-radius: 18px;
padding: 1.3rem 1.25rem 1.4rem; margin-bottom: 1.2rem;
}
.step-card h2 {
font-size: 1.2rem; font-weight: 800; margin: 0 0 0.4rem; text-align: center;
}
.step-tagline { margin: 0 0 1rem; color: var(--muted); text-align: center; font-size: 0.95rem; }
.app-links {
display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; width: 100%;
margin-bottom: 0.85rem;
}
.app-btn {
display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
min-width: 9rem; text-decoration: none; font-weight: 700; font-size: 0.88rem;
padding: 0.55rem 0.85rem; border-radius: 10px;
border: 1px solid rgba(167, 139, 250, 0.45); background: rgba(0,0,0,0.28); color: #e9d5ff;
}
.app-btn.ios { border-color: rgba(232, 168, 56, 0.45); color: #f0d090; }
.app-btn.hub {
flex: 1 1 100%; border-color: rgba(184, 168, 201, 0.35); color: var(--muted);
font-weight: 600; font-size: 0.82rem; gap: 0.5rem;
}
.app-ico { width: 1.15rem; height: 1.15rem; flex-shrink: 0; display: block; }
/* monochrome app icons only — browser-ico uses multi-colour fill attrs */
.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.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-btn.hub .app-ico.branch-ico { color: #f0c86a; }
.app-btn:not(.ios):not(.hub) .app-ico { color: #c4b5fd; }
.app-btn.ios .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.1em;
}
.step-tagline .app-ico.android { color: #c4b5fd; }
.step-tagline .app-ico.ios { color: #f0c86a; }
.meta { font-size: 0.78rem; color: var(--muted); word-break: break-all; text-align: center; margin: 0.4rem 0 0.75rem; }
.links { display: flex; flex-direction: column; gap: 0.55rem; }
.cta {
display: block; text-align: center; text-decoration: none; font-weight: 750;
padding: 0.7rem 1.1rem; border-radius: 12px;
background: linear-gradient(135deg, #c084fc, #7c3aed); color: #1a0b2e;
box-shadow: 0 8px 22px rgba(124, 58, 237, 0.35);
}
.cta.teal {
background: linear-gradient(135deg, #a78bfa, #6366f1); color: #0f0a1a;
}
.cta-sec {
display: block; text-align: center; text-decoration: none; color: var(--accent2);
border: 1px solid rgba(167, 139, 250, 0.45); padding: 0.55rem 0.85rem;
border-radius: 10px; font-weight: 600; font-size: 0.92rem;
}
footer { margin-top: 1.75rem; color: var(--muted); font-size: 0.85rem; text-align: center; }
footer a { color: #d8b4fe; }
code {
font-family: ui-monospace, Menlo, monospace; font-size: 0.86em;
background: rgba(0,0,0,0.35); padding: 0.1em 0.35em; border-radius: 4px; color: #e9d5ff;
}
.shop-box {
background: rgba(24, 18, 40, 0.65);
border: 1px solid rgba(167, 139, 250, 0.38);
border-radius: 16px;
padding: 1.15rem 1.1rem 1.25rem;
margin: 0 0 1.2rem;
}
.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.85rem; text-align: center; font-size: 0.9rem; color: var(--muted);
}
.shop-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 0.55rem;
}
@media (min-width: 420px) { .shop-grid { grid-template-columns: 1fr 1fr 1fr; } }
.shop-item {
background: rgba(0, 0, 0, 0.28);
border: 1px solid rgba(167, 139, 250, 0.28);
border-radius: 12px;
padding: 0.65rem 0.55rem 0.7rem;
text-align: center; min-width: 0;
cursor: pointer;
transition: border-color 0.15s, transform 0.12s, background 0.15s;
text-decoration: none;
color: inherit;
display: block;
}
.shop-item:hover, .shop-item:focus-visible {
border-color: rgba(196, 181, 253, 0.75);
background: rgba(0, 0, 0, 0.4);
transform: translateY(-1px);
outline: none;
}
.shop-item .emoji { display: block; font-size: 1.4rem; line-height: 1.2; margin-bottom: 0.25rem; }
.shop-item .emoji.pill-duo {
display: flex; justify-content: center; align-items: center;
min-height: 1.5rem; margin-bottom: 0.35rem;
}
.shop-item .pill-svg { display: block; }
.shop-item .name {
display: block; font-size: 0.8rem; font-weight: 700; color: #faf5ff; line-height: 1.25;
}
.shop-item .price {
display: block; margin-top: 0.25rem; font-size: 0.82rem; font-weight: 750;
color: #c4b5fd; font-variant-numeric: tabular-nums;
}
.shop-item .hint {
display: block; margin-top: 0.2rem; font-size: 0.65rem; font-weight: 600;
color: var(--muted); line-height: 1.25; letter-spacing: 0.01em;
}
/* Wallet: collapsed box, expand like settlement QR panel */
.wallet-fold {
margin: 0 0 1.15rem;
border: 1px solid rgba(167, 139, 250, 0.38);
border-radius: 14px;
overflow: hidden;
background: rgba(36, 28, 48, 0.72);
}
.wallet-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: #e9d5ff;
font: inherit;
font-size: 0.92rem;
font-weight: 700;
text-align: left;
cursor: pointer;
line-height: 1.3;
}
.wallet-fold-toggle:hover {
background: rgba(167, 139, 250, 0.12);
color: #faf5ff;
}
.wallet-fold-toggle-text { flex: 1; }
.wallet-fold-chevron {
flex-shrink: 0;
transition: transform 0.15s ease;
color: #3ecfbf;
font-size: 1.1rem;
font-weight: 700;
}
.wallet-fold.open .wallet-fold-chevron { transform: rotate(90deg); }
.wallet-fold-body {
display: none;
padding: 0.15rem 0.9rem 1rem;
border-top: 1px solid rgba(167, 139, 250, 0.28);
}
.wallet-fold.open .wallet-fold-body { display: block; }
.wallet-fold-body .app-links { margin-top: 0.65rem; }
/* Expandable package / source rows (blue like bank) */
button.app-btn.cli-toggle {
flex: 1 1 100%;
width: 100%;
justify-content: flex-start;
gap: 0.5rem;
font: inherit;
font-weight: 700;
font-size: 0.88rem;
cursor: pointer;
color: #e9d5ff;
border-color: rgba(167, 139, 250, 0.4);
background: rgba(0,0,0,0.28);
}
button.app-btn.cli-toggle .cli-chevron {
margin-left: auto;
flex-shrink: 0;
color: #3ecfbf;
font-size: 1.15rem;
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[aria-expanded="true"] {
border-color: rgba(62, 207, 191, 0.5);
background: rgba(62, 207, 191, 0.08);
}
.cli-panel {
display: none;
width: 100%;
margin: 0.35rem 0 0.55rem;
text-align: left;
border: 1px solid rgba(167, 139, 250, 0.32);
border-radius: 12px;
background: rgba(0, 0, 0, 0.28);
padding: 0.75rem 0.85rem 0.9rem;
box-sizing: border-box;
}
.cli-panel.open { display: block; }
.cli-panel .cli-lead {
margin: 0 0 0.55rem;
color: var(--muted);
font-size: 0.84rem;
line-height: 1.4;
}
.cli-panel .cli-lead a { color: #c4b5fd; }
.cli-block {
margin: 0;
padding: 0.55rem 0.65rem;
border-radius: 10px;
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(167, 139, 250, 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;
user-select: all;
}
.cli-block .cmt { color: #8a9a90; }
.sym-gold path, .sym-gold circle { fill: #f0c86a; stroke: #f0c86a; }
.sym-gold path[data-ring], .sym-gold circle[data-ring] { fill: none; stroke: #f0c86a; }
.sym-blue path, .sym-blue circle { fill: #3ecfbf; stroke: #3ecfbf; }
.sym-blue path[data-ring], .sym-blue circle[data-ring] { fill: none; stroke: #3ecfbf; }
</style>
</head>
<body>
<p class="stack-bar" role="note" title="Taler software on this merchant service (links → git.taler.net tags)">
<strong>stack</strong><span class="sep">·</span>
<a href="https://git.taler.net/taler/merchant/commit/b5af1c99052f4c5053070cd5c6cc39b68eca2a20.html" target="_blank" rel="noopener noreferrer" title="merchant tag v1.6.9">taler-merchant&nbsp;1.6.9</a>
<span class="sep">·</span>
<a href="https://git.taler.net/taler/taler-typescript-core/commit/d7ab425239226b798a95dbf6742fe4b4a2e30e47.html" target="_blank" rel="noopener noreferrer" title="taler-typescript-core tag v1.6.8-dev.6">taler-merchant-webui&nbsp;1.6.8~dev6</a>
<span class="sep">·</span>
<a href="https://git.taler.net/taler/merchant/commit/b5af1c99052f4c5053070cd5c6cc39b68eca2a20.html" target="_blank" rel="noopener noreferrer" title="merchant tag v1.6.9">libtalermerchant&nbsp;1.6.9</a>
<span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/fed7d0e782ea4d36b2d2e337d93bfd2723c6ed64.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.7-dev.2">libtalerexchange&nbsp;1.6.7~dev2</a>
<span class="sep">·</span>
<a href="https://git.taler.net/taler/exchange/commit/fed7d0e782ea4d36b2d2e337d93bfd2723c6ed64.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.7-dev.2">taler-terms-generator&nbsp;1.6.7~dev2</a>
</p>
<main>
<header class="hero">
<span class="badge">Merchant · dual currency</span>
<h1>GOA Merchant</h1>
<p class="sub">
Payments in <strong>GOA</strong>
(<a href="https://taler.hacktivism.ch/terms" title="Terms: GOA explorational currency">explorational</a>)
and <strong>CHF</strong>
(<a href="https://taler.hacktivism.ch/terms" title="Terms: CHF via taler-ops">taler-ops</a>).
</p>
</header>
<p class="money-hint" role="note">
<strong>Get GOA first.</strong>
You need coins in your wallet before you can pay here.
<br />
<span class="money-hint-detail">Install the wallet → one-step withdraw at the bank, then return to this shop.</span>
<br />
<a class="money-hint-cta" href="https://bank.hacktivism.ch/intro/">Get money at the bank →</a>
</p>
<div class="wallet-fold" id="wallet-fold">
<button type="button" class="wallet-fold-toggle" id="wallet-fold-toggle"
aria-expanded="false" aria-controls="wallet-fold-body">
<span class="wallet-fold-toggle-text">Get the wallet · Android / iOS / web browsers</span>
<span class="wallet-fold-chevron" aria-hidden="true"></span>
</button>
<div class="wallet-fold-body" id="wallet-fold-body" hidden>
<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>
<button type="button" class="app-btn hub cli-toggle" id="pkg-fold-toggle"
aria-expanded="false" aria-controls="pkg-fold-body"
aria-label="Linux packages: Debian and Ubuntu (taler-wallet-cli)">
<svg class="app-ico browser-ico penguin-ico" viewBox="0 0 24 24" role="img" aria-label="Linux">
<ellipse cx="12" cy="14.2" rx="6.4" ry="7.2" fill="#f0c86a"/>
<ellipse cx="12" cy="15.2" rx="3.6" ry="4.8" fill="#1a1410" opacity="0.35"/>
<circle cx="12" cy="7" r="4.1" fill="#f0c86a"/>
<circle cx="10.35" cy="6.6" r="0.85" fill="#1a1410"/>
<circle cx="13.65" cy="6.6" r="0.85" fill="#1a1410"/>
<path fill="#e8a838" d="M11.15 8.1 12 9.55 12.85 8.1z"/>
<ellipse cx="9.4" cy="21" rx="2.1" ry="1.05" fill="#e8a838"/>
<ellipse cx="14.6" cy="21" rx="2.1" ry="1.05" fill="#e8a838"/>
<ellipse cx="6.6" cy="13.5" rx="1.4" ry="2.6" fill="#f0c86a" transform="rotate(-18 6.6 13.5)"/>
<ellipse cx="17.4" cy="13.5" rx="1.4" ry="2.6" fill="#f0c86a" transform="rotate(18 17.4 13.5)"/>
</svg>
Debian · Ubuntu packages
<span class="cli-chevron" aria-hidden="true"></span>
</button>
<div class="cli-panel" id="pkg-fold-body" hidden>
<p class="cli-lead">
Official <strong>taler-wallet-cli</strong> from
<a href="https://deb.taler.net/" target="_blank" rel="noopener noreferrer">deb.taler.net</a>.
Full recipes also on the
<a href="https://bank.hacktivism.ch/intro/">bank landing</a>.
</p>
<pre class="cli-block"><span class="cmt"># Debian example</span>
sudo apt-get update
sudo apt-get install -y taler-wallet-cli</pre>
</div>
</div>
</div>
</div>
<aside class="shop-box" aria-labelledby="shop-title">
<h2 id="shop-title">GOA shop · samples</h2>
<p class="shop-lead">Tap an item → open payment link for the wallet</p>
<div class="shop-grid" id="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>
<button type="button" class="shop-item" data-product="voidwave-playlist">
<span class="emoji" aria-hidden="true">🎧</span>
<span class="name">Voidwave playlist</span>
<span class="price">GOA:8</span>
</button>
<button type="button" class="shop-item" data-product="comet-cap">
<span class="emoji" aria-hidden="true">🧢</span>
<span class="name">Comet cap</span>
<span class="price">GOA:15</span>
</button>
<button type="button" class="shop-item" data-product="shuttle-pass">
<span class="emoji" aria-hidden="true">🛸</span>
<span class="name">Shuttle day pass</span>
<span class="price">GOA:25</span>
</button>
<button type="button" class="shop-item" data-product="beacon-badge">
<span class="emoji" aria-hidden="true">📡</span>
<span class="name">Beacon badge</span>
<span class="price">GOA:3</span>
</button>
<button type="button" class="shop-item" data-product="relay-pin">
<span class="emoji" aria-hidden="true">🛰️</span>
<span class="name">Relay pin</span>
<span class="price">GOA:4</span>
</button>
<button type="button" class="shop-item" data-product="eclipse-shades">
<span class="emoji" aria-hidden="true">🕶️</span>
<span class="name">Eclipse shades</span>
<span class="price">GOA:12</span>
</button>
<button type="button" class="shop-item" data-product="star-chart">
<span class="emoji" aria-hidden="true">📜</span>
<span class="name">Star chart print</span>
<span class="price">GOA:7</span>
</button>
<button type="button" class="shop-item" data-product="rainbow-pill">
<span class="emoji" aria-hidden="true">🌈</span>
<span class="name">Rainbow pill</span>
<span class="price">GOA:4.2</span>
<span class="hint">sample only · joke · not a drug / legal product</span>
</button>
<button type="button" class="shop-item" data-product="blue-or-red-pill">
<span class="emoji pill-duo" aria-hidden="true" title="blue or red">
<svg class="pill-svg" viewBox="0 0 40 20" width="40" height="20">
<defs>
<clipPath id="pill-shape">
<rect x="1" y="3" width="38" height="14" rx="7" ry="7"/>
</clipPath>
</defs>
<g clip-path="url(#pill-shape)">
<rect x="1" y="3" width="19" height="14" fill="#3b82f6"/>
<rect x="20" y="3" width="19" height="14" fill="#ef4444"/>
<line x1="20" y1="3" x2="20" y2="17" stroke="rgba(255,255,255,0.35)" stroke-width="1"/>
</g>
<rect x="1" y="3" width="38" height="14" rx="7" ry="7" fill="none" stroke="rgba(233,213,255,0.45)" stroke-width="1"/>
</svg>
</span>
<span class="name">Blue or red pill</span>
<span class="price">GOA:1.5</span>
<span class="hint">sample only · joke · not a drug / legal product</span>
</button>
</div>
</aside>
<section class="stats" id="stats" aria-labelledby="stats-title">
<h2 id="stats-title">Merchant stats · all payments</h2>
<p class="note">From merchant DB · dual currency · large GOA as kGOA / MGOA / …</p>
<div class="stats-grid">
<div class="stat"><span class="k">Instances</span><span class="v teal" id="st-instances"></span></div>
<div class="stat"><span class="k">All orders</span><span class="v" id="st-orders"></span></div>
<div class="stat"><span class="k">All paid</span><span class="v teal" id="st-paid"></span></div>
<div class="stat"><span class="k">Unpaid</span><span class="v" id="st-unpaid"></span></div>
<div class="stat"><span class="k">Wired</span><span class="v" id="st-wired"></span></div>
<div class="stat"><span class="k">Refunds</span><span class="v" id="st-refunds"></span></div>
</div>
<!-- Static dual panels (not links) -->
<div class="cur-pair" id="st-currencies" aria-label="Amounts by currency">
<div class="cur-panel goa" id="panel-goa">
<div class="cur-name">GOA</div>
<div class="cur-sub">explorational · hacktivism</div>
<span class="amt-big" id="goa-paid-amt"></span>
<span class="amt-label">all paid volume</span>
<div class="cur-meta">
<div><span class="mk">Orders</span><span class="mv" id="goa-orders"></span></div>
<div><span class="mk">Paid</span><span class="mv" id="goa-paid"></span></div>
<div><span class="mk">Wired</span><span class="mv" id="goa-wired"></span></div>
<div><span class="mk">Unpaid</span><span class="mv" id="goa-unpaid"></span></div>
</div>
<div class="amt-secondary">all orders volume <strong id="goa-all-amt"></strong></div>
</div>
<div class="cur-panel chf" id="panel-chf">
<div class="cur-name">CHF</div>
<div class="cur-sub">taler-ops</div>
<span class="amt-big" id="chf-paid-amt"></span>
<span class="amt-label">all paid volume</span>
<div class="cur-meta">
<div><span class="mk">Orders</span><span class="mv" id="chf-orders"></span></div>
<div><span class="mk">Paid</span><span class="mv" id="chf-paid"></span></div>
<div><span class="mk">Wired</span><span class="mv" id="chf-wired"></span></div>
<div><span class="mk">Unpaid</span><span class="mv" id="chf-unpaid"></span></div>
</div>
<div class="amt-secondary">all orders volume <strong id="chf-all-amt"></strong></div>
</div>
</div>
<p class="note" style="margin-top:0.65rem;text-align:center;font-size:0.68rem">Unit names for large GOA</p>
<div id="st-unit-legend" style="text-align:center"></div>
<p class="stats-foot" id="st-foot">Loading…</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">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">/terms</span><span class="v" id="st-terms-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">CPU (container)</span><span class="v teal" id="st-cpu"></span></div>
<div class="stat"><span class="k">Loadavg (host)</span><span class="v sm" id="st-load"></span></div>
<div class="stat"><span class="k">PIDs</span><span class="v" id="st-pids"></span></div>
<div class="stat"><span class="k">Container RSS</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">Taler procs</span><span class="v" id="st-mem-taler"></span></div>
<div class="stat"><span class="k">Nginx</span><span class="v sm" id="st-mem-nginx"></span></div>
</div>
<div style="margin-top:0.85rem;overflow-x:auto">
<p class="note" style="margin-bottom:0.4rem">Top processes (RSS inside container)</p>
<div id="st-mem-top"></div>
</div>
<p class="stats-foot" id="st-perf-foot">RSS from /proc inside container (no memory cgroup in rootless podman). CPU from podman stats.</p>
</section>
<section class="stats" id="activity" aria-labelledby="act-title">
<h2 id="act-title">Latest payments</h2>
<p class="note">Latest payments &amp; refunds per currency (all paid totals above)</p>
<div class="act-pair" id="st-activity-pair">
<div class="act-col goa" aria-labelledby="act-goa-h">
<h3 class="act-col-h" id="act-goa-h">GOA</h3>
<ul class="act-list" id="st-activity-goa">
<li class="empty">Loading…</li>
</ul>
</div>
<div class="act-col chf" aria-labelledby="act-chf-h">
<h3 class="act-col-h" id="act-chf-h">CHF</h3>
<ul class="act-list" id="st-activity-chf">
<li class="empty">Loading…</li>
</ul>
</div>
</div>
</section>
<section class="step-card">
<h2>Merchant SPA</h2>
<p class="step-tagline"><code>taler.hacktivism.ch</code> · GOA + CHF</p>
<div class="links">
<a class="cta teal" href="https://taler.hacktivism.ch/webui/">Open /webui/ →</a>
<a class="cta-sec" href="https://taler.hacktivism.ch/config">/config</a>
<a class="cta-sec" href="https://taler.hacktivism.ch/terms">/terms</a>
<a class="cta-sec" href="https://taler.hacktivism.ch/privacy">/privacy</a>
</div>
</section>
<footer>
<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://taler.hacktivism.ch/terms">Terms</a> ·
<a href="https://taler.hacktivism.ch/privacy">Privacy</a> ·
<a href="https://git.hacktivism.ch/hernani/koopa-admin-log/src/branch/main/configs/merchant-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://taler.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
</p>
</footer>
</main>
<script src="/intro/goa-amount.js"></script>
<script src="/intro/goa-ui.js" defer></script>
<script>
/* Fallback if goa-amount.js not yet deployed (uses amount_alt from stats.json) */
(function () {
if (window.GoaAmount) return;
window.GoaAmount = {
rememberAlt: function () {},
format: function (a) {
var s = a == null || a === "" ? "—" : String(a);
return { display: s, title: s === "—" ? "" : s, raw: s, usedAlt: false };
},
pick: function (row) {
if (row && typeof row === "object") {
if (row.amount_alt) {
return {
display: String(row.amount_alt),
title: String(row.amount_full || row.amount || row.amount_alt),
raw: String(row.amount || ""),
usedAlt: true
};
}
return this.format(row.amount || row.amount_paid_sum || row.amount_sum || "—");
}
return this.format(row);
}
};
})();
</script>
<script>
(function () {
function set(id, v) {
var el = document.getElementById(id);
if (el) el.textContent = v == null || v === "" ? "—" : String(v);
}
function setAmt(id, rowOrAmt, altMap) {
var el = document.getElementById(id);
if (!el) return;
if (window.GoaAmount) {
var f = GoaAmount.pick(rowOrAmt, altMap);
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_paid_sum_alt || rowOrAmt.amount_sum_alt
|| rowOrAmt.amount_paid_sum || rowOrAmt.amount_sum || rowOrAmt.amount || "—")
: (rowOrAmt == null || rowOrAmt === "" ? "—" : String(rowOrAmt));
el.textContent = raw;
}
}
function msLabel(ms, http) {
if (ms == null || ms === "") return "—";
var s = ms + " ms";
if (http && http !== "200") s += " (" + http + ")";
return s;
}
function fillCurrency(prefix, c, altMap) {
if (!c) {
set(prefix + "-paid-amt", "—");
set(prefix + "-all-amt", "—");
set(prefix + "-orders", "—");
set(prefix + "-paid", "—");
set(prefix + "-wired", "—");
set(prefix + "-unpaid", "—");
return;
}
setAmt(prefix + "-paid-amt", {
amount: c.amount_paid_sum,
amount_alt: c.amount_paid_sum_alt,
amount_full: c.amount_paid_sum_full
}, altMap);
setAmt(prefix + "-all-amt", {
amount: c.amount_sum,
amount_alt: c.amount_sum_alt,
amount_full: c.amount_sum_full
}, altMap);
set(prefix + "-orders", c.contracts);
set(prefix + "-paid", c.paid);
set(prefix + "-wired", c.wired);
set(prefix + "-unpaid", c.unpaid);
}
function esc(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function load() {
var foot = document.getElementById("st-foot");
fetch("/intro/stats.json", { cache: "no-store" })
.then(function (r) { if (!r.ok) throw new Error("http"); return r.json(); })
.then(function (d) {
if (!d || !d.ok) throw new Error("bad");
if (window.GoaAmount) {
GoaAmount.rememberAlt(d);
if (GoaAmount.fillLegend) GoaAmount.fillLegend("st-unit-legend", d);
}
set("st-instances", d.instances);
set("st-orders", d.orders);
set("st-paid", d.paid);
set("st-unpaid", d.unpaid);
set("st-wired", d.wired);
set("st-refunds", d.refunds);
var by = {};
(d.by_currency || []).forEach(function (c) {
by[(c.currency || "").toUpperCase()] = c;
});
fillCurrency("goa", by.GOA, d.alt_unit_names);
fillCurrency("chf", by.CHF, d.alt_unit_names);
function fillActivityList(el, acts) {
if (!el) return;
el.innerHTML = "";
acts = acts || [];
if (!acts.length) {
el.innerHTML = '<li class="empty">No recent payments or refunds</li>';
return;
}
acts.forEach(function (a) {
var kind = (a.kind || "other").toLowerCase();
var li = document.createElement("li");
li.className = "act-item";
// no merchant names / ids — amount, optional summary, time only
var amtShow = a.amount || "—";
var amtTitle = a.amount || "";
if (window.GoaAmount) {
var af = GoaAmount.pick(a, d.alt_unit_names);
amtShow = af.display;
amtTitle = af.title || a.amount || "";
} else if (a.amount_alt) {
amtShow = a.amount_alt;
amtTitle = a.amount_full || a.amount || "";
}
li.innerHTML =
'<span class="act-kind ' + esc(kind) + '">' + esc(kind) + "</span>" +
'<div class="act-main">' +
'<div class="act-amt" title="' + esc(amtTitle) + '">' + esc(amtShow) + "</div>" +
(a.summary
? '<div class="act-sum">' + esc(a.summary) + "</div>"
: "") +
"</div>" +
'<div class="act-meta">' +
"<span>" + esc(a.ts_human || a.ts || "") + "</span>" +
"</div>";
el.appendChild(li);
});
}
// Prefer split-by-currency (≤5 each); fall back to flat list split by amount prefix
var byCur = {};
(d.recent_activity_by_currency || []).forEach(function (block) {
byCur[(block.currency || "").toUpperCase()] = block.items || [];
});
if (!Object.keys(byCur).length && (d.recent_activity || []).length) {
byCur.GOA = [];
byCur.CHF = [];
(d.recent_activity || []).forEach(function (a) {
var cur = String(a.amount || "").split(":")[0].toUpperCase();
if (cur === "GOA") byCur.GOA.push(a);
else if (cur === "CHF") byCur.CHF.push(a);
});
byCur.GOA = byCur.GOA.slice(0, 10);
byCur.CHF = byCur.CHF.slice(0, 10);
}
fillActivityList(document.getElementById("st-activity-goa"), byCur.GOA);
fillActivityList(document.getElementById("st-activity-chf"), byCur.CHF);
if (foot) {
foot.textContent = (d.dual_currency ? "Dual-currency · " : "") +
(d.generated_at_human || d.generated_at || "");
}
function escHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;").replace(/</g, "&lt;")
.replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function setMem(id, human, n, bytes) {
var el = document.getElementById(id);
if (!el) return;
var b = bytes != null && bytes !== "" ? Number(bytes) : NaN;
var nn = n != null && n !== "" ? Number(n) : NaN;
var empty =
human == null || human === "" || human === "—" ||
human === "0 B" || human === "0B" ||
(isFinite(b) && b <= 0 && (!isFinite(nn) || nn <= 0));
if (empty) {
el.textContent = "—";
el.removeAttribute("title");
return;
}
var h = String(human);
if (isFinite(nn) && nn > 0) h += " · " + nn + "p";
el.textContent = h;
if (isFinite(b) && b > 0) {
el.setAttribute("title", b.toLocaleString("en-US") + " B");
} else el.removeAttribute("title");
}
var p = d.performance || {};
set("st-config-ms", msLabel(p.config_ms, p.config_http));
set("st-terms-ms", msLabel(p.terms_ms, p.terms_http));
set("st-webui-ms", msLabel(p.webui_ms, p.webui_http));
set("st-cpu", p.cpu_percent_label || (p.cpu_percent != null ? (Number(p.cpu_percent).toFixed(2) + "%") : "—"));
set("st-load", p.loadavg || "—");
set("st-pids", p.pids != null ? p.pids : ((p.memory && p.memory.pids) != null ? p.memory.pids : "—"));
var mem = p.memory || {};
setMem("st-mem-ctr", mem.container_rss_label || mem.container_rss_human || "—", null, mem.container_rss_bytes);
setMem("st-mem-pg", mem.postgres_rss_human, mem.postgres_n, mem.postgres_rss_bytes);
setMem("st-mem-taler", mem.taler_rss_human, mem.taler_n, mem.taler_rss_bytes);
setMem("st-mem-nginx", mem.nginx_rss_human, mem.nginx_n, mem.nginx_rss_bytes);
var topBox = document.getElementById("st-mem-top");
if (topBox) {
var tops = mem.top || [];
if (!tops.length) {
topBox.innerHTML = '<p class="note">—</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) {
var cmd = t.cmd || "";
var cmdFull = t.cmd_full || cmd;
if (cmd.length > 64) cmd = cmd.slice(0, 61) + "…";
th += '<tr><td style="padding:0.25rem;color:#5eead4" title="' +
escHtml(t.rss_bytes != null ? Number(t.rss_bytes).toLocaleString("en-US") + " B" : "") + '">' +
escHtml(t.rss_human || "?") +
'</td><td style="padding:0.25rem">' + escHtml(t.comm || "") +
'</td><td style="padding:0.25rem;word-break:break-all;color:var(--muted)" title="' +
escHtml(cmdFull) + '">' +
escHtml(cmd) + "</td></tr>";
});
th += "</tbody></table>";
topBox.innerHTML = th;
}
}
var pf = document.getElementById("st-perf-foot");
if (pf) {
var basis = mem.memory_basis === "cgroup" ? "cgroup" : "proc RSS sum";
pf.textContent = "Memory: " + basis +
(mem.source ? " · " + mem.source : "") +
(p.loadavg_source ? " · loadavg=" + p.loadavg_source : "") +
" · " + (d.generated_at_human || d.generated_at || "");
}
return fetch("/intro/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.innerHTML = 'Stats offline · <a href="https://bank.hacktivism.ch/intro/" style="color:#e8c878">bank.hacktivism.ch</a>';
["st-activity-goa", "st-activity-chf"].forEach(function (id) {
var list = document.getElementById(id);
if (list) list.innerHTML = '<li class="empty">Stats offline</li>';
});
});
}
load();
})();
</script>
<script>
(function () {
var fold = document.getElementById("wallet-fold");
var btn = document.getElementById("wallet-fold-toggle");
var body = document.getElementById("wallet-fold-body");
if (fold && btn && body) {
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");
else body.setAttribute("hidden", "");
});
}
function wireCli(btnId, bodyId) {
var b = document.getElementById(btnId);
var p = document.getElementById(bodyId);
if (!b || !p) return;
b.addEventListener("click", function () {
var open = !p.classList.contains("open");
p.classList.toggle("open", open);
b.setAttribute("aria-expanded", open ? "true" : "false");
if (open) p.removeAttribute("hidden");
else p.setAttribute("hidden", "");
});
}
wireCli("pkg-fold-toggle", "pkg-fold-body");
})();
</script>
<script src="/intro/shop-pay.js"></script>
</body>
</html>