272 lines
14 KiB
HTML
272 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" data-theme="dark">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>GMWallet — Crypto Payment</title>
|
|
<link rel="icon" href="https://www.gmwallet.app/favicon.png" />
|
|
<link rel="preconnect" href="https://cdn.jsdmirror.com" crossorigin />
|
|
<link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fontsource-variable/noto-sans-sc/index.css" />
|
|
<link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/@fontsource-variable/nunito/index.css" />
|
|
<script src="https://cdn.jsdmirror.com/npm/@tailwindcss/browser@4"></script>
|
|
<style type="text/tailwindcss">
|
|
@variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
|
|
|
@theme inline {
|
|
--font-sans: "Noto Sans SC Variable", "Noto Sans SC", system-ui, sans-serif;
|
|
--font-nunito: "Nunito Variable", "Noto Sans SC Variable", "Noto Sans SC", system-ui, sans-serif;
|
|
--color-background: var(--background);
|
|
--color-foreground: var(--foreground);
|
|
--color-card: var(--card);
|
|
--color-card-foreground: var(--card-foreground);
|
|
--color-popover: var(--popover);
|
|
--color-popover-foreground:var(--popover-foreground);
|
|
--color-primary: var(--primary);
|
|
--color-primary-foreground:var(--primary-foreground);
|
|
--color-primary-hover: var(--primary-hover);
|
|
--color-secondary: var(--secondary);
|
|
--color-muted: var(--muted);
|
|
--color-muted-foreground: var(--muted-foreground);
|
|
--color-accent: var(--accent);
|
|
--color-accent-foreground: var(--accent-foreground);
|
|
--color-border: var(--border);
|
|
--color-border-hover: var(--border-hover);
|
|
--color-success: var(--success);
|
|
--color-warning: var(--warning);
|
|
--shadow-card: var(--card-shadow);
|
|
--shadow-popover: var(--popover-shadow);
|
|
--animate-state-in: state-in .28s cubic-bezier(.34,1.56,.64,1);
|
|
}
|
|
</style>
|
|
<link rel="stylesheet" href="/static/payment.css" />
|
|
<script defer src="https://cdn.jsdmirror.com/npm/lucide@latest/dist/umd/lucide.min.js"></script>
|
|
<script defer src="https://cdn.jsdmirror.com/npm/qrcodejs@latest/qrcode.min.js"></script>
|
|
<script defer src="https://cdn.jsdmirror.com/npm/clipboard@latest/dist/clipboard.min.js"></script>
|
|
</head>
|
|
<body class="bg-background text-foreground min-h-screen flex flex-col items-center justify-center font-sans transition-colors duration-300">
|
|
|
|
<!-- Ambient background blobs -->
|
|
<div class="fixed inset-0 pointer-events-none overflow-hidden" aria-hidden="true">
|
|
<div class="blob blob-1"></div>
|
|
<div class="blob blob-2"></div>
|
|
<div class="blob blob-3"></div>
|
|
</div>
|
|
|
|
<div class="flex flex-col items-center w-full max-w-[400px] px-4 py-8 relative z-10">
|
|
|
|
<!-- Nav bar -->
|
|
<div class="w-full flex items-center justify-between mb-6">
|
|
<div class="flex items-center gap-2">
|
|
<img src="https://www.gmwallet.app/favicon.png" alt="logo" class="h-8 w-8 rounded-[10px] shadow-sm" />
|
|
<span class="text-[17px] font-semibold tracking-[-0.3px] text-card-foreground">GM Pay</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<!-- Language pill -->
|
|
<div class="select-wrap relative select-none" id="dd-lang">
|
|
<div class="select-trigger ios-pill flex items-center gap-1.5 cursor-pointer" onclick="toggleSelect('dd-lang')">
|
|
<span class="text-[13px] font-medium" id="lang-label">EN</span>
|
|
<i data-lucide="chevron-down" class="select-chevron text-muted-foreground shrink-0 transition-transform duration-200" width="11" height="11" stroke-width="2.5"></i>
|
|
</div>
|
|
<div class="select-menu ios-menu absolute z-100 top-[calc(100%+12px)] right-0 min-w-[160px] overflow-hidden opacity-0 -translate-y-2 pointer-events-none transition-all duration-200" id="dd-lang-menu">
|
|
<div class="select-option ios-menu-item is-selected" data-lang="en" onclick="setLang('en')">🇺🇸 English</div>
|
|
<div class="select-option ios-menu-item" data-lang="zh" onclick="setLang('zh')">🇨🇳 中文</div>
|
|
<div class="select-option ios-menu-item" data-lang="ja" onclick="setLang('ja')">🇯🇵 日本語</div>
|
|
<div class="select-option ios-menu-item" data-lang="ko" onclick="setLang('ko')">🇰🇷 한국어</div>
|
|
<div class="select-option ios-menu-item" data-lang="zh-hk" onclick="setLang('zh-hk')">🇭🇰 繁體中文</div>
|
|
<div class="select-option ios-menu-item" data-lang="ru" onclick="setLang('ru')">🇷🇺 Русский</div>
|
|
</div>
|
|
</div>
|
|
<!-- Theme toggle -->
|
|
<button class="ios-pill w-9 h-9 flex items-center justify-center cursor-pointer" onclick="toggleTheme(event)" title="Toggle theme">
|
|
<i data-lucide="moon" id="icon-moon" class="hidden" width="16" height="16" stroke-width="1.8"></i>
|
|
<i data-lucide="sun" id="icon-sun" width="16" height="16" stroke-width="1.8"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hero amount card -->
|
|
<div id="order-info" class="glass-card w-full px-6 pt-6 pb-5 mb-3">
|
|
<p class="text-[13px] font-medium text-muted-foreground mb-1" data-i18n="amount_to_pay">Amount to pay</p>
|
|
<div class="flex items-end gap-3 justify-between">
|
|
<span class="text-[42px] font-bold leading-none tracking-[-0.04em] text-card-foreground font-nunito" id="display-amount">--</span>
|
|
<button class="ios-icon-btn mb-0.5 shrink-0" id="btn-copy-amount" title="Copy">
|
|
<i data-lucide="copy" width="15" height="15" stroke-width="2"></i>
|
|
</button>
|
|
</div>
|
|
<div class="flex items-center gap-1.5 mt-1.5" id="display-network">--</div>
|
|
<div class="mt-3 pt-3 border-t border-border/50">
|
|
<table class="w-full text-[13px] text-muted-foreground border-separate border-spacing-y-1">
|
|
<tbody>
|
|
<tr id="display-fiat"></tr>
|
|
<tr id="display-order-id"></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Flippable payment/state card -->
|
|
<div id="status-card" class="status-card w-full">
|
|
<div class="status-card-inner">
|
|
<div class="status-face status-face-front">
|
|
<div id="payment-panel" class="w-full">
|
|
|
|
<!-- QR section -->
|
|
<div class="glass-card w-full px-5 py-5 mb-3">
|
|
<!-- Scan title + small ring -->
|
|
<div class="flex items-center justify-between mb-3">
|
|
<p class="text-[13px] font-semibold text-card-foreground" data-i18n="scan_title">Scan or copy address to pay</p>
|
|
<div id="timer-row" class="relative w-10 h-10 shrink-0">
|
|
<svg class="w-10 h-10 absolute inset-0" viewBox="0 0 48 48">
|
|
<circle class="timer-ring-track" cx="24" cy="24" r="20" fill="none" stroke-width="3"/>
|
|
<circle id="ring-track" class="timer-ring-track" cx="24" cy="24" r="20" fill="none" stroke-width="3"/>
|
|
<circle id="ring" class="timer-ring-progress" cx="24" cy="24" r="20" fill="none" stroke="#34c759" stroke-width="3"
|
|
stroke-linecap="round"
|
|
stroke-dasharray="125.66" stroke-dashoffset="0"/>
|
|
</svg>
|
|
<div class="absolute inset-0 flex items-center justify-center">
|
|
<i data-lucide="timer" class="timer-icon" width="18" height="18" stroke-width="1.8"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Big countdown above QR -->
|
|
<p class="text-[32px] font-bold font-mono leading-none text-success text-center mb-4" id="countdown">--:--</p>
|
|
<div class="flex justify-center">
|
|
<div class="qr-wrapper">
|
|
<div id="qrcode" class="w-44 h-44"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- hidden inline countdown (used by JS, not shown) -->
|
|
<span id="countdown-inline" class="sr-only"></span>
|
|
|
|
<!-- Copy rows -->
|
|
<div class="glass-card w-full overflow-hidden mb-3">
|
|
<!-- Amount row -->
|
|
<div id="copy-amt-box" class="ios-row cursor-pointer select-none">
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider mb-0.5" data-i18n="amount_to_pay">Amount to pay</p>
|
|
<p class="text-[15px] font-semibold text-card-foreground" id="field-amount">--</p>
|
|
</div>
|
|
<span class="ios-icon-btn shrink-0 pointer-events-none" id="btn-copy-amt2">
|
|
<i data-lucide="copy" width="14" height="14" stroke-width="2"></i>
|
|
</span>
|
|
</div>
|
|
<!-- Divider -->
|
|
<div class="h-px bg-border/50 mx-4"></div>
|
|
<!-- Address row -->
|
|
<div id="copy-addr-box" class="ios-row cursor-pointer select-none">
|
|
<div class="flex-1 min-w-0 pr-3">
|
|
<p class="text-[11px] font-semibold text-muted-foreground uppercase tracking-wider mb-0.5" data-i18n="payment_address">Payment address</p>
|
|
<p class="text-[13px] font-medium text-card-foreground break-all leading-relaxed" id="field-address">--</p>
|
|
</div>
|
|
<span class="ios-icon-btn shrink-0 pointer-events-none self-start mt-0.5" id="btn-copy-addr">
|
|
<i data-lucide="copy" width="14" height="14" stroke-width="2"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CTA -->
|
|
<button class="ios-btn-primary w-full mb-3" id="btn-transferred" onclick="handleTransfer()">
|
|
<span data-i18n="i_have_transferred">I have transferred</span>
|
|
</button>
|
|
|
|
<!-- Status -->
|
|
<div class="flex items-center justify-center gap-1.5 py-1" id="status-row">
|
|
<i data-lucide="loader-circle" class="animate-spin text-muted-foreground" width="13" height="13" stroke-width="2.2"></i>
|
|
<span class="text-[12px] text-muted-foreground" id="status-text" data-i18n="checking_blockchain">Checking blockchain</span>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="status-face status-face-back">
|
|
<div id="state-panel" class="w-full state-panel-shell">
|
|
<div class="glass-card w-full flex items-center justify-center min-h-[300px] py-10 px-6">
|
|
|
|
<!-- Success -->
|
|
<div class="animate-state-in state-screen flex flex-col items-center text-center" id="screen-success">
|
|
<div class="ios-state-icon bg-[rgba(52,199,89,.15)] mb-6">
|
|
<i data-lucide="check" width="38" height="38" stroke-width="2.2" stroke="#34c759"></i>
|
|
</div>
|
|
<p class="text-[20px] font-bold text-card-foreground mb-2" data-i18n="payment_success">Payment Successful</p>
|
|
<p class="text-[14px] text-muted-foreground mb-6" data-i18n="redirecting">Redirecting…</p>
|
|
<i data-lucide="loader-circle" class="animate-spin text-muted-foreground" width="22" height="22" stroke-width="2"></i>
|
|
</div>
|
|
|
|
<!-- Expired -->
|
|
<div class="animate-state-in state-screen flex flex-col items-center text-center" id="screen-expired">
|
|
<div class="ios-state-icon bg-[rgba(255,59,48,.12)] mb-6">
|
|
<i data-lucide="x" width="38" height="38" stroke-width="2.2" stroke="#ff3b30"></i>
|
|
</div>
|
|
<p class="text-[20px] font-bold text-card-foreground mb-2" data-i18n="payment_expired">Payment Expired</p>
|
|
<p class="text-[14px] text-muted-foreground mb-7" data-i18n="expired_sub">Please initiate a new payment</p>
|
|
<button class="ios-btn-secondary" onclick="goBack()" data-i18n="back">Back</button>
|
|
</div>
|
|
|
|
<!-- Timeout -->
|
|
<div class="animate-state-in state-screen flex flex-col items-center text-center" id="screen-timeout">
|
|
<div class="ios-state-icon bg-[rgba(255,149,0,.12)] mb-6">
|
|
<i data-lucide="triangle-alert" width="38" height="38" stroke-width="2.2" stroke="#ff9500"></i>
|
|
</div>
|
|
<p class="text-[20px] font-bold text-card-foreground mb-2" data-i18n="network_timeout">Connection Timeout</p>
|
|
<p class="text-[14px] text-muted-foreground mb-7" data-i18n="timeout_sub">Unable to connect to the payment server</p>
|
|
<div class="flex gap-3">
|
|
<button class="ios-btn-secondary" onclick="goBack()" data-i18n="back">Back</button>
|
|
<button class="ios-btn-primary px-7" onclick="retryPolling()" data-i18n="retry">Retry</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Not Found -->
|
|
<div class="animate-state-in state-screen flex flex-col items-center text-center" id="screen-not-found">
|
|
<div class="ios-state-icon bg-[rgba(142,142,147,.12)] mb-6">
|
|
<i data-lucide="file-x" width="38" height="38" stroke-width="2" stroke="#8e8e93"></i>
|
|
</div>
|
|
<p class="text-[20px] font-bold text-card-foreground mb-2" data-i18n="order_not_found">Order Not Found</p>
|
|
<p class="text-[14px] text-muted-foreground" data-i18n="not_found_sub">The order does not exist or has already expired</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer class="flex items-center justify-center gap-2.5 mt-6 text-[12px] text-muted-foreground select-none">
|
|
<span class="flex items-center gap-1.5">
|
|
Powered by
|
|
<a href="https://www.gmwallet.app" target="_blank" rel="noopener" class="flex items-center gap-1 font-semibold text-card-foreground hover:opacity-70 transition-opacity">
|
|
<img src="https://www.gmwallet.app/favicon.png" alt="GM Wallet" class="h-3.5 w-3.5 rounded-[3px]" />
|
|
GM Wallet
|
|
</a>
|
|
</span>
|
|
<span class="opacity-30">|</span>
|
|
<a href="https://github.com/GMwalletApp" target="_blank" rel="noopener" class="flex items-center gap-1 hover:opacity-70 transition-opacity">
|
|
Open source on <span class="font-semibold text-card-foreground">GitHub</span>
|
|
</a>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
<!-- Toast -->
|
|
<div id="toast">Copied</div>
|
|
|
|
<!-- Order data injected by server (Go template) -->
|
|
<script>
|
|
var ORDER = {
|
|
tradeId: "{{.TradeId}}",
|
|
amount: "{{.Amount}}",
|
|
actualAmount: "{{.ActualAmount}}",
|
|
token: "{{.Token}}",
|
|
currency: "{{.Currency}}",
|
|
network: "{{.Network}}",
|
|
receiveAddress: "{{.ReceiveAddress}}",
|
|
expirationTime: "{{.ExpirationTime}}",
|
|
redirectUrl: "{{.RedirectUrl}}",
|
|
createdAt: "{{.CreatedAt}}"
|
|
};
|
|
</script>
|
|
<script defer src="/static/payment.js"></script>
|
|
</body>
|
|
</html>
|