[claude] Bitcoin block height live counter (#273) #325
33
app.js
33
app.js
@@ -2030,3 +2030,36 @@ function showTimmySpeech(text) {
|
||||
timmySpeechSprite = sprite;
|
||||
timmySpeechState = { startTime: clock.getElapsedTime(), sprite };
|
||||
}
|
||||
|
||||
// === BITCOIN BLOCK HEIGHT ===
|
||||
// Polls blockstream.info every 60 s for the current tip block height.
|
||||
// Shows a flash animation when the block number increments.
|
||||
|
||||
const blockHeightDisplay = document.getElementById('block-height-display');
|
||||
const blockHeightValue = document.getElementById('block-height-value');
|
||||
let lastKnownBlockHeight = null;
|
||||
|
||||
async function fetchBlockHeight() {
|
||||
try {
|
||||
const res = await fetch('https://blockstream.info/api/blocks/tip/height');
|
||||
if (!res.ok) return;
|
||||
const height = parseInt(await res.text(), 10);
|
||||
if (isNaN(height)) return;
|
||||
|
||||
if (lastKnownBlockHeight !== null && height !== lastKnownBlockHeight) {
|
||||
// New block — trigger flash
|
||||
blockHeightDisplay.classList.remove('fresh');
|
||||
// Force reflow so animation restarts
|
||||
void blockHeightDisplay.offsetWidth;
|
||||
blockHeightDisplay.classList.add('fresh');
|
||||
}
|
||||
|
||||
lastKnownBlockHeight = height;
|
||||
blockHeightValue.textContent = height.toLocaleString();
|
||||
} catch (_) {
|
||||
// Network unavailable — keep last known value
|
||||
}
|
||||
}
|
||||
|
||||
fetchBlockHeight();
|
||||
setInterval(fetchBlockHeight, 60000);
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
|
||||
<div id="sovereignty-msg">⚡ SOVEREIGNTY ⚡</div>
|
||||
|
||||
<div id="block-height-display">
|
||||
<span class="block-height-label">⛏ BLOCK</span>
|
||||
<span id="block-height-value">—</span>
|
||||
</div>
|
||||
|
||||
<div id="zoom-indicator">
|
||||
<span>ZOOMED: <span id="zoom-label">Object</span></span>
|
||||
<span class="zoom-hint">[Esc] or double-click to exit</span>
|
||||
|
||||
36
style.css
36
style.css
@@ -234,6 +234,42 @@ body.photo-mode #overview-indicator {
|
||||
100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
|
||||
}
|
||||
|
||||
/* === BITCOIN BLOCK HEIGHT === */
|
||||
#block-height-display {
|
||||
position: fixed;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
z-index: 20;
|
||||
font-family: var(--font-body);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.15em;
|
||||
color: var(--color-primary);
|
||||
background: rgba(0, 0, 8, 0.7);
|
||||
border: 1px solid var(--color-secondary);
|
||||
padding: 4px 10px;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.block-height-label {
|
||||
color: var(--color-text-muted);
|
||||
margin-right: 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
#block-height-value {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
#block-height-display.fresh #block-height-value {
|
||||
animation: block-flash 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes block-flash {
|
||||
0% { color: #ffffff; text-shadow: 0 0 8px #4488ff; }
|
||||
100% { color: var(--color-primary); text-shadow: none; }
|
||||
}
|
||||
|
||||
/* === CRT / CYBERPUNK OVERLAY === */
|
||||
.crt-overlay {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user