Skip to content

Commit bfb0c37

Browse files
CEKA LOADER
1 parent 49c9f7e commit bfb0c37

1 file changed

Lines changed: 108 additions & 186 deletions

File tree

src/components/ui/ceka-loader.tsx

Lines changed: 108 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const CEKALoader: React.FC<CEKALoaderProps> = ({
5151
if (!showProgressMessages) return;
5252
const interval = setInterval(() => {
5353
setMessageIndex((prev) => (prev + 1) % PROGRESS_MESSAGES.length);
54-
}, 2500);
54+
}, 3000);
5555
return () => clearInterval(interval);
5656
}, [showProgressMessages]);
5757

@@ -60,143 +60,138 @@ export const CEKALoader: React.FC<CEKALoaderProps> = ({
6060
const sizes = {
6161
xs: { wrapper: 'w-8 h-8', icon: 16, text: 'text-[10px]' },
6262
sm: { wrapper: 'w-16 h-16', icon: 24, text: 'text-xs' },
63-
md: { wrapper: 'w-24 h-24', icon: 36, text: 'text-sm' },
64-
lg: { wrapper: 'w-32 h-32', icon: 48, text: 'text-base' },
65-
xl: { wrapper: 'w-48 h-48', icon: 64, text: 'text-lg' }
63+
md: { wrapper: 'w-24 h-24', icon: 34, text: 'text-sm' },
64+
lg: { wrapper: 'w-32 h-32', icon: 44, text: 'text-base' },
65+
xl: { wrapper: 'w-48 h-48', icon: 60, text: 'text-lg' }
6666
};
6767

6868
const s = sizes[size];
6969

70-
// Progressive message component
7170
const renderMessage = () => (
72-
<>
71+
<AnimatePresence mode="wait">
7372
{displayMessage && (
74-
<AnimatePresence mode="wait">
75-
<motion.p
76-
key={displayMessage}
77-
initial={{ opacity: 0, y: 5 }}
78-
animate={{ opacity: 1, y: 0 }}
79-
exit={{ opacity: 0, y: -5 }}
80-
className={`${s.text} font-bold text-slate-600 dark:text-slate-300 tracking-tight text-center max-w-[240px] mt-6 px-4`}
81-
style={{ willChange: 'opacity, transform' }}
82-
>
83-
{displayMessage}
84-
</motion.p>
85-
</AnimatePresence>
73+
<motion.p
74+
key={displayMessage}
75+
initial={{ opacity: 0, y: 8, filter: 'blur(4px)' }}
76+
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
77+
exit={{ opacity: 0, y: -8, filter: 'blur(4px)' }}
78+
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
79+
className={`${s.text} font-semibold uppercase tracking-[0.15em] text-slate-500/80 dark:text-slate-400/80 text-center max-w-[280px] mt-8 px-6 font-mono`}
80+
>
81+
{displayMessage}
82+
</motion.p>
8683
)}
87-
<span className="sr-only">Loading...</span>
88-
</>
84+
</AnimatePresence>
8985
);
9086

9187
const renderContent = () => {
92-
if (variant === 'scanning') {
88+
// SIGNATURE: The Unified CEKA Design
89+
// Blends iOS elegance with the Kenyan Identity
90+
if (variant === 'default' || variant === 'ios' || variant === 'scanning') {
91+
const segments = 12;
92+
const isScanning = variant === 'scanning';
93+
9394
return (
9495
<div className={`${s.wrapper} relative flex items-center justify-center`}>
95-
{/* Concentric rings like in the image */}
96-
{[0.6, 0.8, 1.0].map((scale, i) => (
97-
<div
98-
key={i}
99-
className="absolute border border-black/5 dark:border-white/10 rounded-full"
100-
style={{
101-
width: `${scale * 100}%`,
102-
height: `${scale * 100}%`
103-
}}
104-
/>
105-
))}
96+
{/* 1. Signature Aura Flush (Kenyan Identity) */}
97+
<motion.div
98+
className="absolute inset-0 rounded-full bg-gradient-to-tr from-green-500/10 via-black/5 to-red-500/10 blur-2xl"
99+
animate={{
100+
scale: [1, 1.2, 1],
101+
opacity: [0.3, 0.5, 0.3],
102+
rotate: [0, 90, 180, 270, 360]
103+
}}
104+
transition={{ duration: 8, repeat: Infinity, ease: "linear" }}
105+
/>
106106

107-
{/* Rotating segments */}
108-
{[0, 1, 2].map((i) => (
109-
<motion.div
110-
key={`seg-${i}`}
111-
className="absolute rounded-full"
112-
style={{
113-
width: `${70 + i * 15}%`,
114-
height: `${70 + i * 15}%`,
115-
border: '3px solid transparent',
116-
borderTopColor: [COLORS.kenyaGreen, COLORS.black, COLORS.kenyaRed][i],
117-
borderLeftColor: i === 1 ? COLORS.white : 'transparent',
118-
opacity: i === 1 && theme === 'light' ? 0.8 : 1,
119-
filter: 'blur(0.5px)'
120-
}}
121-
animate={{ rotate: 360 }}
122-
transition={{
123-
duration: 1.5 + i * 0.5,
124-
repeat: Infinity,
125-
ease: "linear"
126-
}}
127-
/>
128-
))}
107+
{/* 2. Premium iOS Segments with Fixed Math & Signature Colors */}
108+
<div className="absolute inset-0 flex items-center justify-center">
109+
{[...Array(segments)].map((_, i) => {
110+
// Cycle through Kenyan colors for the signature trail
111+
const segmentColor = i % 3 === 0 ? COLORS.kenyaGreen : (i % 3 === 1 ? (theme === 'dark' ? '#333' : COLORS.black) : COLORS.kenyaRed);
112+
113+
return (
114+
<motion.div
115+
key={i}
116+
className="absolute rounded-full"
117+
style={{
118+
width: '6%',
119+
height: '22%',
120+
backgroundColor: segmentColor,
121+
transform: `rotate(${i * (360 / segments)}deg) translateY(-140%)`,
122+
transformOrigin: '50% 50%',
123+
opacity: 0.1,
124+
boxShadow: isScanning ? `0 0 10px ${segmentColor}40` : 'none'
125+
}}
126+
animate={{
127+
opacity: [0.1, 1, 0.1],
128+
scale: isScanning ? [1, 1.2, 1] : 1
129+
}}
130+
transition={{
131+
duration: 1.2,
132+
repeat: Infinity,
133+
delay: i * (1.2 / segments),
134+
ease: "linear"
135+
}}
136+
/>
137+
);
138+
})}
139+
</div>
129140

141+
{/* 3. The "Pulse" Core (CEKA Identity) */}
130142
<motion.div
131-
className="relative z-10 p-2 rounded-full bg-white/10 backdrop-blur-sm"
132-
animate={{ opacity: [0.7, 1, 0.7], scale: [0.95, 1.05, 0.95] }}
133-
transition={{ duration: 2, repeat: Infinity }}
143+
className="relative z-10 flex items-center justify-center bg-background/40 backdrop-blur-md rounded-full p-3 shadow-xl border border-white/20 dark:border-white/5"
144+
animate={{
145+
scale: [1, 1.05, 1],
146+
boxShadow: [
147+
'0 10px 40px -10px rgba(0,0,0,0.1)',
148+
'0 10px 60px -5px rgba(0,0,0,0.2)',
149+
'0 10px 40px -10px rgba(0,0,0,0.1)'
150+
]
151+
}}
152+
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
134153
>
135154
<img
136155
src={logoSrc}
137156
alt="CEKA"
138157
className={cn(
139158
"object-contain",
140-
size === 'xs' ? 'h-3' : size === 'sm' ? 'h-4' : size === 'md' ? 'h-6' : size === 'lg' ? 'h-10' : 'h-14'
159+
size === 'xs' ? 'h-3' : size === 'sm' ? 'h-5' : size === 'md' ? 'h-8' : size === 'lg' ? 'h-12' : 'h-16'
141160
)}
142161
/>
143162
</motion.div>
144-
</div>
145-
);
146-
}
147163

148-
if (variant === 'ios') {
149-
const segments = 12;
150-
return (
151-
<div className={`${s.wrapper} relative flex items-center justify-center`}>
152-
{[...Array(segments)].map((_, i) => (
153-
<motion.div
154-
key={i}
155-
className="absolute bg-current rounded-full"
156-
style={{
157-
width: '10%',
158-
height: '28%',
159-
top: '36%',
160-
left: '45%',
161-
transformOrigin: 'center -100%',
162-
borderRadius: '1rem',
163-
color: i % 4 === 0 ? COLORS.green : (i % 4 === 2 ? COLORS.red : COLORS.black),
164-
opacity: 0.2
165-
}}
166-
animate={{
167-
opacity: [0.2, 1, 0.2],
168-
transform: `rotate(${i * (360 / segments)}deg) translateY(-80%)`,
169-
}}
170-
transition={{
171-
duration: 0.8,
172-
repeat: Infinity,
173-
delay: i * (0.8 / segments),
174-
ease: "linear"
175-
}}
164+
{/* 4. Scanning Ring (Optional Overlay) */}
165+
{isScanning && (
166+
<motion.div
167+
className="absolute inset-0 rounded-full border-2 border-primary/20"
168+
animate={{ scale: [1, 1.5], opacity: [0.5, 0] }}
169+
transition={{ duration: 1.5, repeat: Infinity, ease: "easeOut" }}
176170
/>
177-
))}
171+
)}
178172
</div>
179173
);
180174
}
181175

182176
if (variant === 'bars') {
183177
return (
184-
<div className="flex items-end gap-1.5 h-10">
178+
<div className="flex items-end gap-2 h-12">
185179
{[0, 1, 2, 3, 4].map((i) => (
186180
<motion.div
187181
key={i}
188-
className="w-2.5 rounded-full"
182+
className="w-1.5 rounded-full"
189183
style={{
190-
background: `linear-gradient(to bottom, ${COLORS.red}, ${COLORS.black}, ${COLORS.green})`,
191-
willChange: 'transform',
184+
background: `linear-gradient(to top, ${COLORS.kenyaGreen}, ${COLORS.white}, ${COLORS.kenyaRed})`,
185+
height: '100%',
192186
}}
193187
animate={{
194-
scaleY: [0.3, 1, 0.3],
188+
scaleY: [0.2, 1, 0.2],
189+
opacity: [0.5, 1, 0.5]
195190
}}
196191
transition={{
197192
duration: 1,
198193
repeat: Infinity,
199-
delay: i * 0.15,
194+
delay: i * 0.1,
200195
ease: 'easeInOut',
201196
}}
202197
/>
@@ -205,115 +200,42 @@ export const CEKALoader: React.FC<CEKALoaderProps> = ({
205200
);
206201
}
207202

208-
if (variant === 'pulse') {
203+
if (variant === 'pulse' || variant === 'orbit') {
209204
return (
210205
<div className={`${s.wrapper} relative flex items-center justify-center`}>
211206
<motion.div
212-
className="absolute inset-0 rounded-full bg-kenya-green/10"
213-
animate={{ scale: [1, 1.8, 1], opacity: [0.3, 0, 0.3] }}
214-
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
215-
style={{ willChange: 'transform, opacity' }}
207+
className="absolute inset-0 rounded-full border-2 border-dashed border-primary/20"
208+
animate={{ rotate: 360 }}
209+
transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
216210
/>
217211
<motion.div
218-
className="absolute inset-0 rounded-full border border-kenya-red/20"
219-
animate={{ scale: [1, 1.4, 1], opacity: [0.5, 0, 0.5] }}
220-
transition={{ duration: 2, repeat: Infinity, delay: 0.5, ease: "easeInOut" }}
221-
style={{ willChange: 'transform, opacity' }}
212+
className="absolute inset-4 rounded-full border border-primary/10"
213+
animate={{ rotate: -360 }}
214+
transition={{ duration: 15, repeat: Infinity, ease: "linear" }}
222215
/>
223-
<div className="relative z-10 flex items-center justify-center">
224-
<img
225-
src={logoSrc}
226-
alt="CEKA"
227-
className={cn(
228-
"object-contain transition-all duration-500",
229-
size === 'xs' ? 'h-4' : size === 'sm' ? 'h-6' : size === 'md' ? 'h-10' : size === 'lg' ? 'h-14' : 'h-20'
230-
)}
231-
/>
232-
</div>
233-
</div>
234-
);
235-
}
236-
237-
if (variant === 'orbit') {
238-
return (
239-
<div className={`${s.wrapper} relative flex items-center justify-center`}>
216+
240217
<motion.div
241-
className="absolute inset-0 flex items-center justify-center"
242-
animate={{ rotateZ: 360 }}
243-
transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
244-
style={{ perspective: 1000, willChange: 'transform' }}
218+
className="relative z-10"
219+
animate={{
220+
scale: [0.95, 1.05, 0.95],
221+
filter: ['drop-shadow(0 0 0px transparent)', 'drop-shadow(0 0 20px rgba(59, 130, 246, 0.3))', 'drop-shadow(0 0 0px transparent)']
222+
}}
223+
transition={{ duration: 3, repeat: Infinity }}
245224
>
246-
{[0, 1, 2].map((i) => (
247-
<div
248-
key={i}
249-
className="absolute"
250-
style={{
251-
transform: `rotateZ(${i * 120}deg) translateY(-35px)`
252-
}}
253-
>
254-
<motion.div
255-
className="w-4 h-4 rounded-full blur-[1px]"
256-
style={{
257-
backgroundColor: [COLORS.green, COLORS.black, COLORS.red][i],
258-
boxShadow: `0 0 10px ${[COLORS.green, COLORS.black, COLORS.red][i]}40`
259-
}}
260-
animate={{ scale: [0.8, 1.2, 0.8] }}
261-
transition={{ duration: 1.5, repeat: Infinity, delay: i * 0.5 }}
262-
/>
263-
</div>
264-
))}
265-
</motion.div>
266-
<div className="relative z-10 flex items-center justify-center scale-75">
267225
<img
268226
src={logoSrc}
269227
alt="CEKA"
270228
className={cn(
271229
"object-contain",
272-
size === 'xs' ? 'h-3' : size === 'sm' ? 'h-5' : size === 'md' ? 'h-8' : size === 'lg' ? 'h-12' : 'h-16'
230+
size === 'xs' ? 'h-4' : size === 'sm' ? 'h-8' : size === 'md' ? 'h-12' : size === 'lg' ? 'h-16' : 'h-24'
273231
)}
274232
/>
275-
</div>
233+
</motion.div>
276234
</div>
277235
);
278236
}
279237

280-
// Default Triple Ring
281-
return (
282-
<div className={`${s.wrapper} relative flex items-center justify-center`}>
283-
<motion.div
284-
className="absolute inset-0 rounded-full border-[3px] border-kenya-green/10"
285-
style={{ borderTopColor: COLORS.green, borderLeftColor: COLORS.green, willChange: 'transform' }}
286-
animate={{ rotateZ: 360 }}
287-
transition={{ duration: 1.2, repeat: Infinity, ease: "easeInOut" }}
288-
/>
289-
<motion.div
290-
className="absolute inset-3 rounded-full border-[3px] border-black/10 dark:border-white/20"
291-
style={{ borderTopColor: theme === 'dark' ? '#ffffff' : COLORS.black, willChange: 'transform' }}
292-
animate={{ rotateZ: -360 }}
293-
transition={{ duration: 1.8, repeat: Infinity, ease: "easeInOut" }}
294-
/>
295-
<motion.div
296-
className="absolute inset-6 rounded-full border-[3px] border-kenya-red/10"
297-
style={{ borderTopColor: COLORS.red, borderRightColor: COLORS.red, willChange: 'transform' }}
298-
animate={{ rotateZ: 360 }}
299-
transition={{ duration: 2.4, repeat: Infinity, ease: "easeInOut" }}
300-
/>
301-
<motion.div
302-
className="absolute inset-0 flex items-center justify-center"
303-
animate={{ opacity: [0.6, 1, 0.6], scale: [0.98, 1.02, 0.98] }}
304-
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
305-
>
306-
<img
307-
src={logoSrc}
308-
alt="CEKA"
309-
className={cn(
310-
"object-contain",
311-
size === 'xs' ? 'h-3' : size === 'sm' ? 'h-4' : size === 'md' ? 'h-6' : size === 'lg' ? 'h-10' : 'h-14'
312-
)}
313-
/>
314-
</motion.div>
315-
</div>
316-
);
238+
return null; // Should not happen with current logic
317239
};
318240

319241
return (

0 commit comments

Comments
 (0)