igneum/site/yourcard.js

43 lines
4.6 KiB
JavaScript

/* The "your card" line: a measured card, how often it finds a block at the network's hash rate right now, and what its
electricity costs a day in your currency. One block a second is the target, so the wait is network hashes per second
over the card's rate. The rates are the bench table's measured numbers (site/miners.html); the watts are the tuned
draws from the same runs (app.html's Ember table: 5090 226.8 W, 4070 75.6 W; the 9070 XT 202 W from docs/bench-log.md,
6 October 2026; Apple silicon reports no draw). The currency follows navigator.language (no IP lookup); the switch saves
a choice in localStorage igneum-currency; the tariff is the public table's default for that region or currency, so the
cost is arithmetic from a typical price, never a promise (currency-21, 7 October 2026). Reads /api/live every 15 s. */
(function () {
var CARDS = [['NVIDIA RTX 5090', 127.7, 226.8], ['NVIDIA RTX 4070', 28.8, 75.6], ['Apple M5 Max', 26.7, null], ['AMD RX 9070 XT', 17.8, 202]];
var els = document.querySelectorAll('[data-yourcard]'); if (!els.length) return;
var net = null, chosen = (function () { try { return localStorage.getItem('igneum-card'); } catch (e) { return null; } })();
var savedCur = (function () { try { return localStorage.getItem('igneum-currency') || ''; } catch (e) { return ''; } })();
function dur(sec) { if (!(sec > 0)) return 'pending'; if (sec < 90) return Math.round(sec) + ' s'; if (sec < 5400) return Math.round(sec / 60) + ' min'; if (sec < 172800) { var h = Math.floor(sec / 3600), m = Math.round((sec % 3600) / 60); return h + ' h' + (m ? ' ' + m + ' min' : ''); } return Math.round(sec / 86400) + ' days'; }
function costWords(watts) {
var M = window.IgneumMoney; if (!M) return '';
var ch = M.currencyChoice(savedCur, navigator.language), t = M.tariffFor(ch);
if (watts === null) return 'no power reading on this card';
if (!t) return 'no typical ' + ch.code + ' tariff in the table; the app uses the price you type';
var c = M.costPerDay(watts, t.price);
return 'about ' + M.fmtMoney(c, ch.code, navigator.language) + ' a day in electricity at ' + t.price + ' ' + M.minorOf(ch.code).abbr + '/kWh (' + t.region + ', typical)';
}
function render() {
els.forEach(function (el) {
var sel = el.querySelector('select.card'), r = parseFloat(sel.value) * 1e6, eta = el.querySelector('.eta'), cost = el.querySelector('.cost');
eta.textContent = net && r ? 'every ' + dur(net / r) : 'pending';
var row = CARDS[sel.selectedIndex]; if (cost) cost.textContent = costWords(row ? row[2] : null);
});
}
function init() {
var M = window.IgneumMoney, ch = M ? M.currencyChoice(savedCur, navigator.language) : null;
els.forEach(function (el) {
el.innerHTML = '<b>Your card</b><select class="card" aria-label="Your card">' + CARDS.map(function (c) { return '<option value="' + c[1] + '"' + (chosen === c[0] ? ' selected' : '') + '>' + c[0] + ' \u00b7 ' + c[1] + ' MH/s</option>'; }).join('') + '</select><span>finds a block about <span class="eta">pending</span> at the network\u2019s hash rate right now.</span>' +
(M ? ' <span class="cost"></span> <select class="cur" aria-label="Currency">' + M.CURRENCY_CODES.map(function (c) { return '<option value="' + c + '"' + (ch.code === c ? ' selected' : '') + '>' + c + '</option>'; }).join('') + '</select>' : '');
el.querySelector('select.card').addEventListener('change', function (e) { var name = e.target.options[e.target.selectedIndex].text.split(' \u00b7 ')[0]; try { localStorage.setItem('igneum-card', name); } catch (x) {} document.querySelectorAll('[data-yourcard] select.card').forEach(function (s) { s.value = e.target.value; }); render(); });
var cur = el.querySelector('select.cur'); if (cur) cur.addEventListener('change', function (e) { savedCur = e.target.value; try { localStorage.setItem('igneum-currency', savedCur); } catch (x) {} document.querySelectorAll('[data-yourcard] select.cur').forEach(function (s) { s.value = savedCur; }); render(); });
});
render();
}
if (window.IgneumMoney) init(); else { var sc = document.createElement('script'); sc.src = '/money.js'; sc.onload = init; sc.onerror = init; document.head.appendChild(sc); }
function poll() { fetch('/api/live?window=30', { cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (d) { if (d && d.ok && d.state && d.state.hashes_per_second_estimate > 0) { net = d.state.hashes_per_second_estimate; render(); } }).catch(function () {}); }
poll(); setInterval(function () { if (!document.hidden) poll(); }, 15000);
window.IgneumYourCard = { update: function (h) { if (h > 0) { net = h; render(); } } };
})();