Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@
--color-tracker-badge-bg: #2a233a;
--color-tracker-active: #836ef9;

/* Block time tracker colors - new design */
--color-block-exec-bar: #38bdf8;
--color-tx-exec-bar: #6e54ff;
--color-text-secondary-new: #a8a3b8;
--color-text-muted-new: #52525e;
--color-bg-primary: #0e100f;
--color-bg-secondary: #18181b;
--color-border-primary: #27272a;
--color-border-active: #e5e5e5;

/* Block state colors */
--color-block-proposed: #342b6f;
--color-block-voted: #6e54ff;
Expand Down
15 changes: 9 additions & 6 deletions frontend/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ import { SwapTransferTracker } from '@/components/swap-transfer-tracker'
export default function Home() {
return (
<div className="min-h-screen text-white font-sans">
<main className="py-6 px-4 max-w-7xl mx-auto sm:py-8 sm:px-6 md:py-12 flex flex-col gap-8 md:gap-12">
<main className="py-6 px-4 max-w-7xl mx-auto sm:py-8 sm:px-6 md:py-12 flex flex-col">
<PageHeader />

<NetworkActivityTracker />
{/* Sections container with continuous left/right borders */}
<div className="flex flex-col border-x border-zinc-800">
<NetworkActivityTracker />

<BlockStateTracker />
<BlockStateTracker />

<BlockTimeExecutionTracker />
<BlockTimeExecutionTracker />

<SwapTransferTracker />
<SwapTransferTracker />
</div>

<section className="flex flex-col md:flex-row gap-8 md:gap-4">
<section className="flex flex-col md:flex-row gap-8 md:gap-4 mt-8 md:mt-12">
<HotAccountsBubbleMap />
<HotSlotsBubbleMap />
</section>
Expand Down
105 changes: 53 additions & 52 deletions frontend/components/block-state-tracker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
'use client'

import { Info, Pause, Play } from 'lucide-react'
import { Pointer } from 'lucide-react'
import { ExternalLink } from '@/components/ui/external-link'
import { SectionHeader } from '@/components/ui/section-header'
import { Switch } from '@/components/ui/switch'
import { BLOCK_STATE_LEGEND } from '@/constants/block-state'
import { useBlockStateTracker } from '@/hooks/use-block-state-tracker'
import { useMouseHover } from '@/hooks/use-mouse-hover'
import { cn } from '@/lib/utils'
import { Blockchain } from './blockchain'
import { SlowMotionControl } from './slow-motion-control'

/**
* Visualizes the blockchain with blocks progressing through states:
Expand All @@ -18,7 +18,6 @@ export function BlockStateTracker() {
const {
blocks,
isSlowMotion,
remainingSeconds,
startSlowMotion,
stopSlowMotion,
isFollowingChain,
Expand All @@ -29,78 +28,80 @@ export function BlockStateTracker() {
const isPaused = !isFollowingChain || isHovering

return (
<div className="w-full flex flex-col gap-4 sm:gap-6">
<div className="w-full flex flex-col">
<SectionHeader
title="Monad Blocks - Speculative Finality"
title="Block Confirmation Progress"
description={
<>
Blocks advancing through speculative finality states according to
Monad BFT. More information{' '}
Shows how Monad blocks progress toward final confirmation.{' '}
<ExternalLink
href="https://docs.monad.xyz/monad-arch/consensus/block-states"
className="text-text-secondary hover:text-white transition-colors underline"
>
here
Learn more
</ExternalLink>
.
</>
}
>
<div className="flex items-center gap-2 flex-wrap sm:flex-nowrap">
/>

{/* Main container - no rounded corners */}
<div className="w-full flex flex-col border-b border-zinc-800 bg-[#0E100F]">
{/* Legend bar with slow mode toggle */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 px-6 sm:px-10 py-4 border-b border-zinc-800">
<div className="flex items-center gap-4 sm:gap-6">
{BLOCK_STATE_LEGEND.map((item) => (
<div key={item.label} className="flex items-center gap-2">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: item.color }}
/>
<span className="text-base text-white">{item.label}</span>
</div>
))}
</div>
<div className="flex items-center gap-4">
<span className="text-base text-white">Slow mode</span>
<Switch
checked={isSlowMotion}
onCheckedChange={(checked) =>
checked ? startSlowMotion() : stopSlowMotion()
}
/>
</div>
</div>

{/* Blockchain visualization */}
<div className="relative" {...hoverProps}>
{/* Left fade gradient - only on sm and above */}
<div className="hidden sm:block absolute left-0 top-0 bottom-0 w-[18.75rem] z-10 pointer-events-none bg-gradient-to-r from-[#0E100F] to-transparent" />
<Blockchain blocks={blocks} isFollowingChain={!isPaused} />
</div>

{/* Footer with hover pause info (desktop) and pause button (mobile) */}
<div className="flex items-center gap-4 px-6 sm:px-10 py-2.5 border-t border-zinc-800">
{/* Mobile pause/resume button */}
<button
type="button"
onClick={() => setIsFollowingChain(!isFollowingChain)}
className={cn(
'flex items-center gap-2 px-4 py-2.5 rounded-lg border text-sm font-medium cursor-pointer transition-all duration-200',
'md:hidden h-9 px-4 py-2 rounded-md font-mono text-sm text-white uppercase cursor-pointer transition-all duration-200',
isFollowingChain
? 'bg-zinc-800 border-zinc-700 text-white hover:bg-zinc-700'
: 'bg-zinc-900 border-zinc-700 text-zinc-400 hover:bg-zinc-800 hover:text-white',
? 'bg-[radial-gradient(ellipse_50%_50%_at_50%_50%,rgba(23,23,23,0.2)_0%,rgba(163,163,163,0.16)_100%),#0A0A0A] shadow-[0_0_0_1px_rgba(0,0,0,0.8)]'
: 'bg-[radial-gradient(ellipse_50%_50%_at_50%_50%,rgba(110,84,255,0)_0%,rgba(255,255,255,0.12)_100%),#6E54FF] shadow-[0_0_0_1px_rgba(79,71,235,0.9)]',
)}
>
{isFollowingChain ? (
<Pause className="w-4 h-4" />
) : (
<Play className="w-4 h-4" />
)}
{isFollowingChain ? 'Pause' : 'Resume'}
</button>
<SlowMotionControl
isActive={isSlowMotion}
remainingSeconds={remainingSeconds}
onStart={startSlowMotion}
onStop={stopSlowMotion}
/>
</div>
</SectionHeader>

<div className="flex items-center gap-4 sm:gap-6">
{BLOCK_STATE_LEGEND.map((item) => (
<div key={item.label} className="flex items-center gap-2">
<div
className="w-2 h-2 rounded-full"
style={{ backgroundColor: item.color }}
/>
<span className="text-xs sm:text-sm text-zinc-400">
{item.label}
{/* Desktop hover pause info */}
<div className="hidden md:flex items-center gap-4">
<Pointer className="w-5 h-5 text-[#52525E]" />
<span className="text-base text-[#52525E]">
Hovering on the Block stream pauses the update.
</span>
</div>
))}
</div>

{/* Info copy - only for mobile */}
<div className="flex items-center gap-2 text-sm text-zinc-500 md:hidden">
<Info className="w-4 h-lh" />
<span>Tap Pause to freeze and scroll through blocks</span>
</div>

<button type="button" className="overflow-visible" {...hoverProps}>
<Blockchain blocks={blocks} isFollowingChain={!isPaused} />
</button>

{/* Info copy - only for desktop */}
<div className="hidden md:flex items-center gap-2 text-sm text-zinc-500">
<Info className="w-4 h-lh" />
<span>Hover to pause</span>
</div>
</div>
</div>
)
Expand Down
44 changes: 9 additions & 35 deletions frontend/components/block-time-tracker/block-time-legend.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,15 @@
import { Info } from 'lucide-react'

export const BlockTimeLegend = () => {
return (
<div className="flex flex-col justify-between gap-6 text-xs sm:gap-4 sm:flex-row sm:text-sm text-zinc-400">
<div className="flex flex-row gap-2 sm:max-w-1/3 text-zinc-500">
<Info className="w-4 h-lh" />
<div className="flex flex-col gap-1">
<span className="text-sm">Height = execution time</span>
<span className="text-sm">
Purple glow = concurrent transactions observed
</span>
</div>
<div className="flex flex-wrap items-center gap-4 sm:gap-6">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-[#38BDF8]" />
<span className="text-base text-white">Block execution time</span>
</div>
<div className="flex flex-wrap gap-2 xs:gap-6">
<div className="flex items-center gap-2">
<div className="w-4 h-10 bg-zinc-600 rounded-t-sm" />
<p className="hidden xs:block">Block Execution Time</p>
<p className="block xs:hidden">Block Exec. Time</p>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-10 bg-zinc-600 rounded-t-sm relative">
<div className="absolute bottom-0 left-0 w-full h-1/2 bg-bg-card-darker rounded-t-sm" />
</div>
<p className="hidden xs:block">Transaction Execution Time</p>
<p className="block xs:hidden">Tx Exec. Time</p>
</div>
<div className="flex items-center gap-2">
<div
className="w-4 h-10 bg-[#7B66A2] rounded-t-sm"
style={{
boxShadow:
'0 0 0.3125rem var(--color-purple-glow), 0 0 0.625rem var(--color-purple-glow)',
}}
/>
<p className="hidden xs:block">Parallel Execution</p>
<p className="block xs:hidden">Parallel Exec.</p>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-[#6E54FF]" />
<span className="text-base text-white">
Total transaction execution
</span>
</div>
</div>
)
Expand Down
23 changes: 10 additions & 13 deletions frontend/components/block-time-tracker/block-time-timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type { Block } from '@/types/block'
import { BlockTime } from './block-time'

const BLOCK_DIMENSIONS = {
small: { itemWidth: 120, gridHeight: 280 },
large: { itemWidth: 140, gridHeight: 280 },
small: { itemWidth: 180, gridHeight: 340 },
large: { itemWidth: 220, gridHeight: 340 },
}

const getResponsiveDimensions = () => {
Expand All @@ -27,28 +27,25 @@ const getResponsiveDimensions = () => {
interface BlockTimeTimelineProps {
blocks: Block[]
isFollowingChain: boolean
normalizedBlockExecutionTime: number
normalizedTimeScaleMs: number
}

interface BlockCellData {
blocks: Block[]
normalizedBlockExecutionTime: number
normalizedTimeScaleMs: number
}

function BlockCell({
columnIndex,
style,
blocks,
normalizedBlockExecutionTime,
normalizedTimeScaleMs,
}: CellComponentProps<BlockCellData>) {
const block = blocks[columnIndex]

return (
<div style={style} className="flex items-center justify-center relative">
<BlockTime
block={block}
normalizedBlockExecutionTime={normalizedBlockExecutionTime}
/>
<BlockTime block={block} normalizedTimeScaleMs={normalizedTimeScaleMs} />
</div>
)
}
Expand All @@ -61,7 +58,7 @@ function BlockCell({
export function BlockTimeTimeline({
blocks,
isFollowingChain,
normalizedBlockExecutionTime,
normalizedTimeScaleMs,
}: BlockTimeTimelineProps) {
const containerRef = useRef<HTMLDivElement>(null)
const [containerWidth, setContainerWidth] = useState(0)
Expand Down Expand Up @@ -111,9 +108,9 @@ export function BlockTimeTimeline({
}, [isFollowingChain])

return (
<div ref={containerRef} className="flex-1 min-h-[280px]">
<div ref={containerRef} className="flex-1 min-h-85">
{sortedBlocks.length === 0 ? (
<div className="flex items-center justify-center w-full h-[280px]">
<div className="flex items-center justify-center w-full h-85">
<Spinner text="Waiting for blocks..." />
</div>
) : (
Expand All @@ -127,7 +124,7 @@ export function BlockTimeTimeline({
defaultWidth={containerWidth}
overscanCount={3}
cellComponent={BlockCell}
cellProps={{ blocks: sortedBlocks, normalizedBlockExecutionTime }}
cellProps={{ blocks: sortedBlocks, normalizedTimeScaleMs }}
className="scrollbar-none"
style={{
overflowX: isFollowingChain ? 'hidden' : 'auto',
Expand Down
Loading