git / moonbite-network / static/mining-god.js · raw

/* ========================================================================== * MoonBite — GOD-TIER Cinematic Mining Engine * Pure canvas. Zero dependencies. Zero coupling to the mining API/logic: * it OBSERVES the existing DOM state (blocks / progress / orb idle-class) * that the page's inline script already maintains, and renders a 3D reactor, * particle field and live hashrate graph on top. Nothing here starts, stops * or talks to mining — it only visualises. * ========================================================================== */(function () {  "use strict";  var stage = document.getElementById("godStage");  if (!stage) return;  var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;  var reactor = document.getElementById("godReactor");  var particles = document.getElementById("godParticles");  var spark = document.getElementById("godSpark");  var rCtx = reactor.getContext("2d");  var pCtx = particles.getContext("2d");  var sCtx = spark.getContext("2d");  // ---- DPI-aware sizing --------------------------------------------------  var DPR = Math.min(window.devicePixelRatio || 1, 2);  var W = 0, H = 0, sW = 0, sH = 0;  function fit(canvas, ctx) {    var r = canvas.getBoundingClientRect();    canvas.width = Math.max(1, Math.round(r.width * DPR));    canvas.height = Math.max(1, Math.round(r.height * DPR));    ctx.setTransform(DPR, 0, 0, DPR, 0, 0);    return { w: r.width, h: r.height };  }  function resize() {    var a = fit(reactor, rCtx); W = a.w; H = a.h;    fit(particles, pCtx);    var s = fit(spark, sCtx); sW = s.w; sH = s.h;  }  window.addEventListener("resize", resize);  resize();  // The stage starts display:none (revealed only when mining begins), so the  // canvases would size to 1x1. Re-fit whenever the stage's box changes size  // — covers the hidden->visible transition and container reflows.  if (typeof ResizeObserver !== "undefined") {    var ro = new ResizeObserver(function () { resize(); });    ro.observe(stage);  }  // ---- State read from the live page ------------------------------------  // intensity 0..1 ramps up while mining, decays when idle.  var intensity = 0;  var targetIntensity = 0;  var hashrate = 0;          // smoothed display value (H/s, synthetic)  var lastBlocks = 0;  var burst = 0;             // transient flash when a block lands  function readState() {    var orb = document.getElementById("miningOrb");    var live = orb ? !orb.classList.contains("idle") : false;    var fillEl = document.getElementById("progressBarFill");    var pct = 0;    if (fillEl) { pct = parseFloat(fillEl.style.width) || 0; }    var blocks = parseInt((document.getElementById("blocksMined") || {}).textContent, 10) || 0;    if (blocks > lastBlocks) { burst = 1; lastBlocks = blocks; }    if (blocks < lastBlocks) { lastBlocks = blocks; } // reset between runs    targetIntensity = live ? 1 : 0;    // Mirror the real readouts into the cinematic HUD.    setText("godHeight", (document.getElementById("currentHeight") || {}).textContent || "--");    setText("godElapsed", (document.getElementById("elapsedTime") || {}).textContent || "0s");    var total = (document.getElementById("totalBlocks") || {}).textContent || "0";    setText("godBlocks", blocks + " / " + total);    // Beam mirrors the real progress width.    var beam = document.getElementById("godBeamFill");    if (beam) beam.style.width = pct + "%";    setText("godPct", Math.round(pct) + "%");    // State pill text.    var pill = document.getElementById("godStateText");    if (pill) pill.textContent = live ? "REACTOR ONLINE" : "STANDBY";    if (live) stage.classList.add("is-live"); else stage.classList.remove("is-live");  }  function setText(id, v) { var el = document.getElementById(id); if (el && el.textContent !== v) el.textContent = v; }  // ---- Hashrate history (sparkline) -------------------------------------  var history = new Array(64).fill(0);  // ---- Particle field ---------------------------------------------------  var parts = [];  function seedParticles() {    parts = [];    var n = reduce ? 26 : 70;    for (var i = 0; i < n; i++) {      parts.push({        x: Math.random(), y: Math.random(),        r: 0.6 + Math.random() * 1.8,        sp: 0.15 + Math.random() * 0.5,        drift: (Math.random() - 0.5) * 0.25,        hue: Math.random()      });    }  }  seedParticles();  function drawParticles() {    pCtx.clearRect(0, 0, W, H);    var cx = W / 2, cy = H * 0.46;    for (var i = 0; i < parts.length; i++) {      var p = parts[i];      // Rise + slight swirl toward the core when live.      p.y -= (p.sp * (0.4 + intensity)) / H;      p.x += (p.drift * (0.3 + intensity)) / W;      if (p.y < -0.05) { p.y = 1.05; p.x = Math.random(); }      var px = p.x * W, py = p.y * H;      // Pull toward the core with intensity for a "vortex" feel.      px += (cx - px) * 0.06 * intensity;      var col = p.hue < 0.5        ? "rgba(124,92,255," : (p.hue < 0.8 ? "rgba(52,211,255," : "rgba(34,229,168,");      var a = (0.15 + 0.55 * intensity) * (0.6 + 0.4 * Math.sin((py + i) * 0.05));      pCtx.beginPath();      pCtx.fillStyle = col + a.toFixed(3) + ")";      pCtx.arc(px, py, p.r * (0.8 + intensity * 0.9), 0, Math.PI * 2);      pCtx.fill();    }  }  // ---- Pseudo-3D reactor (icosahedron wireframe + orbiting nodes) --------  var t = 1.618033988749895;  var verts = [    [-1, t, 0], [1, t, 0], [-1, -t, 0], [1, -t, 0],    [0, -1, t], [0, 1, t], [0, -1, -t], [0, 1, -t],    [t, 0, -1], [t, 0, 1], [-t, 0, -1], [-t, 0, 1]  ];  var edges = [    [0,1],[0,5],[0,7],[0,10],[0,11],[1,5],[1,7],[1,8],[1,9],    [2,3],[2,4],[2,6],[2,10],[2,11],[3,4],[3,6],[3,8],[3,9],    [4,5],[4,9],[4,11],[5,9],[5,11],[6,7],[6,8],[6,10],    [7,8],[7,10],[8,9],[10,11]  ];  var ang = 0;  function rot(p, ax, ay) {    var cosy = Math.cos(ay), siny = Math.sin(ay);    var cosx = Math.cos(ax), sinx = Math.sin(ax);    var x = p[0] * cosy - p[2] * siny;    var z = p[0] * siny + p[2] * cosy;    var y = p[1] * cosx - z * sinx;    z = p[1] * sinx + z * cosx;    return [x, y, z];  }  function drawReactor(dt) {    rCtx.clearRect(0, 0, W, H);    var cx = W / 2, cy = H * 0.46;    var baseR = Math.min(W, H) * 0.20;    var R = baseR * (1 + 0.06 * Math.sin(ang * 2) + burst * 0.18);    ang += dt * (0.15 + intensity * 0.9);    // Projected vertices.    var proj = [];    for (var i = 0; i < verts.length; i++) {      var v = rot(verts[i], ang * 0.6, ang);      var scale = 220 / (220 + v[2] * 40);      proj.push({        x: cx + v[0] * R * scale,        y: cy + v[1] * R * scale,        z: v[2], s: scale      });    }    // Glow core.    var g = rCtx.createRadialGradient(cx, cy, 0, cx, cy, R * 1.7);    var coreA = 0.35 + intensity * 0.4 + burst * 0.3;    g.addColorStop(0, "rgba(180,210,255," + Math.min(0.9, coreA) + ")");    g.addColorStop(0.4, "rgba(124,92,255," + (0.25 + intensity * 0.3) + ")");    g.addColorStop(1, "rgba(124,92,255,0)");    rCtx.fillStyle = g;    rCtx.beginPath();    rCtx.arc(cx, cy, R * 1.7, 0, Math.PI * 2);    rCtx.fill();    // Edges (depth-shaded).    rCtx.lineWidth = 1.2;    for (var e = 0; e < edges.length; e++) {      var a = proj[edges[e][0]], b = proj[edges[e][1]];      var depth = (a.z + b.z) / 2;      var alpha = 0.25 + (depth + 2) / 4 * (0.55 + intensity * 0.35);      rCtx.strokeStyle = depth > 0        ? "rgba(52,211,255," + alpha.toFixed(3) + ")"        : "rgba(124,92,255," + (alpha * 0.7).toFixed(3) + ")";      rCtx.beginPath();      rCtx.moveTo(a.x, a.y);      rCtx.lineTo(b.x, b.y);      rCtx.stroke();    }    // Vertex nodes.    for (var j = 0; j < proj.length; j++) {      var pt = proj[j];      var nr = (1.6 + pt.s * 1.6) * (1 + intensity * 0.5);      rCtx.beginPath();      rCtx.fillStyle = pt.z > 0 ? "#8ff0ff" : "#b9a4ff";      rCtx.globalAlpha = 0.5 + pt.s * 0.5;      rCtx.arc(pt.x, pt.y, nr, 0, Math.PI * 2);      rCtx.fill();    }    rCtx.globalAlpha = 1;    // Two orbiting energy rings.    for (var k = 0; k < 2; k++) {      var tilt = k === 0 ? 0.5 : -0.85;      rCtx.save();      rCtx.translate(cx, cy);      rCtx.scale(1, 0.34 + 0.12 * Math.sin(ang + k));      rCtx.rotate(ang * (k ? -0.8 : 1.1) + tilt);      rCtx.beginPath();      rCtx.strokeStyle = k === 0        ? "rgba(34,229,168," + (0.35 + intensity * 0.4) + ")"        : "rgba(255,92,200," + (0.30 + intensity * 0.4) + ")";      rCtx.lineWidth = 1.6;      rCtx.arc(0, 0, R * 1.55, 0, Math.PI * 2);      rCtx.stroke();      // A traveling spark on the ring.      var sx = Math.cos(ang * 3 + k) * R * 1.55;      var sy = Math.sin(ang * 3 + k) * R * 1.55;      rCtx.beginPath();      rCtx.fillStyle = k === 0 ? "#22e5a8" : "#ff5cc8";      rCtx.arc(sx, sy, 3 + intensity * 2, 0, Math.PI * 2);      rCtx.fill();      rCtx.restore();    }  }  // ---- Sparkline --------------------------------------------------------  function drawSpark() {    sCtx.clearRect(0, 0, sW, sH);    var max = 1;    for (var i = 0; i < history.length; i++) if (history[i] > max) max = history[i];    // baseline    sCtx.strokeStyle = "rgba(52,211,255,0.15)";    sCtx.lineWidth = 1;    sCtx.beginPath(); sCtx.moveTo(0, sH - 0.5); sCtx.lineTo(sW, sH - 0.5); sCtx.stroke();    // area + line    var grd = sCtx.createLinearGradient(0, 0, 0, sH);    grd.addColorStop(0, "rgba(52,211,255,0.45)");    grd.addColorStop(1, "rgba(124,92,255,0.02)");    sCtx.beginPath();    for (var j = 0; j < history.length; j++) {      var x = (j / (history.length - 1)) * sW;      var y = sH - (history[j] / max) * (sH - 4) - 2;      if (j === 0) sCtx.moveTo(x, y); else sCtx.lineTo(x, y);    }    sCtx.lineTo(sW, sH); sCtx.lineTo(0, sH); sCtx.closePath();    sCtx.fillStyle = grd; sCtx.fill();    sCtx.beginPath();    for (var k = 0; k < history.length; k++) {      var x2 = (k / (history.length - 1)) * sW;      var y2 = sH - (history[k] / max) * (sH - 4) - 2;      if (k === 0) sCtx.moveTo(x2, y2); else sCtx.lineTo(x2, y2);    }    sCtx.strokeStyle = "#34d3ff";    sCtx.lineWidth = 1.5;    sCtx.stroke();  }  // ---- Hashrate readout formatting --------------------------------------  function fmtRate(v) {    if (v >= 1e6) return (v / 1e6).toFixed(2) + " MH/s";    if (v >= 1e3) return (v / 1e3).toFixed(2) + " kH/s";    return Math.round(v) + " H/s";  }  // ---- Main loop --------------------------------------------------------  var last = performance.now();  var sparkAccum = 0;  function frame(now) {    var dt = Math.min(0.05, (now - last) / 1000);    last = now;    // Safety: if the reactor canvas box no longer matches its backing store    // (e.g. it was 1x1 while hidden), re-fit before drawing.    var rr = reactor.getBoundingClientRect();    if (Math.abs(rr.width - W) > 1 || Math.abs(rr.height - H) > 1) { resize(); }    readState();    // Smooth intensity + decay burst.    intensity += (targetIntensity - intensity) * Math.min(1, dt * 3);    burst *= Math.pow(0.02, dt); // fast decay    // Synthetic-but-lively hashrate: scales with intensity, jitters, spikes on burst.    var target = intensity * (85000 + Math.sin(now * 0.002) * 12000) + burst * 40000;    hashrate += (target - hashrate) * Math.min(1, dt * 4);    var jitter = hashrate * (1 + (Math.random() - 0.5) * 0.06 * intensity);    var rateEl = document.getElementById("godHashrate");    if (rateEl) rateEl.textContent = intensity > 0.02 ? fmtRate(jitter) : "0 H/s";    // Push into history ~15x/sec.    sparkAccum += dt;    if (sparkAccum > 0.066) {      history.push(jitter);      history.shift();      sparkAccum = 0;      drawSpark();    }    if (reduce) {      // Static-ish: draw once-ish but cheaply.      drawReactor(dt * 0.15);      drawParticles();    } else {      drawReactor(dt);      drawParticles();    }    raf = requestAnimationFrame(frame);  }  var raf = requestAnimationFrame(frame);  // Pause when tab hidden to save cycles.  document.addEventListener("visibilitychange", function () {    if (document.hidden) { cancelAnimationFrame(raf); }    else { last = performance.now(); raf = requestAnimationFrame(frame); }  });})();