Dark, light, or whatever the browser asks for. Auto is the default, so a reader who has never touched it gets their own system's answer rather than ours. The light palette is selected, not inverted. `#bd8829` carries every magnitude on this site at 6.6:1 against the warm-black and **2.77:1** against paper — the validator says so, and 2.77 is under the 3:1 floor a mark has to clear. So light mode gets its own step of the same bronze, and its `--data-bright` sits *darker* than `--data`, because emphasis on paper is weight rather than glare. Same for the washes: a glow at 0.08 alpha on warm-black is a smear at 0.08 on paper, so the nine colour literals that were still loose in the stylesheet became tokens — each one was a colour the second theme could not have overridden. Every value was chosen by running the dataviz validator against the surface it actually sits on, both modes. The single-hue rule is untouched and still load-bearing in both: bronze and crimson fail CVD separation as a categorical pair whichever ground they are on. `auto` is a preference rather than a third palette. It resolves to a concrete `light` or `dark` before the stylesheet ever sees it, which is what keeps this to one definition per palette instead of one per palette per media query — and it has to resolve before the *first paint*, because anything running after the bundle loads runs after the page has been painted once, and on a light preference that is a full-screen flash of warm-black. Hence the inline script, whose duplication of `lib/theme.ts` is the cheaper of the two costs. Two things that would otherwise bite: `localStorage` throws rather than returning null where site data is blocked, so every access is guarded and falls back to what the browser wants; and `auto` keeps listening, so a machine that turns dark at sunset does not leave a reader on the daylight palette until they reload. The toggle shows the state it is in, never the state it would move to — a control that displays its own destination is why these get guessed at — and its accessible name carries that state, since the icon cannot. Checked in a browser, both themes, on the standings, a miner page and the share chart. Worth recording what that turned up: dark carries 46 text elements under 4.5:1 and light carries 3, each beating its dark counterpart. The gap is `--text-muted` at 3.7, the deliberate existing value CLAUDE.md has always documented — not introduced here, and not something to change without deciding to change the dark design. Closes #12 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
79 lines
3.2 KiB
HTML
79 lines
3.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>blackbeard.observer — Quantus mining leaderboard</title>
|
|
<meta
|
|
name="description"
|
|
content="Live miner leaderboard and network hashrate for the Quantus blockchain and its Planck testnet. Every author decoded from block headers — no registration, no opt-in."
|
|
/>
|
|
<meta name="color-scheme" content="dark light" />
|
|
<!-- Rewritten by the script below, and again by the toggle. The value here
|
|
is only what a reader with no stored preference and no JavaScript
|
|
gets. -->
|
|
<meta name="theme-color" content="#0d0b09" />
|
|
<link rel="icon" href="/sigil.svg" type="image/svg+xml" />
|
|
|
|
<meta property="og:title" content="blackbeard.observer" />
|
|
<meta
|
|
property="og:description"
|
|
content="Live Quantus mining leaderboard and network hashrate."
|
|
/>
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:url" content="https://blackbeard.observer/" />
|
|
|
|
<style>
|
|
/* Inline so the background is right on the first paint rather than after
|
|
the CSS bundle arrives. Both values, switched by the same attribute the
|
|
stylesheet reads — the script below sets it before this rule is ever
|
|
applied to anything. */
|
|
html {
|
|
background: #0d0b09;
|
|
}
|
|
html[data-theme='light'] {
|
|
background: #fbf8f2;
|
|
}
|
|
</style>
|
|
<script>
|
|
/* Resolve the theme before the first paint.
|
|
*
|
|
* This is a deliberate copy of the resolution in `src/lib/theme.ts`, and
|
|
* it is here rather than in the bundle because anything that runs after
|
|
* the bundle loads runs after the page has been painted once — which on a
|
|
* light preference is a full-screen flash of warm-black, and on a dark
|
|
* one a flash of white. Seven lines duplicated is the cheaper of the two.
|
|
*
|
|
* Wrapped because `localStorage` *throws* rather than returning null in a
|
|
* browser set to block site data; the browser's own preference is the
|
|
* right fallback and is what `auto` would have chosen anyway.
|
|
*/
|
|
;(function () {
|
|
var preference = 'auto'
|
|
try {
|
|
var stored = localStorage.getItem('blackbeard.theme')
|
|
if (stored === 'auto' || stored === 'light' || stored === 'dark') preference = stored
|
|
} catch (e) {}
|
|
var theme =
|
|
preference === 'auto'
|
|
? matchMedia('(prefers-color-scheme: light)').matches
|
|
? 'light'
|
|
: 'dark'
|
|
: preference
|
|
document.documentElement.setAttribute('data-theme', theme)
|
|
document
|
|
.querySelector('meta[name="theme-color"]')
|
|
.setAttribute('content', theme === 'light' ? '#fbf8f2' : '#0d0b09')
|
|
})()
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="root"></div>
|
|
<noscript>
|
|
blackbeard.observer streams the chain over a WebSocket and needs JavaScript. The same data is
|
|
available without it from the API at <code>/v1/chains</code>.
|
|
</noscript>
|
|
<script type="module" src="/src/main.tsx"></script>
|
|
</body>
|
|
</html>
|