", {
class: "toast",
text: message,
css: {
backgroundColor: "#f7931a",
color: "#000",
padding: "10px 15px",
borderRadius: "5px",
marginTop: "10px",
boxShadow: "0 0 10px rgba(247, 147, 26, 0.5)",
fontFamily: "var(--terminal-font)",
opacity: 0,
transition: "opacity 0.3s ease"
}
}).appendTo(toastContainer);
// Show the toast
setTimeout(() => {
toast.css("opacity", 1);
// Hide and remove the toast after 3 seconds
setTimeout(() => {
toast.css("opacity", 0);
setTimeout(() => toast.remove(), 300);
}, 3000);
}, 100);
}
// Pool color mapping function - add this after your existing helper functions
function getPoolColor(poolName) {
// Normalize the pool name (lowercase and remove special characters)
const normalizedName = poolName.toLowerCase().replace(/[^a-z0-9]/g, '');
// Define color mappings for common mining pools with Ocean pool featured prominently
const poolColors = {
// OCEAN pool with a distinctive bright cyan color for prominence
'ocean': '#00ffff', // Bright Cyan for Ocean
'oceanpool': '#00ffff', // Bright Cyan for Ocean
'oceanxyz': '#00ffff', // Bright Cyan for Ocean
'ocean.xyz': '#00ffff', // Bright Cyan for Ocean
// Other common mining pools with more muted colors
'f2pool': '#1a9eff', // Blue
'antpool': '#ff7e33', // Orange
'binancepool': '#f3ba2f', // Binance gold
'foundryusa': '#b150e2', // Purple
'viabtc': '#ff5c5c', // Red
'luxor': '#2bae2b', // Green
'slushpool': '#3355ff', // Bright blue
'btccom': '#ff3355', // Pink
'poolin': '#ffaa22', // Amber
'sbicrypto': '#cc9933', // Bronze
'mara': '#8844cc', // Violet
'ultimuspool': '#09c7be', // Teal
'unknown': '#999999' // Grey for unknown pools
};
// Check for partial matches in pool names (for variations like "F2Pool" vs "F2pool.com")
for (const [key, color] of Object.entries(poolColors)) {
if (normalizedName.includes(key)) {
return color;
}
}
// If no match is found, generate a consistent color based on the pool name
let hash = 0;
for (let i = 0; i < poolName.length; i++) {
hash = poolName.charCodeAt(i) + ((hash << 5) - hash);
}
// Generate HSL color with fixed saturation and lightness for readability
// Use the hash to vary the hue only (0-360)
const hue = Math.abs(hash % 360);
return `hsl(${hue}, 70%, 60%)`;
}
// Function to create a block card
function createBlockCard(block) {
const timestamp = formatTimestamp(block.timestamp);
const formattedSize = formatFileSize(block.size);
const formattedTxCount = numberWithCommas(block.tx_count);
// Get the pool name or "Unknown"
const poolName = block.extras && block.extras.pool ? block.extras.pool.name : "Unknown";
// Get pool color
const poolColor = getPoolColor(poolName);
// Check if this is an Ocean pool block for special styling
const isOceanPool = poolName.toLowerCase().includes('ocean');
// Calculate total fees in BTC
const totalFees = block.extras ? (block.extras.totalFees / 100000000).toFixed(8) : "N/A";
// Create the block card
const blockCard = $("
", {
class: "block-card",
"data-height": block.height,
"data-hash": block.id
});
// Apply pool color border - with special emphasis for Ocean pool
if (isOceanPool) {
// Give Ocean pool blocks a more prominent styling
blockCard.css({
"border": `2px solid ${poolColor}`,
"box-shadow": `0 0 10px ${poolColor}`,
"background": `linear-gradient(to bottom, rgba(0, 255, 255, 0.1), rgba(0, 0, 0, 0))`
});
} else {
// Standard styling for other pools
blockCard.css({
"border-left": `4px solid ${poolColor}`,
"border-top": `1px solid ${poolColor}30`,
"border-right": `1px solid ${poolColor}30`,
"border-bottom": `1px solid ${poolColor}30`
});
}
// Create the block header
const blockHeader = $("
", {
class: "block-info-label",
text: "Transactions"
}));
// Determine transaction count color based on thresholds
let txCountClass = "green"; // Default for high transaction counts (2000+)
if (block.tx_count < 500) {
txCountClass = "red"; // Less than 500 transactions
} else if (block.tx_count < 2000) {
txCountClass = "yellow"; // Between 500 and 1999 transactions
}
txCountItem.append($("
", {
class: "block-info-value yellow",
text: block.extras && block.extras.avgFeeRate ? block.extras.avgFeeRate + " sat/vB" : "N/A"
}));
blockInfo.append(feesItem);
blockCard.append(blockInfo);
// Add event listener for clicking on the block card
blockCard.on("click", function () {
showBlockDetails(block);
});
return blockCard;
}
// Function to load blocks from a specific height
function loadBlocksFromHeight(height) {
if (isLoading) return;
// Convert to integer
height = parseInt(height);
if (isNaN(height) || height < 0) {
showToast("Please enter a valid block height");
return;
}
isLoading = true;
currentStartHeight = height;
// Check if we already have this data in cache
if (blocksCache[height]) {
displayBlocks(blocksCache[height]);
isLoading = false;
return;
}
// Show loading state
$("#blocks-grid").html('
Loading blocks from height ' + height + '
');
// Fetch blocks from the API
$.ajax({
url: `${mempoolBaseUrl}/api/v1/blocks/${height}`,
method: "GET",
dataType: "json",
timeout: 10000,
success: function(data) {
// Cache the data
blocksCache[height] = data;
// Display the blocks
displayBlocks(data);
// Update latest block stats
if (data.length > 0) {
updateLatestBlockStats(data[0]);
}
},
error: function(xhr, status, error) {
console.error("Error fetching blocks:", error);
$("#blocks-grid").html('
Error fetching blocks. Please try again later.
');
// Show error toast
showToast("Failed to load blocks. Please try again later.");
},
complete: function() {
isLoading = false;
}
});
}
// Function to load the latest blocks and return a promise with the latest block height
function loadLatestBlocks() {
if (isLoading) return Promise.resolve(null);
isLoading = true;
// Show loading state
$("#blocks-grid").html('
Loading latest blocks
');
// Fetch the latest blocks from the API
return $.ajax({
url: `${mempoolBaseUrl}/api/v1/blocks`,
method: "GET",
dataType: "json",
timeout: 10000,
success: function (data) {
// Cache the data (use the first block's height as the key)
if (data.length > 0) {
currentStartHeight = data[0].height;
blocksCache[currentStartHeight] = data;
// Update the block height input with the latest height
$("#block-height").val(currentStartHeight);
// Update latest block stats
updateLatestBlockStats(data[0]);
}
// Display the blocks
displayBlocks(data);
},
error: function (xhr, status, error) {
console.error("Error fetching latest blocks:", error);
$("#blocks-grid").html('
Error fetching blocks. Please try again later.
');
// Show error toast
showToast("Failed to load latest blocks. Please try again later.");
},
complete: function () {
isLoading = false;
}
}).then(data => data.length > 0 ? data[0].height : null);
}
// Refresh blocks page every 60 seconds if there are new blocks
setInterval(function () {
console.log("Checking for new blocks at " + new Date().toLocaleTimeString());
loadLatestBlocks().then(latestHeight => {
if (latestHeight && latestHeight > currentStartHeight) {
console.log("New blocks detected, refreshing the page");
location.reload();
} else {
console.log("No new blocks detected");
}
});
}, 60000);
// Function to update the latest block stats section
function updateLatestBlockStats(block) {
if (!block) return;
$("#latest-height").text(block.height);
$("#latest-time").text(formatTimestamp(block.timestamp));
$("#latest-tx-count").text(numberWithCommas(block.tx_count));
$("#latest-size").text(formatFileSize(block.size));
$("#latest-difficulty").text(numberWithCommas(Math.round(block.difficulty)));
// Pool info with color coding
const poolName = block.extras && block.extras.pool ? block.extras.pool.name : "Unknown";
const poolColor = getPoolColor(poolName);
const isOceanPool = poolName.toLowerCase().includes('ocean');
// Clear previous content of the pool span
const poolSpan = $("#latest-pool");
poolSpan.empty();
// Create the pool name element with styling
const poolElement = $("", {
text: poolName,
css: {
color: poolColor,
textShadow: isOceanPool ? `0 0 8px ${poolColor}` : `0 0 6px ${poolColor}80`,
fontWeight: isOceanPool ? "bold" : "normal"
}
});
// Add star icon for Ocean pool
if (isOceanPool) {
poolElement.prepend($("", {
html: "★ ",
css: { color: poolColor }
}));
}
// Add the styled element to the DOM
poolSpan.append(poolElement);
// If this is the latest block from Ocean pool, add a subtle highlight to the stats card
const statsCard = $(".latest-block-stats").closest(".card");
if (isOceanPool) {
statsCard.css({
"border": `2px solid ${poolColor}`,
"box-shadow": `0 0 10px ${poolColor}`,
"background": `linear-gradient(to bottom, rgba(0, 255, 255, 0.05), rgba(0, 0, 0, 0))`
});
} else {
// Reset to default styling if not Ocean pool
statsCard.css({
"border": "",
"box-shadow": "",
"background": ""
});
}
// Average Fee Rate
if (block.extras && block.extras.avgFeeRate) {
$("#latest-fee-rate").text(block.extras.avgFeeRate + " sat/vB");
} else {
$("#latest-fee-rate").text("N/A");
}
}
// Function to display the blocks in the grid
function displayBlocks(blocks) {
const blocksGrid = $("#blocks-grid");
// Clear the grid
blocksGrid.empty();
if (!blocks || blocks.length === 0) {
blocksGrid.html('
No blocks found
');
return;
}
// Create a card for each block
blocks.forEach(function(block) {
const blockCard = createBlockCard(block);
blocksGrid.append(blockCard);
});
// Add navigation controls if needed
addNavigationControls(blocks);
}
// Function to add navigation controls to the blocks grid
function addNavigationControls(blocks) {
// Get the height of the first and last block in the current view
const firstBlockHeight = blocks[0].height;
const lastBlockHeight = blocks[blocks.length - 1].height;
// Create navigation controls
const navControls = $("
", {
class: "block-navigation"
});
// Newer blocks button (if not already at the latest blocks)
if (firstBlockHeight !== currentStartHeight) {
const newerButton = $("