feat: an about route, and somewhere to say thanks
deploy / build (push) Successful in 8m29s
deploy / deploy-web (push) Successful in 5s
deploy / deploy-api (push) Successful in 15s

Adds `/:chain/about`, linked from the header just before the connection light
and the theme toggle — it is about the site rather than the chain, so it sits
there instead of in the section nav, and `header: true` on its `SECTIONS` entry
keeps it out while still letting the URL parse.

The page says what the observer is, that it and the CUDA-optimised miner are
built and freely sourced by a member of the Quantus user community rather than
by the project, and takes donations in QTC.

**The QR is generated in the browser from the address printed beside it, never
shipped as an image.** A supplied PNG next to a typed address is two
independent claims about where money goes, and nothing on the page could tell a
reader if they ever stopped agreeing — a mismatch sends funds somewhere
unrecoverable.

Verified rather than trusted. The address was put through this crate's own SS58
decoder: checksum and network prefix both hold, and it round-trips to the same
string. The QR matrix was then decoded from first principles — function-pattern
map, mask 3 unapplied, codewords de-interleaved across version 4-M's two blocks
— and reads back the address exactly. A note for whoever checks this again: an
independent encoder produces a different matrix for the same string at every
mask, because mode segmentation differs, so comparing matrices proves nothing
and only a decode does.

`the_donation_address_on_the_site_is_a_valid_quantus_address` reads the literal
out of the frontend source and validates it, so a typo fails CI rather than
costing somebody their money. Confirmed to fail on one changed character.

Also on the vesting page, from verifying it live: a "claimable now" figure and
a per-grant claimable column. Two grants began vesting at launch while the other
forty-six wait for 2027, so 68,871 QTC is already claimable and none of it has
been taken — a page that only said "first claimable" would have missed it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 16:48:21 +03:00
co-authored by Claude Opus 5
parent 2bbe8e4d5d
commit 6ed9a6c35a
10 changed files with 316 additions and 2 deletions
+122
View File
@@ -0,0 +1,122 @@
/**
* Who made this, and how to say thanks.
*
* The QR is **generated in the browser from the same string the page prints**,
* never shipped as an image. A supplied PNG and the text beneath it are two
* independent claims about where money should go, and nothing on the page could
* tell you if they ever stopped agreeing — a mismatch sends funds somewhere
* unrecoverable. Generated, they cannot disagree: there is one address, and the
* code is a rendering of it.
*/
import qrcode from 'qrcode-generator'
/** Where donations go. Also the string the QR is built from — see above. */
export const DONATION_ADDRESS = 'qzkQq9ybiPm7gQR9E1ViQbW9RhqA2Rex9ZQkScU5zdJqjrre4'
const MINER_REPO = 'https://git.lair.cafe/quantus/miner'
const OBSERVER_REPO = 'https://git.lair.cafe/quantus/blackbeard.observer'
/**
* The address as a QR, drawn as one SVG path.
*
* Error-correction level `M` and an automatic version: the address is 49
* characters, which fits comfortably and leaves the code readable at the size
* a phone is held from a screen. Rendered as a single path of rectangles rather
* than a grid of elements — one node instead of a thousand, and it scales to
* whatever box the layout gives it.
*/
function AddressQr({ value }: { value: string }) {
const qr = qrcode(0, 'M')
qr.addData(value)
qr.make()
const count = qr.getModuleCount()
// A four-module quiet zone, which the spec requires and scanners rely on.
const quiet = 4
const size = count + quiet * 2
let path = ''
for (let row = 0; row < count; row += 1) {
for (let col = 0; col < count; col += 1) {
if (qr.isDark(row, col)) {
path += `M${col + quiet} ${row + quiet}h1v1h-1z`
}
}
}
return (
<svg
className="qr"
viewBox={`0 0 ${size} ${size}`}
role="img"
aria-label={`QR code for the donation address ${value}`}
>
{/* The light modules are drawn, not left transparent: a QR on a dark
surface with a transparent background does not scan, and this site has
a dark theme. */}
<rect x="0" y="0" width={size} height={size} className="qr-light" />
<path d={path} className="qr-dark" />
</svg>
)
}
export function AboutPanel() {
return (
<>
<section className="panel" style={{ marginBottom: 26 }}>
<div className="panel-head">
<h2 className="panel-title">About</h2>
<span className="eyebrow">who made this</span>
</div>
<div className="about-prose">
<p>
<strong>blackbeard.observer</strong> is a mining leaderboard and block explorer for the
Quantus network. It is built and its source provided freely by a member of the Quantus
user community — not by the Quantus project, and with no standing behind it beyond the
code being open and the numbers being checkable.
</p>
<p>
The same author maintains a{' '}
<a href={MINER_REPO} rel="noreferrer">
CUDA performance-optimised miner
</a>
, also free and open source. Between them they are the reason a good deal of this site
exists: a miner needs somewhere to see whether it is winning.
</p>
<p>
Everything the observer shows is derived from public block headers, chain state and the
network&apos;s own telemetry feed. The code that derives it is at{' '}
<a href={OBSERVER_REPO} rel="noreferrer">
git.lair.cafe/quantus/blackbeard.observer
</a>
, so any figure here can be traced to the read that produced it.
</p>
</div>
</section>
<section className="panel" style={{ marginBottom: 26 }}>
<div className="panel-head">
<h2 className="panel-title">Donations</h2>
<span className="eyebrow">entirely optional</span>
</div>
<div className="about-donate">
<AddressQr value={DONATION_ADDRESS} />
<div className="about-prose">
<p>
If the observer or the miner have been useful and you are inclined, donations toward
development, maintenance and hosting are gratefully accepted in QTC at:
</p>
<p>
<code className="about-address">{DONATION_ADDRESS}</code>
</p>
<p className="panel-note" style={{ padding: 0, border: 0 }}>
Nothing here is gated behind it, and nothing about the site changes if you do. The
code above is generated in your browser from the address printed beside it, so the two
cannot drift apart — scan it or copy the text, whichever you trust more.
</p>
</div>
</div>
</section>
</>
)
}
+1 -1
View File
@@ -52,7 +52,7 @@ export function SectionNav({
>
Genesis
</Link>
{SECTIONS.map((s) => (
{SECTIONS.filter((s) => !s.header).map((s) => (
<Link
key={s.id}
to={href({ chain, index: s.id })}
+25
View File
@@ -191,6 +191,18 @@ export function VestingPanel({
return null
}
})()
// Vested and not yet taken, summed across every grant. The headline figure on
// this chain today: two grants began vesting at launch while the other
// forty-six wait for 2027, so a page that only said "first claimable" would
// miss that a great deal already is.
const claimableNow = (() => {
try {
return data.schedules.reduce((sum, s) => sum + BigInt(s.claimable), 0n).toString()
} catch {
return null
}
})()
const firstCliff = data.schedules.reduce(
(a, s) => (a === null || s.cliff < a ? s.cliff : a),
null as string | null,
@@ -215,6 +227,11 @@ export function VestingPanel({
value={outstanding === null ? '—' : amount(outstanding)}
note="scheduled less claimed"
/>
<Field
label="Claimable now"
value={claimableNow === null ? '—' : amount(claimableNow)}
note="vested and not yet taken"
/>
<Field label="Grants" value={fmtHeight(data.schedules.length)} />
<Field
label="First claimable"
@@ -258,6 +275,12 @@ export function VestingPanel({
whole grant from its end, and linearly in between — so this is that arithmetic summed
across every schedule, not a model of what somebody intends.
</p>
<p className="panel-note">
The step at the left is not the whole story told small:{' '}
<strong>the grants do not share a cliff</strong>. Read the table for which ones have
started — a curve that rises early and then sits flat for a year is two grants vesting
from launch while the rest wait, not a gentle beginning.
</p>
</section>
<section className="panel" style={{ marginBottom: 26 }}>
@@ -275,6 +298,7 @@ export function VestingPanel({
</th>
<th scope="col">Grant</th>
<th scope="col">Vested</th>
<th scope="col">Claimable</th>
<th scope="col">Claimed</th>
<th scope="col" className="left">
Unlocks
@@ -294,6 +318,7 @@ export function VestingPanel({
</td>
<td className="numeral">{tokens(s.total, decimals, 0)}</td>
<td className="numeral">{tokens(s.vested, decimals, 0)}</td>
<td className="numeral">{tokens(s.claimable, decimals, 0)}</td>
<td className="numeral">{tokens(s.claimed, decimals, 0)}</td>
<td className="left">{day(s.cliff)}</td>
<td className="left">{day(s.end)}</td>