Skip to content

Commit e1775ae

Browse files
BitHighlanderclaude
andcommitted
feat: spice up balance loading screen with hero spinner and skeleton rows
Replace the plain centered spinner + "Loading balances…" text with a multi-layer animated hero spinner (triple counter-rotating rings, pulsing glow, breathing center dot) above skeleton cards that mirror the real asset row layout. Adds a subtle kk-logo watermark behind everything and a teal shimmer sweep that travels across each skeleton card staggered by 150ms. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c9c3914 commit e1775ae

1 file changed

Lines changed: 199 additions & 6 deletions

File tree

pages/side-panel/src/components/Balances.tsx

Lines changed: 199 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
import React, { useState, useEffect } from 'react';
2-
import { Flex, Spinner, Avatar, Box, Text, Badge, Card, Stack, HStack } from '@chakra-ui/react';
2+
import {
3+
Flex,
4+
Spinner,
5+
Avatar,
6+
Box,
7+
Text,
8+
Badge,
9+
Card,
10+
Stack,
11+
HStack,
12+
Skeleton,
13+
SkeletonCircle,
14+
} from '@chakra-ui/react';
315
import { ChevronRightIcon } from '@chakra-ui/icons';
416
import AssetSelect from './AssetSelect';
517
import { COIN_MAP_LONG, NetworkIdToChain } from '@extension/shared';
@@ -80,12 +92,193 @@ const Balances = ({ onSelectAsset }: BalancesProps) => {
8092
}
8193

8294
if (loading) {
95+
const SkeletonRow = ({ delay = 0 }: { delay?: number }) => (
96+
<Card
97+
borderRadius="lg"
98+
p={3}
99+
mb={2}
100+
width="100%"
101+
bg="rgba(255, 255, 255, 0.03)"
102+
border="1px solid"
103+
borderColor="whiteAlpha.100"
104+
position="relative"
105+
overflow="hidden"
106+
sx={{
107+
'&::after': {
108+
content: '""',
109+
position: 'absolute',
110+
inset: 0,
111+
background:
112+
'linear-gradient(90deg, transparent 0%, rgba(56, 178, 172, 0.06) 45%, rgba(56, 178, 172, 0.14) 50%, rgba(56, 178, 172, 0.06) 55%, transparent 100%)',
113+
backgroundSize: '200% 100%',
114+
animation: 'kk-shimmer 2.2s ease-in-out infinite',
115+
animationDelay: `${delay}s`,
116+
pointerEvents: 'none',
117+
},
118+
}}>
119+
<Flex align="center" width="100%">
120+
<SkeletonCircle size="10" startColor="whiteAlpha.100" endColor="whiteAlpha.300" />
121+
<Box ml={3} flex="1" minWidth="0">
122+
<HStack spacing={2}>
123+
<Skeleton height="14px" width="60px" borderRadius="sm" startColor="whiteAlpha.100" endColor="whiteAlpha.300" />
124+
<Skeleton height="14px" width="44px" borderRadius="full" startColor="whiteAlpha.100" endColor="whiteAlpha.200" />
125+
</HStack>
126+
<Skeleton mt={2} height="12px" width="96px" borderRadius="sm" startColor="whiteAlpha.100" endColor="whiteAlpha.300" />
127+
</Box>
128+
<Flex direction="column" align="flex-end" minW="80px">
129+
<Skeleton height="14px" width="56px" borderRadius="sm" startColor="whiteAlpha.100" endColor="whiteAlpha.300" />
130+
</Flex>
131+
</Flex>
132+
</Card>
133+
);
134+
83135
return (
84-
<Flex direction="column" justifyContent="center" alignItems="center" width="100%" flex="1" minH="60vh" gap={3}>
85-
<Spinner size="xl" color="teal.400" thickness="3px" speed="0.8s" />
86-
<Text color="whiteAlpha.600" fontSize="sm">
87-
Loading balances…
88-
</Text>
136+
<Flex direction="column" width="100%" flex="1" position="relative" overflow="hidden">
137+
<style>{`
138+
@keyframes kk-shimmer {
139+
0% { background-position: -200% 0; }
140+
100% { background-position: 200% 0; }
141+
}
142+
@keyframes kk-breathe {
143+
0%, 100% { opacity: 0.035; transform: scale(1); }
144+
50% { opacity: 0.07; transform: scale(1.02); }
145+
}
146+
@keyframes kk-spin-cw {
147+
0% { transform: rotate(0deg); }
148+
100% { transform: rotate(360deg); }
149+
}
150+
@keyframes kk-spin-ccw {
151+
0% { transform: rotate(0deg); }
152+
100% { transform: rotate(-360deg); }
153+
}
154+
@keyframes kk-glow {
155+
0%, 100% {
156+
box-shadow: 0 0 18px 2px rgba(56, 178, 172, 0.25), 0 0 36px 6px rgba(56, 178, 172, 0.10);
157+
transform: scale(1);
158+
}
159+
50% {
160+
box-shadow: 0 0 28px 4px rgba(56, 178, 172, 0.45), 0 0 52px 10px rgba(56, 178, 172, 0.18);
161+
transform: scale(1.08);
162+
}
163+
}
164+
@keyframes kk-dot-pulse {
165+
0%, 100% { opacity: 0.4; transform: scale(0.85); }
166+
50% { opacity: 1; transform: scale(1.15); }
167+
}
168+
@keyframes kk-text-fade {
169+
0%, 100% { opacity: 0.45; letter-spacing: 0.25em; }
170+
50% { opacity: 0.85; letter-spacing: 0.35em; }
171+
}
172+
`}</style>
173+
174+
{/* Subtle KK watermark behind everything */}
175+
<Box
176+
position="absolute"
177+
inset={0}
178+
pointerEvents="none"
179+
display="flex"
180+
alignItems="center"
181+
justifyContent="center"
182+
sx={{ animation: 'kk-breathe 4s ease-in-out infinite' }}>
183+
<Box
184+
as="img"
185+
src={chrome.runtime.getURL('kk-logo.png')}
186+
alt=""
187+
width="200px"
188+
borderRadius="2xl"
189+
filter="grayscale(1) brightness(1.4) contrast(0.9)"
190+
/>
191+
</Box>
192+
193+
{/* Hero spinner above the skeletons */}
194+
<Flex
195+
direction="column"
196+
align="center"
197+
gap={3}
198+
pt={2}
199+
pb={5}
200+
position="relative"
201+
zIndex={2}>
202+
<Box position="relative" width="88px" height="88px">
203+
{/* Soft pulsing glow */}
204+
<Box
205+
position="absolute"
206+
top="50%"
207+
left="50%"
208+
width="56px"
209+
height="56px"
210+
borderRadius="full"
211+
transform="translate(-50%, -50%)"
212+
bg="rgba(56, 178, 172, 0.15)"
213+
sx={{ animation: 'kk-glow 2.4s ease-in-out infinite' }}
214+
/>
215+
{/* Outer ring — clockwise, teal */}
216+
<Box
217+
position="absolute"
218+
inset={0}
219+
borderRadius="full"
220+
border="3px solid transparent"
221+
borderTopColor="teal.300"
222+
borderRightColor="teal.400"
223+
sx={{ animation: 'kk-spin-cw 1.4s cubic-bezier(0.5, 0, 0.5, 1) infinite' }}
224+
/>
225+
{/* Middle ring — counter-clockwise, paler */}
226+
<Box
227+
position="absolute"
228+
top="10px"
229+
left="10px"
230+
right="10px"
231+
bottom="10px"
232+
borderRadius="full"
233+
border="2px solid transparent"
234+
borderBottomColor="teal.200"
235+
borderLeftColor="whiteAlpha.400"
236+
sx={{ animation: 'kk-spin-ccw 2.1s cubic-bezier(0.4, 0, 0.6, 1) infinite' }}
237+
/>
238+
{/* Inner ring — clockwise, thin */}
239+
<Box
240+
position="absolute"
241+
top="22px"
242+
left="22px"
243+
right="22px"
244+
bottom="22px"
245+
borderRadius="full"
246+
border="1.5px solid transparent"
247+
borderTopColor="whiteAlpha.600"
248+
sx={{ animation: 'kk-spin-cw 0.9s linear infinite' }}
249+
/>
250+
{/* Breathing center dot */}
251+
<Box
252+
position="absolute"
253+
top="50%"
254+
left="50%"
255+
width="10px"
256+
height="10px"
257+
borderRadius="full"
258+
transform="translate(-50%, -50%)"
259+
bg="teal.300"
260+
boxShadow="0 0 10px 2px rgba(56, 178, 172, 0.6)"
261+
sx={{ animation: 'kk-dot-pulse 1.2s ease-in-out infinite' }}
262+
/>
263+
</Box>
264+
<Text
265+
color="whiteAlpha.700"
266+
fontSize="xs"
267+
textTransform="uppercase"
268+
fontWeight="medium"
269+
sx={{ animation: 'kk-text-fade 2.2s ease-in-out infinite' }}>
270+
Fetching balances
271+
</Text>
272+
</Flex>
273+
274+
{/* Skeleton rows matching the real asset card layout */}
275+
<Stack width="100%" position="relative" zIndex={1}>
276+
<SkeletonRow delay={0} />
277+
<SkeletonRow delay={0.15} />
278+
<SkeletonRow delay={0.3} />
279+
<SkeletonRow delay={0.45} />
280+
<SkeletonRow delay={0.6} />
281+
</Stack>
89282
</Flex>
90283
);
91284
}

0 commit comments

Comments
 (0)