igneum/tools/launch/income-page.mjs
igneum-labs 7cf223fa2b Site, 7 October 2026 evening (the project lead's four asks through main, one landing): (1) the bar's nav to the right: the mark and the devnet pill on the left, Mine, Network, Learn and the controls right-aligned with the same gaps and sizes, each open panel anchored under its button (the script sets --panel-left) and a panel that would run past the container aligned to its button's right edge, the phone sheet unchanged; (2) no lateral scroll: tools/ci/scroll-width-check.mjs on the gate (scrollWidth equals clientWidth on every route at 390, 768, 1024, 1280 and 1440 in both themes; only a table, code or diagram scrolls inside its own overflow-x box; self-test with a 100vw plus 20 px fixture known-failed first); (3) LG-5, the launch text: the front page leads with the three wants (hardware that stays useful, income without a cliff, a fair supply) and the live network with finality is the second screen; the litepaper's regulatory note reads "Not legal advice." alone over its eight sentences from future.md 8.3 (the project lead: counsel not yet; the string "counsel is engaged" is in both forbidden-strings lists and gone from the research heading), the pay sentence stays; the journey's phase line reads Devnet 3 live, testnet next; (4) LG-1, /income: docs/analysis/income-tiers.md published as a page by tools/launch/income-page.mjs (checked on the gate against the document), listed under Learn, labels on every number, no price of IGN anywhere
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 19:12:04 +00:00

77 lines
5.2 KiB
JavaScript

#!/usr/bin/env node
// /income (LG-1, 7 October 2026): docs/analysis/income-tiers.md published as a site page. The document is the generator's
// output (tools/launch/income-tiers.mjs, checked on the gate); this script turns its tables and tier lines into
// site/income.html inside the site's shell markers, so the page and the document cannot disagree. Labels on every
// number, no price of IGN anywhere (the page says so). node tools/launch/income-page.mjs [--check]
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const here = dirname(fileURLToPath(import.meta.url)), root = join(here, '../..');
const md = readFileSync(join(root, 'docs/analysis/income-tiers.md'), 'utf8');
const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const inline = (s) => esc(s).replace(/\*\*(.+?)\*\*/g, '<b>$1</b>').replace(/`(.+?)`/g, '<code>$1</code>');
const section = (title) => { const i = md.indexOf('## ' + title); if (i < 0) throw new Error('section missing: ' + title); const j = md.indexOf('\n## ', i + 3); return md.slice(i, j < 0 ? md.length : j); };
const table = (text) => { const rows = text.split('\n').filter((l) => l.startsWith('|')); if (rows.length < 3) return ''; const cells = (l) => l.slice(1, -1).split('|').map((c) => c.trim()); const head = cells(rows[0]); const body = rows.slice(2).map(cells); return '<div class="tbl"><table><thead><tr>' + head.map((h, i) => `<th${i ? ' class="num"' : ''}>${inline(h)}</th>`).join('') + '</tr></thead><tbody>' + body.map((r) => '<tr>' + r.map((c, i) => `<td${i >= 2 ? ' class="num"' : ''}>${inline(c)}</td>`).join('') + '</tr>').join('') + '</tbody></table></div>'; };
const paras = (text) => text.split('\n').filter((l) => l.trim() && !l.startsWith('|') && !l.startsWith('#') && !l.startsWith('- ')).map((l) => `<p>${inline(l)}</p>`).join('\n');
const bullets = (text) => { const items = text.split('\n').filter((l) => l.startsWith('- ')); return items.length ? '<ul class="tiers">' + items.map((l) => `<li>${inline(l.slice(2))}</li>`).join('\n') + '</ul>' : ''; };
const sched = section('The schedule the figures use'), cards = section('The cards, measured'), ign = section('IGN a day per card, solo, after the ramp (day 90)'), elec = section('Electricity a day, and the electricity cost of one mined IGN'), means = section('What it means per tier, and what is being done'), owed = section('Owed');
const generated = /Generated by[^\n]*/.exec(md)?.[0] || '';
const 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>Mining income per tier</title>
<meta name="description" content="What one card, a rig and a pool user mine on Igneum: IGN a day at three network sizes from measured cards, and the electricity a day at three tariffs. Labels on every number, no price of IGN.">
<link rel="canonical" href="https://igneum.network/income">
<!-- head:start -->
<!-- head:end -->
</head>
<body>
<!-- nav:start -->
<!-- nav:end -->
<main id="main">
<section class="page-hero"><div class="container">
<div class="breadcrumb"><a href="/">Igneum</a><span>/</span><span>Income per tier</span></div>
<h1>What a card mines, by tier.</h1>
<p class="lead">One 8, 12, 16, 24 or 32 GB card, a rig and a pool user: IGN a day at three network sizes from measured cards, and the electricity a day at three tariffs. Every number carries its label. No price of IGN appears here: nobody sells IGN and the project quotes none.</p>
</div></section>
<section class="section"><div class="container">
<h2>The schedule the figures use</h2>
${paras(sched)}
${table(sched)}
</div></section>
<section class="section"><div class="container">
<h2>The cards, measured</h2>
${paras(cards)}
${table(cards)}
</div></section>
<section class="section"><div class="container">
<h2>IGN a day per card, solo, after the ramp (day 90)</h2>
${paras(ign)}
${table(ign)}
</div></section>
<section class="section"><div class="container">
<h2>Electricity a day, and the electricity cost of one mined IGN</h2>
${paras(elec)}
${table(elec)}
</div></section>
<section class="section"><div class="container">
<h2>What it means per tier, and what is being done</h2>
${bullets(means)}
</div></section>
<section class="section compact"><div class="container">
<h2>Owed</h2>
${bullets(owed)}
<p class="note">${inline(generated)}</p>
</div></section>
</main>
<!-- footer:start -->
<!-- footer:end -->
</body>
</html>
`;
const out = join(root, 'site/income.html');
// --check: the build decorates card names with the GPU marks; the words are what the document owns, so they are compared bare
if (process.argv.includes('--check')) { let cur = ''; try { cur = readFileSync(out, 'utf8'); } catch (e) { } const main = (s) => { const m = /<main id="main">[\s\S]*?<\/main>/.exec(s); return (m ? m[0] : '').replace(/<span class="cardname">(?:<div class="badge[\s\S]*?<\/div>)([\s\S]*?)<\/span>/g, '$1'); }; if (main(cur) !== main(html)) { console.error('site/income.html is not the generator\'s output: node tools/launch/income-page.mjs'); process.exit(1); } console.log('income page: equals the document'); }
else { writeFileSync(out, html); console.log('wrote site/income.html'); }