Adds app/static/i18n.js: a flat key -> string table for en/it/es/fr/de/ru/zh, loaded before app.js so t() is available everywhere. No build step and no fetch, consistent with the rest of these static pages. Language comes from localStorage, then navigator.language, then en. Static markup is translated by attribute (data-i18n and its -html/-placeholder/ -title/-aria-label/-alt variants); anything rendered from server data goes through t() in app.js and is re-rendered by onLanguageChange(). An element belongs to one mechanism or the other, never both, or the two overwrite each other — which is why #bet-btn has no data-i18n: its label carries the admin-configurable bet amount, so renderBetButton() owns it and reads the amount from /rounds/current instead of hardcoding "10 PLM" in seven files. The switcher sits in the chain-bar rather than the navbar because the navbar is hidden until login, which would leave the landing page and the login form untranslatable for exactly the users who need to switch. It uses language names rather than flag emoji: flags don't render on every platform and don't map one-to-one onto languages. withLoading now snapshots innerHTML instead of textContent — several of these buttons wrap an <svg> plus a <span data-i18n>, both of which a textContent round-trip flattened away, permanently losing the icon and the translation hook. It re-applies translations to the restored subtree in case the language changed while the request was in flight. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
259 lines
16 KiB
HTML
259 lines
16 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>PLM Lottery</title>
|
|
<link rel="icon" type="image/svg+xml" href="/logo.svg">
|
|
<link rel="stylesheet" href="/style.css">
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="hidden" id="app-navbar" data-i18n-aria-label="nav.ariaSections" aria-label="Sezioni">
|
|
<div class="app-navbar-top">
|
|
<div class="app-navbar-top-inner">
|
|
<span class="brand">
|
|
<img class="brand-mark" src="/logo.svg" alt="">
|
|
PLM Lottery
|
|
</span>
|
|
<div class="app-navbar-account">
|
|
<span class="navbar-username" id="dash-username"></span>
|
|
<span class="navbar-balance mono">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 21V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v16"/></svg>
|
|
<span id="navbar-balance">— PLM</span>
|
|
</span>
|
|
<a class="link icon-link" href="/guida" target="_blank" rel="noopener" data-i18n-title="nav.guideTitle" title="Guida" data-i18n-aria-label="nav.guideAria" aria-label="Apri la guida utente">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 2-3 4"/><path d="M12 17h.01"/></svg>
|
|
</a>
|
|
<a class="link icon-link" href="https://github.com/REPLACE_ME/plm-lottery/issues/new" target="_blank" rel="noopener" data-i18n-title="nav.bugReport" title="Segnala un bug" data-i18n-aria-label="nav.bugReport" aria-label="Segnala un bug su GitHub">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v3M16 2v3M12 12v-2a2 2 0 1 1 2 2h-2Z"/><rect x="6" y="10" width="12" height="10" rx="4"/><path d="M6 15H3M21 15h-3M9 20v-3M15 20v-3"/></svg>
|
|
</a>
|
|
<button class="link icon-link" onclick="logout()" data-i18n-title="nav.logoutTitle" title="Esci" data-i18n-aria-label="nav.logoutAria" aria-label="Esci dall'account">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="M16 17l5-5-5-5M21 12H9"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="app-navbar-tabs">
|
|
<button class="navbar-tab active" id="nav-deposit" onclick="switchPanel('deposit')">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 21V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v16"/></svg>
|
|
<span data-i18n="nav.deposit">Deposito</span>
|
|
</button>
|
|
<button class="navbar-tab" id="nav-bet" onclick="switchPanel('bet')">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h.01M12 12h.01M18 12h.01"/></svg>
|
|
<span data-i18n="nav.bet">Bet</span>
|
|
</button>
|
|
<button class="navbar-tab" id="nav-withdraw" onclick="switchPanel('withdraw')">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
|
|
<span data-i18n="nav.withdraw">Prelievo</span>
|
|
</button>
|
|
<button class="navbar-tab" id="nav-profile" onclick="switchPanel('profile')">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
|
<span data-i18n="nav.profile">Profilo</span>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="app-shell">
|
|
|
|
<div class="chain-bar" id="chain-bar">
|
|
<span class="chain-status-pill">
|
|
<span class="status-dot" id="chain-status-dot"></span>
|
|
<span id="chain-status-label" data-i18n="chain.connecting">Connecting…</span>
|
|
</span>
|
|
<span class="chain-bar-right">
|
|
<span class="chain-block mono" id="chain-block">—</span>
|
|
<!-- Deliberately here and not in the navbar: the navbar is hidden until login,
|
|
which would leave the landing page and the login form untranslatable for
|
|
anyone who can't read the browser-detected default. -->
|
|
<select id="lang-switcher" class="lang-switcher" onchange="setLanguage(this.value)" aria-label="Language">
|
|
<option value="en">English</option>
|
|
<option value="it">Italiano</option>
|
|
<option value="es">Español</option>
|
|
<option value="fr">Français</option>
|
|
<option value="de">Deutsch</option>
|
|
<option value="ru">Русский</option>
|
|
<option value="zh">中文</option>
|
|
</select>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="maintenance-banner hidden" id="maintenance-banner">
|
|
<span>⚠️</span>
|
|
<span data-i18n="maintenance.banner">Manutenzione in programma: il round in corso viene completato regolarmente (vincitore incluso), ma il round successivo non si aprirà finché la manutenzione non sarà terminata.</span>
|
|
</div>
|
|
|
|
<section id="landing-hero" class="hero">
|
|
<h1>PLM Lottery</h1>
|
|
<p class="lead" data-i18n="hero.lead">Deposita PLM, entra nel round con una quota fissa, e se viene estratto il tuo numero vinci il montepremi.</p>
|
|
|
|
<div class="hero-steps">
|
|
<div class="hero-step">
|
|
<div class="step-icon"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 21V5a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v16"/></svg></div>
|
|
<div class="step-title" data-i18n="hero.step1.title">1. Deposita</div>
|
|
<div class="step-hint" data-i18n="hero.step1.hint">Ricevi un indirizzo PLM personale, tuo per sempre</div>
|
|
</div>
|
|
<div class="hero-step">
|
|
<div class="step-icon"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 12h.01M12 12h.01M18 12h.01"/></svg></div>
|
|
<div class="step-title" data-i18n="hero.step2.title">2. Gioca</div>
|
|
<div class="step-hint" data-i18n="hero.step2.hint">Una bet a quota fissa per entrare nel round corrente</div>
|
|
</div>
|
|
<div class="hero-step">
|
|
<div class="step-icon"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 21h8M12 17v4M7 4h10v4a5 5 0 0 1-10 0V4Z"/><path d="M7 5H4a1 1 0 0 0-1 1v1a4 4 0 0 0 4 4M17 5h3a1 1 0 0 1 1 1v1a4 4 0 0 1-4 4"/></svg></div>
|
|
<div class="step-title" data-i18n="hero.step3.title">3. Vinci</div>
|
|
<div class="step-hint" data-i18n="hero.step3.hint">Estrazione dal blocco, montepremi accreditato subito</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="trust-row">
|
|
<span class="trust-pill"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg><span data-i18n="trust.fixedRate">Quota fissa dichiarata</span></span>
|
|
<span class="trust-pill"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg><span data-i18n="trust.blockHash">Estrazione da hash di blocco</span></span>
|
|
<span class="trust-pill"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg><span data-i18n="trust.freeWithdraw">Prelievo libero in ogni momento</span></span>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="auth-section" class="card">
|
|
<div class="tabs">
|
|
<div class="tab active" id="tab-login" onclick="switchTab('login')" data-i18n="auth.tabLogin">Login</div>
|
|
<div class="tab" id="tab-register" onclick="switchTab('register')" data-i18n="auth.tabRegister">Registrati</div>
|
|
</div>
|
|
|
|
<div class="tab-panel active" id="panel-login">
|
|
<label for="login-username" data-i18n="auth.username">Username</label>
|
|
<input id="login-username" autocomplete="username">
|
|
<label for="login-password" data-i18n="auth.password">Password</label>
|
|
<input id="login-password" type="password" autocomplete="current-password">
|
|
<button onclick="login()" id="login-btn" data-i18n="auth.loginBtn">Accedi</button>
|
|
</div>
|
|
|
|
<div class="tab-panel" id="panel-register">
|
|
<label for="reg-username" data-i18n="auth.username">Username</label>
|
|
<input id="reg-username" autocomplete="username">
|
|
<label for="reg-password" data-i18n="auth.password">Password</label>
|
|
<input id="reg-password" type="password" autocomplete="new-password">
|
|
<label for="reg-password-confirm" data-i18n="auth.passwordConfirm">Conferma password</label>
|
|
<input id="reg-password-confirm" type="password" autocomplete="new-password">
|
|
<button onclick="register()" id="register-btn" data-i18n="auth.registerBtn">Crea account</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="dashboard-section" class="hidden">
|
|
|
|
<div class="card round-card" id="round-card">
|
|
<div class="row-between" id="round-normal-row">
|
|
<h2 id="round-title">Round —</h2>
|
|
<span class="mono" id="round-timer" style="font-size:1.1rem;font-weight:700">--:--</span>
|
|
</div>
|
|
<div class="row-between" style="margin-top:10px" id="round-stats-row">
|
|
<div>
|
|
<div class="hint" style="margin-bottom:2px" data-i18n="round.players">Giocatori</div>
|
|
<span class="mono" id="round-players">—</span>
|
|
</div>
|
|
<div style="text-align:right">
|
|
<div class="hint" style="margin-bottom:2px" data-i18n="round.jackpot">Jackpot</div>
|
|
<span class="mono" id="round-jackpot">—</span> <span class="balance-unit">PLM</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="draw-state" id="draw-state">
|
|
<div class="draw-spinner"></div>
|
|
<div class="draw-label" id="draw-label" data-i18n="draw.defaultLabel">Estrazione del vincitore in corso…</div>
|
|
</div>
|
|
|
|
<div class="hidden" id="draw-result"></div>
|
|
</div>
|
|
|
|
<div class="dash-panel active" id="panel-deposit">
|
|
<div class="card">
|
|
<h2 data-i18n="deposit.balanceTitle">Saldo interno</h2>
|
|
<p class="hint" data-i18n="deposit.balanceHint">Aggiornato dopo 1 conferma sulla rete</p>
|
|
<div class="row-between">
|
|
<div><span class="balance-value mono" id="dash-balance">—</span> <span class="balance-unit">PLM</span></div>
|
|
<button class="secondary" onclick="refreshMe()" id="refresh-btn" data-i18n-aria-label="deposit.refreshAria" aria-label="Aggiorna saldo">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-2.64-6.36M21 3v6h-6"/></svg>
|
|
<span data-i18n="deposit.refreshBtn">Aggiorna</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2 data-i18n="deposit.addressTitle">Indirizzo di deposito</h2>
|
|
<p class="hint" data-i18n="deposit.addressHint">È anche l'indirizzo su cui ricevi eventuali vincite</p>
|
|
<div class="address-box">
|
|
<span class="mono" id="dash-address"></span>
|
|
<button class="secondary" onclick="copyAddress()" data-i18n-aria-label="deposit.copyAria" aria-label="Copia indirizzo">
|
|
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="qr-box">
|
|
<img id="dash-qr" data-i18n-alt="deposit.qrAlt" alt="QR code dell'indirizzo di deposito">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dash-panel" id="panel-bet">
|
|
<div class="card">
|
|
<h2 data-i18n="bet.title">Bet</h2>
|
|
<p class="hint" data-i18n="bet.hint">Ingresso fisso al round corrente</p>
|
|
<!-- No data-i18n here: the label carries the live bet amount, which is
|
|
admin-configurable, so it's rendered by renderBetButton() in app.js. -->
|
|
<button onclick="placeBet()" id="bet-btn">Place bet</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dash-panel" id="panel-withdraw">
|
|
<div class="card">
|
|
<h2 data-i18n="withdraw.title">Withdrawal</h2>
|
|
<p class="hint" data-i18n="withdraw.hint">Invia fondi a un indirizzo PLM esterno</p>
|
|
<label for="wd-address" data-i18n="withdraw.addressLabel">Indirizzo esterno</label>
|
|
<input id="wd-address" class="mono" placeholder="plm1q...">
|
|
<p class="hint" data-i18n-html="withdraw.addressHint">Solo indirizzi P2WPKH bech32 (quelli che iniziano con <code>plm1q...</code>). Indirizzi legacy (<code>P...</code>) o P2SH non sono supportati.</p>
|
|
<label for="wd-amount" data-i18n="withdraw.amountLabel">Importo (PLM)</label>
|
|
<input id="wd-amount" inputmode="decimal" data-i18n-placeholder="withdraw.amountPlaceholder" placeholder="es. 2">
|
|
<label class="checkbox-row">
|
|
<input type="checkbox" id="wd-full-amount" onchange="toggleWithdrawFullAmount()">
|
|
<span data-i18n="withdraw.fullAmountPrefix">Preleva l'intero importo (</span><span class="mono" id="wd-full-amount-value">—</span><span data-i18n="withdraw.fullAmountSuffix"> PLM)</span>
|
|
</label>
|
|
<button onclick="withdraw()" id="withdraw-btn" data-i18n="withdraw.button">Preleva</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dash-panel" id="panel-profile">
|
|
<div class="card">
|
|
<h2 data-i18n="profile.title">Profilo</h2>
|
|
<p class="hint" data-i18n="profile.hint">Le tue informazioni account</p>
|
|
<label data-i18n="profile.usernameLabel">Username</label>
|
|
<div class="address-box"><span id="profile-username">—</span></div>
|
|
<label data-i18n="profile.addressLabel">Indirizzo di deposito</label>
|
|
<div class="address-box"><span class="mono" id="profile-address">—</span></div>
|
|
<label data-i18n="profile.balanceLabel">Saldo interno</label>
|
|
<div class="address-box"><span class="mono" id="profile-balance">—</span> <span class="balance-unit">PLM</span></div>
|
|
<label data-i18n="profile.createdLabel">Utente dal</label>
|
|
<div class="address-box"><span id="profile-created-at">—</span></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2 data-i18n="settings.title">Impostazioni</h2>
|
|
<p class="hint" data-i18n="settings.hint">Cambia la password del tuo account</p>
|
|
<label for="settings-current-password" data-i18n="settings.currentPasswordLabel">Password attuale</label>
|
|
<input id="settings-current-password" type="password" autocomplete="current-password">
|
|
<label for="settings-new-password" data-i18n="settings.newPasswordLabel">Nuova password</label>
|
|
<input id="settings-new-password" type="password" autocomplete="new-password">
|
|
<label for="settings-new-password-confirm" data-i18n="settings.newPasswordConfirmLabel">Conferma nuova password</label>
|
|
<input id="settings-new-password-confirm" type="password" autocomplete="new-password">
|
|
<button onclick="changePassword()" id="change-password-btn" data-i18n="settings.updateBtn">Aggiorna password</button>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
<div id="toast-container" aria-live="polite"></div>
|
|
|
|
<script src="/i18n.js"></script>
|
|
<script src="/app.js"></script>
|
|
|
|
</body>
|
|
</html>
|