<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>🔗 simple blockchain · visual demo</title> <style> * { box-sizing: border-box; margin: 0; font-family: 'Segoe UI', Roboto, system-ui, sans-serif; } body { background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; } .card { max-width: 1100px; width: 100%; background: rgba(255,255,255,0.05); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.1); border-radius: 2.5rem; padding: 2rem 2rem 2.5rem; box-shadow: 0 30px 50px rgba(0,0,0,0.7), inset 0 1px 1px rgba(255,255,255,0.1); } /* introduction area with features */ .intro { margin-bottom: 2.5rem; } h1 { color: #f0e9db; font-weight: 500; font-size: 2.4rem; letter-spacing: -0.5px; display: flex; align-items: center; gap: 10px; margin-bottom: 0.5rem; } h1 span { background: #fbbf24; color: #0f172a; font-size: 1rem; font-weight: 600; padding: 0.2rem 1rem; border-radius: 40px; margin-left: 12px; box-shadow: 0 4px 0 #b45309; } .tagline { color: #b0c6e0; font-size: 1.1rem; margin-bottom: 0.75rem; border-left: 4px solid #3b82f6; padding-left: 1rem; } .feature-grid { display: flex; flex-wrap: wrap; gap: 1rem 2rem; background: #1a2533b0; border-radius: 50px; padding: 0.9rem 2rem; margin-top: 1rem; border: 1px solid #4f6188; justify-content: space-evenly; } .feature-item { display: flex; align-items: center; gap: 8px; color: #d6e3ff; font-size: 0.95rem; font-weight: 500; } .feature-icon { background: #3b82f6; color: white; border-radius: 30px; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center; font-size: 1rem; box-shadow: 0 3px 0 #1e3a8a; } /* chain container */ .chain { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; margin: 2rem 0 1.8rem; } /* individual block */ .block { background: #2d3b4f; border-radius: 28px; padding: 1.3rem 1rem 1rem 1rem; width: 200px; box-shadow: 0 12px 0 #0b111e, 0 0 0 2px #52647c inset; transition: all 0.15s; border: 1px solid #7f8fa3; } .block.genesis { background: #2c3e4e; box-shadow: 0 12px 0 #1a2632, 0 0 0 2px #d4a373 inset; } .block-header { display: flex; justify-content: space-between; align-items: center; color: #bdd3ff; font-weight: 500; font-size: 0.8rem; padding: 0 0.25rem 0.5rem; border-bottom: 2px dashed #5f7392; } .index-badge { background: #3b4b62; padding: 0.2rem 0.9rem; border-radius: 30px; font-size: 0.75rem; font-weight: 600; color: #d9e6ff; } .nonce { background: #1e2b3a; padding: 0.2rem 0.6rem; border-radius: 20px; font-family: 'Courier New', monospace; font-weight: 600; color: #fbbf24; } .data { background: #1f2a36; margin: 0.8rem 0 0.9rem; padding: 0.7rem 0.5rem; border-radius: 16px; text-align: center; font-size: 0.9rem; font-weight: 500; word-break: break-word; color: #f0f3fa; border: 1px solid #4f6180; box-shadow: inset 0 2px 5px #00000055; min-height: 3.8rem; display: flex; align-items: center; justify-content: center; } .hash { background: #10161e; color: #b0e0b0; font-family: 'Courier New', monospace; font-size: 0.7rem; padding: 0.5rem 0.5rem; border-radius: 30px; margin: 0.5rem 0 0.3rem; border: 1px solid #3f5e3f; text-align: center; word-break: break-all; line-height: 1.3; letter-spacing: -0.2px; } .prev-hash { background: #262f3d; color: #e2c08d; font-family: 'Courier New', monospace; font-size: 0.65rem; padding: 0.4rem 0.5rem; border-radius: 40px; border: 1px dashed #bc8c5c; margin: 0.6rem 0 0.2rem; word-break: break-all; } .prev-label { font-size: 0.6rem; color: #afc9ff; margin-right: 4px; } .chain-link { font-size: 2.5rem; align-self: center; color: #fbbf24; opacity: 0.8; font-weight: 300; text-shadow: 0 2px 0 #92400e; margin: 0 -10px; } /* controls */ .action-panel { background: #1f2a37b3; border-radius: 70px; padding: 1.8rem 2rem; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2rem; margin-top: 1.5rem; border: 1px solid #5d738b; } .input-group { display: flex; align-items: center; gap: 0.5rem; background: #0f172a; padding: 0.2rem 0.2rem 0.2rem 1.2rem; border-radius: 60px; border: 1px solid #3b4f66; } .input-group label { color: #d1dffa; font-weight: 500; } .input-group input { background: #1e2b3c; border: none; padding: 0.8rem 1rem; border-radius: 40px; color: white; font-size: 1rem; width: 170px; outline: 2px solid transparent; transition: 0.1s; } .input-group input:focus { outline: 2px solid #fbbf24; background: #263649; } button { background: #fbbf24; border: none; padding: 0.8rem 2rem; border-radius: 50px; font-weight: 600; font-size: 1.1rem; color: #0f172a; cursor: pointer; transition: 0.1s ease; box-shadow: 0 7px 0 #b45309; display: inline-flex; align-items: center; gap: 8px; border: 1px solid #ffd966; } button:active { transform: translateY(5px); box-shadow: 0 2px 0 #b45309; } .reset-btn { background: #475569; box-shadow: 0 7px 0 #1e293b; color: white; border: 1px solid #94a3b8; } .mine-hash { background: #3b82f6; box-shadow: 0 7px 0 #1e3a8a; color: white; padding: 0.8rem 1.5rem; } .info-tip { color: #9bb5d4; text-align: center; margin-top: 1rem; font-size: 0.85rem; } .block.genesis .hash { background: #1f3a3f; } /* small adjustment for feature layout */ @media (max-width: 650px) { .feature-grid { border-radius: 30px; padding: 1rem 1rem; gap: 0.5rem 1rem; } h1 { font-size: 2rem; } } </style> </head> <body> <div class="card"> <!-- INTRODUCTION SECTION WITH FEATURES --> <div class="intro"> <h1> ⛓️ simple block chain <span>demo</span> </h1> <div class="tagline"> 🔍 understand blockchain fundamentals — each block links to the previous via a cryptographic hash </div> <div class="feature-grid"> <div class="feature-item"><span class="feature-icon">⛏️</span> mine new blocks (proof‑of‑work: '00' hash)</div> <div class="feature-item"><span class="feature-icon">🔗</span> previous hash links chain</div> <div class="feature-item"><span class="feature-icon">🖱️</span> double‑click data to tamper → see validation</div> <div class="feature-item"><span class="feature-icon">🔄</span> reset to original chain</div> </div> </div> <!-- blockchain view injected by js --> <div id="chainContainer" class="chain"> <!-- dynamic blocks render here --> </div> <!-- controls --> <div class="action-panel"> <div class="input-group"> <label>📦 data</label> <input type="text" id="blockData" value="tx: 5 BTC" placeholder="e.g. hello world" maxlength="30"> </div> <button id="addBlockBtn" class="mine-hash">⛏️ add block</button> <button id="resetBtn" class="reset-btn">⟲ reset chain</button> </div> <div class="info-tip"> 💡 each block is ‘mined’ by finding a nonce that makes its hash start with "00" </div> </div> <script> (function() { // ---------- Simple hash (non‑cryptographic, just for demo) ---------- function simpleHash(input) { let hash = 0; if (input.length === 0) return hash.toString(16); for (let i = 0; i < input.length; i++) { const char = input.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash |= 0; // convert to 32-bit int } // convert to unsigned and make hex string, pad to 8 chars return (hash >>> 0).toString(16).padStart(8, '0'); } // ------ mineBlock: find nonce that makes hash start with '00' ------ function mineBlock(index, prevHash, data, difficulty = 2) { const prefix = '0'.repeat(difficulty); let nonce = 0; let hash = ''; while (true) { const combined = index + prevHash + data + nonce; hash = simpleHash(combined); if (hash.startsWith(prefix)) { break; } nonce++; if (nonce > 50000) { // fallback – just for safety, force two leading zeros visually hash = '00' + hash.slice(2); break; } } return { hash, nonce }; } // ----- initial blockchain (genesis + 2 blocks) ----- let blockchain = []; function initChain() { const genesisPrev = "0".repeat(8); const genesisData = "🏆 genesis"; const minedGenesis = mineBlock(0, genesisPrev, genesisData); const genesis = { index: 0, prevHash: genesisPrev, data: genesisData, nonce: minedGenesis.nonce, hash: minedGenesis.hash, }; // Block 1 const data1 = "alice → bob 2btc"; const mined1 = mineBlock(1, genesis.hash, data1); const block1 = { index: 1, prevHash: genesis.hash, data: data1, nonce: mined1.nonce, hash: mined1.hash, }; // Block 2 const data2 = "bob → carol 1.5btc"; const mined2 = mineBlock(2, block1.hash, data2); const block2 = { index: 2, prevHash: block1.hash, data: data2, nonce: mined2.nonce, hash: mined2.hash, }; blockchain = [genesis, block1, block2]; } initChain(); // ----- render chain in DOM ----- const chainContainer = document.getElementById('chainContainer'); const blockDataInput = document.getElementById('blockData'); const addBtn = document.getElementById('addBlockBtn'); const resetBtn = document.getElementById('resetBtn'); function renderChain() { let html = ''; for (let i = 0; i < blockchain.length; i++) { const block = blockchain[i]; const isGenesis = (i === 0); html += `<div class="block ${isGenesis ? 'genesis' : ''}">`; html += `<div class="block-header">`; html += `<span class="index-badge">#${block.index}</span>`; html += `<span class="nonce">⚡ ${block.nonce}</span>`; html += `</div>`; html += `<div class="data" title="block data">${block.data}</div>`; html += `<div class="hash">🔒 ${block.hash}</div>`; if (!isGenesis) { html += `<div class="prev-hash"><span class="prev-label">⬆ prev</span> ${block.prevHash}</div>`; } else { html += `<div class="prev-hash" style="border-color:#4b5563;"><span class="prev-label">🌱 genesis</span> ${block.prevHash}</div>`; } html += `</div>`; if (i < blockchain.length - 1) { html += `<div class="chain-link">⤳</div>`; } } chainContainer.innerHTML = html; } // ----- add new block with user data ----- function addBlock() { const newData = blockDataInput.value.trim() || "empty block"; const prevBlock = blockchain[blockchain.length - 1]; const newIndex = prevBlock.index + 1; const { hash, nonce } = mineBlock(newIndex, prevBlock.hash, newData, 2); const newBlock = { index: newIndex, prevHash: prevBlock.hash, data: newData, nonce: nonce, hash: hash, }; blockchain.push(newBlock); renderChain(); attachTamperListeners(); } function resetChain() { initChain(); renderChain(); attachTamperListeners(); } // ----- tamper detection & validation highlight ----- function attachTamperListeners() { // double-click any data field to edit (tamper) document.querySelectorAll('.data').forEach((dataDiv, idx) => { dataDiv.addEventListener('dblclick', (e) => { e.stopPropagation(); const originalText = dataDiv.innerText; const newText = prompt('✏️ tamper block data (this will break chain):', originalText); if (newText && newText !== originalText) { if (idx < blockchain.length) { blockchain[idx].data = newText; renderChain(); attachTamperListeners(); } } }); }); // highlight invalid links / broken hashes const blocksDom = document.querySelectorAll('.block'); if (blocksDom.length === blockchain.length) { for (let i = 0; i < blockchain.length; i++) { const block = blockchain[i]; const expectedPrev = (i === 0) ? "0".repeat(8) : blockchain[i-1].hash; const hashOk = (block.prevHash === expectedPrev); const blockDiv = blocksDom[i]; // reset styles first (remove previous red markings) const prevDiv = blockDiv.querySelector('.prev-hash'); const hashDiv = blockDiv.querySelector('.hash'); if (prevDiv) { prevDiv.style.background = ''; prevDiv.style.border = ''; prevDiv.style.color = ''; } if (hashDiv) { hashDiv.style.background = ''; hashDiv.style.color = ''; hashDiv.style.textDecoration = ''; } // if previous hash mismatch (broken link) if (!hashOk && i > 0) { if (prevDiv) { prevDiv.style.background = '#6b2e2e'; prevDiv.style.border = '2px solid red'; prevDiv.style.color = '#ffb3b3'; } } // check if current hash matches recomputed hash with difficulty 2 const recomputed = mineBlock(block.index, block.prevHash, block.data, 2); if (recomputed.hash !== block.hash) { if (hashDiv) { hashDiv.style.background = '#a13d3d'; hashDiv.style.color = 'white'; hashDiv.style.textDecoration = 'line-through 2px red'; } } else { // also verify genesis separately if needed (already covered by recomputed) } } } } // override render to auto-attach listeners const originalRender = renderChain; renderChain = function() { originalRender(); attachTamperListeners(); }; // events addBtn.addEventListener('click', addBlock); resetBtn.addEventListener('click', resetChain); blockDataInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') addBlock(); }); // initial render renderChain(); })(); </script> </body> </html> Understanding the Blockchain Demo
The interface is designed to make blockchain concepts tangible. Here’s how the features you see connect to real-world mechanics:
- Mining Simulation: The "add block" button doesn't just append data. It runs a simple proof-of-work algorithm that finds a
nonce(displayed on each block) resulting in a hash starting with "00". This mimics the computational effort in real blockchains. - Immutability & Tampering: You can double-click any block's data field to change it. The code then re-checks all hashes. Blocks with incorrect data or broken links to the previous block are highlighted in red, visually demonstrating how tampering breaks the chain's integrity.
- Genesis Block: The first block (index #0) is visually distinct and has a hardcoded previous hash of all zeros, representing the start of the chain.