Skip to content

Commit 1adbaf0

Browse files
refactor(frontend): extract pause/resume control into reusable component and move button styles to globals.css
1 parent c1ff61c commit 1adbaf0

File tree

5 files changed

+130
-75
lines changed

5 files changed

+130
-75
lines changed

frontend/app/globals.css

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,3 +747,60 @@
747747
);
748748
box-shadow: 0 0 30px hsl(260 90% 70% / 0.3);
749749
}
750+
751+
/* Button variant styles */
752+
.btn-primary {
753+
background:
754+
radial-gradient(
755+
50% 50% at 50% 50%,
756+
rgba(110, 84, 255, 0) 0%,
757+
rgba(255, 255, 255, 0.12) 100%
758+
), #6e54ff;
759+
box-shadow:
760+
0 1px 2px 0 rgba(0, 0, 0, 0.2),
761+
inset 0 1px 0.5px 0 rgba(255, 255, 255, 0.25),
762+
inset 0 -1px 0.5px 0 rgba(255, 255, 255, 0.25),
763+
0 0 0 1px rgba(79, 71, 235, 0.9);
764+
}
765+
766+
.btn-primary:hover {
767+
background:
768+
radial-gradient(
769+
50% 50% at 50% 50%,
770+
rgba(110, 84, 255, 0.66) 29.81%,
771+
rgba(255, 255, 255, 0.5) 100%
772+
), #6e54ff;
773+
box-shadow:
774+
0 1px 2px 0 rgba(0, 0, 0, 0.2),
775+
inset 0 0.75px 0.66px 0 rgba(255, 255, 255, 0.8),
776+
inset 0 -0.75px 0.66px 0 rgba(255, 255, 255, 0.8),
777+
0 0 0 1px rgba(79, 71, 235, 0.5);
778+
}
779+
780+
.btn-secondary {
781+
background:
782+
radial-gradient(
783+
50% 50% at 50% 50%,
784+
rgba(23, 23, 23, 0.2) 0%,
785+
rgba(163, 163, 163, 0.16) 100%
786+
), #0a0a0a;
787+
box-shadow:
788+
0 1px 2px 0 rgba(0, 0, 0, 0.2),
789+
inset 0 0.5px 0.5px 0 rgba(255, 255, 255, 0.25),
790+
inset 0 -0.5px 0.5px 0 rgba(255, 255, 255, 0.25),
791+
0 0 0 1px rgba(0, 0, 0, 0.8);
792+
}
793+
794+
.btn-secondary:hover {
795+
background:
796+
radial-gradient(
797+
50% 50% at 50% 50%,
798+
rgba(23, 23, 23, 0.66) 0%,
799+
rgba(163, 163, 163, 0.53) 100%
800+
), #0a0a0a;
801+
box-shadow:
802+
0 1px 2px 0 rgba(0, 0, 0, 0.2),
803+
inset 0 0.5px 0.5px 0 rgba(255, 255, 255, 0.25),
804+
inset 0 -0.5px 0.5px 0 rgba(255, 255, 255, 0.25),
805+
0 0 0 1px rgba(0, 0, 0, 0.8);
806+
}

frontend/components/block-state-tracker/index.tsx

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import { Info, Pointer } from 'lucide-react'
4+
import { PauseResumeControl } from '@/components/common/pause-resume-control'
45
import { SectionHeader } from '@/components/ui/section-header'
56
import { Switch } from '@/components/ui/switch'
67
import {
@@ -11,7 +12,6 @@ import {
1112
import { BLOCK_STATE_LEGEND } from '@/constants/block-state'
1213
import { useBlockStateTracker } from '@/hooks/use-block-state-tracker'
1314
import { useMouseHover } from '@/hooks/use-mouse-hover'
14-
import { cn } from '@/lib/utils'
1515
import { ExternalLink } from '../ui/external-link'
1616
import { Blockchain } from './blockchain'
1717

@@ -51,52 +51,30 @@ export function BlockStateTracker() {
5151
}
5252
/>
5353

54-
{/* Mobile pause/resume button - below section header */}
55-
<div className="md:hidden flex items-center gap-4 px-6 py-4 bg-[#0E100F]">
56-
<button
57-
type="button"
58-
onClick={() => setIsFollowingChain(!isFollowingChain)}
59-
className={cn(
60-
'h-9 px-4 py-2 rounded-md font-mono text-sm text-white uppercase cursor-pointer transition-all duration-200',
61-
isFollowingChain
62-
? '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)]'
63-
: '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)]',
64-
)}
65-
>
66-
{isFollowingChain ? 'Pause' : 'Resume'}
67-
</button>
68-
<span className="text-sm text-[#52525E]">
69-
{isFollowingChain
70-
? 'Pause to freeze and scroll'
71-
: 'Resume to follow chain'}
72-
</span>
73-
</div>
74-
7554
{/* Main container */}
76-
<div className="w-full flex flex-col border-b border-zinc-800 bg-[#0E100F]">
77-
{/* Legend bar with slow mode toggle */}
78-
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 px-6 sm:px-10 py-4">
79-
<div className="flex items-center gap-4 sm:gap-6">
55+
<div className="w-full flex flex-col bg-[#0E100F]">
56+
{/* Legend + Slow mode row */}
57+
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-6 px-4 sm:px-10 py-4">
58+
{/* Legend */}
59+
<div className="flex flex-wrap items-center gap-3 sm:gap-6">
8060
{BLOCK_STATE_LEGEND.map((item) => (
8161
<div key={item.label} className="flex items-center gap-2">
8262
<div
8363
className="w-3 h-3 rounded-full"
8464
style={{ backgroundColor: item.color }}
8565
/>
86-
<span className="text-base text-white">{item.label}</span>
66+
<span className="text-sm sm:text-base text-white">
67+
{item.label}
68+
</span>
8769
</div>
8870
))}
8971
</div>
90-
<div className="flex items-center gap-4">
91-
<span className="text-base text-white">
72+
73+
{/* Slow mode toggle */}
74+
<div className="flex items-center gap-3">
75+
<span className="text-sm sm:text-base text-white">
9276
{isSlowMotion ? `Slow mode (${remainingSeconds}s)` : 'Slow mode'}
9377
</span>
94-
<Switch
95-
checked={isSlowMotion}
96-
onCheckedChange={(checked) =>
97-
checked ? startSlowMotion() : stopSlowMotion()
98-
}
99-
/>
10078
<Tooltip delayDuration={300}>
10179
<TooltipTrigger asChild>
10280
<button type="button" className="cursor-help">
@@ -113,11 +91,25 @@ export function BlockStateTracker() {
11391
</p>
11492
</TooltipContent>
11593
</Tooltip>
94+
<Switch
95+
checked={isSlowMotion}
96+
onCheckedChange={(checked) =>
97+
checked ? startSlowMotion() : stopSlowMotion()
98+
}
99+
/>
116100
</div>
117101
</div>
118102

103+
{/* Mobile: Pause/Resume button */}
104+
<div className="md:hidden px-4 pb-4">
105+
<PauseResumeControl
106+
isFollowingChain={isFollowingChain}
107+
onToggle={() => setIsFollowingChain(!isFollowingChain)}
108+
/>
109+
</div>
110+
119111
{/* Blockchain visualization */}
120-
<div className="relative sm:py-6 sm:px-10" {...hoverProps}>
112+
<div className="relative py-4 px-4 sm:py-6 sm:px-10" {...hoverProps}>
121113
{/* Left fade gradient - only on sm and above */}
122114
<div className="hidden sm:block absolute left-0 top-0 bottom-0 w-75 z-10 pointer-events-none bg-linear-to-r from-[#0E100F] to-transparent" />
123115
<Blockchain blocks={blocks} isFollowingChain={!isPaused} />

frontend/components/block-time-tracker/index.tsx

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
import { Clock, Pointer, TrendingUp } from 'lucide-react'
44
import { useMemo, useState } from 'react'
5+
import { PauseResumeControl } from '@/components/common/pause-resume-control'
56
import { SectionHeader } from '@/components/ui/section-header'
67
import { StatCard } from '@/components/ui/stat-card'
78
import { useBlockExecutionTracker } from '@/hooks/use-block-execution-tracker'
89
import { useMouseHover } from '@/hooks/use-mouse-hover'
910
import { fromNsToMsPrecise } from '@/lib/block-metrics'
10-
import { cn } from '@/lib/utils'
1111
import { BlockTimeLegend } from './block-time-legend'
1212
import { BlockTimeTimeline } from './block-time-timeline'
1313

@@ -80,36 +80,23 @@ export function BlockTimeExecutionTracker() {
8080
description="Visualize block and transaction execution times. Taller transaction bars indicate parallel execution within block."
8181
/>
8282

83-
{/* Mobile pause/resume button - below section header */}
84-
<div className="md:hidden flex items-center gap-4 px-6 py-4 bg-[#0E100F]">
85-
<button
86-
type="button"
87-
onClick={() => setIsFollowingChain(!isFollowingChain)}
88-
className={cn(
89-
'h-9 px-4 py-2 rounded-md font-mono text-sm text-white uppercase cursor-pointer transition-all duration-200',
90-
isFollowingChain
91-
? '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)]'
92-
: '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)]',
93-
)}
94-
>
95-
{isFollowingChain ? 'Pause' : 'Resume'}
96-
</button>
97-
<span className="text-sm text-[#52525E]">
98-
{isFollowingChain
99-
? 'Pause to freeze and scroll'
100-
: 'Resume to follow chain'}
101-
</span>
102-
</div>
103-
10483
{/* Main container */}
10584
<div className="w-full pt-4 flex flex-col bg-[#0E100F]">
106-
{/* Legend bar */}
107-
<div className="flex items-center px-6 sm:px-10 py-4">
85+
{/* Legend bar - visible on all screens */}
86+
<div className="flex items-center px-4 sm:px-10 py-4">
10887
<BlockTimeLegend />
10988
</div>
11089

90+
{/* Mobile: Pause/Resume button */}
91+
<div className="md:hidden p-4">
92+
<PauseResumeControl
93+
isFollowingChain={isFollowingChain}
94+
onToggle={() => setIsFollowingChain(!isFollowingChain)}
95+
/>
96+
</div>
97+
11198
{/* Blocks timeline area */}
112-
<div className="relative sm:py-6 sm:px-10" {...hoverProps}>
99+
<div className="relative py-4 px-4 sm:py-6 sm:px-10" {...hoverProps}>
113100
{/* Left fade gradient - only on sm and above */}
114101
<div className="hidden sm:block absolute left-0 top-0 bottom-0 w-75 z-10 pointer-events-none bg-linear-to-r from-[#0E100F] to-transparent" />
115102

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use client'
2+
3+
import { Button } from '@/components/ui/button'
4+
5+
interface PauseResumeControlProps {
6+
isFollowingChain: boolean
7+
onToggle: () => void
8+
pausedText?: string
9+
resumedText?: string
10+
}
11+
12+
export function PauseResumeControl({
13+
isFollowingChain,
14+
onToggle,
15+
pausedText = 'Resume to follow chain',
16+
resumedText = 'Pause to freeze and scroll',
17+
}: PauseResumeControlProps) {
18+
return (
19+
<div className="flex items-center gap-3">
20+
<Button
21+
variant={isFollowingChain ? 'secondary' : 'primary'}
22+
onClick={onToggle}
23+
>
24+
{isFollowingChain ? 'Pause' : 'Resume'}
25+
</Button>
26+
<span className="text-sm text-[#52525E]">
27+
{isFollowingChain ? resumedText : pausedText}
28+
</span>
29+
</div>
30+
)
31+
}

frontend/components/ui/button.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,8 @@ const buttonVariants = cva(
77
{
88
variants: {
99
variant: {
10-
primary: [
11-
'rounded-md text-white',
12-
'bg-[radial-gradient(50%_50%_at_50%_50%,rgba(110,84,255,0)_0%,rgba(255,255,255,0.12)_100%),#6E54FF]',
13-
'shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_1px_0.5px_0_rgba(255,255,255,0.25)_inset,0_-1px_0.5px_0_rgba(255,255,255,0.25)_inset,0_0_0_1px_rgba(79,71,235,0.90)]',
14-
'hover:bg-[radial-gradient(50%_50%_at_50%_50%,rgba(110,84,255,0.66)_29.81%,rgba(255,255,255,0.50)_100%),#6E54FF]',
15-
'hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_0.75px_0.66px_0_rgba(255,255,255,0.80)_inset,0_-0.75px_0.66px_0_rgba(255,255,255,0.80)_inset,0_0_0_1px_rgba(79,71,235,0.50)]',
16-
],
17-
secondary: [
18-
'rounded-md text-white',
19-
'bg-[radial-gradient(50%_50%_at_50%_50%,rgba(23,23,23,0.20)_0%,rgba(163,163,163,0.16)_100%),#0A0A0A]',
20-
'shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_-0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_0_0_1px_rgba(0,0,0,0.80)]',
21-
'hover:bg-[radial-gradient(50%_50%_at_50%_50%,rgba(23,23,23,0.66)_0%,rgba(163,163,163,0.53)_100%),#0A0A0A]',
22-
'hover:shadow-[0_1px_2px_0_rgba(0,0,0,0.20),0_0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_-0.5px_0.5px_0_rgba(255,255,255,0.25)_inset,0_0_0_1px_rgba(0,0,0,0.80)]',
23-
],
10+
primary: 'rounded-md text-white btn-primary',
11+
secondary: 'rounded-md text-white btn-secondary',
2412
},
2513
size: {
2614
default: 'h-9 px-4 py-2',

0 commit comments

Comments
 (0)