Measured at 375, 768, 1280 and 1680 with headless Chrome. One token set in all four stylesheets: container 1200 px with a clamp(16px,4vw,32px) gutter, section rhythm clamp(56px,8vw,96px), cards clamp(18px,3vw,28px) padding and 18 px radius, tiles 18 by 20 px padding and 14 px radius, 24 px card gap and 12 px tile gap, headings h1 clamp(32,5.5vw,56) h2 clamp(28,4.2vw,44) h3 clamp(18,2vw,22), tile values clamp(20,2.2vw,26). Home: the live strip spans the full hero width with economics-spec tiles; stat strip and economics tiles share one spec; journey phases and log take the tile and card boxes; inline padding-top overrides removed, consecutive dark sections share one gap; every nav anchor lands the heading 11 px under the sticky bar at every width (padded sections subtract their own padding). Live: head, strip, cards and gaps on the tokens; four tiles a row, two on phones; network name no longer clips. Litepaper: real gutter instead of a fixed 16 px, cover, layout, tables, figures, pull quotes and stat tiles on the tokens, pager stacks on phones. Engineering log: same tokens, long code tokens wrap so phones no longer scroll sideways, duplicate h1 hidden. Copy shortened so no label orphans at any width: stat strip labels, the economics tiles, the strip tile labels, the litepaper stat tiles, the mining-program eyebrow. The light-client card block is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
362 lines
28 KiB
HTML
362 lines
28 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
<title>Igneum live devnet</title>
|
|
<meta name="description" content="The Igneum devnet as it runs: blocks, miners, difficulty and peers, read from a node every two seconds.">
|
|
<link rel="canonical" href="https://igneum.network/live">
|
|
<meta name="theme-color" content="#0C0C0E">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:site_name" content="Igneum">
|
|
<meta property="og:title" content="Igneum live devnet">
|
|
<meta property="og:description" content="The Igneum devnet as it runs: blocks, miners, difficulty and peers, read from a node every two seconds.">
|
|
<meta property="og:url" content="https://igneum.network/live">
|
|
<meta property="og:image" content="https://igneum.network/og-small.png">
|
|
<meta property="og:image:width" content="256">
|
|
<meta property="og:image:height" content="256">
|
|
<meta property="og:image:alt" content="Igneum. Mined by GPUs. Proven by fire.">
|
|
<meta name="twitter:card" content="summary">
|
|
<meta name="twitter:title" content="Igneum live devnet">
|
|
<meta name="twitter:description" content="The Igneum devnet as it runs: blocks, miners, difficulty and peers, read from a node every two seconds.">
|
|
<meta name="twitter:image" content="https://igneum.network/og-small.png">
|
|
<meta name="twitter:image:alt" content="Igneum. Mined by GPUs. Proven by fire.">
|
|
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='50' r='48' fill='%230C0C0E'/%3E%3Ccircle cx='50' cy='50' r='45' fill='none' stroke='%23F2541B' stroke-width='6'/%3E%3Cg transform='translate(24 24) scale(0.52)'%3E%3Cpolygon points='50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34' fill='%23F2541B'/%3E%3Cpolygon points='50,42 59,58 50,82 41,58' fill='%230C0C0E'/%3E%3C/g%3E%3C/svg%3E" type="image/svg+xml" type="image/svg+xml">
|
|
<link rel="icon" href="/favicon.ico" sizes="48x48">
|
|
<link rel="icon" href="/icon-192.png" type="image/png" sizes="192x192">
|
|
<link rel="icon" href="/icon-512.png" type="image/png" sizes="512x512">
|
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
|
|
<link rel="manifest" href="/site.webmanifest">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;700;900&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
|
|
<style>
|
|
:root{--obsidian:#0C0C0E;--graphite:#16161A;--line:#2A2A30;--line-2:#3A3A42;--ember:#F2541B;--molten:#FFB35C;--bone:#F4F1EC;--ash:#9A9A9E;--ink-2:#C9C7C2;--ember-ink:#0C0C0E;
|
|
/* one system for every page: container, gutter, section rhythm, card and tile boxes, heading scale (same tokens as the homepage) */
|
|
--max:1200px;--gutter:clamp(16px,4vw,32px);--sec:clamp(56px,8vw,96px);--head:clamp(40px,6vw,64px);
|
|
--card-pad:clamp(18px,3vw,28px);--card-r:18px;--tile-pad:18px 20px;--tile-r:14px;--gap:24px;--gap-tile:12px;
|
|
--fs-hero:clamp(40px,7vw,76px);--fs-h1:clamp(32px,5.5vw,56px);--fs-h2:clamp(28px,4.2vw,44px);--fs-h3:clamp(18px,2vw,22px);--fs-tile:clamp(20px,2.2vw,26px)}
|
|
*{box-sizing:border-box}
|
|
html{scroll-behavior:smooth}
|
|
body{margin:0;background:var(--obsidian);color:var(--bone);font-family:'IBM Plex Sans',system-ui,-apple-system,sans-serif;font-size:17px;line-height:1.55;-webkit-font-smoothing:antialiased;overflow-x:hidden}
|
|
a{color:inherit;text-decoration:none}
|
|
a:hover{color:var(--molten)}
|
|
a:focus-visible,button:focus-visible{outline:2px solid var(--ember);outline-offset:3px;border-radius:6px}
|
|
img,svg,canvas{max-width:100%}
|
|
.wrap{max-width:var(--max);margin:0 auto;padding-inline:var(--gutter)}
|
|
.mono{font-family:'IBM Plex Mono',monospace}
|
|
.eyebrow{font-family:'IBM Plex Mono',monospace;font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--ash)}
|
|
h1,h2,h3{font-family:'Unbounded',sans-serif;margin:0;line-height:1.08;text-wrap:balance}
|
|
h1{font-weight:900;font-size:var(--fs-h1);letter-spacing:-.01em}
|
|
h2{font-weight:700;font-size:var(--fs-h3)}
|
|
p{margin:0}
|
|
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:48px;padding:12px 22px;border-radius:10px;font-weight:600;font-size:16px;border:1px solid var(--line-2);color:var(--bone);background:transparent;cursor:pointer}
|
|
.btn.primary{background:var(--ember);color:var(--ember-ink);border-color:var(--ember)}
|
|
.btn.primary:hover{background:#FF6A2B;border-color:#FF6A2B;color:var(--ember-ink)}
|
|
.card{background:var(--graphite);border:1px solid var(--line);border-radius:var(--card-r);padding:var(--card-pad);min-width:0}
|
|
|
|
/* nav, same as the homepage */
|
|
.nav{position:sticky;top:0;z-index:20;background:rgba(12,12,14,.82);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid rgba(42,42,48,.6)}
|
|
.nav .wrap{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:68px}
|
|
.brand{display:flex;align-items:center;gap:10px}
|
|
.brand .word{font-family:'Unbounded',sans-serif;font-weight:900;font-size:20px;letter-spacing:.06em}
|
|
.links{display:flex;align-items:center;gap:26px;font-size:15px;font-weight:500}
|
|
.links .btn{min-height:42px;padding:10px 16px;font-size:15px}
|
|
.links a.here{color:var(--molten)}
|
|
.burger{display:none;background:none;border:1px solid var(--line-2);color:var(--bone);border-radius:10px;width:44px;height:44px;align-items:center;justify-content:center;cursor:pointer}
|
|
@media (max-width:900px){
|
|
.links{display:none;position:absolute;left:0;right:0;top:68px;background:var(--obsidian);border-bottom:1px solid var(--line);padding:16px var(--gutter) 24px;flex-direction:column;align-items:stretch;gap:6px}
|
|
.links a{padding:12px 6px;border-radius:8px}
|
|
.links .btn{width:100%;margin-top:6px}
|
|
.links.open{display:flex}
|
|
.burger{display:inline-flex}
|
|
}
|
|
|
|
/* page */
|
|
.head{padding-block:var(--head) var(--gap);display:flex;flex-direction:column;gap:14px}
|
|
.head p{color:var(--ink-2);max-width:60ch;font-size:16px}
|
|
.strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--gap-tile);margin-bottom:var(--gap)}
|
|
@media (max-width:700px){.strip{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
.cell{background:var(--graphite);border:1px solid var(--line);border-radius:var(--tile-r);padding:var(--tile-pad);display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
.cell .k{font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--ash)}
|
|
.cell .v{font-family:'Unbounded',sans-serif;font-weight:700;font-size:var(--fs-tile);line-height:1.1;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
.cell .v.name{font-family:'IBM Plex Mono',monospace;font-size:15px;font-weight:500}
|
|
.cell .s{font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--ash);text-wrap:balance}
|
|
.cell.status .v{display:flex;align-items:center;gap:10px}
|
|
.dot{width:10px;height:10px;border-radius:50%;background:var(--molten);display:inline-block;flex:0 0 10px;animation:pulse 2s ease-in-out infinite}
|
|
.off .dot{background:var(--ash);animation:none}
|
|
.off .cell.status .v{color:var(--ash)}
|
|
.on .cell.status .v{color:var(--molten)}
|
|
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(255,179,92,.5)}50%{box-shadow:0 0 0 8px rgba(255,179,92,0)}}
|
|
@media (prefers-reduced-motion:reduce){.dot{animation:none}}
|
|
|
|
.viz-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px 20px;margin-bottom:12px}
|
|
.viz-stats{display:flex;flex-wrap:wrap;gap:14px 18px;font-size:12px;color:var(--ash)}
|
|
.viz-stats b{color:var(--bone);font-weight:500;font-variant-numeric:tabular-nums}
|
|
#dag{display:block;width:100%;height:clamp(240px,38vw,380px);border-radius:12px;background:var(--obsidian)}
|
|
.legend{display:flex;flex-wrap:wrap;gap:16px 20px;margin-top:16px;font-size:13px;color:var(--ink-2)}
|
|
.legend span{display:inline-flex;align-items:center;gap:8px}
|
|
.sw{width:14px;height:14px;border-radius:4px;display:inline-block}
|
|
|
|
.two{display:grid;gap:var(--gap);grid-template-columns:1fr;margin-top:var(--gap)}
|
|
@media (min-width:900px){.two{grid-template-columns:1.1fr .9fr}}
|
|
.tbl{overflow-x:auto;margin-top:12px;-webkit-overflow-scrolling:touch}
|
|
table{border-collapse:collapse;width:100%;font-size:14px}
|
|
th,td{padding:10px 10px;text-align:left;border-bottom:1px solid var(--line);white-space:nowrap}
|
|
th{font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--ash);font-weight:500}
|
|
td.n{font-variant-numeric:tabular-nums;text-align:right}th.n{text-align:right}
|
|
tr:last-child td{border-bottom:0}
|
|
.id{font-family:'IBM Plex Mono',monospace;color:var(--molten)}
|
|
.bar{height:6px;border-radius:3px;background:var(--line);overflow:hidden;min-width:60px}
|
|
.bar i{display:block;height:100%;background:var(--ember)}
|
|
.feed{display:flex;flex-direction:column;margin-top:12px;font-family:'IBM Plex Mono',monospace;font-size:13px;color:var(--ink-2)}
|
|
.feed div{display:flex;justify-content:space-between;gap:12px;border-bottom:1px solid var(--line);padding:9px 0;align-items:baseline}
|
|
.feed div:last-child{border-bottom:0}
|
|
.feed .t{color:var(--ash);flex:0 0 auto;font-size:12px}
|
|
.feed .k{color:var(--molten)}
|
|
.empty{color:var(--ash);font-size:14px;padding:12px 0}
|
|
#spark{display:block;width:100%;height:120px;border-radius:12px;background:var(--obsidian);margin-top:12px}
|
|
.note{font-size:13px;color:var(--ash);margin-top:14px}
|
|
main{padding-bottom:var(--sec)}
|
|
|
|
/* footer, same as the homepage */
|
|
footer{border-top:1px solid var(--line);padding-block:48px 32px}
|
|
.foot-grid{display:grid;gap:32px;grid-template-columns:1fr;margin-bottom:32px}
|
|
@media (min-width:720px){.foot-grid{grid-template-columns:1.4fr 1fr 1fr}}
|
|
.foot-brand p{color:var(--ash);font-size:15px;margin-top:12px;max-width:40ch}
|
|
.foot-col{display:flex;flex-direction:column;gap:10px;font-size:15px}
|
|
.foot-col .eyebrow{margin-bottom:6px}
|
|
.foot-col a{color:var(--ink-2)}
|
|
.foot-base{display:flex;flex-wrap:wrap;justify-content:space-between;gap:12px;border-top:1px solid var(--line);padding-top:24px;font-size:13px;color:var(--ash)}
|
|
.foot-base span{text-wrap:balance}
|
|
</style>
|
|
</head>
|
|
<body class="off">
|
|
|
|
<nav class="nav" aria-label="Main">
|
|
<div class="wrap">
|
|
<a href="/" class="brand" aria-label="Igneum home">
|
|
<svg viewBox="0 0 100 100" width="30" height="30" aria-hidden="true"><polygon points="50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34" fill="#F2541B"></polygon><polygon points="50,42 59,58 50,82 41,58" fill="#0C0C0E"></polygon></svg>
|
|
<span class="word">IGNEUM</span>
|
|
</a>
|
|
<button class="burger" id="burger" aria-expanded="false" aria-controls="links" aria-label="Menu"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 7h16M4 12h16M4 17h16"></path></svg></button>
|
|
<div class="links" id="links">
|
|
<a href="/#mine">Mine</a>
|
|
<a href="/#prove">Prove</a>
|
|
<a href="/#build">Build</a>
|
|
<a href="/#randomx">vs RandomX</a>
|
|
<a href="/#journey">Journey</a>
|
|
<a href="/#economics">Economics</a>
|
|
<a href="/litepaper">Litepaper</a>
|
|
<a href="/bench">Log</a>
|
|
<a href="/live" class="here" aria-current="page">Live</a>
|
|
<a href="/#mine" class="btn primary">Get the miner</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<main class="wrap">
|
|
<div class="head">
|
|
<div class="eyebrow">devnet v0</div>
|
|
<h1>Live devnet</h1>
|
|
<p>A node is read every two seconds. Every block below is real: its parents, its miner, whether it sits on the selected chain, and whether it is a locked checkpoint. A checkpoint locks when the finality rule's quorum signs it. Proving is not in the devnet yet.</p>
|
|
</div>
|
|
|
|
<div class="strip" aria-live="polite">
|
|
<div class="cell status"><div class="k">Status</div><div class="v"><span class="dot"></span><span id="st-status">OFFLINE</span></div><div class="s" id="st-age">no observer update yet</div></div>
|
|
<div class="cell"><div class="k">Network</div><div class="v name" id="st-network">igneum-devnet</div><div class="s" id="st-node">node</div></div>
|
|
<div class="cell"><div class="k">Blocks / s</div><div class="v" id="st-bps">0.00</div><div class="s">over 60 s</div></div>
|
|
<div class="cell"><div class="k">Blue score</div><div class="v" id="st-blue">0</div><div class="s" id="st-count">0 blocks</div></div>
|
|
<div class="cell"><div class="k">Difficulty</div><div class="v" id="st-diff">0</div><div class="s">target per block</div></div>
|
|
<div class="cell"><div class="k">Hash rate</div><div class="v" id="st-hps">0</div><div class="s">network estimate</div></div>
|
|
<div class="cell"><div class="k">Miners</div><div class="v" id="st-miners">0</div><div class="s">active in 10 min</div></div>
|
|
<div class="cell"><div class="k">Peers</div><div class="v" id="st-peers">0</div><div class="s" id="st-mempool">mempool 0</div></div>
|
|
</div>
|
|
|
|
<div class="card" aria-label="Block DAG, live">
|
|
<div class="viz-head">
|
|
<div class="eyebrow"><span class="dot" style="margin-right:8px"></span>block dag</div>
|
|
<div class="viz-stats mono"><span>on screen <b id="c-screen">0</b></span><span>chain <b id="c-chain">0</b></span><span>side <b id="c-side">0</b></span><span>newest blue <b id="c-newest">0</b></span><span>locked <b id="c-locked">none</b></span></div>
|
|
</div>
|
|
<canvas id="dag" aria-hidden="true"></canvas>
|
|
<div class="legend">
|
|
<span><i class="sw" style="background:var(--ember)"></i>chain block</span>
|
|
<span><i class="sw" style="border:2px solid var(--ash)"></i>side block (blue or red, off the selected chain)</span>
|
|
<span><i class="sw" style="border:3px solid var(--ember);border-radius:50%;background:transparent"></i>locked checkpoint, final</span>
|
|
<span><i class="sw" style="border:2px solid var(--molten);box-shadow:0 0 8px rgba(255,179,92,.7)"></i>just arrived</span>
|
|
<span><i class="sw" style="width:22px;height:0;border-top:1px solid var(--ash)"></i>parent edge</span>
|
|
<span><span class="mono" style="color:var(--molten)">2ea5116e</span>miner short id, one lane each</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="two">
|
|
<div class="card">
|
|
<div class="viz-head"><h2>Miners</h2><div class="eyebrow">last 10 min</div></div>
|
|
<div class="tbl">
|
|
<table>
|
|
<thead><tr><th>Miner</th><th class="n">Blocks</th><th>Share</th><th>Last seen</th><th>Engine</th></tr></thead>
|
|
<tbody id="miners"><tr><td colspan="5" class="empty">No blocks in the last 10 minutes.</td></tr></tbody>
|
|
</table>
|
|
</div>
|
|
<p class="note">The short id is the first 8 hex characters of the vote key hash in each header. The engine is what the miner writes into the coinbase; the devnet miner writes nothing yet.</p>
|
|
</div>
|
|
<div class="card">
|
|
<div class="viz-head"><h2>Events</h2><div class="eyebrow">newest first</div></div>
|
|
<div class="feed" id="events"><div class="empty">No events yet.</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" style="margin-top:var(--gap)">
|
|
<div class="viz-head"><h2>Blocks per minute</h2><div class="eyebrow" id="spark-range">last hour</div></div>
|
|
<canvas id="spark" aria-hidden="true"></canvas>
|
|
<p class="note" id="spark-note">Counted by the observer as blocks arrive.</p>
|
|
</div>
|
|
</main>
|
|
|
|
<footer>
|
|
<div class="wrap">
|
|
<div class="foot-grid">
|
|
<div class="foot-brand">
|
|
<div class="brand"><svg viewBox="0 0 100 100" width="26" height="26" aria-hidden="true"><polygon points="50,4 74,34 67,58 80,54 61,96 39,96 20,54 33,58 26,34" fill="#F2541B"></polygon><polygon points="50,42 59,58 50,82 41,58" fill="#0C0C0E"></polygon></svg><span class="word" style="font-size:16px;color:var(--bone)">IGNEUM</span></div>
|
|
<p>Mined by GPUs. Proven by fire.</p>
|
|
</div>
|
|
<nav class="foot-col" aria-label="Read">
|
|
<div class="eyebrow">Read</div>
|
|
<a href="/litepaper">Litepaper</a>
|
|
<a href="/litepaper#limits">What Igneum does not claim</a>
|
|
<a href="/litepaper#randomx">Igneum vs RandomX</a>
|
|
<a href="/bench">Engineering log</a>
|
|
</nav>
|
|
<nav class="foot-col" aria-label="Follow">
|
|
<div class="eyebrow">Follow</div>
|
|
<a href="/#journey">Journey</a>
|
|
<a href="/live">Live devnet</a>
|
|
<a href="https://github.com/igneum-network" rel="noopener" style="display:inline-flex;align-items:center;gap:6px"><svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" aria-hidden="true"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.3.8-.6v-2.1c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.2 1.2.9-.3 1.9-.4 2.9-.4s2 .1 2.9.4c2.2-1.5 3.2-1.2 3.2-1.2.6 1.6.2 2.8.1 3.1.8.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.2c0 .3.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5z"/></svg>GitHub</a>
|
|
<a href="/#mine">Get the miner</a>
|
|
</nav>
|
|
</div>
|
|
<div class="foot-base">
|
|
<span>© 2026 Igneum. Nothing on this page is an offer to sell anything.</span>
|
|
<span class="mono">igneum.network</span>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
(function(){
|
|
var reduce=window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
var dpr=Math.min(2,window.devicePixelRatio||1);
|
|
|
|
// nav
|
|
var burger=document.getElementById('burger'),links=document.getElementById('links');
|
|
burger.addEventListener('click',function(){var open=links.classList.toggle('open');burger.setAttribute('aria-expanded',open?'true':'false');});
|
|
|
|
// formatting
|
|
function $(id){return document.getElementById(id);}
|
|
function compact(n){if(n===null||n===undefined||isNaN(n))return '0';var a=Math.abs(n);if(a>=1e12)return (n/1e12).toFixed(2)+'T';if(a>=1e9)return (n/1e9).toFixed(2)+'G';if(a>=1e6)return (n/1e6).toFixed(2)+'M';if(a>=1e3)return (n/1e3).toFixed(1)+'k';return String(Math.round(n));}
|
|
function hashrate(n){if(n===null||n===undefined)return 'n/a';var u=['H/s','kH/s','MH/s','GH/s','TH/s'],i=0;while(n>=1000&&i<u.length-1){n/=1000;i++;}return (i?n.toFixed(1):Math.round(n))+' '+u[i];}
|
|
function rel(ms){var s=Math.max(0,Math.round(ms/1000));if(s<60)return s+' s ago';var m=Math.floor(s/60);if(m<60)return m+' min ago';var h=Math.floor(m/60);if(h<24)return h+' h '+(m%60)+' min ago';return Math.floor(h/24)+' d ago';}
|
|
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
|
|
// ---------- DAG scene ----------
|
|
var c=$('dag'),x=c.getContext('2d'),W,H,S,blocks=[],byHash={},lanesOf={},laneOrder=[],maxLanes=6,pxPerSec=40,targetPx=40;
|
|
var live=false,serverNow=0,serverAt=0,frozenAt=null,note='',bps=0;
|
|
function size(){W=c.clientWidth;H=c.clientHeight;c.width=W*dpr;c.height=H*dpr;x.setTransform(dpr,0,0,dpr,0,0);S=W<600?16:22;maxLanes=H<260?4:6;}
|
|
size();window.addEventListener('resize',function(){size();draw(performance.now());});
|
|
function laneCount(){return Math.max(1,Math.min(maxLanes,laneOrder.length));}
|
|
function laneFor(m){m=m||'?';if(!(m in lanesOf)){lanesOf[m]=laneOrder.length;laneOrder.push(m);}return lanesOf[m]%laneCount();}
|
|
function laneY(l){var n=laneCount();if(n===1)return H*0.5;return H*0.16+l*(H*0.62/(n-1));}
|
|
function jitter(h){var v=0;for(var i=0;i<8;i++)v=(v*31+h.charCodeAt(i))%1000;return (v/1000-0.5)*S*0.5;}
|
|
function vnow(){if(frozenAt!==null)return frozenAt;return serverNow+(performance.now()-serverAt);}
|
|
function ingest(d){
|
|
var now=performance.now();serverNow=new Date(d.now).getTime();serverAt=now;
|
|
var seen={};
|
|
d.blocks.forEach(function(b){seen[b.hash]=1;var e=byHash[b.hash];
|
|
if(e){e.chain=b.chain;e.locked=!!b.locked;e.final=!!b.final;return;}
|
|
laneFor(b.miner);
|
|
e={hash:b.hash,ts:b.ts,rx:b.rx,blue_score:b.blue_score,parents:b.parents,chain:b.chain,miner:b.miner||'?',born:now,j:jitter(b.hash),locked:!!b.locked,final:!!b.final};
|
|
byHash[b.hash]=e;blocks.push(e);});
|
|
// drop what fell off the left edge long ago
|
|
var cut=vnow()-(W/pxPerSec)*1000-20000;
|
|
blocks=blocks.filter(function(b){var keep=b.ts>cut;if(!keep)delete byHash[b.hash];return keep;});
|
|
blocks.sort(function(a,b){return a.ts-b.ts;});
|
|
bps=d.state.blocks_per_second_60s||0;
|
|
targetPx=Math.max(S*0.4,Math.min(420,bps*S*1.7));
|
|
var chain=0;blocks.forEach(function(b){if(b.chain)chain++;});
|
|
$('c-screen').textContent=blocks.length;$('c-chain').textContent=chain;$('c-side').textContent=blocks.length-chain;
|
|
$('c-newest').textContent=blocks.length?compact(blocks[blocks.length-1].blue_score):'0';
|
|
var f=d.finality;$('c-locked').textContent=f&&f.latest_locked_index?('#'+f.latest_locked_index+' at blue '+compact(f.latest_locked_blue_score)):(f&&f.supported?'none yet':'n/a');
|
|
}
|
|
function rrect(px,py,w,h,r){x.beginPath();x.moveTo(px+r,py);x.lineTo(px+w-r,py);x.quadraticCurveTo(px+w,py,px+w,py+r);x.lineTo(px+w,py+h-r);x.quadraticCurveTo(px+w,py+h,px+w-r,py+h);x.lineTo(px+r,py+h);x.quadraticCurveTo(px,py+h,px,py+h-r);x.lineTo(px,py+r);x.quadraticCurveTo(px,py,px+r,py);x.closePath();}
|
|
function xOf(b,t){return Math.min(W-S,W-S*1.2-(t-b.ts)*pxPerSec/1000);}
|
|
function draw(now){
|
|
pxPerSec+=(targetPx-pxPerSec)*0.05;
|
|
var t=vnow();
|
|
x.clearRect(0,0,W,H);
|
|
// lane rails and miner labels
|
|
var n=laneCount();x.font='500 10px IBM Plex Mono, monospace';x.textAlign='left';
|
|
for(var l=0;l<n;l++){var y=laneY(l);x.strokeStyle='rgba(42,42,48,0.7)';x.lineWidth=1;x.beginPath();x.moveTo(0,y);x.lineTo(W,y);x.stroke();
|
|
var names=laneOrder.filter(function(m,i){return i%n===l;});if(names.length){x.fillStyle='rgba(154,154,158,0.75)';x.fillText(names.slice(0,2).join(' ')+(names.length>2?' +'+(names.length-2):''),8,y-S*0.75);}}
|
|
var pos={};blocks.forEach(function(b){pos[b.hash]={x:xOf(b,t),y:laneY(laneFor(b.miner))+b.j};});
|
|
// edges
|
|
blocks.forEach(function(b){var p=pos[b.hash];b.parents.forEach(function(h){var q=pos[h];if(!q)return;var both=b.chain&&byHash[h]&&byHash[h].chain;
|
|
x.strokeStyle=both?'rgba(242,84,27,0.75)':'rgba(201,199,194,0.45)';x.lineWidth=both?2:1.25;x.beginPath();x.moveTo(p.x-S/2,p.y);var mx=(p.x+q.x)/2;x.bezierCurveTo(mx,p.y,mx,q.y,q.x+S/2,q.y);x.stroke();});});
|
|
// lock line: everything left of the newest locked checkpoint is final (mirrors the homepage simulation)
|
|
var lk=null;blocks.forEach(function(b){if(b.locked&&(!lk||b.ts>lk.ts))lk=b;});
|
|
if(lk){var lp=pos[lk.hash];x.strokeStyle='rgba(242,84,27,0.55)';x.lineWidth=1.5;x.setLineDash([5,6]);x.beginPath();x.moveTo(lp.x,6);x.lineTo(lp.x,H-6);x.stroke();x.setLineDash([]);x.fillStyle='rgba(154,154,158,0.9)';x.font='500 10px IBM Plex Mono, monospace';x.textAlign='right';x.fillText('final',lp.x-8,14);}
|
|
// blocks
|
|
blocks.forEach(function(b){var p=pos[b.hash],h=S/2,age=now-b.born,fl=Math.max(0,1-age/900);
|
|
if(fl>0){var g=x.createRadialGradient(p.x,p.y,0,p.x,p.y,S*1.8);g.addColorStop(0,'rgba(255,179,92,'+(0.55*fl)+')');g.addColorStop(1,'rgba(255,179,92,0)');x.fillStyle=g;x.beginPath();x.arc(p.x,p.y,S*1.8,0,Math.PI*2);x.fill();}
|
|
if(b.chain){x.fillStyle='#F2541B';rrect(p.x-h,p.y-h,S,S,S*0.24);x.fill();}
|
|
else{x.fillStyle='#0C0C0E';rrect(p.x-h,p.y-h,S,S,S*0.24);x.fill();x.strokeStyle=fl>0?'rgba(255,179,92,0.95)':'rgba(154,154,158,0.9)';x.lineWidth=2;rrect(p.x-h,p.y-h,S,S,S*0.24);x.stroke();}
|
|
if(fl>0&&b.chain){x.strokeStyle='rgba(255,179,92,'+fl+')';x.lineWidth=2;rrect(p.x-h-2,p.y-h-2,S+4,S+4,S*0.3);x.stroke();}
|
|
if(b.locked){x.strokeStyle='#F2541B';x.lineWidth=3;x.beginPath();x.arc(p.x,p.y,S*0.95,0,Math.PI*2);x.stroke();}
|
|
x.fillStyle=b.chain?'rgba(244,241,236,0.9)':'rgba(154,154,158,0.9)';x.font='500 '+(S<20?8:9)+'px IBM Plex Mono, monospace';x.textAlign='center';x.fillText(b.miner,p.x,p.y+h+11);});
|
|
// tip marker
|
|
if(blocks.length){var tip=blocks[blocks.length-1],tp=pos[tip.hash];x.fillStyle='rgba(154,154,158,0.9)';x.font='500 10px IBM Plex Mono, monospace';x.textAlign='right';x.fillText('tip',Math.min(W-6,tp.x+S*0.5),Math.max(12,tp.y-S*0.75));}
|
|
if(!blocks.length){x.fillStyle='rgba(154,154,158,0.9)';x.font='500 13px IBM Plex Mono, monospace';x.textAlign='center';x.fillText(live?'waiting for the first block':'no observer data',W/2,H/2);}
|
|
if(note){x.fillStyle='rgba(12,12,14,0.72)';x.fillRect(0,H/2-22,W,44);x.fillStyle='#F4F1EC';x.font='500 13px IBM Plex Mono, monospace';x.textAlign='center';x.fillText(note,W/2,H/2+5);}
|
|
}
|
|
function frame(now){draw(now);if(!reduce)requestAnimationFrame(frame);}
|
|
requestAnimationFrame(frame);
|
|
|
|
// ---------- sparkline ----------
|
|
var sc=$('spark'),sx=sc.getContext('2d');
|
|
function spark(pairs){var w=sc.clientWidth,h=sc.clientHeight;sc.width=w*dpr;sc.height=h*dpr;sx.setTransform(dpr,0,0,dpr,0,0);sx.clearRect(0,0,w,h);
|
|
if(!pairs||!pairs.length){sx.fillStyle='rgba(154,154,158,0.9)';sx.font='500 12px IBM Plex Mono, monospace';sx.textAlign='center';sx.fillText('no data yet',w/2,h/2);return;}
|
|
var vals=pairs.map(function(p){return p[1];}),max=Math.max(1,Math.max.apply(null,vals)),pad=10,n=vals.length;
|
|
function px(i){return pad+i*(w-2*pad)/Math.max(1,n-1);}function py(v){return h-pad-(v/max)*(h-2*pad-12);}
|
|
sx.strokeStyle='rgba(42,42,48,0.9)';sx.lineWidth=1;sx.beginPath();sx.moveTo(pad,py(0));sx.lineTo(w-pad,py(0));sx.stroke();
|
|
sx.beginPath();sx.moveTo(px(0),py(0));vals.forEach(function(v,i){sx.lineTo(px(i),py(v));});sx.lineTo(px(n-1),py(0));sx.closePath();sx.fillStyle='rgba(242,84,27,0.14)';sx.fill();
|
|
sx.beginPath();vals.forEach(function(v,i){i?sx.lineTo(px(i),py(v)):sx.moveTo(px(i),py(v));});sx.strokeStyle='#F2541B';sx.lineWidth=2;sx.lineJoin='round';sx.stroke();
|
|
var li=n-1;sx.fillStyle='#FFB35C';sx.beginPath();sx.arc(px(li),py(vals[li]),3,0,Math.PI*2);sx.fill();
|
|
sx.fillStyle='rgba(154,154,158,0.9)';sx.font='500 10px IBM Plex Mono, monospace';sx.textAlign='left';sx.fillText('max '+max+' / min',pad,12);sx.textAlign='right';sx.fillText('now',w-pad,12);
|
|
var total=vals.reduce(function(a,b){return a+b;},0);$('spark-note').textContent=total+' blocks in the last hour, '+(total/60).toFixed(1)+' per minute on average.';}
|
|
|
|
// ---------- polling ----------
|
|
var minersEl=$('miners'),eventsEl=$('events'),failures=0;
|
|
function setStatus(on,ageText){document.body.className=on?'on':'off';$('st-status').textContent=on?'LIVE':'OFFLINE';$('st-age').textContent=ageText;}
|
|
function render(d){
|
|
var s=d.state,on=!s.stale;
|
|
setStatus(on,s.age_s===null?'no observer update yet':'observer updated '+rel(s.age_s*1000));
|
|
$('st-network').textContent=s.network||'igneum-devnet';$('st-node').textContent=s.node_version?'node '+s.node_version:'node';
|
|
$('st-bps').textContent=(s.blocks_per_second_60s||0).toFixed(2);
|
|
$('st-blue').textContent=compact(s.blue_score);$('st-count').textContent=compact(s.block_count)+' blocks';
|
|
$('st-diff').textContent=compact(s.difficulty);$('st-hps').textContent=hashrate(s.hashes_per_second_estimate);
|
|
$('st-miners').textContent=s.miners_10m;$('st-peers').textContent=s.peers===null?'0':s.peers;$('st-mempool').textContent='mempool '+(s.mempool||0);
|
|
var now=new Date(d.now).getTime();
|
|
if(d.miners.length){minersEl.innerHTML=d.miners.map(function(m){return '<tr><td><span class="id">'+esc(m.id)+'</span></td><td class="n">'+m.blocks+'</td><td><div style="display:flex;align-items:center;gap:8px"><div class="bar" style="width:70px"><i style="width:'+m.share+'%"></i></div><span class="mono" style="font-size:12px">'+m.share+'%</span></div></td><td>'+rel(now-new Date(m.last_seen).getTime())+'</td><td>'+(m.engine?esc(m.engine):'<span style="color:var(--ash)">not reported</span>')+'</td></tr>';}).join('');}
|
|
else minersEl.innerHTML='<tr><td colspan="5" class="empty">No blocks in the last 10 minutes.</td></tr>';
|
|
if(d.events.length){eventsEl.innerHTML=d.events.map(function(e){return '<div><span><span class="k">'+esc(e.kind)+'</span> '+esc(e.text)+'</span><span class="t">'+rel(now-new Date(e.ts).getTime())+'</span></div>';}).join('');}
|
|
else eventsEl.innerHTML='<div class="empty">No events yet.</div>';
|
|
spark(s.blocks_per_minute);
|
|
live=on;
|
|
if(on){frozenAt=null;note='';ingest(d);}
|
|
else{if(frozenAt===null)frozenAt=vnow()||now;note='observer offline, last update '+(s.age_s===null?'unknown':rel(s.age_s*1000));}
|
|
}
|
|
function poll(){fetch('/api/live',{cache:'no-store'}).then(function(r){if(!r.ok)throw new Error(r.status);return r.json();}).then(function(d){if(!d.ok)throw new Error(d.error);failures=0;render(d);}).catch(function(){failures++;if(failures>=3){setStatus(false,'api unreachable');if(frozenAt===null)frozenAt=vnow();note='api unreachable, scene frozen';}});}
|
|
poll();setInterval(poll,2000);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|