11import { useState , useEffect } from 'react' ;
2+ import { useNavigate } from 'react-router' ;
3+ import { HeartCrack , Heart } from 'lucide-react' ;
24import { mockAliens } from '../data/mockAliens' ;
35import type { AlienProfile } from '../data/mockAliens' ;
46import { useAppContext } from '../context/AppContext' ;
@@ -7,20 +9,23 @@ import { getCompatibility } from '../utils/compatibility';
79import { useRocketNav } from '../context/TransitionContext' ;
810
911export default function OrbitSystem ( ) {
12+ const navigate = useNavigate ( ) ;
1013 const { preferences, addMatch, matches } = useAppContext ( ) ;
1114 const triggerRocketNav = useRocketNav ( ) ;
1215 const [ selectedAlien , setSelectedAlien ] = useState < AlienProfile | null > ( null ) ;
1316 const [ dismissedIds , setDismissedIds ] = useState < Set < string > > ( new Set ( ) ) ;
14-
17+ const [ showBreakingAnim , setShowBreakingAnim ] = useState ( false ) ;
18+ const [ animStage , setAnimStage ] = useState < 'none' | 'heart' | 'break' | 'final' > ( 'none' ) ;
19+
1520 // Keep exactly 5 slots for the 5 orbit tracks
1621 const [ activeIds , setActiveIds ] = useState < ( string | null ) [ ] > ( [ null , null , null , null , null ] ) ;
1722
1823 useEffect ( ( ) => {
1924 if ( ! preferences ) return ;
20-
25+
2126 // Available aliens are those within distance, not matched, not dismissed, and not currently active
22- const available = mockAliens . filter ( a =>
23- a . distanceAU <= preferences . maxDistanceAU &&
27+ const available = mockAliens . filter ( a =>
28+ a . distanceLY <= preferences . maxDistanceLY &&
2429 ! matches . find ( m => m . id === a . id ) &&
2530 ! dismissedIds . has ( a . id ) &&
2631 ! activeIds . includes ( a . id )
@@ -32,13 +37,13 @@ export default function OrbitSystem() {
3237
3338 let changed = false ;
3439 const newActiveIds = [ ...activeIds ] ;
35-
40+
3641 // Check each of the 5 tracks
3742 for ( let i = 0 ; i < 5 ; i ++ ) {
3843 const currentId = newActiveIds [ i ] ;
3944 // Check if the current alien in this track is still valid
40- const isStillValid = currentId &&
41- mockAliens . find ( a => a . id === currentId && a . distanceAU <= preferences . maxDistanceAU ) &&
45+ const isStillValid = currentId &&
46+ mockAliens . find ( a => a . id === currentId && a . distanceLY <= preferences . maxDistanceLY ) &&
4247 ! matches . find ( m => m . id === currentId ) &&
4348 ! dismissedIds . has ( currentId ) ;
4449
@@ -53,7 +58,16 @@ export default function OrbitSystem() {
5358 if ( changed ) {
5459 setActiveIds ( newActiveIds ) ;
5560 }
56- } , [ preferences , matches , dismissedIds , activeIds ] ) ;
61+
62+ // Trigger breaking animation if empty and not already shown
63+ if ( newActiveIds . every ( id => id === null ) && animStage === 'none' ) {
64+ setAnimStage ( 'heart' ) ;
65+ setTimeout ( ( ) => setAnimStage ( 'break' ) , 1500 ) ;
66+ setTimeout ( ( ) => setAnimStage ( 'final' ) , 2500 ) ;
67+ } else if ( newActiveIds . some ( id => id !== null ) ) {
68+ setAnimStage ( 'none' ) ;
69+ }
70+ } , [ preferences , matches , dismissedIds , activeIds , animStage ] ) ;
5771
5872 if ( ! preferences ) return null ;
5973
@@ -138,29 +152,48 @@ export default function OrbitSystem() {
138152 transform: scale(1.2);
139153 box-shadow: 0 0 25px var(--color-secondary);
140154 }
155+ @keyframes fadeIn {
156+ from { opacity: 0; transform: translateY(10px); }
157+ to { opacity: 1; transform: translateY(0); }
158+ }
159+ @keyframes heartPulse {
160+ 0% { transform: scale(1); opacity: 0; }
161+ 50% { transform: scale(1.1); opacity: 1; }
162+ 100% { transform: scale(1); opacity: 1; }
163+ }
164+ @keyframes heartBreakLeft {
165+ 0% { transform: translateX(0) rotate(0); }
166+ 100% { transform: translateX(-50px) translateY(20px) rotate(-20deg); opacity: 0; }
167+ }
168+ @keyframes heartBreakRight {
169+ 0% { transform: translateX(0) rotate(0); }
170+ 100% { transform: translateX(50px) translateY(20px) rotate(20deg); opacity: 0; }
171+ }
141172 ` } </ style >
142173
143174 < div style = { { position : 'relative' , width : '100%' , flex : 1 , display : 'flex' , alignItems : 'center' , justifyContent : 'center' , overflow : 'hidden' } } >
144-
175+
145176 { /* User Center */ }
146- < div style = { {
147- width : '12vmin' ,
148- height : '12vmin' ,
149- minWidth : '60px' ,
150- minHeight : '60px' ,
151- borderRadius : '50%' ,
152- background : 'linear-gradient(135deg, var(--color-primary), var(--color-secondary))' ,
153- boxShadow : '0 0 30px var(--color-secondary)' ,
154- display : 'flex' ,
155- alignItems : 'center' ,
156- justifyContent : 'center' ,
157- fontWeight : 'bold' ,
158- zIndex : 10 ,
159- fontSize : 'clamp(1rem, 2.5vmin, 1.5rem)' ,
160- color : 'white'
161- } } >
162- { preferences . name . substring ( 0 , 2 ) . toUpperCase ( ) || 'YOU' }
163- </ div >
177+ { ! activeIds . every ( id => id === null ) && (
178+ < div style = { {
179+ width : '12vmin' ,
180+ height : '12vmin' ,
181+ minWidth : '60px' ,
182+ minHeight : '60px' ,
183+ borderRadius : '50%' ,
184+ background : 'linear-gradient(135deg, var(--color-primary), var(--color-secondary))' ,
185+ boxShadow : '0 0 30px var(--color-secondary)' ,
186+ display : 'flex' ,
187+ alignItems : 'center' ,
188+ justifyContent : 'center' ,
189+ fontWeight : 'bold' ,
190+ zIndex : 10 ,
191+ fontSize : 'clamp(1rem, 2.5vmin, 1.5rem)' ,
192+ color : 'white'
193+ } } >
194+ { preferences . name . substring ( 0 , 2 ) . toUpperCase ( ) || 'YOU' }
195+ </ div >
196+ ) }
164197
165198 { /* Orbit Rings and Aliens */ }
166199 { activeIds
@@ -171,19 +204,19 @@ export default function OrbitSystem() {
171204 . map ( ( alien , i ) => {
172205
173206 // Assign each slot to a distinct track (0 to 4)
174- const rx = 120 + ( i * 65 ) ;
175- const ry = 80 + ( i * 40 ) ;
176- const duration = 15 + ( i * 8 ) ;
177-
207+ const rx = 120 + ( i * 65 ) ;
208+ const ry = 80 + ( i * 40 ) ;
209+ const duration = 15 + ( i * 8 ) ;
210+
178211 const delay = - 1 * ( i * ( duration / 5 ) ) ;
179212
180213 return (
181214 < div key = { `track-${ i } -${ alien . id } ` } >
182215 { /* Ring */ }
183216 < div className = "orbit-ring" style = { { width : `${ rx * 2 } px` , height : `${ ry * 2 } px` } } />
184-
217+
185218 { /* Profile */ }
186- < div
219+ < div
187220 className = "orbit-item"
188221 onClick = { ( ) => setSelectedAlien ( alien ) }
189222 style = { {
@@ -194,20 +227,90 @@ export default function OrbitSystem() {
194227 '--duration' : `${ duration } s` ,
195228 animationDelay : `${ delay } s`
196229 } }
197- title = { `${ alien . name } (${ alien . distanceAU } AU )` }
230+ title = { `${ alien . name } (${ alien . distanceLY } Light years )` }
198231 >
199232 < div className = "orbit-avatar" style = { { backgroundImage : `url(${ alien . profilePic } )` } } />
200233 </ div >
201234 </ div >
202235 ) ;
203236 } ) }
237+
238+ { /* Empty state message / Breaking Animation */ }
239+ { animStage !== 'none' && (
240+ < div style = { {
241+ position : 'absolute' ,
242+ zIndex : 100 ,
243+ display : 'flex' ,
244+ flexDirection : 'column' ,
245+ alignItems : 'center' ,
246+ justifyContent : 'center' ,
247+ textAlign : 'center' ,
248+ width : '100%' ,
249+ height : '100%' ,
250+ pointerEvents : animStage === 'final' ? 'auto' : 'none'
251+ } } >
252+ { ( animStage === 'heart' || animStage === 'break' ) && (
253+ < div style = { { position : 'relative' , width : '300px' , height : '300px' , display : 'flex' , alignItems : 'center' , justifyContent : 'center' } } >
254+ < div style = { {
255+ position : 'absolute' ,
256+ animation : animStage === 'break' ? 'heartBreakLeft 1.2s ease-in forwards' : 'heartPulse 1.5s ease-out infinite' ,
257+ } } >
258+ < Heart
259+ size = { 250 }
260+ fill = "var(--color-secondary)"
261+ color = "var(--color-secondary)"
262+ style = { { clipPath : 'inset(0 50% 0 0)' } }
263+ />
264+ </ div >
265+ < div style = { {
266+ position : 'absolute' ,
267+ animation : animStage === 'break' ? 'heartBreakRight 1.2s ease-in forwards' : 'heartPulse 1.5s ease-out infinite' ,
268+ } } >
269+ < Heart
270+ size = { 250 }
271+ fill = "var(--color-secondary)"
272+ color = "var(--color-secondary)"
273+ style = { { clipPath : 'inset(0 0 0 50%)' } }
274+ />
275+ </ div >
276+ </ div >
277+ ) }
278+
279+ { animStage === 'final' && (
280+ < div className = "glass-panel" style = { {
281+ padding : '40px' ,
282+ maxWidth : '450px' ,
283+ animation : 'fadeIn 0.8s ease-out forwards' ,
284+ display : 'flex' ,
285+ flexDirection : 'column' ,
286+ alignItems : 'center' ,
287+ gap : '24px' ,
288+ pointerEvents : 'auto'
289+ } } >
290+ < p style = { { margin : 0 , fontSize : '2.8rem' , lineHeight : 1.1 , color : 'var(--color-secondary)' , fontWeight : 'bold' } } >
291+ The stars are not aligned
292+ </ p >
293+ < p style = { { margin : 0 , fontSize : '1.2rem' , color : 'rgba(234, 222, 218, 0.8)' , lineHeight : 1.4 , marginTop : '8px' } } >
294+ Looks like you've run out of potential matches, check back later
295+ </ p >
296+ < button
297+ onClick = { ( ) => { window . location . href = '/preferences' ; } }
298+ className = "btn-outline"
299+ style = { { marginTop : '8px' , padding : '12px 40px' , fontSize : '1rem' , fontWeight : 'bold' , cursor : 'pointer' } }
300+ >
301+ Back
302+ </ button >
303+ </ div >
304+ ) }
305+ </ div >
306+ ) }
204307 </ div >
205308
206309 { selectedAlien && (
207- < ProfileModal
208- alien = { selectedAlien }
209- onClose = { ( ) => setSelectedAlien ( null ) }
210- onMatch = { handleMatch }
310+ < ProfileModal
311+ alien = { selectedAlien }
312+ onClose = { ( ) => setSelectedAlien ( null ) }
313+ onMatch = { handleMatch }
211314 onDismiss = { handleDismiss }
212315 />
213316 ) }
0 commit comments