Files
observer/web/src/components/AboutPanel.tsx
T
grenadeandClaude Opus 5 0de203c819
deploy / build (push) Successful in 1m30s
deploy / deploy-api (push) Skipped
deploy / deploy-web (push) Successful in 15s
fix(ui): link the repositories at their new paths
Both moved from quantus/ to blackbeard/: the observer to blackbeard/observer,
the miner to blackbeard/miner. Gitea 301s the old paths, but a redirect
survives only until something else takes the name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E4Ldjix2ofMdQ7dzqgnYA8
2026-09-22 15:30:02 +03:00

143 lines
5.5 KiB
TypeScript

/**
* 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'
import robPortrait from '../assets/rob.png'
/** 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/blackbeard/miner'
const OBSERVER_REPO = 'https://git.lair.cafe/blackbeard/observer'
const AUTHOR_NAME = 'Rob Thijssen'
const AUTHOR_SITE = 'https://rob.tn'
/**
* 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-body">
<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/blackbeard/observer
</a>
, so any figure here can be traced to the read that produced it.
</p>
</div>
{/* Imported rather than served from `public/`: Vite hashes it by
content, so a replaced photograph invalidates its own cache instead
of being served stale from behind a year-long max-age. */}
<a className="about-portrait" href={AUTHOR_SITE} rel="noreferrer" title={AUTHOR_NAME}>
<img
src={robPortrait}
alt={AUTHOR_NAME}
width={280}
height={320}
loading="lazy"
decoding="async"
/>
</a>
</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>
</>
)
}