git / moonbite-network / templates/halving.html · raw
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <meta name="theme-color" content="#0B0D12"> <title>The Halving — MoonBite</title> <meta name="description" content="The MoonBite block reward halves at block {{ '{:,}'.format(consensus.halving_interval) }} and never rises again. Watch the countdown, and the staircase down to nothing."> <link rel="icon" type="image/svg+xml" href="/static/moonbite-logo.svg"> <link rel="stylesheet" href="/static/fonts/moonbite-fonts.css"> <style> /* Doc 04: three roles. A heavy grotesque for display, a quiet sans for reading, a monospace for anything that is a number. Archivo Black and Inter are self-hosted from /static/fonts (latin subset, OFL-licensed). They are deliberately not loaded from Google Fonts: /privacy states the site loads nothing that phones home, and a webfont CDN sends every visitor's IP to Google on every page load. Every figure that is money, a hash, an address or a block height is monospace with tabular figures — amounts that shift width as they update look untrustworthy. */ :root{ --font-display:'Archivo Black','Helvetica Neue','Arial Black',sans-serif; --font-body:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; --font-data:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; } h1,h2,.big,.reveal .big,.balance-amount,.elapsed,.mort-big{ font-family:var(--font-display); font-weight:400; letter-spacing:-.02em; } .num,.mono,.addr,.cert .amt,.cl .v,.tot .v,.mcell .v,.odds .v, .loss .v,.count .v,.zero-band .v,.total .v,.vault-v,td.n,th.n{ font-family:var(--font-data); font-variant-numeric:tabular-nums; } * { margin:0; padding:0; box-sizing:border-box; } body { background:#0B0D12; color:#F5F2EA; font-family:var(--font-body); overflow-x:hidden; -webkit-font-smoothing:antialiased; } ::selection { background:#D9A441; color:#0B0D12; } a,button { -webkit-tap-highlight-color:transparent; font-family:inherit; } #sky { position:fixed; inset:0; z-index:0; } .wrap { position:relative; z-index:1; max-width:560px; margin:0 auto; padding:0 18px 70px; } .num { font-variant-numeric:tabular-nums; } .kicker { letter-spacing:.3em; font-size:.62rem; color:#D9A441; text-transform:uppercase; font-weight:800; } /* ----------------------------------------------------------- COUNTDOWN */ .head { text-align:center; padding-top:5vh; } h1 { font-size:clamp(2.1rem,9vw,3.4rem); font-weight:800; line-height:1.02; letter-spacing:-.025em; margin:13px 0 12px; } h1 .r { color:#D9A441; } /* accent word: Moongold; red stays functional-only */ .lede { color:#8A8580; font-size:.96rem; line-height:1.62; max-width:420px; margin:0 auto; } .clock { display:grid; grid-template-columns:repeat(4,1fr); gap:8px; margin-top:26px; } .cl { background:#161A22; border:1px solid rgba(217,164,65,.22); border-radius:10px; padding:15px 4px 11px; } .cl .v { font-size:clamp(1.5rem,7vw,2.3rem); font-weight:800; color:#D9A441; line-height:1; letter-spacing:-.02em; } .cl .k { font-size:.5rem; letter-spacing:.18em; color:#8A8580; text-transform:uppercase; margin-top:7px; font-weight:800; } .prog { background:#161A22; border:1px solid rgba(245,242,234,.08); border-radius:20px; padding:17px 16px; margin-top:14px; } .prog-top { display:flex; justify-content:space-between; font-size:.62rem; letter-spacing:.16em; color:#8A8580; text-transform:uppercase; font-weight:800; } .prog-top b { color:#D9A441; } .pbar { height:9px; border-radius:5px; background:rgba(245,242,234,.08); overflow:hidden; margin-top:11px; } .pfill { height:100%; width:0; min-width:3px; border-radius:5px; background:linear-gradient(90deg,#D9A441,#B8863B); transition:width 1s ease; } .prog-b { display:flex; justify-content:space-between; font-size:.68rem; color:#8A8580; margin-top:8px; } /* ---------------------------------------------------------- STAIRCASE */ .stair-card { background:#161A22; border:1px solid rgba(217,164,65,.2); border-radius:20px; padding:20px 14px 14px; margin-top:16px; } .stair-t { font-size:.6rem; letter-spacing:.2em; color:#8A8580; text-transform:uppercase; font-weight:800; } .stair-s { color:#8A8580; font-size:.77rem; margin-top:5px; margin-bottom:12px; } #stairs { width:100%; height:264px; display:block; } /* ------------------------------------------------------------- CHANGE */ .swap { display:grid; grid-template-columns:1fr auto 1fr; gap:10px; align-items:center; margin-top:16px; } .sw { background:#161A22; border:1px solid rgba(245,242,234,.09); border-radius:20px; padding:18px 12px; text-align:center; } .sw.now { border-color:rgba(217,164,65,.4); } .sw .v { font-size:clamp(1.3rem,6vw,1.9rem); font-weight:800; line-height:1; } .sw.now .v { color:#D9A441; } .sw.after .v { color:#C8402F; } .sw .k { font-size:.53rem; letter-spacing:.16em; color:#8A8580; text-transform:uppercase; margin-top:7px; font-weight:800; } .arrow { color:#8A8580; font-size:1.4rem; font-weight:800; } /* --------------------------------------------------------------- ERAS */ .card { background:#161A22; border:1px solid rgba(245,242,234,.09); border-radius:20px; padding:20px 16px; margin-top:16px; } .card-t { font-size:.6rem; letter-spacing:.2em; color:#8A8580; text-transform:uppercase; font-weight:800; margin-bottom:12px; } table { width:100%; border-collapse:collapse; } th { text-align:left; font-size:.52rem; letter-spacing:.14em; color:#8A8580; text-transform:uppercase; padding:0 5px 10px; font-weight:800; } th.n, td.n { text-align:right; font-variant-numeric:tabular-nums; } td { padding:11px 5px; border-top:1px solid rgba(245,242,234,.07); font-size:.86rem; } tr.cur td { background:rgba(217,164,65,.1); } tr.cur td:first-child { box-shadow:inset 3px 0 0 #D9A441; } tr.cur td { color:#F5F2EA; font-weight:800; } .era { color:#8A8580; width:34px; font-weight:700; } .rw { color:#D9A441; font-weight:800; } .actions { display:flex; gap:10px; justify-content:center; flex-wrap:wrap; margin-top:24px; } .btn { border:none; border-radius:10px; padding:15px 24px; min-height:50px; font-weight:800; font-size:.94rem; cursor:pointer; text-decoration:none; display:inline-flex; align-items:center; justify-content:center; gap:8px; } .btn-gold { background:#D9A441; color:#0B0D12; box-shadow:0 12px 36px rgba(217,164,65,.28); } .btn-ghost { background:transparent; color:#F5F2EA; border:1px solid rgba(245,242,234,.24); } .fine { font-size:.73rem; color:#8A8580; line-height:1.72; text-align:center; margin-top:22px; } .toast { position:fixed; left:50%; bottom:24px; transform:translateX(-50%) translateY(16px); background:#2E9E6B; color:#04160E; font-weight:800; padding:12px 22px; border-radius:10px; opacity:0; transition:all .3s; z-index:60; pointer-events:none; font-size:.88rem; } .toast.show { opacity:1; transform:translateX(-50%) translateY(0); } #card { display:none; } @media (prefers-reduced-motion: reduce){ .pfill { transition:none; } } /* Doc 17: visible keyboard focus on every interactive element. */ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, [tabindex]:focus-visible { outline:2px solid #D9A441; outline-offset:3px; border-radius:6px; } </style> </head> <body> <canvas id="sky"></canvas> {% include 'partials/nav.html' %} <div class="wrap"> <section class="head"> <div class="kicker">THE HALVING</div> <h1>{{ consensus.initial_subsidy_coins }} becomes {{ consensus.initial_subsidy_coins // 2 }}.<br>It <span class="r">never</span> comes back.</h1> <p class="lede">At block {{ '{:,}'.format(consensus.halving_interval) }} the reward for every MoonBite block is cut in half — permanently. Then again. And again, until there is nothing left to cut.</p> <div class="clock"> <div class="cl"><div class="v num" id="cD">—</div><div class="k">Days</div></div> <div class="cl"><div class="v num" id="cH">—</div><div class="k">Hours</div></div> <div class="cl"><div class="v num" id="cM">—</div><div class="k">Minutes</div></div> <div class="cl"><div class="v num" id="cS">—</div><div class="k">Seconds</div></div> </div> <div class="prog"> <div class="prog-top"><span>ERA <b id="pEra">0</b> PROGRESS</span><span id="pPct">0.00%</span></div> <div class="pbar"><div class="pfill" id="pFill"></div></div> <div class="prog-b"><span class="num" id="pNow">block 0</span> <span class="num">of {{ '{:,}'.format(consensus.halving_interval) }}</span></div> </div> </section> <!-- the staircase --> <div class="stair-card"> <div class="stair-t">THE STAIRCASE DOWN</div> <div class="stair-s">Every {{ consensus.halving_years }} years the step halves. There is no step back up — the shape of the money supply is decided and cannot be voted on.</div> <canvas id="stairs"></canvas> </div> <!-- what changes --> <div class="swap"> <div class="sw now"> <div class="v num">{{ consensus.initial_subsidy_coins }}</div> <div class="k">MBITE per block now</div> </div> <div class="arrow">→</div> <div class="sw after"> <div class="v num">{{ consensus.initial_subsidy_coins // 2 }}</div> <div class="k">After the halving</div> </div> </div> <div class="swap" style="margin-top:10px;"> <div class="sw now"> <div class="v num">{{ '{:,}'.format(consensus.daily_emission_coins) }}</div> <div class="k">Mined per day now</div> </div> <div class="arrow">→</div> <div class="sw after"> <div class="v num">{{ '{:,}'.format(consensus.daily_emission_coins // 2) }}</div> <div class="k">Per day after</div> </div> </div> <!-- eras --> <div class="card"> <div class="card-t">EVERY ERA, TO THE END</div> <table> <thead><tr><th style="width:34px;">ERA</th><th>REWARD</th> <th class="n">ENDS</th><th class="n">SUPPLY MINED</th></tr></thead> <tbody id="eras"></tbody> </table> <p class="fine" style="margin-top:14px; text-align:left;"> The subsidy reaches zero around <b style="color:#D9A441;">{{ consensus.final_block_year }}</b>. Dates assume {{ consensus.block_time_min }}-minute blocks and shift with real network timing.</p> </div> <div class="actions"> <a class="btn btn-gold" href="/start">Mine at {{ consensus.initial_subsidy_coins }} MBITE</a> <button class="btn btn-ghost" id="btnShare">Share the clock</button> </div> <p class="fine"> Half of every MBITE that will ever exist is mined in era 0. Whoever is here for it cannot be joined later — only remembered.<br> MBITE has no market value and is not an investment. </p> </div> {% include 'partials/footer.html' %} <canvas id="card" width="1080" height="1080"></canvas> <div class="toast" id="toast"></div> <script nonce="{{ csp_nonce }}"> (function () { 'use strict'; var C = { reward: {{ consensus.initial_subsidy_coins }}, blockSec: {{ consensus.block_time_sec }}, blocksPerDay: {{ consensus.blocks_per_day }}, halving: {{ consensus.halving_interval }}, nextH: {{ consensus.next_halving_height }}, height: {{ consensus.height or 0 }}, years: {{ consensus.halving_years }}, launch: {{ consensus.launch_year }}, cap: {{ consensus.max_supply_coins }} }; function $(id){ return document.getElementById(id); } function fmt(n){ return Math.round(n).toLocaleString('en-US'); } /* Doc 11: block heights are never thousands-separated — only amounts are. */ function ht(n){ return String(Math.round(n||0)); } /* The subsidy is an exact integer number of cents, so its decimal always terminates (12.5, 6.25, 3.125 ...). Print it exactly — rounding would misstate consensus on the very page that explains it. */ function coin(n){ return n.toLocaleString('en-US',{maximumFractionDigits:8}); } function toast(m){ var t=$('toast'); t.textContent=m; t.classList.add('show'); setTimeout(function(){ t.classList.remove('show'); },2200); } function copyText(s){ try { if(navigator.clipboard && navigator.clipboard.writeText) return navigator.clipboard.writeText(s).catch(function(){}); } catch(e){} return null; } if (!CanvasRenderingContext2D.prototype.roundRect) { CanvasRenderingContext2D.prototype.roundRect=function(x,y,w,h,r){ r=Math.min(r,w/2,h/2); this.moveTo(x+r,y); this.arcTo(x+w,y,x+w,y+h,r); this.arcTo(x+w,y+h,x,y+h,r); this.arcTo(x,y+h,x,y,r); this.arcTo(x,y,x+w,y,r); this.closePath(); return this; }; } var reduced=window.matchMedia('(prefers-reduced-motion: reduce)').matches; var DPR=Math.min(window.devicePixelRatio||1,2); /* --------------------------------------------------------------- sky */ var sky=$('sky'), sg=sky.getContext('2d'), W,H,stars=[]; function resize(){ W=innerWidth; H=innerHeight; sky.width=W*DPR; sky.height=H*DPR; sky.style.width=W+'px'; sky.style.height=H+'px'; sg.setTransform(DPR,0,0,DPR,0,0); stars=[]; for(var i=0,n=Math.min(140,(W*H/10000)|0); i<n; i++) stars.push({x:Math.random()*W,y:Math.random()*H,r:Math.random()*1.1+.25,tw:Math.random()*6.28}); } addEventListener('resize',function(){ resize(); drawStairs(); }); resize(); var t=0; (function loop(){ t+=reduced?0:.008; sg.fillStyle='#0B0D12'; sg.fillRect(0,0,W,H); for(var i=0;i<stars.length;i++){ var s=stars[i]; sg.globalAlpha=.15+.3*Math.abs(Math.sin(t+s.tw)); sg.fillStyle='#F5F2EA'; sg.beginPath(); sg.arc(s.x,s.y,s.r,0,6.2832); sg.fill(); } sg.globalAlpha=1; requestAnimationFrame(loop); })(); /* ------------------------------------------------------------ eras */ var eras=[], cum=0, r=C.reward, e=0; while (r >= 0.00000001 && e < 34){ var coins = r * C.halving; cum += coins; eras.push({ i:e, reward:r, endYear: Math.round(C.launch + (e+1)*C.years), cum:cum, pct: 100*cum/C.cap }); r = r/2; e++; } var curEra = Math.floor(C.height / C.halving); $('eras').innerHTML = eras.slice(0,9).map(function(x){ var rw = coin(x.reward); return '<tr class="'+(x.i===curEra?'cur':'')+'">'+ '<td class="era">'+x.i+'</td>'+ '<td class="rw">'+rw+' MBITE</td>'+ '<td class="n">'+x.endYear+'</td>'+ '<td class="n">'+x.pct.toFixed(1)+'%</td></tr>'; }).join('') + '<tr><td class="era">…</td><td style="color:#8A8580;">down to zero</td>'+ '<td class="n" style="color:#8A8580;">'+eras[eras.length-1].endYear+'</td>'+ '<td class="n" style="color:#8A8580;">100%</td></tr>'; /* -------------------------------------------------------- staircase */ var sc=$('stairs'), g=sc.getContext('2d'), grow=0, growRAF=null; function drawStairs(){ var Wc=sc.clientWidth, Hc=264; if(sc.width!==Wc*DPR){ sc.width=Wc*DPR; sc.height=Hc*DPR; } sc.style.height=Hc+'px'; g.setTransform(DPR,0,0,DPR,0,0); g.clearRect(0,0,Wc,Hc); var N=7, padB=40, padT=22, gap=5; var bw=(Wc-gap*(N-1))/N, maxH=Hc-padB-padT; for(var i=0;i<N;i++){ var x=i*(bw+gap); var full=maxH*(eras[i].reward/C.reward); var h=Math.max(3, full*grow); var y=Hc-padB-h; var isCur = (i===curEra); var grad=g.createLinearGradient(0,y,0,y+h); if(isCur){ grad.addColorStop(0,'#E8C878'); grad.addColorStop(1,'#D9A441'); } else if(i<curEra){ grad.addColorStop(0,'rgba(138,133,128,.5)'); grad.addColorStop(1,'rgba(138,133,128,.25)'); } else { var f=(i-curEra)/N; grad.addColorStop(0,'rgba(217,164,65,'+(0.5-0.4*f)+')'); grad.addColorStop(1,'rgba(217,164,65,'+(0.16-0.12*f)+')'); } g.fillStyle=grad; g.beginPath(); g.roundRect(x,y,bw,h,4); g.fill(); /* the drop line — shows the step down to the next era */ if(i<N-1){ var nextH2=maxH*(eras[i+1].reward/C.reward)*grow; g.strokeStyle='rgba(200,64,47,.42)'; g.lineWidth=1; g.setLineDash([3,3]); g.beginPath(); g.moveTo(x+bw, y); g.lineTo(x+bw+gap, Hc-padB-nextH2); g.stroke(); g.setLineDash([]); } /* reward label */ g.textAlign='center'; g.fillStyle = isCur ? '#F5F2EA' : '#8A8580'; g.font=(isCur?'800 ':'700 ')+'10px system-ui,sans-serif'; var lbl = coin(eras[i].reward); g.fillText(lbl, x+bw/2, y-7); /* year */ g.fillStyle = isCur ? '#D9A441' : 'rgba(138,133,128,.8)'; g.font='700 9px system-ui,sans-serif'; g.fillText(eras[i].endYear, x+bw/2, Hc-padB+15); if(isCur){ g.fillStyle='#D9A441'; g.font='800 8px system-ui,sans-serif'; g.fillText('YOU ARE HERE', x+bw/2, Hc-padB+28); } } g.strokeStyle='rgba(245,242,234,.1)'; g.lineWidth=1; g.beginPath(); g.moveTo(0,Hc-padB+.5); g.lineTo(Wc,Hc-padB+.5); g.stroke(); } (function animateStairs(){ if(reduced){ grow=1; drawStairs(); return; } grow=0; (function step(){ grow += (1-grow)*0.1 + 0.006; if(grow>=1){ grow=1; drawStairs(); return; } drawStairs(); growRAF=requestAnimationFrame(step); })(); })(); /* --------------------------------------------------------- countdown */ var height=C.height, t0=Date.now(); function paint(){ var left=Math.max(0, C.nextH-height); var secs=Math.max(0, left*C.blockSec - (Date.now()-t0)/1000); var d=Math.floor(secs/86400), h=Math.floor(secs%86400/3600), m=Math.floor(secs%3600/60), s=Math.floor(secs%60); $('cD').textContent=fmt(d); $('cH').textContent=(h<10?'0':'')+h; $('cM').textContent=(m<10?'0':'')+m; $('cS').textContent=(s<10?'0':'')+s; var into=height-(curEra*C.halving); var pct=100*into/C.halving; $('pEra').textContent=curEra; $('pPct').textContent=pct.toFixed(2)+'%'; $('pFill').style.width=Math.max(.4,pct)+'%'; $('pNow').textContent='block '+ht(height); } paint(); setInterval(paint,1000); function pollChain(){ fetch('/api/blockchain/info').then(function(r){return r.json();}).then(function(j){ var h=j.height||j.blocks||0; if(h>=0 && h!==height){ height=h; t0=Date.now(); paint(); } }).catch(function(){}); } pollChain(); setInterval(pollChain,20000); /* ------------------------------------------------------------ share */ function shareText(){ return 'MoonBite pays '+C.reward+' MBITE a block today. At block '+fmt(C.halving)+ ' that halves — forever, and it never rises again. '+ $('cD').textContent+' days left. moonbite.org/halving'; } $('btnShare').addEventListener('click', function(){ if(navigator.share){ navigator.share({text:shareText()}).catch(function(){}); } else { copyText(shareText()); toast('Copied'); } var cv=$('card'), x=cv.getContext('2d'); var bg=x.createLinearGradient(0,0,0,1080); bg.addColorStop(0,'#0B0D12'); bg.addColorStop(1,'#161A22'); x.fillStyle=bg; x.fillRect(0,0,1080,1080); for(var i=0;i<80;i++){ x.globalAlpha=.12+Math.random()*.45; x.fillStyle='#F5F2EA'; x.beginPath(); x.arc(Math.random()*1080,Math.random()*1080,Math.random()*1.8+.4,0,6.2832); x.fill(); } x.globalAlpha=1; x.textAlign='center'; x.beginPath(); x.arc(540,150,64,0,6.2832); x.fillStyle='#F5F2EA'; x.fill(); x.beginPath(); x.arc(575,100,50,0,6.2832); x.fillStyle='#0B0D12'; x.fill(); x.fillStyle='#D9A441'; x.beginPath(); x.roundRect(442,128,196,8,4); x.fill(); x.beginPath(); x.roundRect(448,162,184,8,4); x.fill(); x.fillStyle='#8A8580'; x.font='700 28px Arial'; x.fillText('THE MOONBITE HALVING', 540, 268); /* mini staircase on the card */ var bx=250, by=470, bwc=88, gp=10; for(var k=0;k<5;k++){ var hh=170*(eras[k].reward/C.reward); x.fillStyle = k===curEra ? '#D9A441' : 'rgba(217,164,65,'+(0.4-0.06*k)+')'; x.beginPath(); x.roundRect(bx+k*(bwc+gp), by-hh, bwc, hh, 5); x.fill(); x.fillStyle='rgba(245,242,234,.6)'; x.font='700 19px Arial'; x.fillText(coin(eras[k].reward), bx+k*(bwc+gp)+bwc/2, by-hh-11); } x.fillStyle='#C8402F'; x.font='800 76px Arial'; x.fillText(C.reward+' → '+(C.reward/2), 540, 610); x.fillStyle='#F5F2EA'; x.font='400 38px Arial'; x.fillText('and it never rises again.', 540, 672); x.fillStyle='#D9A441'; x.font='800 60px Arial'; x.fillText($('cD').textContent+' DAYS LEFT', 540, 790); x.fillStyle='#8A8580'; x.font='400 30px Arial'; x.fillText('Half of all MBITE is mined in this era.', 540, 850); x.fillStyle='#D9A441'; x.font='700 30px Arial'; x.fillText('moonbite.org/halving', 540, 936); x.fillStyle='rgba(245,242,234,.32)'; x.font='400 19px Arial'; x.fillText('MBITE has no market value and is not an investment.', 540, 1000); var a=document.createElement('a'); a.download='moonbite-halving.png'; a.href=cv.toDataURL('image/png'); a.click(); }); })(); </script> </body> </html>