Home page restored and polished for the miner: the step scene with the shards, the glow and the final line brought up to date; live network strip; measured card picker; Discord; no calendar month

The owner, 6 October 2026: "Im not sure about the site can we revert it but polish it? minimal, simple but everything needed
for the dopamine and emotional triggers of the gpu miner", then "cant we have a dag animation like we had before? with the
shards making up the block, then the final line and the other bits that looked really cool but brought upto date?"

Restored (a new commit, no history rewrite): site/index.html as it stood at 14da2b8, the step-view page with its hero, facts,
scene, three things to check, downloads, build, wallet, journey, economics and the ledger band; nothing from tonight's other
work is undone (the litepaper, /live, the roadmap and benchmark wording, ledger X31 to X33, the scroll-zoom fix). Where the
page named a month it now carries tonight's sentences: the proofs card reads "Live rows arrive with the public testnet. The
public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes."; the
journey lead reads "Six phases, four public gates ... No calendar dates: each phase closes at its gate." and its inlined
phases are the gate-worded ones. No "August 2027", no month anywhere on the page.

The scene (site/live-steps.js): the original bits, every one driven by the live feed. A real block arrives from the right
with a glow and its chain number under it; its outline is grey while pending and ember once included; as its shards are
verified or paid, particles fly in from the edges and its quarter cells fill molten; when every shard is proven the block
turns ember and the caption says how many provers were paid; a locked checkpoint takes a ring and a ripple and the dashed
"final" line sweeps in from the right to it, drawn only while finality is active (the legend no longer says "final to its
left"; the wallet card's state word "final, checkpoint 5" is the wallet's own vocabulary and stays). A proof or a lock that
lands on a block already off screen re-enters from the right as its own event, so every step is seen when it happens. The
counters gain blocks per second. The caption follows the newest block that changed step.

The miner's triggers, each one element with live or measured numbers, none invented: a network strip under the facts
(hash rate, blocks in the last ten minutes, the latest block's age ticking every second with its chain number and word,
shards proven and paid with the last ten minutes), each value flashing molten when it changes; a card picker from the bench
table with the time a card alone takes to find a block at the live hash rate (RTX 5090 127.7 MH/s at 227 W and RTX 4070
28.8 MH/s at 76 W from the 6 October Ember Tune on the three-card Windows rig, RX 9070 XT 17.8 MH/s from the 5 October eGPU
entry, Apple M5 Max 26.7 MH/s from the first live swap; the RTX 4090 and RX 7900 XTX shown as not yet measured with the
bench table linked) with the pool note; the fairness line (graphics cards only, a new program every hour, your card proves
the blocks, 80% to the miner and 20% to the provers, nothing to anyone else); the download buttons with sizes; a Discord join
line (the standing invite, recorded in docs/community/discord-hooks.md so nobody asks again); the testnet sentence.

docs/fud-ledger.md: G4, C2 and X8 each gain a status line saying the restored home page carries their sentence again; the
ledger-text check guards them on the home page again (55 sentences, 0 missing).

Measured headless over 40 s against the live feed: 19 captioned transitions, the strip reading 2.3 GH/s, 562 blocks in ten
minutes, 8 s ago, 2,970 shards paid, the picker's times 18 s (5090), 79 s (4070), 2 min (9070 XT), 86 s (M5 Max) at that
hash rate, 0.92 blocks/s; no console errors at 1440 or 390 in either theme; no horizontal overflow. Captures in
docs/plans/site-ui-3-shots/after-v2/: home-{1440,390}-{dark,light}-fold.jpg, home-{1440,390}-{dark,light}.jpg and
home-steps-1440-dark.webm (ten seconds of the scene). Checks: identity grep 0 hits on served files, link check 934 links
across 16 pages 0 broken, ledger text 55 of 55, contrast 32 pairs 0 under 4.5:1, api tests 5 pass. Not deployed: the owner
sees it first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-06 21:34:11 +00:00
parent 362872af49
commit 13fafe3338
14 changed files with 561 additions and 127 deletions

View file

@ -80,3 +80,7 @@ Dry run against the live API at 18:51 UTC rendered all six posts (pulse 641 char
from the release plan. Finality was paused at render time (`finality.active` false, last lock 6842 at about 18:42Z, the newest
checkpoints at 55% of total weight): the watcher's first condition, seen in dry run. Not yet run: a live post (no credentials
file yet), the install on the box.
## The server invite
The standing invite is https://discord.gg/beCy8G5ZR (created 6 October 2026, main; the vanity names discord.gg/igneum and discord.gg/igneumnetwork were free on 3 October and are not yet claimed). The home page's join line and any public text use this URL; nobody asks for it again.

View file

@ -614,6 +614,8 @@ Evidence: design doc, decisions table row "Who are you?". Journey: `site/journey
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Governance, "There are no admin keys in consensus"; `site/index.html`, tile "admin keys in consensus". Was: Conceded, wording fix needed.
Status: Conceded, stated (6 October 2026, later that night): the home page was restored to its fuller shape on the owner's word ("revert it but polish it") and carries the tile "admin keys in consensus" again, beside the litepaper's Governance sentence.
Status: Conceded, stated (6 October 2026, night): the home page was redrawn as one statement, the live scene, three facts and the downloads, so the tile "admin keys in consensus" is no longer on `site/index.html`; the sentence stands on `site/litepaper.html`, Governance ("There are no admin keys in consensus"). The 5 October line below is the history.
Answer: Correct. Consensus has no admin keys. The genesis apps are contracts and each will have an upgrade policy; the bridge's is the one that matters, and if it starts as a committee bridge (E7) it has keys by definition. The development fund contract named in the quote no longer exists: the fund was removed on 3 October 2026. The litepaper's sentence must be scoped to consensus and each genesis contract must publish its key policy before launch.
@ -689,6 +691,8 @@ Evidence: litepaper "For miners", hardware paragraph. Wording: overclaims list,
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Mining, "no chip publicly shipped, approximate" and "Monero is precedent, not proof"; `site/index.html`, hero, "a chip gains too little to take your place". Was: Conceded, label needed.
Status: Conceded, stated (6 October 2026, later that night): the restored home page carries the RandomX paragraph again, "since 2019 (approximate)", beside the litepaper.
Status: Conceded, stated (6 October 2026, night): the home page no longer carries the RandomX paragraph; "since 2019 (approximate)" and "precedent, not proof" stand on `site/litepaper.html` (vs RandomX, Mining). The 5 October line below is the history.
Answer: True. "No chip publicly shipped, approximate" is the defensible phrasing. The argument from Monero is precedent, not proof, and the bounty exists because precedent is not proof.
@ -958,6 +962,8 @@ Sweep (5 October 2026, evening): stated in part. `site/partials/footer.html` on
Status: Conceded, stated (5 October 2026, night): `site/litepaper.html`, Roadmap phase 6, and `site/journey.json` phase 6, "No listing is arranged, promised or sought by the project". Was: Conceded, fix now.
Status: Conceded, stated (6 October 2026, later that night): the restored home page shows the journey again; phase 6 reads "No listing is arranged, promised or sought by the project" there and in the litepaper's roadmap.
Status: Conceded, stated (6 October 2026, night): the home page's journey is no longer shown (the inlined feed remains in the page source); the sentence "No listing is arranged, promised or sought by the project" stands on `site/litepaper.html` Roadmap phase 6 and in `site/journey.json`. The 5 October line below is the history.
Answer: Correct. No listing is arranged, promised or sought by the project. The phrase comes off the roadmap and the homepage journey.

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,48 +1,45 @@
/* Igneum live steps (6 October 2026, the owner: "I liked it when we could see each step of the algo working").
The original step view with real blocks: a lane chart where every square is a block the observer stored, released at a
calm pace (the feed is sampled, the counters elsewhere read the chain), and every square shows its step: mined (an outline,
grey while pending, ember once included), shards being proven (molten quarter cells fill as shards are verified or paid),
proven (ember fill), locked checkpoint (a ring; a dashed "final" line marks that everything to its left is final).
Each transition is animated as it happens (a glow and a ring pulse) and captioned in one line for the newest block that
changed. The wheel is never touched: the page scrolls through the scene. Light and dark from the CSS tokens; a still frame
under reduced motion. Needs /scenes/feed.js (IgneumFeed) on the page.
Usage: var steps = IgneumSteps.mount(canvas, { caption: el, window: 300 }); then IgneumFeed.start({ subs: [steps], window: 300 }). */
/* Igneum live steps (6 October 2026). The owner: "I liked it when we could see each step of the algo working", and later,
"can we have a dag animation like we had before? with the shards making up the block, then the final line and the other
bits that looked really cool but brought up to date?"
The lane chart with the original bits, every one driven by the live feed: a real block arrives from the right with a glow
and its chain number under it; its outline is grey while pending and ember once included; as its shards are verified or
paid, particles fly in from the edges and its quarter cells fill molten; when every shard is proven the block turns ember
and its provers are paid (the caption says to how many); a locked checkpoint takes a ring and a ripple and the dashed
"final" line sweeps in from the right to it, drawn only while finality is active. A proof or a lock that lands on a block
which has already left the screen re-enters from the right as its own event, so every step is seen when it happens. The
stream is sampled (one block every 2.5 s, a crossing of about 34 s); the counters elsewhere read the chain. No wheel
handler: the page scrolls through the scene. Light and dark from the CSS tokens; a still frame under reduced motion.
Needs /scenes/feed.js (IgneumFeed) on the page.
Usage: var steps = IgneumSteps.mount(canvas, { caption: el }); then IgneumFeed.start({ subs: [steps], window: 300 }). */
(function (root) {
'use strict';
var F = root.IgneumFeed;
function pad(n) { return String(n).padStart(2, '0'); }
function hms(ms) { var d = new Date(ms); return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); }
function fmt(n) { return n === null || n === undefined ? null : Number(n).toLocaleString('en-GB'); }
function stepOf(b) {
var sh = b.shards || [], done = function (x) { return x.state === 'verified' || x.state === 'paid'; };
if (b.proven || (sh.length && sh.every(done))) return 'proven';
if (sh.some(function (x) { return x.state === 'proving' || done(x); })) return 'proving';
return 'mined';
}
function isDone(x) { return x.state === 'verified' || x.state === 'paid'; }
function stepOf(b) { var sh = b.shards || []; if (b.proven || (sh.length && sh.every(isDone))) return 'proven'; if (sh.some(function (x) { return x.state === 'proving' || isDone(x); })) return 'proving'; return 'mined'; }
function shardKey(b) { return (b.shards || []).map(function (x) { return x.state[0]; }).join(''); }
function mount(canvas, opts) {
opts = opts || {};
var ctx = canvas.getContext('2d'), size = F.sizer(canvas, ctx), dims = size(), T = F.theme();
var lanes = 5, S, GAP, speed, blocks = [], byHash = {}, queue = [], laneOf = {}, laneN = 0, last = 0, prevT = 0;
// one block released every 2.5 s and a crossing of about 34 s at 1440: the chain merges a pending block in about 10 s, so a
// square is still on screen when its step changes (the original pace of 10 s lost most transitions off the left edge)
var RELEASE_MS = 2500;
var caption = opts.caption || null, lastChange = 0, latest = null, finality = null, proving = null, serverNow = 0;
var lanes = 5, S, GAP, speed, RELEASE_MS = 2500, blocks = [], byHash = {}, known = {}, queue = [], events = [], sparks = [], ripples = [], laneOf = {}, laneN = 0, last = 0, prevT = 0;
var caption = opts.caption || null, lastChange = 0, latest = null, finality = null, proving = null, seeded = false, lockX = null, lockTarget = null, lockHash = null;
function metrics() { S = dims.W < 600 ? 18 : 26; GAP = dims.W < 600 ? S * 2.5 : S * 3.1; speed = GAP / RELEASE_MS; }
metrics();
root.addEventListener('resize', function () { dims = size(); metrics(); L.kick(); });
function laneY(l) { return dims.H * 0.14 + l * (dims.H * 0.72 / (lanes - 1)); }
metrics(); root.addEventListener('resize', function () { dims = size(); metrics(); L.kick(); });
function laneY(l) { return dims.H * 0.14 + l * (dims.H * 0.70 / (lanes - 1)); }
function laneFor(m) { if (!(m in laneOf)) laneOf[m] = laneN++ % lanes; return laneOf[m]; }
function cpIndexOf(hash) { var cps = (finality && finality.checkpoints) || []; for (var i = cps.length - 1; i >= 0; i--) if (cps[i].hash === hash) return cps[i].index; return null; }
// the record of a block for the caption: its words with their times
function provers(b) { var set = {}; (b.shards || []).forEach(function (x) { if (x.prover) set[x.prover] = 1; }); return Object.keys(set).length; }
function describe(e) {
var b = e.src, parts = [], name = b.number !== null && b.number !== undefined ? 'block ' + fmt(b.number) : 'block ' + b.hash.slice(0, 8);
parts.push('mined ' + hms(b.ts) + (b.color === 'red' ? ', excluded' : b.chain ? ', on the selected chain' : b.color === 'blue' ? ', included' : ', pending'));
var sh = b.shards || [];
if (sh.length) {
var proving = sh.filter(function (x) { return x.state === 'proving'; }), done = sh.filter(function (x) { return x.state === 'verified' || x.state === 'paid'; });
var provers = {}; sh.forEach(function (x) { if (x.prover) provers[x.prover] = 1; }); var np = Object.keys(provers).length;
if (e.step === 'proven') { var lag = Math.max.apply(null, sh.map(function (x) { return x.lag || 0; })); parts.push(sh.length + (sh.length === 1 ? ' shard' : ' shards') + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : '')); parts.push('proven in ' + (lag > 0 ? lag : Math.max(1, Math.round((Date.now() - b.ts) / 1000))) + ' s'); }
else if (proving.length || done.length) parts.push((done.length ? done.length + ' of ' + sh.length + ' shards proven' : sh.length + (sh.length === 1 ? ' shard' : ' shards') + ' proving') + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : ''));
var done = sh.filter(isDone), np = provers(b), paid = sh.filter(function (x) { return x.state === 'paid'; }).length;
if (e.step === 'proven') { var lag = Math.max.apply(null, sh.map(function (x) { return x.lag || 0; })); parts.push(sh.length + (sh.length === 1 ? ' shard' : ' shards') + ' proven in ' + (lag > 0 ? lag : Math.max(1, Math.round((Date.now() - b.ts) / 1000))) + ' s'); if (paid) parts.push(np ? np + (np === 1 ? ' prover paid' : ' provers paid') : 'provers paid'); }
else if (done.length) parts.push(done.length + ' of ' + sh.length + ' shards proven' + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : ''));
else if (sh.some(function (x) { return x.state === 'proving'; })) parts.push(sh.length + (sh.length === 1 ? ' shard' : ' shards') + ' proving' + (np ? ' on ' + np + (np === 1 ? ' card' : ' cards') : ''));
else parts.push(sh.length + (sh.length === 1 ? ' shard planned' : ' shards planned'));
}
if (b.locked) { var idx = cpIndexOf(b.hash); parts.push('locked at checkpoint' + (idx !== null ? ' ' + fmt(idx) : '')); }
@ -50,64 +47,75 @@
return name + ': ' + parts.join('; ');
}
function setCaption(text, muted) { if (!caption) return; caption.textContent = text; caption.classList.toggle('muted', !!muted); }
function apply(e, src, now, announce) {
var before = e.step + '|' + e.src.color + '|' + e.src.locked + '|' + e.src.final + '|' + (e.src.chain ? 1 : 0);
e.src = src; e.step = stepOf(src);
var after = e.step + '|' + src.color + '|' + src.locked + '|' + src.final + '|' + (src.chain ? 1 : 0);
if (announce && after !== before) { e.glow = 1; if (src.locked && !e.ringed) { e.ringed = true; e.ripple = 1; } lastChange = now; latest = e; setCaption(describe(e)); if (opts.onChange) opts.onChange(e, before, after); }
function rr(x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); }
function cellOf(b, i) { var h = S / 2, q = S / 2 - 3; return { x: b.x - h + 3 + (i % 2) * q + (q - 1) / 2, y: b.y - h + 3 + Math.floor(i / 2) * q + (q - 1) / 2 }; }
// a particle per newly proven shard, from an edge to the block's quarter cell (the original scene's sparks, now real)
function sparkFor(e, i) { var fromTop = Math.random() < 0.5; sparks.push({ x: Math.random() * dims.W, y: fromTop ? -6 : dims.H + 6, b: e, i: i, p: 0 }); }
function announce(e, now, reason) { e.glow = 1; lastChange = now; latest = e; setCaption(describe(e)); if (opts.onChange) opts.onChange(e, reason); }
function apply(e, src, now) {
var before = e.step + '|' + e.src.color + '|' + e.src.locked + '|' + (e.src.chain ? 1 : 0) + '|' + shardKey(e.src);
var oldSh = e.src.shards || []; e.src = src; e.step = stepOf(src);
var after = e.step + '|' + src.color + '|' + src.locked + '|' + (src.chain ? 1 : 0) + '|' + shardKey(src);
if (after === before) return false;
(src.shards || []).forEach(function (x, i) { if (i < 4 && isDone(x) && !(oldSh[i] && isDone(oldSh[i])) && e.x !== null) sparkFor(e, i); });
if (src.locked && !e.ringed) { e.ringed = true; if (e.x !== null) { ripples.push({ x: e.x, y: e.y, r: S, a: 1 }); ripples.push({ x: e.x, y: e.y, r: S * 0.5, a: 1 }); } }
announce(e, now, after.split('|')[0]); return true;
}
var seeded = false;
function push(d) {
var now = performance.now(); finality = d.finality || null; proving = d.proving || null; serverNow = new Date(d.now).getTime();
if (!seeded) {
// the first reply fills the scene: the newest blocks placed across the width, oldest at the left, so nothing starts empty
seeded = true; var n = Math.ceil(dims.W / GAP) + 1, recent = d.blocks.slice(-n);
recent.forEach(function (b, i) { var e = { hash: b.hash, src: b, step: stepOf(b), x: dims.W - S - (recent.length - 1 - i) * GAP, y: laneY(laneFor(b.miner || '?')), glow: 0, ripple: 0, ringed: !!b.locked, born: now }; byHash[b.hash] = e; blocks.push(e); });
}
var now = performance.now(); finality = d.finality || null; proving = d.proving || null;
if (finality && !finality.active) { lockTarget = null; lockX = null; lockHash = null; }
if (!seeded) { seeded = true; var n = Math.ceil(dims.W / GAP) + 1, recent = d.blocks.slice(-n);
recent.forEach(function (b, i) { var e = { hash: b.hash, src: b, step: stepOf(b), x: dims.W - S - (recent.length - 1 - i) * GAP, y: laneY(laneFor(b.miner || '?')), glow: 0, ringed: !!b.locked, born: now }; byHash[b.hash] = e; known[b.hash] = e; blocks.push(e); }); }
d.blocks.forEach(function (b) {
var e = byHash[b.hash];
if (e) { apply(e, b, now, true); return; }
e = { hash: b.hash, src: b, step: stepOf(b), x: null, y: null, glow: 0, ripple: 0, ringed: !!b.locked, born: 0 };
byHash[b.hash] = e; queue.push(e); if (queue.length > 6) { var dropped = queue.shift(); delete byHash[dropped.hash]; }
var e = known[b.hash];
if (e) { var changed = apply(e, b, now); if (changed && e.x === null && blocks.indexOf(e) < 0 && (e.step === 'proven' || b.locked)) { e.event = true; queue.push(e); } return; }
e = { hash: b.hash, src: b, step: stepOf(b), x: null, y: null, glow: 0, ringed: !!b.locked, born: 0 }; known[b.hash] = e; queue.push(e);
});
var seen = {}; d.blocks.forEach(function (b) { seen[b.hash] = 1; });
// a block the feed no longer carries keeps its last words on screen until it leaves at the left
// forget blocks that have left both the feed and the screen
var inFeed = {}; d.blocks.forEach(function (b) { inFeed[b.hash] = 1; });
Object.keys(known).forEach(function (h) { var e = known[h]; if (!inFeed[h] && blocks.indexOf(e) < 0 && queue.indexOf(e) < 0) delete known[h]; });
if (queue.length > 12) queue = queue.filter(function (e) { return e.event; }).concat(queue.filter(function (e) { return !e.event; }).slice(-6));
if (!latest || now - lastChange > 60000) {
var why = finality && finality.supported && !finality.active ? 'finality paused: under two thirds of the weight is signing, so no checkpoint locks' : proving && proving.supported && proving.active && !(proving.shards_paid_10m > 0) ? 'no proof landed in the last 10 minutes; proofs land about ' + (proving.median_proof_lag_s ? Math.round(proving.median_proof_lag_s) : 380) + ' s behind the tip' : 'no block changed step in the last minute';
setCaption('Blocks arrive every second. ' + why.charAt(0).toUpperCase() + why.slice(1) + '.', true);
}
L.kick();
}
function rr(x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); }
function spawn(e, t) { e.x = dims.W + S; e.y = laneY(laneFor(e.src.miner || '?')); e.born = t; blocks.push(e); if (blocks.length > 80) { var old = blocks.shift(); if (!queue.some(function (q) { return q === old; })) delete byHash[old.hash]; } }
function spawn(e, t) { e.x = dims.W + S; e.y = laneY(laneFor(e.src.miner || '?')); e.born = t; e.glow = 1; blocks.push(e); byHash[e.hash] = e;
if (e.event) { (e.src.shards || []).forEach(function (x, i) { if (i < 4 && isDone(x)) sparkFor(e, i); }); if (e.src.locked) { ripples.push({ x: e.x, y: e.y, r: S, a: 1 }); } }
if (blocks.length > 80) { var old = blocks.shift(); delete byHash[old.hash]; } }
function draw(t) {
var W = dims.W, H = dims.H; T = F.theme();
var dt = prevT ? Math.min(50, t - prevT) : 16; prevT = t;
if (queue.length && t - last > RELEASE_MS) { spawn(queue.pop(), t); queue.length = 0; last = t; } // the newest waiting block; the rest are sampled out
blocks.forEach(function (b) { b.x -= speed * dt; if (b.glow > 0) b.glow -= 0.02; if (b.ripple > 0) b.ripple -= 0.012; });
blocks = blocks.filter(function (b) { if (b.x < -S * 2) { if (byHash[b.hash] === b && queue.indexOf(b) < 0) delete byHash[b.hash]; return false; } return true; });
if (queue.length && t - last > RELEASE_MS) { var ev = queue.findIndex(function (e) { return e.event; }); spawn(ev >= 0 ? queue.splice(ev, 1)[0] : queue.pop(), t); if (ev < 0) queue = queue.filter(function (e) { return e.event; }); last = t; }
blocks.forEach(function (b) { b.x -= speed * dt; if (b.glow > 0) b.glow -= 0.015; });
blocks = blocks.filter(function (b) { if (b.x < -S * 2) { b.x = null; delete byHash[b.hash]; return false; } return true; });
sparks.forEach(function (sp) { sp.p += 0.035; }); sparks = sparks.filter(function (sp) { return sp.p < 1 && blocks.indexOf(sp.b) >= 0; });
ripples.forEach(function (r) { r.r += 2.2; r.a -= 0.012; r.x -= speed * dt; }); ripples = ripples.filter(function (r) { return r.a > 0; });
ctx.clearRect(0, 0, W, H);
ctx.strokeStyle = F.rgba(T.line, 0.9); ctx.lineWidth = 1; for (var l = 0; l < lanes; l++) { ctx.beginPath(); ctx.moveTo(0, laneY(l)); ctx.lineTo(W, laneY(l)); ctx.stroke(); }
// parent edges between blocks on screen
blocks.forEach(function (b) { (b.src.parents || []).forEach(function (h) { var q = byHash[h]; if (!q || q.x === null || blocks.indexOf(q) < 0) return; var both = b.step === 'proven' && q.step === 'proven'; ctx.strokeStyle = both ? F.rgba(T.ember, 0.45) : F.rgba(T.ash, 0.22); ctx.lineWidth = both ? 1.5 : 1; ctx.beginPath(); ctx.moveTo(b.x, b.y); var mx = (b.x + q.x) / 2; ctx.bezierCurveTo(mx, b.y, mx, q.y, q.x, q.y); ctx.stroke(); }); });
// the lock line: everything left of the newest locked block is final
// the lock line and the word final are drawn only while finality is active (the owner's rule: no "final" anywhere while finality is paused)
var lk = null; if (!finality || finality.active) blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; });
if (lk) { ctx.strokeStyle = F.rgba(T.ember, 0.55); ctx.lineWidth = 1.5; ctx.setLineDash([5, 6]); ctx.beginPath(); ctx.moveTo(lk.x, 6); ctx.lineTo(lk.x, H - 6); ctx.stroke(); ctx.setLineDash([]); ctx.fillStyle = T.ash; ctx.font = '500 ' + Math.max(10, Math.round(S * 0.42)) + 'px IBM Plex Mono, monospace'; ctx.textAlign = 'right'; ctx.textBaseline = 'alphabetic'; ctx.fillText('final', lk.x - 8, 14); }
blocks.forEach(function (b) { (b.src.parents || []).forEach(function (h) { var q = byHash[h]; if (!q || q.x === null) return; var both = b.step === 'proven' && q.step === 'proven'; ctx.strokeStyle = both ? F.rgba(T.ember, 0.45) : F.rgba(T.ash, 0.22); ctx.lineWidth = both ? 1.5 : 1; ctx.beginPath(); ctx.moveTo(b.x, b.y); var mx = (b.x + q.x) / 2; ctx.bezierCurveTo(mx, b.y, mx, q.y, q.x, q.y); ctx.stroke(); }); });
// the final line: drawn only while finality is active; it sweeps in from the right to the newest locked block on screen
if (finality && finality.active) { var lk = null; blocks.forEach(function (b) { if (b.src.locked && (!lk || b.x > lk.x)) lk = b; });
if (lk) { if (lockHash !== lk.hash) { lockHash = lk.hash; if (lockX === null) lockX = W + 20; } lockTarget = lk.x; lockX += (lockTarget - lockX) * 0.08;
ctx.strokeStyle = F.rgba(T.ember, 0.55); ctx.lineWidth = 1.5; ctx.setLineDash([5, 6]); ctx.beginPath(); ctx.moveTo(lockX, 6); ctx.lineTo(lockX, H - 6); ctx.stroke(); ctx.setLineDash([]);
ctx.fillStyle = T.ash; ctx.font = '500 ' + Math.max(10, Math.round(S * 0.42)) + 'px IBM Plex Mono, monospace'; ctx.textAlign = 'right'; ctx.textBaseline = 'alphabetic'; ctx.fillText('final', lockX - 8, 14); } }
blocks.forEach(function (b) {
var h = S / 2, w = b.step, src = b.src, ex = src.color === 'red';
if (b.glow > 0) { var g = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, S * 1.6); g.addColorStop(0, F.rgba(T.ember, 0.45 * b.glow)); g.addColorStop(1, F.rgba(T.ember, 0)); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(b.x, b.y, S * 1.6, 0, Math.PI * 2); ctx.fill(); }
if (ex) ctx.globalAlpha = 0.45;
if (w === 'proven') { ctx.fillStyle = T.ember; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill(); }
else {
ctx.fillStyle = T.row; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill();
else { ctx.fillStyle = T.row; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.fill();
ctx.strokeStyle = w === 'proving' ? F.rgba(T.molten, 0.9) : (src.chain || src.color === 'blue') ? F.rgba(T.ember, 0.8) : F.rgba(T.ash, 0.7); ctx.lineWidth = 2; rr(b.x - h, b.y - h, S, S, S * 0.24); ctx.stroke();
if (w === 'proving') { var sh = src.shards || [], n = Math.min(4, sh.length), q = S / 2 - 3; for (var i = 0; i < n; i++) { var st = sh[i].state; if (st === 'verified' || st === 'paid' || st === 'proving') { ctx.fillStyle = st === 'proving' ? F.rgba(T.molten, 0.45) : T.molten; var qx = b.x - h + 3 + (i % 2) * q, qy = b.y - h + 3 + Math.floor(i / 2) * q; rr(qx, qy, q - 1, q - 1, 2); ctx.fill(); } } }
}
var sh = src.shards || [], n = Math.min(4, sh.length), q = S / 2 - 3;
for (var i = 0; i < n; i++) { var st = sh[i].state; if (st === 'verified' || st === 'paid' || st === 'proving') { ctx.fillStyle = st === 'proving' ? F.rgba(T.molten, 0.45) : T.molten; var qx = b.x - h + 3 + (i % 2) * q, qy = b.y - h + 3 + Math.floor(i / 2) * q; rr(qx, qy, q - 1, q - 1, 2); ctx.fill(); } } }
if (src.locked) { var pulse = 0.8 + 0.2 * Math.sin(t / 350); ctx.strokeStyle = F.rgba(T.ember, pulse); ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(b.x, b.y, S * 1.15, 0, Math.PI * 2); ctx.stroke(); ctx.fillStyle = T.bone; ctx.beginPath(); ctx.arc(b.x, b.y, S * 0.16, 0, Math.PI * 2); ctx.fill(); }
if (b.ripple > 0) { ctx.strokeStyle = F.rgba(T.ember, b.ripple); ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(b.x, b.y, S * (1.3 + (1 - b.ripple) * 2), 0, Math.PI * 2); ctx.stroke(); }
// the chain number under the block (the short hash off the chain), at the wide size only
if (S >= 26) { ctx.fillStyle = F.rgba(T.ash, ex ? 0.6 : 0.9); ctx.font = '400 10px IBM Plex Mono, monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'top'; ctx.fillText(src.number !== null && src.number !== undefined ? fmt(src.number) : src.hash.slice(0, 6), b.x, b.y + h + 5); }
ctx.globalAlpha = 1;
});
sparks.forEach(function (sp) { var c = cellOf(sp.b, sp.i), e = 1 - Math.pow(1 - sp.p, 3), sx = sp.x + (c.x - sp.x) * e, sy = sp.y + (c.y - sp.y) * e; ctx.strokeStyle = F.rgba(T.molten, 0.5 * (1 - sp.p)); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(sp.x + (c.x - sp.x) * Math.max(0, e - 0.15), sp.y + (c.y - sp.y) * Math.max(0, e - 0.15)); ctx.lineTo(sx, sy); ctx.stroke(); ctx.fillStyle = T.molten; ctx.beginPath(); ctx.arc(sx, sy, 2.2, 0, Math.PI * 2); ctx.fill(); });
ripples.forEach(function (r) { ctx.strokeStyle = F.rgba(T.ember, r.a); ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(r.x, r.y, r.r, 0, Math.PI * 2); ctx.stroke(); });
}
var L = F.loop(draw, canvas); L.kick();
return { push: push, redraw: L.kick };

View file

@ -13,6 +13,11 @@ const REQUIRED = {
['E5', 'The one payment to the project is the Ember software\u2019s optional 1% dev fee', 'The one payment to the project is the Ember software\'s optional 1% dev fee'],
['X2', 'Public testnet: not yet open; the devnet build is here for people who want to look'],
['X7', 'hello@igneum.network'],
// restored with the home page on 6 October 2026 (the owner: "revert it but polish it")
['G4', 'admin keys in consensus'],
['C2', 'since 2019 (approximate)'],
['X8', 'No listing is arranged, promised or sought by the project'],
['X31', 'The public testnet is weeks away: three seed nodes and the public RPC are up, and it opens when the go checklist closes.'],
],
'litepaper.html': [
['X3', 'Live rows arrive with the public testnet.'],