@@ -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