mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-05 08:08:15 -07:00
website_next: snapshot
This commit is contained in:
@@ -208,6 +208,7 @@ impl Query {
|
|||||||
let computer = self.computer();
|
let computer = self.computer();
|
||||||
let reader = self.reader();
|
let reader = self.reader();
|
||||||
let all_pools = pools();
|
let all_pools = pools();
|
||||||
|
let pool_heights = computer.pools.pool_heights.read();
|
||||||
|
|
||||||
// Bulk read all indexed data
|
// Bulk read all indexed data
|
||||||
let blockhashes = indexer.vecs.blocks.blockhash.collect_range_at(begin, end);
|
let blockhashes = indexer.vecs.blocks.blockhash.collect_range_at(begin, end);
|
||||||
@@ -398,6 +399,11 @@ impl Query {
|
|||||||
|
|
||||||
let pool_slug = pool_slugs[i];
|
let pool_slug = pool_slugs[i];
|
||||||
let pool = all_pools.get(pool_slug);
|
let pool = all_pools.get(pool_slug);
|
||||||
|
let height = begin + i;
|
||||||
|
let block_number = pool_heights
|
||||||
|
.get(&pool_slug)
|
||||||
|
.map(|heights| heights.partition_point(|h| h.to_usize() <= height) as u64)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let miner_names = if pool_slug == PoolSlug::Ocean {
|
let miner_names = if pool_slug == PoolSlug::Ocean {
|
||||||
Self::parse_datum_miner_names(&scriptsig_bytes)
|
Self::parse_datum_miner_names(&scriptsig_bytes)
|
||||||
@@ -410,7 +416,7 @@ impl Query {
|
|||||||
|
|
||||||
let info = BlockInfo {
|
let info = BlockInfo {
|
||||||
id: blockhashes[i],
|
id: blockhashes[i],
|
||||||
height: Height::from(begin + i),
|
height: Height::from(height),
|
||||||
version: header.version,
|
version: header.version,
|
||||||
timestamp: timestamps[i],
|
timestamp: timestamps[i],
|
||||||
bits: header.bits,
|
bits: header.bits,
|
||||||
@@ -444,6 +450,7 @@ impl Query {
|
|||||||
id: pool.mempool_unique_id(),
|
id: pool.mempool_unique_id(),
|
||||||
name: pool.name.to_string(),
|
name: pool.name.to_string(),
|
||||||
slug: pool_slug,
|
slug: pool_slug,
|
||||||
|
block_number,
|
||||||
miner_names,
|
miner_names,
|
||||||
},
|
},
|
||||||
avg_fee: Sats::from(total_fees_u64.checked_div(non_coinbase).unwrap_or(0)),
|
avg_fee: Sats::from(total_fees_u64.checked_div(non_coinbase).unwrap_or(0)),
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ pub struct BlockPool {
|
|||||||
/// URL-friendly pool identifier
|
/// URL-friendly pool identifier
|
||||||
pub slug: PoolSlug,
|
pub slug: PoolSlug,
|
||||||
|
|
||||||
|
/// This block's ordinal among blocks attributed to this pool
|
||||||
|
#[schemars(example = 215_000)]
|
||||||
|
pub block_number: u64,
|
||||||
|
|
||||||
/// Miner name tags found in coinbase scriptsig
|
/// Miner name tags found in coinbase scriptsig
|
||||||
pub miner_names: Option<Vec<String>>,
|
pub miner_names: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,6 +259,7 @@ Matches mempool.space/bitcoin-cli behavior.
|
|||||||
* @property {number} id - Unique pool identifier
|
* @property {number} id - Unique pool identifier
|
||||||
* @property {string} name - Pool name
|
* @property {string} name - Pool name
|
||||||
* @property {PoolSlug} slug - URL-friendly pool identifier
|
* @property {PoolSlug} slug - URL-friendly pool identifier
|
||||||
|
* @property {number} blockNumber - This block's ordinal among blocks attributed to this pool
|
||||||
* @property {?string[]=} minerNames - Miner name tags found in coinbase scriptsig
|
* @property {?string[]=} minerNames - Miner name tags found in coinbase scriptsig
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { createFeeChart } from "./fee-chart.js";
|
|||||||
/** @typedef {Awaited<ReturnType<typeof brk.getBlocksV1>>[number]} Block */
|
/** @typedef {Awaited<ReturnType<typeof brk.getBlocksV1>>[number]} Block */
|
||||||
|
|
||||||
const MAX_BLOCK_WEIGHT = 4_000_000;
|
const MAX_BLOCK_WEIGHT = 4_000_000;
|
||||||
|
const DIFFICULTY_EPOCH_BLOCKS = 2_016;
|
||||||
|
const HALVING_EPOCH_BLOCKS = 210_000;
|
||||||
|
|
||||||
/** @param {number} bytes */
|
/** @param {number} bytes */
|
||||||
function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
@@ -100,54 +102,11 @@ function createRow(term, value) {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} label
|
|
||||||
* @param {string | Node} value
|
|
||||||
*/
|
|
||||||
function createStat(label, value) {
|
|
||||||
const stat = document.createElement("div");
|
|
||||||
const name = document.createElement("span");
|
|
||||||
const amount = document.createElement("strong");
|
|
||||||
|
|
||||||
stat.dataset.stat = "";
|
|
||||||
name.textContent = label;
|
|
||||||
amount.append(value);
|
|
||||||
stat.append(name, amount);
|
|
||||||
|
|
||||||
return stat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {[string, string | Node][]} items */
|
|
||||||
function createStats(items) {
|
|
||||||
const stats = document.createElement("div");
|
|
||||||
|
|
||||||
stats.dataset.stats = "";
|
|
||||||
stats.append(...items.map(([label, value]) => createStat(label, value)));
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} title */
|
/** @param {string} title */
|
||||||
function groupName(title) {
|
function groupName(title) {
|
||||||
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} title
|
|
||||||
* @param {[string, string | Node][]} stats
|
|
||||||
* @param {Node[]} [children]
|
|
||||||
*/
|
|
||||||
function createStatBox(title, stats, children = []) {
|
|
||||||
const box = document.createElement("div");
|
|
||||||
const heading = document.createElement("h3");
|
|
||||||
|
|
||||||
box.dataset.statBox = groupName(title);
|
|
||||||
heading.textContent = title;
|
|
||||||
box.append(heading, createStats(stats), ...children);
|
|
||||||
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} label
|
* @param {string} label
|
||||||
* @param {(string | Node)[]} values
|
* @param {(string | Node)[]} values
|
||||||
@@ -168,11 +127,12 @@ function createInlineRow(label, values) {
|
|||||||
/**
|
/**
|
||||||
* @param {string} label
|
* @param {string} label
|
||||||
* @param {string | Node} value
|
* @param {string | Node} value
|
||||||
|
* @param {string} [type]
|
||||||
*/
|
*/
|
||||||
function createInlineBox(label, value) {
|
function createInlineBox(label, value, type = "inline") {
|
||||||
const box = document.createElement("div");
|
const box = document.createElement("div");
|
||||||
|
|
||||||
box.dataset.blockBox = "inline";
|
box.dataset.blockBox = type;
|
||||||
box.append(createInlineRow(label, [value]));
|
box.append(createInlineRow(label, [value]));
|
||||||
|
|
||||||
return box;
|
return box;
|
||||||
@@ -183,21 +143,16 @@ function formatBlockFill(block) {
|
|||||||
return `${((block.weight / MAX_BLOCK_WEIGHT) * 100).toFixed(1)}%`;
|
return `${((block.weight / MAX_BLOCK_WEIGHT) * 100).toFixed(1)}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {number | string} bits */
|
|
||||||
function formatBits(bits) {
|
|
||||||
return typeof bits === "number" ? `0x${bits.toString(16)}` : bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} label
|
* @param {string} label
|
||||||
* @param {string} value
|
* @param {string} value
|
||||||
*/
|
*/
|
||||||
function createMinerStat(label, value) {
|
function createMetricStat(label, value) {
|
||||||
const stat = document.createElement("div");
|
const stat = document.createElement("div");
|
||||||
const name = document.createElement("span");
|
const name = document.createElement("span");
|
||||||
const amount = document.createElement("strong");
|
const amount = document.createElement("strong");
|
||||||
|
|
||||||
stat.dataset.minerStat = "";
|
stat.dataset.metricStat = "";
|
||||||
name.textContent = label;
|
name.textContent = label;
|
||||||
amount.textContent = value;
|
amount.textContent = value;
|
||||||
stat.append(name, amount);
|
stat.append(name, amount);
|
||||||
@@ -205,34 +160,114 @@ function createMinerStat(label, value) {
|
|||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {string} raw */
|
||||||
|
function getCoinbaseMessage(raw) {
|
||||||
|
return (raw.match(/[\x20-\x7e]{2,}/g) ?? [])
|
||||||
|
.map((value) => value.trim())
|
||||||
|
.filter((value) => /[A-Za-z0-9]/.test(value))
|
||||||
|
.join(" · ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} raw */
|
||||||
|
function createCoinbaseMessage(raw) {
|
||||||
|
const message = getCoinbaseMessage(raw);
|
||||||
|
|
||||||
|
if (!message) return null;
|
||||||
|
|
||||||
|
const element = document.createElement("p");
|
||||||
|
|
||||||
|
element.dataset.coinbaseMessage = "";
|
||||||
|
element.textContent = message;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} label
|
||||||
|
* @param {number} height
|
||||||
|
* @param {number} length
|
||||||
|
* @param {string} color
|
||||||
|
*/
|
||||||
|
function createEpochProgress(label, height, length, color) {
|
||||||
|
const progress = (height % length) + 1;
|
||||||
|
const row = document.createElement("div");
|
||||||
|
const head = document.createElement("div");
|
||||||
|
const name = document.createElement("span");
|
||||||
|
const value = document.createElement("strong");
|
||||||
|
const bar = document.createElement("div");
|
||||||
|
const done = document.createElement("span");
|
||||||
|
const remaining = document.createElement("span");
|
||||||
|
|
||||||
|
row.dataset.epoch = "";
|
||||||
|
head.dataset.epochHead = "";
|
||||||
|
bar.dataset.epochBar = "";
|
||||||
|
done.dataset.epochSegment = "done";
|
||||||
|
remaining.dataset.epochSegment = "remaining";
|
||||||
|
row.style.setProperty("--epoch-color", color);
|
||||||
|
done.style.setProperty("--share", `${(progress / length) * 100}%`);
|
||||||
|
remaining.style.setProperty("--share", `${((length - progress) / length) * 100}%`);
|
||||||
|
|
||||||
|
name.textContent = label;
|
||||||
|
value.textContent = `${((progress / length) * 100).toFixed(1)}%`;
|
||||||
|
head.append(name, value);
|
||||||
|
bar.append(done, remaining);
|
||||||
|
row.append(head, bar);
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
/** @param {Block} block */
|
/** @param {Block} block */
|
||||||
function createMinerSummary(block) {
|
function createMinerSummary(block) {
|
||||||
const { pool } = block.extras;
|
const { pool } = block.extras;
|
||||||
const pane = document.createElement("div");
|
const pane = document.createElement("div");
|
||||||
const head = document.createElement("div");
|
const head = document.createElement("div");
|
||||||
const identity = document.createElement("div");
|
const identity = document.createElement("div");
|
||||||
|
const title = document.createElement("div");
|
||||||
const name = document.createElement("strong");
|
const name = document.createElement("strong");
|
||||||
|
const blockNumber = document.createElement("span");
|
||||||
const slug = document.createElement("span");
|
const slug = document.createElement("span");
|
||||||
const stats = document.createElement("div");
|
|
||||||
const logo = createPoolLogo(pool);
|
const logo = createPoolLogo(pool);
|
||||||
|
const coinbaseMessage = createCoinbaseMessage(block.extras.coinbaseSignatureAscii);
|
||||||
|
|
||||||
pane.dataset.minerPane = "";
|
pane.dataset.minerPane = "";
|
||||||
head.dataset.minerHead = "";
|
head.dataset.minerHead = "";
|
||||||
identity.dataset.minerIdentity = "";
|
identity.dataset.minerIdentity = "";
|
||||||
|
title.dataset.minerTitle = "";
|
||||||
slug.dataset.minerSlug = "";
|
slug.dataset.minerSlug = "";
|
||||||
stats.dataset.minerStats = "";
|
|
||||||
logo.dataset.minerLogo = "";
|
logo.dataset.minerLogo = "";
|
||||||
|
|
||||||
name.textContent = pool.name;
|
name.textContent = pool.name;
|
||||||
slug.textContent = `#${pool.slug}`;
|
// TODO: remove fallback after the server includes pool.blockNumber everywhere.
|
||||||
identity.append(name, slug);
|
blockNumber.textContent = `#${(pool.blockNumber || 0).toLocaleString()}`;
|
||||||
|
slug.textContent = pool.slug;
|
||||||
|
title.append(name, blockNumber);
|
||||||
|
identity.append(title, slug);
|
||||||
head.append(identity, logo);
|
head.append(identity, logo);
|
||||||
stats.append(
|
pane.append(head, ...(coinbaseMessage ? [coinbaseMessage] : []));
|
||||||
createMinerStat("Difficulty", block.difficulty.toLocaleString()),
|
|
||||||
createMinerStat("Bits", formatBits(block.bits)),
|
return pane;
|
||||||
createMinerStat("Nonce", block.nonce.toLocaleString()),
|
}
|
||||||
|
|
||||||
|
/** @param {Block} block */
|
||||||
|
function createDifficultySummary(block) {
|
||||||
|
const pane = document.createElement("div");
|
||||||
|
|
||||||
|
pane.dataset.metricList = "";
|
||||||
|
pane.append(
|
||||||
|
createMetricStat("Difficulty", block.difficulty.toLocaleString()),
|
||||||
|
createEpochProgress(
|
||||||
|
"Difficulty epoch",
|
||||||
|
block.height,
|
||||||
|
DIFFICULTY_EPOCH_BLOCKS,
|
||||||
|
"var(--orange)",
|
||||||
|
),
|
||||||
|
createEpochProgress(
|
||||||
|
"Halving epoch",
|
||||||
|
block.height,
|
||||||
|
HALVING_EPOCH_BLOCKS,
|
||||||
|
"var(--red)",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
pane.append(head, stats);
|
|
||||||
|
|
||||||
return pane;
|
return pane;
|
||||||
}
|
}
|
||||||
@@ -320,16 +355,19 @@ function createRewardPart(type, label, sats, total, price) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {string} label
|
||||||
* @param {number} sats
|
* @param {number} sats
|
||||||
* @param {number} price
|
* @param {number} price
|
||||||
*/
|
*/
|
||||||
function createRewardTotal(sats, price) {
|
function createRewardTotal(label, sats, price) {
|
||||||
const total = document.createElement("div");
|
const total = document.createElement("div");
|
||||||
|
const name = document.createElement("span");
|
||||||
const amount = createBtcAmount("strong", sats);
|
const amount = createBtcAmount("strong", sats);
|
||||||
const usd = createSatsUsdAmount(sats, price);
|
const usd = createSatsUsdAmount(sats, price);
|
||||||
|
|
||||||
total.dataset.rewardTotal = "";
|
total.dataset.rewardTotal = "";
|
||||||
total.append(amount, usd);
|
name.textContent = label;
|
||||||
|
total.append(name, amount, usd);
|
||||||
|
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
@@ -365,18 +403,11 @@ function setRewardPreview(rewards, activeKey) {
|
|||||||
/** @param {Block["extras"]} extras */
|
/** @param {Block["extras"]} extras */
|
||||||
function createRewardSummary(extras) {
|
function createRewardSummary(extras) {
|
||||||
const subsidy = extras.reward - extras.totalFees;
|
const subsidy = extras.reward - extras.totalFees;
|
||||||
|
const rewards = document.createElement("div");
|
||||||
const bar = document.createElement("div");
|
const bar = document.createElement("div");
|
||||||
const split = createLegendList({ fill: true });
|
const split = createLegendList({ fill: true });
|
||||||
const rewards = createStatBox(
|
|
||||||
"Rewards",
|
|
||||||
[],
|
|
||||||
[
|
|
||||||
createRewardTotal(extras.reward, extras.price),
|
|
||||||
bar,
|
|
||||||
split,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
|
rewards.dataset.statBox = "rewards";
|
||||||
appendLegendListItem(
|
appendLegendListItem(
|
||||||
split,
|
split,
|
||||||
createRewardPart("subsidy", "Subsidy", subsidy, extras.reward, extras.price),
|
createRewardPart("subsidy", "Subsidy", subsidy, extras.reward, extras.price),
|
||||||
@@ -390,6 +421,7 @@ function createRewardSummary(extras) {
|
|||||||
createRewardSegment("subsidy", subsidy, extras.reward),
|
createRewardSegment("subsidy", subsidy, extras.reward),
|
||||||
createRewardSegment("fees", extras.totalFees, extras.reward),
|
createRewardSegment("fees", extras.totalFees, extras.reward),
|
||||||
);
|
);
|
||||||
|
rewards.append(createRewardTotal("Rewards", extras.reward, extras.price), bar, split);
|
||||||
|
|
||||||
rewards.addEventListener("pointerenter", (event) => {
|
rewards.addEventListener("pointerenter", (event) => {
|
||||||
setRewardPreview(rewards, getRewardKey(event.target));
|
setRewardPreview(rewards, getRewardKey(event.target));
|
||||||
@@ -415,8 +447,8 @@ function createTransactionSummary(block) {
|
|||||||
transactions.dataset.blockBox = "tx";
|
transactions.dataset.blockBox = "tx";
|
||||||
io.dataset.blockIo = "";
|
io.dataset.blockIo = "";
|
||||||
io.append(
|
io.append(
|
||||||
createInlineBox("Input", extras.totalInputs.toLocaleString()),
|
createInlineBox("Input", extras.totalInputs.toLocaleString(), "input"),
|
||||||
createInlineBox("Output", extras.totalOutputs.toLocaleString()),
|
createInlineBox("Output", extras.totalOutputs.toLocaleString(), "output"),
|
||||||
);
|
);
|
||||||
transactions.append(
|
transactions.append(
|
||||||
createInlineRow("Tx", [block.txCount.toLocaleString()]),
|
createInlineRow("Tx", [block.txCount.toLocaleString()]),
|
||||||
@@ -466,9 +498,10 @@ export function createBlockDetails() {
|
|||||||
const header = document.createElement("header");
|
const header = document.createElement("header");
|
||||||
const titleRow = document.createElement("div");
|
const titleRow = document.createElement("div");
|
||||||
const title = document.createElement("h1");
|
const title = document.createElement("h1");
|
||||||
const summary = document.createElement("p");
|
const date = document.createElement("time");
|
||||||
|
const meta = document.createElement("div");
|
||||||
|
const hash = document.createElement("p");
|
||||||
const price = createUsdAmount("output", 0, {
|
const price = createUsdAmount("output", 0, {
|
||||||
size: "title",
|
|
||||||
tone: "positive",
|
tone: "positive",
|
||||||
});
|
});
|
||||||
const content = document.createElement("div");
|
const content = document.createElement("div");
|
||||||
@@ -476,8 +509,12 @@ export function createBlockDetails() {
|
|||||||
element.id = "block-details";
|
element.id = "block-details";
|
||||||
element.hidden = true;
|
element.hidden = true;
|
||||||
titleRow.dataset.blockTitle = "";
|
titleRow.dataset.blockTitle = "";
|
||||||
titleRow.append(title, price);
|
date.dataset.blockDate = "";
|
||||||
header.append(titleRow, summary);
|
meta.dataset.blockMeta = "";
|
||||||
|
hash.dataset.blockHashLine = "";
|
||||||
|
titleRow.append(title, date);
|
||||||
|
meta.append(hash, price);
|
||||||
|
header.append(titleRow, meta);
|
||||||
element.append(header, content);
|
element.append(header, content);
|
||||||
|
|
||||||
/** @param {Block} block */
|
/** @param {Block} block */
|
||||||
@@ -486,13 +523,10 @@ export function createBlockDetails() {
|
|||||||
|
|
||||||
element.hidden = false;
|
element.hidden = false;
|
||||||
title.replaceChildren(...createTitle(block.height));
|
title.replaceChildren(...createTitle(block.height));
|
||||||
summary.replaceChildren(
|
date.dateTime = new Date(block.timestamp * 1_000).toISOString();
|
||||||
createHashElement(block.id),
|
date.textContent = formatDateTime(block.timestamp);
|
||||||
document.createElement("br"),
|
hash.replaceChildren(createHashElement(block.id));
|
||||||
formatDateTime(block.timestamp),
|
|
||||||
);
|
|
||||||
renderUsdAmount(price, extras.price, {
|
renderUsdAmount(price, extras.price, {
|
||||||
size: "title",
|
|
||||||
tone: "positive",
|
tone: "positive",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -503,13 +537,15 @@ export function createBlockDetails() {
|
|||||||
|
|
||||||
appendGroup(content, "Mining", [], [createMinerSummary(block)], false);
|
appendGroup(content, "Mining", [], [createMinerSummary(block)], false);
|
||||||
|
|
||||||
|
appendGroup(content, "Difficulty", [], [createDifficultySummary(block)], false);
|
||||||
|
|
||||||
appendGroup(content, "Rewards", [], [createRewardSummary(extras)], false);
|
appendGroup(content, "Rewards", [], [createRewardSummary(extras)], false);
|
||||||
|
|
||||||
appendGroup(content, "Block", [], [createTransactionSummary(block)], false);
|
appendGroup(content, "Block", [], [createTransactionSummary(block)], false);
|
||||||
|
|
||||||
appendGroup(content, "Fees", [], [
|
appendGroup(content, "Fees", [], [
|
||||||
createFeeChart(extras.feeRange, extras.avgFeeRate, formatFeeRate),
|
createFeeChart(extras.feeRange, extras.avgFeeRate, formatFeeRate),
|
||||||
]);
|
], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return /** @type {const} */ ({
|
return /** @type {const} */ ({
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
padding-bottom: 1.25rem;
|
padding-bottom: 1.25rem;
|
||||||
|
|
||||||
[data-block-title] {
|
:is([data-block-title], [data-block-meta]) {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-wrap: wrap;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
align-items: baseline;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 0.35rem 1rem;
|
gap: 0.35rem 1rem;
|
||||||
|
align-items: baseline;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@@ -56,15 +56,28 @@
|
|||||||
line-height: var(--line-height-lg);
|
line-height: var(--line-height-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
[data-block-date],
|
||||||
|
[data-block-hash-line] {
|
||||||
color: var(--gray);
|
color: var(--gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-block-date] {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-block-hash-line] {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 1rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
@@ -82,7 +95,7 @@
|
|||||||
|
|
||||||
[data-miner-pane] {
|
[data-miner-pane] {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.75rem;
|
gap: 1rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,12 +103,21 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: start;
|
align-items: center;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-miner-identity] {
|
[data-miner-identity] {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
gap: 0.125rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-miner-title] {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: baseline;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
> strong {
|
> strong {
|
||||||
@@ -106,29 +128,49 @@
|
|||||||
font-weight: 450;
|
font-weight: 450;
|
||||||
line-height: var(--line-height-sm);
|
line-height: var(--line-height-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: var(--gray);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
line-height: var(--line-height-sm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-miner-slug] {
|
[data-miner-slug] {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
color: var(--gray);
|
color: var(--gray);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-sm);
|
||||||
line-height: var(--line-height-xs);
|
line-height: var(--line-height-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-miner-logo] {
|
[data-miner-logo] {
|
||||||
width: 2rem;
|
width: 1.25rem;
|
||||||
height: 2rem;
|
height: 1.25rem;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-miner-stats] {
|
[data-coinbase-message] {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--white);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-style: italic;
|
||||||
|
line-height: var(--line-height-sm);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&:is([data-group="mining"], [data-group="difficulty"]) {
|
||||||
|
[data-metric-list] {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.35rem;
|
gap: 0.5rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-miner-stat] {
|
[data-metric-stat] {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(5.5rem, auto) minmax(0, 1fr);
|
grid-template-columns: minmax(5.5rem, auto) minmax(0, 1fr);
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
@@ -152,6 +194,61 @@
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-epoch] {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.25rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-epoch-head] {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: var(--epoch-color);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
line-height: var(--line-height-xs);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--white);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: 450;
|
||||||
|
line-height: var(--line-height-sm);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-epoch-bar] {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.125rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-epoch-segment] {
|
||||||
|
width: var(--share);
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
|
||||||
|
&[data-epoch-segment="done"] {
|
||||||
|
background: var(--epoch-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-epoch-segment="remaining"] {
|
||||||
|
background: color-mix(in oklch, var(--gray) 35%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-group="difficulty"] {
|
||||||
|
--section-color: var(--orange);
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-group="block"] {
|
&[data-group="block"] {
|
||||||
@@ -169,59 +266,9 @@
|
|||||||
&[data-group="rewards"] {
|
&[data-group="rewards"] {
|
||||||
[data-stat-box] {
|
[data-stat-box] {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.75rem;
|
|
||||||
min-width: 0;
|
|
||||||
border: 1px solid
|
|
||||||
color-mix(in oklch, var(--section-color) 28%, transparent);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
padding: 0.75rem;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
color: var(--section-color);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
font-weight: 450;
|
|
||||||
line-height: var(--line-height-xs);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-stat-box] {
|
|
||||||
border-color: color-mix(
|
|
||||||
in oklch,
|
|
||||||
var(--section-color) 18%,
|
|
||||||
transparent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-stats] {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-stat] {
|
|
||||||
display: grid;
|
|
||||||
gap: 0.25rem;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
color: var(--section-color);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
line-height: var(--line-height-xs);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
min-width: 0;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
color: var(--white);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
font-weight: 450;
|
|
||||||
line-height: var(--line-height-sm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-group="block"] {
|
&[data-group="block"] {
|
||||||
@@ -230,16 +277,20 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border: 1px solid
|
border: 1px solid
|
||||||
color-mix(in oklch, var(--section-color) 28%, transparent);
|
color-mix(in oklch, var(--section-color) 35%, transparent);
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
|
|
||||||
[data-block-box] {
|
&[data-block-box="tx"] {
|
||||||
border-color: color-mix(
|
border-color: color-mix(in oklch, var(--orange) 35%, transparent);
|
||||||
in oklch,
|
}
|
||||||
var(--section-color) 18%,
|
|
||||||
transparent
|
&[data-block-box="input"] {
|
||||||
);
|
border-color: color-mix(in oklch, var(--yellow) 55%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-block-box="output"] {
|
||||||
|
border-color: color-mix(in oklch, var(--red) 55%, transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +314,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
color: var(--white);
|
color: var(--section-color);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-weight: 450;
|
font-weight: 450;
|
||||||
line-height: var(--line-height-sm);
|
line-height: var(--line-height-sm);
|
||||||
@@ -274,38 +325,55 @@
|
|||||||
[data-block-io] {
|
[data-block-io] {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
gap: 0.5rem;
|
gap: 0.75rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
||||||
[data-block-box] {
|
[data-block-box] {
|
||||||
aspect-ratio: 1 / 1;
|
align-content: center;
|
||||||
place-content: center;
|
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-inline-row] {
|
[data-inline-row] {
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: auto auto;
|
||||||
justify-items: center;
|
justify-content: space-between;
|
||||||
gap: 0.25rem;
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
strong {
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-block-box="tx"] > [data-inline-row] {
|
||||||
|
> span,
|
||||||
|
strong {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:is([data-block-box="input"], [data-block-box="output"]) > [data-inline-row] {
|
||||||
|
> span,
|
||||||
|
strong {
|
||||||
|
color: var(--block-box-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-block-box="input"] {
|
||||||
|
--block-box-color: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-block-box="output"] {
|
||||||
|
--block-box-color: var(--red);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-group="rewards"] {
|
&[data-group="rewards"] {
|
||||||
[data-reward-total] {
|
[data-reward-total] {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.25rem;
|
gap: 0.125rem;
|
||||||
justify-items: center;
|
justify-items: start;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
color: var(--gray);
|
color: var(--gray);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
line-height: var(--line-height-xs);
|
line-height: var(--line-height-xs);
|
||||||
text-align: center;
|
text-align: left;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -315,6 +383,11 @@
|
|||||||
font-weight: 450;
|
font-weight: 450;
|
||||||
line-height: var(--line-height-sm);
|
line-height: var(--line-height-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> span:first-child {
|
||||||
|
color: var(--section-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-reward-bar] {
|
[data-reward-bar] {
|
||||||
@@ -416,12 +489,6 @@
|
|||||||
grid-column: auto;
|
grid-column: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
section[data-group="rewards"] {
|
|
||||||
[data-stats] {
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dl > div {
|
dl > div {
|
||||||
grid-template-columns: minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 0.15rem;
|
gap: 0.15rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user