Add a marketing landing hero and a live chain/round status strip
The user page's login screen was a bare test dashboard with no explanation of how the lottery works; it now leads with a hero (3-step explainer, trust pills) shown only while logged out, plus a bento-style nav and a glowing round card during the draw. Both the user and admin pages now show the current chain tip height and lottery status (open / drawing / waiting for next round) via a small status strip, polled from /rounds/current (extended with chain_tip_height sourced from ElectrumListener.tip_height).
This commit is contained in:
+64
-1
@@ -57,6 +57,26 @@
|
||||
.navbar .nav-tab:hover { color: var(--color-foreground); }
|
||||
.navbar .spacer { flex: 1; }
|
||||
|
||||
.chain-status-pill { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; font-size: 0.82rem; white-space: nowrap; }
|
||||
.status-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
|
||||
background: var(--color-muted-foreground);
|
||||
}
|
||||
.status-dot.status-open {
|
||||
background: var(--color-success);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-success) 18%, transparent);
|
||||
}
|
||||
.status-dot.status-drawing {
|
||||
background: var(--color-primary);
|
||||
animation: status-dot-pulse 1400ms ease-in-out infinite;
|
||||
}
|
||||
.status-dot.status-waiting { background: var(--color-muted-foreground); }
|
||||
@keyframes status-dot-pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-primary) 45%, transparent); }
|
||||
50% { box-shadow: 0 0 0 5px transparent; }
|
||||
}
|
||||
.chain-block { color: var(--color-muted-foreground); font-size: 0.82rem; white-space: nowrap; }
|
||||
|
||||
main { max-width: 960px; margin: 0 auto; padding: 24px 20px 80px; }
|
||||
|
||||
.view { display: none; }
|
||||
@@ -187,7 +207,12 @@
|
||||
<span class="nav-tab" id="nav-pending" onclick="switchView('pending')">Transazioni pendenti</span>
|
||||
<span class="nav-tab" id="nav-audit" onclick="switchView('audit')">Audit log</span>
|
||||
<span class="spacer"></span>
|
||||
<button class="secondary" style="margin:8px 0" onclick="adminLogout()">Esci</button>
|
||||
<span class="chain-status-pill">
|
||||
<span class="status-dot" id="chain-status-dot"></span>
|
||||
<span id="chain-status-label">Connessione…</span>
|
||||
</span>
|
||||
<span class="chain-block mono" id="chain-block">Blocco —</span>
|
||||
<button class="secondary" style="margin:8px 0 8px 14px" onclick="adminLogout()">Esci</button>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
@@ -342,6 +367,42 @@ function fmtDate(iso) {
|
||||
return new Date(iso).toLocaleString('it-IT');
|
||||
}
|
||||
|
||||
const DRAWING_STATUSES = ['closing', 'drawing', 'paying_out'];
|
||||
const CHAIN_STATUS_LABELS = {
|
||||
waiting: 'In attesa del prossimo round',
|
||||
open: 'Round aperto',
|
||||
drawing: 'Estrazione in corso',
|
||||
};
|
||||
let chainStatusInterval = null;
|
||||
|
||||
async function refreshChainStatus() {
|
||||
try {
|
||||
const res = await fetch('/rounds/current');
|
||||
const data = await res.json();
|
||||
let statusKey;
|
||||
if (!data.round_id) statusKey = 'waiting';
|
||||
else if (DRAWING_STATUSES.includes(data.status)) statusKey = 'drawing';
|
||||
else statusKey = 'open';
|
||||
document.getElementById('chain-status-dot').className = 'status-dot status-' + statusKey;
|
||||
document.getElementById('chain-status-label').textContent = CHAIN_STATUS_LABELS[statusKey];
|
||||
document.getElementById('chain-block').textContent =
|
||||
'Blocco ' + (data.chain_tip_height != null ? '#' + data.chain_tip_height : '—');
|
||||
} catch (e) {
|
||||
// leave the last-known status on screen rather than blanking it out
|
||||
}
|
||||
}
|
||||
|
||||
function startChainStatusPolling() {
|
||||
refreshChainStatus();
|
||||
clearInterval(chainStatusInterval);
|
||||
chainStatusInterval = setInterval(refreshChainStatus, 15000);
|
||||
}
|
||||
|
||||
function stopChainStatusPolling() {
|
||||
clearInterval(chainStatusInterval);
|
||||
chainStatusInterval = null;
|
||||
}
|
||||
|
||||
const VIEWS = ['parametri', 'utenti', 'round', 'pending', 'audit'];
|
||||
|
||||
function switchView(name) {
|
||||
@@ -356,6 +417,7 @@ function switchView(name) {
|
||||
function showDashboard() {
|
||||
document.getElementById('login-section').classList.add('hidden');
|
||||
document.getElementById('dashboard-section').classList.remove('hidden');
|
||||
startChainStatusPolling();
|
||||
}
|
||||
|
||||
async function loadDashboard() {
|
||||
@@ -379,6 +441,7 @@ async function adminLogin() {
|
||||
}
|
||||
|
||||
function adminLogout() {
|
||||
stopChainStatusPolling();
|
||||
sessionStorage.removeItem('plm_admin_token');
|
||||
adminToken = null;
|
||||
document.getElementById('admin-token').value = '';
|
||||
|
||||
Reference in New Issue
Block a user