Compare commits
	
		
			8 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 76a1b592ac | |||
|   | 223f1e8165 | ||
| 03d3d272df | |||
|   | aa8dad9809 | ||
|   | 6422189f6c | ||
|   | 752faff486 | ||
|   | 3862c3c3fe | ||
|   | 7928abf9e8 | 
					 10 changed files with 2339 additions and 60 deletions
				
			
		
							
								
								
									
										17
									
								
								src/App.vue
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								src/App.vue
									
										
									
									
									
								
							|  | @ -1,18 +1,12 @@ | |||
| <template> | ||||
|   <Login /> | ||||
|   <LoggedinPage /> | ||||
|   <div id="app"> | ||||
|     <router-view /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import Login from './components/UserLogin.vue'; | ||||
| import LoggedinPage from './components/LoggedinPage.vue'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'App', | ||||
|   components: { | ||||
|     Login, | ||||
|     LoggedinPage | ||||
|   } | ||||
|   name: 'App' | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  | @ -23,6 +17,5 @@ export default { | |||
|   -moz-osx-font-smoothing: grayscale; | ||||
|   text-align: center; | ||||
|   color: #2c3e50; | ||||
|   margin-top: 60px; | ||||
| } | ||||
| </style> | ||||
| </style> | ||||
							
								
								
									
										513
									
								
								src/assets/css/components/homepage.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								src/assets/css/components/homepage.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,513 @@ | |||
| * { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .homepage { | ||||
|   font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | ||||
|   background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%); | ||||
|   color: #ffffff; | ||||
|   overflow-x: hidden; | ||||
|   min-height: 100vh; | ||||
| } | ||||
| 
 | ||||
| .container { | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
|   padding: 0 20px; | ||||
| } | ||||
| 
 | ||||
| /* Navigation */ | ||||
| .navbar { | ||||
|   position: fixed; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   z-index: 100; | ||||
|   backdrop-filter: blur(20px); | ||||
|   background: rgba(12, 12, 12, 0.8); | ||||
|   border-bottom: 1px solid rgba(255, 255, 255, 0.1); | ||||
|   transition: all 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .nav-content { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   padding: 1rem 2rem; | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| 
 | ||||
| .logo { | ||||
|   font-size: 1.8rem; | ||||
|   font-weight: 800; | ||||
|   color: #ffffff; | ||||
|   cursor: pointer; | ||||
|   text-decoration: none; | ||||
| } | ||||
| 
 | ||||
| .logo-accent { | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
| } | ||||
| 
 | ||||
| .nav-links { | ||||
|   display: flex; | ||||
|   list-style: none; | ||||
|   gap: 2rem; | ||||
| } | ||||
| 
 | ||||
| .nav-links a { | ||||
|   color: #ffffff; | ||||
|   text-decoration: none; | ||||
|   font-weight: 500; | ||||
|   transition: all 0.3s ease; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .nav-links a:hover { | ||||
|   color: #00d4ff; | ||||
| } | ||||
| 
 | ||||
| .nav-links a::after { | ||||
|   content: ''; | ||||
|   position: absolute; | ||||
|   bottom: -5px; | ||||
|   left: 0; | ||||
|   width: 0; | ||||
|   height: 2px; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   transition: width 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .nav-links a:hover::after { | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| /* Hero Section */ | ||||
| .hero { | ||||
|   min-height: 100vh; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .hero-bg { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   background: radial-gradient(circle at 30% 70%, rgba(0, 212, 255, 0.1) 0%, transparent 50%), | ||||
|               radial-gradient(circle at 80% 20%, rgba(124, 58, 237, 0.1) 0%, transparent 50%); | ||||
| } | ||||
| 
 | ||||
| .floating-elements { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| .floating-element { | ||||
|   position: absolute; | ||||
|   width: 4px; | ||||
|   height: 4px; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   border-radius: 50%; | ||||
|   animation: float 6s ease-in-out infinite; | ||||
| } | ||||
| 
 | ||||
| @keyframes float { | ||||
|   0%, 100% {  | ||||
|     transform: translateY(0px) rotate(0deg);  | ||||
|     opacity: 0.5;  | ||||
|   } | ||||
|   50% {  | ||||
|     transform: translateY(-20px) rotate(180deg);  | ||||
|     opacity: 1;  | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .hero-container { | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
|   padding: 0 2rem; | ||||
|   display: grid; | ||||
|   grid-template-columns: 1fr 1fr; | ||||
|   gap: 4rem; | ||||
|   align-items: center; | ||||
|   position: relative; | ||||
|   z-index: 2; | ||||
| } | ||||
| 
 | ||||
| .hero-content { | ||||
|   max-width: 600px; | ||||
| } | ||||
| 
 | ||||
| .hero-title { | ||||
|   font-size: 3.5rem; | ||||
|   font-weight: 900; | ||||
|   line-height: 1.1; | ||||
|   margin-bottom: 1.5rem; | ||||
|   background: linear-gradient(135deg, #ffffff 0%, #00d4ff 50%, #7c3aed 100%); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
|   animation: slideInUp 1s ease-out; | ||||
| } | ||||
| 
 | ||||
| .hero-subtitle { | ||||
|   font-size: 1.25rem; | ||||
|   color: #a0a0a0; | ||||
|   margin-bottom: 2rem; | ||||
|   line-height: 1.6; | ||||
|   animation: slideInUp 1s ease-out 0.2s both; | ||||
| } | ||||
| 
 | ||||
| .cta-section { | ||||
|   animation: slideInUp 1s ease-out 0.4s both; | ||||
| } | ||||
| 
 | ||||
| .cta-section h3 { | ||||
|   font-size: 1.5rem; | ||||
|   margin-bottom: 0.5rem; | ||||
|   color: #00d4ff; | ||||
| } | ||||
| 
 | ||||
| .cta-section p { | ||||
|   color: #a0a0a0; | ||||
|   margin-bottom: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .email-form { | ||||
|   display: flex; | ||||
|   gap: 1rem; | ||||
|   max-width: 400px; | ||||
| } | ||||
| 
 | ||||
| .email-input { | ||||
|   flex: 1; | ||||
|   padding: 1rem; | ||||
|   border: 2px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 12px; | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   color: #ffffff; | ||||
|   font-size: 1rem; | ||||
|   backdrop-filter: blur(10px); | ||||
|   transition: all 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .email-input:focus { | ||||
|   outline: none; | ||||
|   border-color: #00d4ff; | ||||
|   box-shadow: 0 0 20px rgba(0, 212, 255, 0.2); | ||||
| } | ||||
| 
 | ||||
| .email-input::placeholder { | ||||
|   color: #888; | ||||
| } | ||||
| 
 | ||||
| .notify-btn { | ||||
|   padding: 1rem 1.5rem; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   color: white; | ||||
|   border: none; | ||||
|   border-radius: 12px; | ||||
|   font-weight: 600; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s ease; | ||||
|   white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| .notify-btn:hover:not(:disabled) { | ||||
|   transform: translateY(-2px); | ||||
|   box-shadow: 0 10px 30px rgba(0, 212, 255, 0.4); | ||||
| } | ||||
| 
 | ||||
| .notify-btn:disabled { | ||||
|   opacity: 0.5; | ||||
|   cursor: not-allowed; | ||||
| } | ||||
| 
 | ||||
| /* Phone Mockup */ | ||||
| .hero-visual { | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
| } | ||||
| 
 | ||||
| .phone-mockup { | ||||
|   width: 280px; | ||||
|   height: 560px; | ||||
|   background: linear-gradient(145deg, #2a2a3e, #1a1a2e); | ||||
|   border-radius: 35px; | ||||
|   padding: 15px; | ||||
|   position: relative; | ||||
|   box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | ||||
| } | ||||
| 
 | ||||
| .phone-mockup::before { | ||||
|   content: ''; | ||||
|   position: absolute; | ||||
|   top: 20px; | ||||
|   left: 50%; | ||||
|   transform: translateX(-50%); | ||||
|   width: 120px; | ||||
|   height: 4px; | ||||
|   background: #444; | ||||
|   border-radius: 2px; | ||||
| } | ||||
| 
 | ||||
| .screen { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   background: linear-gradient(135deg, #0c0c0c, #1a1a2e); | ||||
|   border-radius: 25px; | ||||
|   padding: 30px 20px; | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| .app-interface { | ||||
|   height: 100%; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| .app-logo { | ||||
|   text-align: center; | ||||
|   font-size: 1.5rem; | ||||
|   font-weight: 800; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
|   margin-bottom: 2rem; | ||||
| } | ||||
| 
 | ||||
| .stats-grid { | ||||
|   display: grid; | ||||
|   grid-template-columns: 1fr 1fr; | ||||
|   gap: 1rem; | ||||
|   flex: 1; | ||||
| } | ||||
| 
 | ||||
| .stat-card { | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   border: 1px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 15px; | ||||
|   padding: 1.5rem 1rem; | ||||
|   text-align: center; | ||||
|   transition: all 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .stat-card.active { | ||||
|   background: rgba(0, 212, 255, 0.1); | ||||
|   border-color: rgba(0, 212, 255, 0.3); | ||||
|   transform: scale(1.05); | ||||
| } | ||||
| 
 | ||||
| .stat-number { | ||||
|   font-size: 1.8rem; | ||||
|   font-weight: 800; | ||||
|   color: #00d4ff; | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .stat-label { | ||||
|   font-size: 0.75rem; | ||||
|   color: #888; | ||||
|   font-weight: 600; | ||||
|   letter-spacing: 0.5px; | ||||
| } | ||||
| 
 | ||||
| /* Features Section */ | ||||
| .features { | ||||
|   padding: 8rem 0; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .section-header { | ||||
|   text-align: center; | ||||
|   margin-bottom: 4rem; | ||||
|   max-width: 800px; | ||||
|   margin-left: auto; | ||||
|   margin-right: auto; | ||||
| } | ||||
| 
 | ||||
| .section-title { | ||||
|   font-size: 3rem; | ||||
|   font-weight: 800; | ||||
|   margin-bottom: 1rem; | ||||
|   background: linear-gradient(135deg, #ffffff, #00d4ff); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
| } | ||||
| 
 | ||||
| .section-subtitle { | ||||
|   font-size: 1.2rem; | ||||
|   color: #a0a0a0; | ||||
|   line-height: 1.6; | ||||
| } | ||||
| 
 | ||||
| .features-container { | ||||
|   max-width: 1200px; | ||||
|   margin: 0 auto; | ||||
|   padding: 0 2rem; | ||||
| } | ||||
| 
 | ||||
| .features-grid { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); | ||||
|   gap: 2rem; | ||||
| } | ||||
| 
 | ||||
| .feature-card { | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   backdrop-filter: blur(20px); | ||||
|   border: 1px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 20px; | ||||
|   padding: 2.5rem; | ||||
|   transition: all 0.3s ease; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| .feature-card:hover { | ||||
|   border-color: rgba(0, 212, 255, 0.3); | ||||
|   box-shadow: 0 20px 60px rgba(0, 212, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .feature-icon { | ||||
|   width: 60px; | ||||
|   height: 60px; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   border-radius: 15px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   margin-bottom: 1.5rem; | ||||
|   font-size: 1.5rem; | ||||
|   color: white; | ||||
| } | ||||
| 
 | ||||
| .feature-title { | ||||
|   font-size: 1.5rem; | ||||
|   font-weight: 700; | ||||
|   margin-bottom: 1.5rem; | ||||
|   color: #ffffff; | ||||
| } | ||||
| 
 | ||||
| .feature-list { | ||||
|   list-style: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| .feature-list li { | ||||
|   margin-bottom: 1rem; | ||||
|   color: #a0a0a0; | ||||
|   line-height: 1.6; | ||||
| } | ||||
| 
 | ||||
| .feature-list li:last-child { | ||||
|   margin-bottom: 0; | ||||
| } | ||||
| 
 | ||||
| .feature-list strong { | ||||
|   color: #00d4ff; | ||||
| } | ||||
| 
 | ||||
| /* Stats Section */ | ||||
| .stats { | ||||
|   padding: 6rem 0; | ||||
|   background: rgba(255, 255, 255, 0.03); | ||||
|   backdrop-filter: blur(20px); | ||||
| } | ||||
| 
 | ||||
| .stats-grid { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | ||||
|   gap: 3rem; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .stat-number { | ||||
|   font-size: 3.5rem; | ||||
|   font-weight: 900; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .stat-label { | ||||
|   font-size: 1.1rem; | ||||
|   color: #a0a0a0; | ||||
|   font-weight: 500; | ||||
| } | ||||
| 
 | ||||
| /* Footer */ | ||||
| .footer { | ||||
|   padding: 4rem 0 2rem; | ||||
|   border-top: 1px solid rgba(255, 255, 255, 0.1); | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| .footer-content { | ||||
|   color: #a0a0a0; | ||||
| } | ||||
| 
 | ||||
| /* Animations */ | ||||
| @keyframes slideInUp { | ||||
|   from { | ||||
|     opacity: 0; | ||||
|     transform: translateY(50px); | ||||
|   } | ||||
|   to { | ||||
|     opacity: 1; | ||||
|     transform: translateY(0); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /* Responsive */ | ||||
| @media (max-width: 968px) { | ||||
|   .hero-container { | ||||
|     grid-template-columns: 1fr; | ||||
|     text-align: center; | ||||
|   } | ||||
|    | ||||
|   .hero-title { | ||||
|     font-size: 2.5rem; | ||||
|   } | ||||
|    | ||||
|   .features-grid { | ||||
|     grid-template-columns: 1fr; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 768px) { | ||||
|   .nav-links { | ||||
|     display: none; | ||||
|   } | ||||
|    | ||||
|   .email-form { | ||||
|     flex-direction: column; | ||||
|   } | ||||
|    | ||||
|   .phone-mockup { | ||||
|     width: 240px; | ||||
|     height: 480px; | ||||
|   } | ||||
|    | ||||
|   .hero-container { | ||||
|     padding: 0 1rem; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										394
									
								
								src/assets/css/components/login.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								src/assets/css/components/login.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,394 @@ | |||
| * { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .login-page { | ||||
|   min-height: 100vh; | ||||
|   background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%); | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
|   font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | ||||
| } | ||||
| 
 | ||||
| .login-bg { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   background: radial-gradient(circle at 20% 80%, rgba(0, 212, 255, 0.05) 0%, transparent 50%), | ||||
|               radial-gradient(circle at 80% 20%, rgba(124, 58, 237, 0.05) 0%, transparent 50%); | ||||
| } | ||||
| 
 | ||||
| .floating-elements { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| .floating-element { | ||||
|   position: absolute; | ||||
|   width: 3px; | ||||
|   height: 3px; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   border-radius: 50%; | ||||
|   animation: float 8s ease-in-out infinite; | ||||
| } | ||||
| 
 | ||||
| @keyframes float { | ||||
|   0%, 100% {  | ||||
|     transform: translateY(0px) rotate(0deg);  | ||||
|     opacity: 0.3;  | ||||
|   } | ||||
|   50% {  | ||||
|     transform: translateY(-30px) rotate(180deg);  | ||||
|     opacity: 0.8;  | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .login-container { | ||||
|   position: relative; | ||||
|   z-index: 2; | ||||
|   width: 100%; | ||||
|   max-width: 420px; | ||||
|   padding: 2rem; | ||||
| } | ||||
| 
 | ||||
| .login-card { | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
|   backdrop-filter: blur(20px); | ||||
|   border: 1px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 24px; | ||||
|   padding: 3rem; | ||||
|   box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | ||||
|   animation: slideInUp 0.8s ease-out; | ||||
| } | ||||
| 
 | ||||
| @keyframes slideInUp { | ||||
|   from { | ||||
|     opacity: 0; | ||||
|     transform: translateY(50px); | ||||
|   } | ||||
|   to { | ||||
|     opacity: 1; | ||||
|     transform: translateY(0); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .login-header { | ||||
|   text-align: center; | ||||
|   margin-bottom: 2.5rem; | ||||
| } | ||||
| 
 | ||||
| .logo { | ||||
|   font-size: 2rem; | ||||
|   font-weight: 800; | ||||
|   color: #ffffff; | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
| 
 | ||||
| .logo-accent { | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
| } | ||||
| 
 | ||||
| .login-title { | ||||
|   font-size: 1.75rem; | ||||
|   font-weight: 700; | ||||
|   color: #ffffff; | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .login-subtitle { | ||||
|   color: #a0a0a0; | ||||
|   font-size: 0.95rem; | ||||
| } | ||||
| 
 | ||||
| .login-form { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .form-group { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .form-label { | ||||
|   font-weight: 600; | ||||
|   color: #ffffff; | ||||
|   font-size: 0.9rem; | ||||
| } | ||||
| 
 | ||||
| .input-wrapper { | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .input-icon { | ||||
|   position: absolute; | ||||
|   left: 1rem; | ||||
|   top: 50%; | ||||
|   transform: translateY(-50%); | ||||
|   color: #888; | ||||
|   font-size: 0.9rem; | ||||
| } | ||||
| 
 | ||||
| .form-input { | ||||
|   width: 100%; | ||||
|   padding: 1rem 1rem 1rem 2.75rem; | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   border: 2px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 12px; | ||||
|   color: #ffffff; | ||||
|   font-size: 1rem; | ||||
|   transition: all 0.3s ease; | ||||
|   backdrop-filter: blur(10px); | ||||
| } | ||||
| 
 | ||||
| .form-input:focus { | ||||
|   outline: none; | ||||
|   border-color: #00d4ff; | ||||
|   box-shadow: 0 0 20px rgba(0, 212, 255, 0.2); | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
| } | ||||
| 
 | ||||
| .form-input.error { | ||||
|   border-color: #ff4757; | ||||
|   box-shadow: 0 0 20px rgba(255, 71, 87, 0.2); | ||||
| } | ||||
| 
 | ||||
| .form-input::placeholder { | ||||
|   color: #666; | ||||
| } | ||||
| 
 | ||||
| .password-toggle { | ||||
|   position: absolute; | ||||
|   right: 1rem; | ||||
|   top: 50%; | ||||
|   transform: translateY(-50%); | ||||
|   background: none; | ||||
|   border: none; | ||||
|   color: #888; | ||||
|   cursor: pointer; | ||||
|   padding: 0.25rem; | ||||
|   transition: color 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .password-toggle:hover { | ||||
|   color: #00d4ff; | ||||
| } | ||||
| 
 | ||||
| .form-options { | ||||
|   display: flex; | ||||
|   justify-content: space-between; | ||||
|   align-items: center; | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .checkbox-wrapper { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   cursor: pointer; | ||||
|   gap: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .checkbox-input { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .checkbox-custom { | ||||
|   width: 18px; | ||||
|   height: 18px; | ||||
|   border: 2px solid rgba(255, 255, 255, 0.3); | ||||
|   border-radius: 4px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   transition: all 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .checkbox-input:checked + .checkbox-custom { | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   border-color: transparent; | ||||
| } | ||||
| 
 | ||||
| .checkbox-input:checked + .checkbox-custom::after { | ||||
|   content: '✓'; | ||||
|   color: white; | ||||
|   font-size: 12px; | ||||
|   font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .checkbox-label { | ||||
|   color: #a0a0a0; | ||||
|   font-size: 0.9rem; | ||||
| } | ||||
| 
 | ||||
| .forgot-password { | ||||
|   color: #00d4ff; | ||||
|   text-decoration: none; | ||||
|   font-size: 0.9rem; | ||||
|   transition: color 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .forgot-password:hover { | ||||
|   color: #7c3aed; | ||||
| } | ||||
| 
 | ||||
| .login-button { | ||||
|   width: 100%; | ||||
|   padding: 1rem; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   color: white; | ||||
|   border: none; | ||||
|   border-radius: 12px; | ||||
|   font-weight: 600; | ||||
|   font-size: 1.1rem; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s ease; | ||||
|   margin-top: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .login-button:hover:not(:disabled) { | ||||
|   transform: translateY(-2px); | ||||
|   box-shadow: 0 15px 40px rgba(0, 212, 255, 0.4); | ||||
| } | ||||
| 
 | ||||
| .login-button:disabled { | ||||
|   opacity: 0.7; | ||||
|   cursor: not-allowed; | ||||
|   transform: none; | ||||
| } | ||||
| 
 | ||||
| .loading-text { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   gap: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .divider { | ||||
|   position: relative; | ||||
|   text-align: center; | ||||
|   margin: 1.5rem 0; | ||||
|   color: #666; | ||||
| } | ||||
| 
 | ||||
| .divider::before { | ||||
|   content: ''; | ||||
|   position: absolute; | ||||
|   top: 50%; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   height: 1px; | ||||
|   background: rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .divider span { | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
|   padding: 0 1rem; | ||||
|   position: relative; | ||||
|   z-index: 1; | ||||
| } | ||||
| 
 | ||||
| .social-login { | ||||
|   width: 100%; | ||||
|   padding: 0.875rem; | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   border: 2px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 12px; | ||||
|   color: #ffffff; | ||||
|   font-weight: 600; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s ease; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   gap: 0.75rem; | ||||
|   margin-bottom: 0.75rem; | ||||
|   backdrop-filter: blur(10px); | ||||
| } | ||||
| 
 | ||||
| .social-login:hover { | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
|   border-color: rgba(255, 255, 255, 0.2); | ||||
| } | ||||
| 
 | ||||
| .social-login.google:hover { | ||||
|   border-color: rgba(219, 68, 55, 0.5); | ||||
|   box-shadow: 0 0 20px rgba(219, 68, 55, 0.1); | ||||
| } | ||||
| 
 | ||||
| .social-login.github:hover { | ||||
|   border-color: rgba(255, 255, 255, 0.5); | ||||
|   box-shadow: 0 0 20px rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .error-message { | ||||
|   background: rgba(255, 71, 87, 0.1); | ||||
|   border: 1px solid rgba(255, 71, 87, 0.3); | ||||
|   border-radius: 12px; | ||||
|   padding: 1rem; | ||||
|   color: #ff4757; | ||||
|   font-size: 0.9rem; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 0.5rem; | ||||
|   margin-top: 1rem; | ||||
|   animation: shake 0.5s ease-in-out; | ||||
| } | ||||
| 
 | ||||
| @keyframes shake { | ||||
|   0%, 100% { transform: translateX(0); } | ||||
|   25% { transform: translateX(-5px); } | ||||
|   75% { transform: translateX(5px); } | ||||
| } | ||||
| 
 | ||||
| .signup-prompt { | ||||
|   text-align: center; | ||||
|   margin-top: 2rem; | ||||
|   padding-top: 2rem; | ||||
|   border-top: 1px solid rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .signup-prompt p { | ||||
|   color: #a0a0a0; | ||||
| } | ||||
| 
 | ||||
| .signup-link { | ||||
|   color: #00d4ff; | ||||
|   text-decoration: none; | ||||
|   font-weight: 600; | ||||
|   transition: color 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .signup-link:hover { | ||||
|   color: #7c3aed; | ||||
| } | ||||
| 
 | ||||
| /* Responsive */ | ||||
| @media (max-width: 480px) { | ||||
|   .login-container { | ||||
|     padding: 1rem; | ||||
|   } | ||||
|    | ||||
|   .login-card { | ||||
|     padding: 2rem; | ||||
|   } | ||||
|    | ||||
|   .form-options { | ||||
|     flex-direction: column; | ||||
|     gap: 1rem; | ||||
|     align-items: flex-start; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										517
									
								
								src/assets/css/components/signup.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										517
									
								
								src/assets/css/components/signup.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,517 @@ | |||
| * { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .signup-page { | ||||
|   min-height: 100vh; | ||||
|   background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%); | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   position: relative; | ||||
|   overflow: hidden; | ||||
|   font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | ||||
| } | ||||
| 
 | ||||
| .signup-bg { | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   background: radial-gradient(circle at 20% 80%, rgba(0, 212, 255, 0.05) 0%, transparent 50%), | ||||
|               radial-gradient(circle at 80% 20%, rgba(124, 58, 237, 0.05) 0%, transparent 50%); | ||||
| } | ||||
| 
 | ||||
| .floating-elements { | ||||
|   position: absolute; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| .floating-element { | ||||
|   position: absolute; | ||||
|   width: 3px; | ||||
|   height: 3px; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   border-radius: 50%; | ||||
|   animation: float 8s ease-in-out infinite; | ||||
| } | ||||
| 
 | ||||
| @keyframes float { | ||||
|   0%, 100% {  | ||||
|     transform: translateY(0px) rotate(0deg);  | ||||
|     opacity: 0.3;  | ||||
|   } | ||||
|   50% {  | ||||
|     transform: translateY(-30px) rotate(180deg);  | ||||
|     opacity: 0.8;  | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .signup-container { | ||||
|   position: relative; | ||||
|   z-index: 2; | ||||
|   width: 100%; | ||||
|   max-width: 480px; | ||||
|   padding: 2rem; | ||||
| } | ||||
| 
 | ||||
| .signup-card { | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
|   backdrop-filter: blur(20px); | ||||
|   border: 1px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 24px; | ||||
|   padding: 3rem; | ||||
|   box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | ||||
|   animation: slideInUp 0.8s ease-out; | ||||
| } | ||||
| 
 | ||||
| @keyframes slideInUp { | ||||
|   from { | ||||
|     opacity: 0; | ||||
|     transform: translateY(50px); | ||||
|   } | ||||
|   to { | ||||
|     opacity: 1; | ||||
|     transform: translateY(0); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .signup-header { | ||||
|   text-align: center; | ||||
|   margin-bottom: 2.5rem; | ||||
| } | ||||
| 
 | ||||
| .logo { | ||||
|   font-size: 2rem; | ||||
|   font-weight: 800; | ||||
|   color: #ffffff; | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
| 
 | ||||
| .logo-accent { | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
| } | ||||
| 
 | ||||
| .signup-title { | ||||
|   font-size: 1.75rem; | ||||
|   font-weight: 700; | ||||
|   color: #ffffff; | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .signup-subtitle { | ||||
|   color: #a0a0a0; | ||||
|   font-size: 0.95rem; | ||||
| } | ||||
| 
 | ||||
| .signup-form { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .form-row { | ||||
|   display: flex; | ||||
|   gap: 1rem; | ||||
| } | ||||
| 
 | ||||
| .form-group { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 0.5rem; | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
| 
 | ||||
| .form-group.half-width { | ||||
|   flex: 1; | ||||
|   margin-bottom: 0; | ||||
| } | ||||
| 
 | ||||
| .form-label { | ||||
|   font-weight: 600; | ||||
|   color: #ffffff; | ||||
|   font-size: 0.9rem; | ||||
| } | ||||
| 
 | ||||
| .input-wrapper { | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .input-icon { | ||||
|   position: absolute; | ||||
|   left: 1rem; | ||||
|   top: 50%; | ||||
|   transform: translateY(-50%); | ||||
|   color: #888; | ||||
|   font-size: 0.9rem; | ||||
| } | ||||
| 
 | ||||
| .form-input { | ||||
|   width: 100%; | ||||
|   padding: 1rem 1rem 1rem 2.75rem; | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   border: 2px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 12px; | ||||
|   color: #ffffff; | ||||
|   font-size: 1rem; | ||||
|   transition: all 0.3s ease; | ||||
|   backdrop-filter: blur(10px); | ||||
| } | ||||
| 
 | ||||
| .form-input:focus { | ||||
|   outline: none; | ||||
|   border-color: #00d4ff; | ||||
|   box-shadow: 0 0 20px rgba(0, 212, 255, 0.2); | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
| } | ||||
| 
 | ||||
| .form-input.error { | ||||
|   border-color: #ff4757; | ||||
|   box-shadow: 0 0 20px rgba(255, 71, 87, 0.2); | ||||
| } | ||||
| 
 | ||||
| .form-input::placeholder { | ||||
|   color: #666; | ||||
| } | ||||
| 
 | ||||
| .password-toggle { | ||||
|   position: absolute; | ||||
|   right: 1rem; | ||||
|   top: 50%; | ||||
|   transform: translateY(-50%); | ||||
|   background: none; | ||||
|   border: none; | ||||
|   color: #888; | ||||
|   cursor: pointer; | ||||
|   padding: 0.25rem; | ||||
|   transition: color 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .password-toggle:hover { | ||||
|   color: #00d4ff; | ||||
| } | ||||
| 
 | ||||
| .password-strength { | ||||
|   margin-top: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .strength-bar { | ||||
|   width: 100%; | ||||
|   height: 4px; | ||||
|   background-color: rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 2px; | ||||
|   overflow: hidden; | ||||
|   margin-bottom: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .strength-fill { | ||||
|   height: 100%; | ||||
|   transition: width 0.3s ease, background-color 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .strength-fill.weak { | ||||
|   background: linear-gradient(45deg, #ff4757, #ff6b7a); | ||||
| } | ||||
| 
 | ||||
| .strength-fill.fair { | ||||
|   background: linear-gradient(45deg, #ffa502, #ffb347); | ||||
| } | ||||
| 
 | ||||
| .strength-fill.good { | ||||
|   background: linear-gradient(45deg, #2ed573, #7bed9f); | ||||
| } | ||||
| 
 | ||||
| .strength-fill.strong { | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
| } | ||||
| 
 | ||||
| .strength-text { | ||||
|   font-size: 0.75rem; | ||||
|   font-weight: 500; | ||||
| } | ||||
| 
 | ||||
| .strength-text.weak { | ||||
|   color: #ff4757; | ||||
| } | ||||
| 
 | ||||
| .strength-text.fair { | ||||
|   color: #ffa502; | ||||
| } | ||||
| 
 | ||||
| .strength-text.good { | ||||
|   color: #2ed573; | ||||
| } | ||||
| 
 | ||||
| .strength-text.strong { | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   background-clip: text; | ||||
|   -webkit-background-clip: text; | ||||
|   color: transparent; | ||||
| } | ||||
| 
 | ||||
| .password-mismatch { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 0.5rem; | ||||
|   margin-top: 0.5rem; | ||||
|   font-size: 0.75rem; | ||||
|   color: #ff4757; | ||||
|   animation: shake 0.5s ease-in-out; | ||||
| } | ||||
| 
 | ||||
| .form-options { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 1rem; | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .checkbox-wrapper { | ||||
|   display: flex; | ||||
|   align-items: flex-start; | ||||
|   cursor: pointer; | ||||
|   gap: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .checkbox-input { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .checkbox-custom { | ||||
|   width: 18px; | ||||
|   height: 18px; | ||||
|   border: 2px solid rgba(255, 255, 255, 0.3); | ||||
|   border-radius: 4px; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   transition: all 0.3s ease; | ||||
|   flex-shrink: 0; | ||||
|   margin-top: 1px; | ||||
| } | ||||
| 
 | ||||
| .checkbox-input:checked + .checkbox-custom { | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   border-color: transparent; | ||||
| } | ||||
| 
 | ||||
| .checkbox-input:checked + .checkbox-custom::after { | ||||
|   content: '✓'; | ||||
|   color: white; | ||||
|   font-size: 12px; | ||||
|   font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .checkbox-label { | ||||
|   color: #a0a0a0; | ||||
|   font-size: 0.9rem; | ||||
|   line-height: 1.4; | ||||
| } | ||||
| 
 | ||||
| .link { | ||||
|   color: #00d4ff; | ||||
|   text-decoration: none; | ||||
|   transition: color 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .link:hover { | ||||
|   color: #7c3aed; | ||||
|   text-decoration: underline; | ||||
| } | ||||
| 
 | ||||
| .signup-button { | ||||
|   width: 100%; | ||||
|   padding: 1rem; | ||||
|   background: linear-gradient(45deg, #00d4ff, #7c3aed); | ||||
|   color: white; | ||||
|   border: none; | ||||
|   border-radius: 12px; | ||||
|   font-weight: 600; | ||||
|   font-size: 1.1rem; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s ease; | ||||
|   margin-top: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .signup-button:hover:not(:disabled) { | ||||
|   transform: translateY(-2px); | ||||
|   box-shadow: 0 15px 40px rgba(0, 212, 255, 0.4); | ||||
| } | ||||
| 
 | ||||
| .signup-button:disabled { | ||||
|   opacity: 0.5; | ||||
|   cursor: not-allowed; | ||||
|   transform: none; | ||||
|   background: rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .loading-text { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   gap: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .divider { | ||||
|   position: relative; | ||||
|   text-align: center; | ||||
|   margin: 1.5rem 0; | ||||
|   color: #666; | ||||
| } | ||||
| 
 | ||||
| .divider::before { | ||||
|   content: ''; | ||||
|   position: absolute; | ||||
|   top: 50%; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   height: 1px; | ||||
|   background: rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .divider span { | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
|   padding: 0 1rem; | ||||
|   position: relative; | ||||
|   z-index: 1; | ||||
| } | ||||
| 
 | ||||
| .social-signup { | ||||
|   width: 100%; | ||||
|   padding: 0.875rem; | ||||
|   background: rgba(255, 255, 255, 0.05); | ||||
|   border: 2px solid rgba(255, 255, 255, 0.1); | ||||
|   border-radius: 12px; | ||||
|   color: #ffffff; | ||||
|   font-weight: 600; | ||||
|   cursor: pointer; | ||||
|   transition: all 0.3s ease; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
|   gap: 0.75rem; | ||||
|   margin-bottom: 0.75rem; | ||||
|   backdrop-filter: blur(10px); | ||||
| } | ||||
| 
 | ||||
| .social-signup:hover { | ||||
|   background: rgba(255, 255, 255, 0.08); | ||||
|   border-color: rgba(255, 255, 255, 0.2); | ||||
| } | ||||
| 
 | ||||
| .social-signup.google:hover { | ||||
|   border-color: rgba(219, 68, 55, 0.5); | ||||
|   box-shadow: 0 0 20px rgba(219, 68, 55, 0.1); | ||||
| } | ||||
| 
 | ||||
| .social-signup.github:hover { | ||||
|   border-color: rgba(255, 255, 255, 0.5); | ||||
|   box-shadow: 0 0 20px rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .error-message { | ||||
|   background: rgba(255, 71, 87, 0.1); | ||||
|   border: 1px solid rgba(255, 71, 87, 0.3); | ||||
|   border-radius: 12px; | ||||
|   padding: 1rem; | ||||
|   color: #ff4757; | ||||
|   font-size: 0.9rem; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 0.5rem; | ||||
|   margin-top: 1rem; | ||||
|   animation: shake 0.5s ease-in-out; | ||||
| } | ||||
| 
 | ||||
| .success-message { | ||||
|   background: rgba(46, 213, 115, 0.1); | ||||
|   border: 1px solid rgba(46, 213, 115, 0.3); | ||||
|   border-radius: 12px; | ||||
|   padding: 1rem; | ||||
|   color: #2ed573; | ||||
|   font-size: 0.9rem; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 0.5rem; | ||||
|   margin-top: 1rem; | ||||
|   animation: slideInUp 0.5s ease-out; | ||||
| } | ||||
| 
 | ||||
| @keyframes shake { | ||||
|   0%, 100% { transform: translateX(0); } | ||||
|   25% { transform: translateX(-5px); } | ||||
|   75% { transform: translateX(5px); } | ||||
| } | ||||
| 
 | ||||
| .login-prompt { | ||||
|   text-align: center; | ||||
|   margin-top: 2rem; | ||||
|   padding-top: 2rem; | ||||
|   border-top: 1px solid rgba(255, 255, 255, 0.1); | ||||
| } | ||||
| 
 | ||||
| .login-prompt p { | ||||
|   color: #a0a0a0; | ||||
| } | ||||
| 
 | ||||
| .login-link { | ||||
|   color: #00d4ff; | ||||
|   text-decoration: none; | ||||
|   font-weight: 600; | ||||
|   transition: color 0.3s ease; | ||||
| } | ||||
| 
 | ||||
| .login-link:hover { | ||||
|   color: #7c3aed; | ||||
| } | ||||
| 
 | ||||
| /* Responsive */ | ||||
| @media (max-width: 640px) { | ||||
|   .signup-container { | ||||
|     padding: 1rem; | ||||
|     max-width: 100%; | ||||
|   } | ||||
|    | ||||
|   .signup-card { | ||||
|     padding: 2rem; | ||||
|   } | ||||
|    | ||||
|   .form-row { | ||||
|     flex-direction: column; | ||||
|     gap: 0; | ||||
|   } | ||||
|    | ||||
|   .form-group.half-width { | ||||
|     margin-bottom: 1rem; | ||||
|   } | ||||
|    | ||||
|   .checkbox-wrapper { | ||||
|     align-items: flex-start; | ||||
|   } | ||||
|    | ||||
|   .checkbox-label { | ||||
|     font-size: 0.85rem; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 480px) { | ||||
|   .signup-card { | ||||
|     padding: 1.5rem; | ||||
|   } | ||||
|    | ||||
|   .signup-title { | ||||
|     font-size: 1.5rem; | ||||
|   } | ||||
|    | ||||
|   .logo { | ||||
|     font-size: 1.75rem; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										348
									
								
								src/components/Home.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										348
									
								
								src/components/Home.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,348 @@ | |||
| <template> | ||||
|   <div class="homepage"> | ||||
|     <nav class="navbar"> | ||||
|       <div class="nav-content"> | ||||
|         <div class="logo" @click="scrollTo('hero')"> | ||||
|           Ride<span class="logo-accent">Aware</span> | ||||
|         </div> | ||||
|         <ul class="nav-links"> | ||||
|           <li> | ||||
|             <a href="#" @click.prevent="scrollTo('features')">Features</a> | ||||
|           </li> | ||||
|           <li> | ||||
|             <router-link to="/login" class="login-link">Login</router-link> | ||||
|           </li> | ||||
|           <li> | ||||
|             <router-link to="/signup" class="signup-btn">Sign Up</router-link> | ||||
|           </li> | ||||
|         </ul> | ||||
|       </div> | ||||
|     </nav> | ||||
| 
 | ||||
|     <section id="hero" class="hero"> | ||||
|       <div class="hero-bg"></div> | ||||
|       <div class="floating-elements"> | ||||
|         <div  | ||||
|           v-for="(element, index) in floatingElements"  | ||||
|           :key="index" | ||||
|           class="floating-element" | ||||
|           :style="element.style"> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class="hero-container"> | ||||
|         <div class="hero-content"> | ||||
|           <h1 class="hero-title">{{ heroTitle }}</h1> | ||||
|           <p class="hero-subtitle">{{ heroSubtitle }}</p> | ||||
|            | ||||
|           <div class="cta-section"> | ||||
|             <h3>Coming soon!</h3> | ||||
|             <p>Join us while waiting for launch</p> | ||||
|              | ||||
|             <div class="email-form"> | ||||
|               <input  | ||||
|                 type="email"  | ||||
|                 class="email-input"  | ||||
|                 v-model="emailInput" | ||||
|                 placeholder="Enter your email address"  | ||||
|                 required | ||||
|                 @keypress.enter="handleNotifyMe"> | ||||
|               <button class="notify-btn" @click="handleNotifyMe" :disabled="!isValidEmail"> | ||||
|                 Notify Me | ||||
|               </button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="hero-visual"> | ||||
|           <div class="phone-mockup"> | ||||
|             <div class="screen"> | ||||
|               <div class="app-interface"> | ||||
|                 <div class="app-logo">RideAware</div> | ||||
|                 <div class="stats-grid"> | ||||
|                   <div  | ||||
|                     v-for="stat in mockupStats"  | ||||
|                     :key="stat.id"  | ||||
|                     class="stat-card" | ||||
|                     :class="{ 'active': stat.id === activeStat }" | ||||
|                     @mouseenter="activeStat = stat.id" | ||||
|                     @mouseleave="activeStat = null"> | ||||
|                     <div class="stat-number">{{ stat.value }}</div> | ||||
|                     <div class="stat-label">{{ stat.label }}</div> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
| 
 | ||||
|     <section id="features" class="features"> | ||||
|       <div class="section-header"> | ||||
|         <h2 class="section-title">{{ featuresTitle }}</h2> | ||||
|         <p class="section-subtitle">{{ featuresSubtitle }}</p> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="features-container"> | ||||
|         <div class="features-grid"> | ||||
|           <div  | ||||
|             v-for="feature in features"  | ||||
|             :key="feature.id"  | ||||
|             class="feature-card" | ||||
|             @mouseenter="animateCard($event)" | ||||
|             @mouseleave="resetCard($event)"> | ||||
|             <div class="feature-icon"> | ||||
|               <i :class="feature.icon"></i> | ||||
|             </div> | ||||
|             <h3 class="feature-title">{{ feature.title }}</h3> | ||||
|             <ul class="feature-list"> | ||||
|               <li v-for="item in feature.items" :key="item.title"> | ||||
|                 <strong>{{ item.title }}:</strong> {{ item.description }} | ||||
|               </li> | ||||
|             </ul> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
| 
 | ||||
|     <section class="stats"> | ||||
|       <div class="container"> | ||||
|         <div class="stats-grid"> | ||||
|           <div v-for="stat in platformStats" :key="stat.id" class="stat-item"> | ||||
|             <div class="stat-number">{{ animatedStats[stat.id] || 0 }}{{ stat.suffix }}</div> | ||||
|             <div class="stat-label">{{ stat.label }}</div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </section> | ||||
| 
 | ||||
|     <footer class="footer"> | ||||
|       <div class="container"> | ||||
|         <div class="footer-content"> | ||||
|           <p>© {{ currentYear }} RideAware. All rights reserved.</p> | ||||
|         </div> | ||||
|       </div> | ||||
|     </footer> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import { ref, reactive, computed, onMounted } from 'vue' | ||||
| import '@/assets/css/components/homepage.css'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'Homepage', | ||||
|   setup() { | ||||
|     const heroTitle = ref("Elevate Your Cycling Journey") | ||||
|     const heroSubtitle = ref("The ultimate smart training platform for cyclists who demand excellence in every ride.") | ||||
|     const featuresTitle = ref("Powerful Features for Every Cyclist") | ||||
|     const featuresSubtitle = ref("From beginners to professionals, RideAware provides comprehensive tools to optimize your training and performance.") | ||||
|     const currentYear = ref(new Date().getFullYear()) | ||||
|      | ||||
|     const emailInput = ref('') | ||||
|     const activeStat = ref(null) | ||||
| 
 | ||||
|     const isValidEmail = computed(() => { | ||||
|       const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ | ||||
|       return emailRegex.test(emailInput.value) | ||||
|     }) | ||||
| 
 | ||||
|     const mockupStats = reactive([ | ||||
|       { id: 'speed', value: '24.5', label: 'KM/H AVG' }, | ||||
|       { id: 'time', value: '45', label: 'MINUTES' }, | ||||
|       { id: 'calories', value: '285', label: 'CALORIES' }, | ||||
|       { id: 'distance', value: '18.2', label: 'DISTANCE' } | ||||
|     ]) | ||||
| 
 | ||||
|     const features = reactive([ | ||||
|       { | ||||
|         id: 1, | ||||
|         icon: "fas fa-calendar-alt", | ||||
|         title: "Smart Training Plans", | ||||
|         items: [ | ||||
|           { title: "AI-Powered Planning", description: "Customized training plans based on your goals and fitness level" }, | ||||
|           { title: "Adaptive Scheduling", description: "Smart workout scheduling with automated reminders" }, | ||||
|           { title: "Goal Tracking", description: "Set and monitor your cycling objectives in real-time" } | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         id: 2, | ||||
|         icon: "fas fa-chart-line", | ||||
|         title: "Advanced Analytics", | ||||
|         items: [ | ||||
|           { title: "Detailed Logging", description: "Track exercises, sets, reps, and performance metrics" }, | ||||
|           { title: "Data Visualization", description: "Interactive charts, graphs, and progress statistics" }, | ||||
|           { title: "Progress Insights", description: "Monitor your improvement over time with AI analysis" } | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         id: 3, | ||||
|         icon: "fas fa-bicycle", | ||||
|         title: "Virtual Training", | ||||
|         items: [ | ||||
|           { title: "Expert Coaching", description: "Professional guidance to achieve your cycling goals" }, | ||||
|           { title: "Immersive Rides", description: "Virtual training experiences to boost performance" }, | ||||
|           { title: "Structured Workouts", description: "Designed programs for fitness and performance gains" } | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         id: 4, | ||||
|         icon: "fas fa-heart", | ||||
|         title: "Health & Recovery", | ||||
|         items: [ | ||||
|           { title: "Nutrition Tracking", description: "Plan and monitor your dietary intake for optimal performance" }, | ||||
|           { title: "Recovery Optimization", description: "Tools and resources for effective rest and recovery" }, | ||||
|           { title: "Injury Prevention", description: "Proactive measures to prevent and manage injuries" } | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         id: 5, | ||||
|         icon: "fas fa-users", | ||||
|         title: "Community & Social", | ||||
|         items: [ | ||||
|           { title: "Social Sharing", description: "Share achievements and progress on social platforms" }, | ||||
|           { title: "Active Community", description: "Connect with fellow cyclists and share experiences" }, | ||||
|           { title: "Competitive Leaderboards", description: "Challenge yourself against the community" } | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         id: 6, | ||||
|         icon: "fas fa-sync-alt", | ||||
|         title: "Smart Integration", | ||||
|         items: [ | ||||
|           { title: "Wearable Sync", description: "Connect with fitness trackers and smart devices" }, | ||||
|           { title: "Music Integration", description: "Seamlessly sync with your favorite music services" }, | ||||
|           { title: "Data Portability", description: "Easy import/export to other cycling platforms" } | ||||
|         ] | ||||
|       } | ||||
|     ]) | ||||
| 
 | ||||
|     const platformStats = reactive([ | ||||
|       { id: 'users', number: 50000, suffix: '+', label: 'Active Cyclists' }, | ||||
|       { id: 'rides', number: 1000000, suffix: '+', label: 'Rides Tracked' }, | ||||
|       { id: 'distance', number: 25000000, suffix: 'km', label: 'Total Distance' }, | ||||
|       { id: 'rating', number: 4.8, suffix: '/5', label: 'App Rating' } | ||||
|     ]) | ||||
| 
 | ||||
|     const animatedStats = reactive({}) | ||||
|     const floatingElements = reactive([]) | ||||
| 
 | ||||
|     const generateFloatingElements = () => { | ||||
|       for (let i = 0; i < 15; i++) { | ||||
|         floatingElements.push({ | ||||
|           style: { | ||||
|             left: Math.random() * 100 + '%', | ||||
|             top: Math.random() * 100 + '%', | ||||
|             animationDelay: Math.random() * 6 + 's', | ||||
|             animationDuration: (6 + Math.random() * 4) + 's' | ||||
|           } | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const animateStats = () => { | ||||
|       platformStats.forEach(stat => { | ||||
|         let current = 0 | ||||
|         const increment = stat.number / 100 | ||||
|         const timer = setInterval(() => { | ||||
|           current += increment | ||||
|           if (current >= stat.number) { | ||||
|             animatedStats[stat.id] = stat.number | ||||
|             clearInterval(timer) | ||||
|           } else { | ||||
|             animatedStats[stat.id] = Math.floor(current) | ||||
|           } | ||||
|         }, 20) | ||||
|       }) | ||||
|     } | ||||
| 
 | ||||
|     const scrollTo = (elementId) => { | ||||
|       const element = document.getElementById(elementId) | ||||
|       if (element) { | ||||
|         element.scrollIntoView({ behavior: 'smooth' }) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const goToNewsletters = () => { | ||||
|       window.location.href = '/newsletters' | ||||
|     } | ||||
| 
 | ||||
|     const handleNotifyMe = () => { | ||||
|       if (isValidEmail.value) { | ||||
|         alert(`Thanks! We'll notify you at ${emailInput.value} when RideAware launches.`) | ||||
|         emailInput.value = '' | ||||
|       } else { | ||||
|         alert('Please enter a valid email address.') | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const animateCard = (event) => { | ||||
|       const card = event.currentTarget | ||||
|       card.style.transform = 'translateY(-10px) rotateX(2deg)' | ||||
|       card.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' | ||||
|     } | ||||
| 
 | ||||
|     const resetCard = (event) => { | ||||
|       const card = event.currentTarget | ||||
|       card.style.transform = 'translateY(0) rotateX(0)' | ||||
|     } | ||||
| 
 | ||||
|     onMounted(() => { | ||||
|       generateFloatingElements() | ||||
|        | ||||
|       const observer = new IntersectionObserver((entries) => { | ||||
|         entries.forEach(entry => { | ||||
|           if (entry.isIntersecting) { | ||||
|             animateStats() | ||||
|             observer.unobserve(entry.target) | ||||
|           } | ||||
|         }) | ||||
|       }) | ||||
| 
 | ||||
|       const statsSection = document.querySelector('.stats') | ||||
|       if (statsSection) { | ||||
|         observer.observe(statsSection) | ||||
|       } | ||||
| 
 | ||||
|       window.addEventListener('scroll', () => { | ||||
|         const scrolled = window.pageYOffset | ||||
|         const parallax = document.querySelector('.hero-bg') | ||||
|         if (parallax) { | ||||
|           const speed = scrolled * 0.3 | ||||
|           parallax.style.transform = `translateY(${speed}px)` | ||||
|         } | ||||
|       }) | ||||
| 
 | ||||
|       let statIndex = 0 | ||||
|       setInterval(() => { | ||||
|         activeStat.value = mockupStats[statIndex].id | ||||
|         statIndex = (statIndex + 1) % mockupStats.length | ||||
|         setTimeout(() => { | ||||
|           activeStat.value = null | ||||
|         }, 1000) | ||||
|       }, 3000) | ||||
|     }) | ||||
| 
 | ||||
|     return { | ||||
|       heroTitle, | ||||
|       heroSubtitle, | ||||
|       featuresTitle, | ||||
|       featuresSubtitle, | ||||
|       currentYear, | ||||
|       emailInput, | ||||
|       isValidEmail, | ||||
|       activeStat, | ||||
|       mockupStats, | ||||
|       features, | ||||
|       platformStats, | ||||
|       animatedStats, | ||||
|       floatingElements, | ||||
|       scrollTo, | ||||
|       goToNewsletters, | ||||
|       handleNotifyMe, | ||||
|       animateCard, | ||||
|       resetCard | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | @ -1,19 +0,0 @@ | |||
| <template> | ||||
|     <div class="logged-in"> | ||||
|       <h2>You have successfully logged in!</h2> | ||||
|       <p>Welcome back to RideAware!</p> | ||||
|     </div> | ||||
|   </template> | ||||
|    | ||||
|   <script> | ||||
|   export default { | ||||
|     name: 'LoggedInPage', | ||||
|   }; | ||||
|   </script> | ||||
|    | ||||
|   <style scoped> | ||||
|   .logged-in { | ||||
|     text-align: center; | ||||
|   } | ||||
|   </style> | ||||
|    | ||||
|  | @ -1,23 +1,129 @@ | |||
| <template> | ||||
|   <div class="login"> | ||||
|     <h2>Login to RideAware</h2> | ||||
|     <form @submit.prevent="login"> | ||||
|       <div> | ||||
|         <label for="username">Username:</label> | ||||
|         <input type="text" id="username" v-model="username" required /> | ||||
|   <div class="login-page"> | ||||
|     <div class="login-bg"> | ||||
|       <div class="floating-elements"> | ||||
|         <div  | ||||
|           v-for="(element, index) in floatingElements"  | ||||
|           :key="index" | ||||
|           class="floating-element" | ||||
|           :style="element.style"> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div> | ||||
|         <label for="password">Password:</label> | ||||
|         <input type="password" id="password" v-model="password" required /> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="login-container"> | ||||
|       <div class="login-card"> | ||||
|         <div class="login-header"> | ||||
|           <div class="logo"> | ||||
|             Ride<span class="logo-accent">Aware</span> | ||||
|           </div> | ||||
|           <h2 class="login-title">Welcome Back</h2> | ||||
|           <p class="login-subtitle">Sign in to continue your cycling journey</p> | ||||
|         </div> | ||||
| 
 | ||||
|         <form @submit.prevent="login" class="login-form"> | ||||
|           <div class="form-group"> | ||||
|             <label for="username" class="form-label">Username</label> | ||||
|             <div class="input-wrapper"> | ||||
|               <i class="fas fa-user input-icon"></i> | ||||
|               <input  | ||||
|                 type="text"  | ||||
|                 id="username"  | ||||
|                 v-model="username"  | ||||
|                 class="form-input" | ||||
|                 :class="{ 'error': error && !username }" | ||||
|                 placeholder="Enter your username" | ||||
|                 required  | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-group"> | ||||
|             <label for="password" class="form-label">Password</label> | ||||
|             <div class="input-wrapper"> | ||||
|               <i class="fas fa-lock input-icon"></i> | ||||
|               <input  | ||||
|                 :type="showPassword ? 'text' : 'password'"  | ||||
|                 id="password"  | ||||
|                 v-model="password"  | ||||
|                 class="form-input" | ||||
|                 :class="{ 'error': error && !password }" | ||||
|                 placeholder="Enter your password" | ||||
|                 required  | ||||
|               /> | ||||
|               <button  | ||||
|                 type="button"  | ||||
|                 class="password-toggle" | ||||
|                 @click="showPassword = !showPassword" | ||||
|               > | ||||
|                 <i :class="showPassword ? 'fas fa-eye-slash' : 'fas fa-eye'"></i> | ||||
|               </button> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-options"> | ||||
|             <label class="checkbox-wrapper"> | ||||
|               <input type="checkbox" v-model="rememberMe" class="checkbox-input"> | ||||
|               <span class="checkbox-custom"></span> | ||||
|               <span class="checkbox-label">Remember me</span> | ||||
|             </label> | ||||
|             <a href="#" class="forgot-password">Forgot password?</a> | ||||
|           </div> | ||||
| 
 | ||||
|           <button  | ||||
|             type="submit"  | ||||
|             class="login-button" | ||||
|             :class="{ 'loading': isLoading, 'success': loginSuccess }" | ||||
|             :disabled="isLoading" | ||||
|           > | ||||
|             <span v-if="!isLoading && !loginSuccess">Sign In</span> | ||||
|             <span v-else-if="isLoading" class="loading-text"> | ||||
|               <i class="fas fa-spinner fa-spin"></i> | ||||
|               Signing In... | ||||
|             </span> | ||||
|             <span v-else-if="loginSuccess" class="success-text"> | ||||
|               <i class="fas fa-check"></i> | ||||
|               Success! Redirecting... | ||||
|             </span> | ||||
|           </button> | ||||
| 
 | ||||
|           <div class="divider"> | ||||
|             <span>or</span> | ||||
|           </div> | ||||
| 
 | ||||
|           <button type="button" class="social-login google"> | ||||
|             <i class="fab fa-google"></i> | ||||
|             Continue with Google | ||||
|           </button> | ||||
| 
 | ||||
|           <button type="button" class="social-login github"> | ||||
|             <i class="fab fa-github"></i> | ||||
|             Continue with GitHub | ||||
|           </button> | ||||
|         </form> | ||||
| 
 | ||||
|         <div v-if="error" class="error-message"> | ||||
|           <i class="fas fa-exclamation-triangle"></i> | ||||
|           {{ error }} | ||||
|         </div> | ||||
| 
 | ||||
|         <div v-if="loginSuccess" class="success-message"> | ||||
|           <i class="fas fa-check-circle"></i> | ||||
|           Login successful! Redirecting to homepage... | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="signup-prompt"> | ||||
|           <p>Don't have an account? <a href="/signup" class="signup-link">Sign up</a></p> | ||||
|         </div> | ||||
|       </div> | ||||
|       <button type="submit">Login</button> | ||||
|     </form> | ||||
|     <p v-if="error" class="error">{{ error }}</p> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import axios from 'axios'; | ||||
| import '@/assets/css/components/login.css'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'UserLogin', | ||||
|   data() { | ||||
|  | @ -25,23 +131,75 @@ export default { | |||
|       username: '', | ||||
|       password: '', | ||||
|       error: null, | ||||
|       isLoading: false, | ||||
|       loginSuccess: false, | ||||
|       showPassword: false, | ||||
|       rememberMe: false, | ||||
|       floatingElements: [] | ||||
|     }; | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.generateFloatingElements(); | ||||
|   }, | ||||
|   methods: { | ||||
|     async login() { | ||||
|       if (!this.username || !this.password) { | ||||
|         this.error = 'Please fill in all fields'; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       this.isLoading = true; | ||||
|       this.error = null; | ||||
|       this.loginSuccess = false; | ||||
| 
 | ||||
|       try { | ||||
|         const response = await axios.post('http://127.0.0.1:5000/login', { | ||||
|         const response = await axios.post('http://localhost:5000/api/login', { | ||||
|           username: this.username, | ||||
|           password: this.password, | ||||
|         }); | ||||
|          | ||||
|         console.log('Login successful:', response.data); | ||||
|         // Redirect to logged-in page on success | ||||
|         this.$router.push('/logged-in'); | ||||
|          | ||||
|         const userData = { | ||||
|           user_id: response.data.user_id, | ||||
|           username: this.username, | ||||
|           loginTime: new Date().toISOString() | ||||
|         }; | ||||
| 
 | ||||
|         if (this.rememberMe) { | ||||
|           localStorage.setItem('user', JSON.stringify(userData)); | ||||
|           localStorage.setItem('authToken', 'authenticated'); | ||||
|         } else { | ||||
|           sessionStorage.setItem('user', JSON.stringify(userData)); | ||||
|           sessionStorage.setItem('authToken', 'authenticated'); | ||||
|         } | ||||
|          | ||||
|         this.loginSuccess = true; | ||||
|          | ||||
|         setTimeout(() => { | ||||
|           this.$router.push('/'); | ||||
|         }, 1500); | ||||
|          | ||||
|       } catch (error) { | ||||
|         console.error('Login failed:', error.response.data); | ||||
|         this.error = error.response.data.error || 'An error occurred'; | ||||
|         console.error('Login failed:', error.response?.data); | ||||
|         this.error = error.response?.data?.error || 'Login failed. Please try again.'; | ||||
|       } finally { | ||||
|         this.isLoading = false; | ||||
|       } | ||||
|     }, | ||||
|   }, | ||||
|      | ||||
|     generateFloatingElements() { | ||||
|       for (let i = 0; i < 12; i++) { | ||||
|         this.floatingElements.push({ | ||||
|           style: { | ||||
|             left: Math.random() * 100 + '%', | ||||
|             top: Math.random() * 100 + '%', | ||||
|             animationDelay: Math.random() * 8 + 's', | ||||
|             animationDuration: (6 + Math.random() * 4) + 's' | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
| </script> | ||||
							
								
								
									
										344
									
								
								src/components/UserSignup.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								src/components/UserSignup.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,344 @@ | |||
| <template> | ||||
|   <div class="signup-page"> | ||||
|     <div class="signup-bg"> | ||||
|       <div class="floating-elements"> | ||||
|         <div  | ||||
|           v-for="(element, index) in floatingElements"  | ||||
|           :key="index" | ||||
|           class="floating-element" | ||||
|           :style="element.style"> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="signup-container"> | ||||
|       <div class="signup-card"> | ||||
|         <div class="signup-header"> | ||||
|           <div class="logo"> | ||||
|             Ride<span class="logo-accent">Aware</span> | ||||
|           </div> | ||||
|           <h2 class="signup-title">Join RideAware</h2> | ||||
|           <p class="signup-subtitle">Create your account to start tracking your cycling journey</p> | ||||
|         </div> | ||||
| 
 | ||||
|         <form @submit.prevent="signup" class="signup-form"> | ||||
|           <div class="form-row"> | ||||
|             <div class="form-group half-width"> | ||||
|               <label for="firstName" class="form-label">First Name</label> | ||||
|               <div class="input-wrapper"> | ||||
|                 <i class="fas fa-user input-icon"></i> | ||||
|                 <input  | ||||
|                   type="text"  | ||||
|                   id="firstName"  | ||||
|                   v-model="firstName"  | ||||
|                   class="form-input" | ||||
|                   :class="{ 'error': error && !firstName }" | ||||
|                   placeholder="First name" | ||||
|                   required  | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
| 
 | ||||
|             <div class="form-group half-width"> | ||||
|               <label for="lastName" class="form-label">Last Name</label> | ||||
|               <div class="input-wrapper"> | ||||
|                 <i class="fas fa-user input-icon"></i> | ||||
|                 <input  | ||||
|                   type="text"  | ||||
|                   id="lastName"  | ||||
|                   v-model="lastName"  | ||||
|                   class="form-input" | ||||
|                   :class="{ 'error': error && !lastName }" | ||||
|                   placeholder="Last name" | ||||
|                   required  | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-group"> | ||||
|             <label for="username" class="form-label">Username</label> | ||||
|             <div class="input-wrapper"> | ||||
|               <i class="fas fa-at input-icon"></i> | ||||
|               <input  | ||||
|                 type="text"  | ||||
|                 id="username"  | ||||
|                 v-model="username"  | ||||
|                 class="form-input" | ||||
|                 :class="{ 'error': error && !username }" | ||||
|                 placeholder="Choose a username" | ||||
|                 required  | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-group"> | ||||
|             <label for="email" class="form-label">Email</label> | ||||
|             <div class="input-wrapper"> | ||||
|               <i class="fas fa-envelope input-icon"></i> | ||||
|               <input  | ||||
|                 type="email"  | ||||
|                 id="email"  | ||||
|                 v-model="email"  | ||||
|                 class="form-input" | ||||
|                 :class="{ 'error': error && !email }" | ||||
|                 placeholder="Enter your email" | ||||
|                 required  | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-group"> | ||||
|             <label for="password" class="form-label">Password</label> | ||||
|             <div class="input-wrapper"> | ||||
|               <i class="fas fa-lock input-icon"></i> | ||||
|               <input  | ||||
|                 :type="showPassword ? 'text' : 'password'"  | ||||
|                 id="password"  | ||||
|                 v-model="password"  | ||||
|                 class="form-input" | ||||
|                 :class="{ 'error': error && !password }" | ||||
|                 placeholder="Create a password" | ||||
|                 required  | ||||
|               /> | ||||
|               <button  | ||||
|                 type="button"  | ||||
|                 class="password-toggle" | ||||
|                 @click="showPassword = !showPassword" | ||||
|               > | ||||
|                 <i :class="showPassword ? 'fas fa-eye-slash' : 'fas fa-eye'"></i> | ||||
|               </button> | ||||
|             </div> | ||||
|             <div class="password-strength" v-if="password"> | ||||
|               <div class="strength-bar"> | ||||
|                 <div  | ||||
|                   class="strength-fill"  | ||||
|                   :class="passwordStrength.class" | ||||
|                   :style="{ width: passwordStrength.width }" | ||||
|                 ></div> | ||||
|               </div> | ||||
|               <span class="strength-text" :class="passwordStrength.class"> | ||||
|                 {{ passwordStrength.text }} | ||||
|               </span> | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-group"> | ||||
|             <label for="confirmPassword" class="form-label">Confirm Password</label> | ||||
|             <div class="input-wrapper"> | ||||
|               <i class="fas fa-lock input-icon"></i> | ||||
|               <input  | ||||
|                 :type="showConfirmPassword ? 'text' : 'password'"  | ||||
|                 id="confirmPassword"  | ||||
|                 v-model="confirmPassword"  | ||||
|                 class="form-input" | ||||
|                 :class="{ 'error': error && !confirmPassword || passwordMismatch }" | ||||
|                 placeholder="Confirm your password" | ||||
|                 required  | ||||
|               /> | ||||
|               <button  | ||||
|                 type="button"  | ||||
|                 class="password-toggle" | ||||
|                 @click="showConfirmPassword = !showConfirmPassword" | ||||
|               > | ||||
|                 <i :class="showConfirmPassword ? 'fas fa-eye-slash' : 'fas fa-eye'"></i> | ||||
|               </button> | ||||
|             </div> | ||||
|             <div v-if="passwordMismatch" class="password-mismatch"> | ||||
|               <i class="fas fa-exclamation-triangle"></i> | ||||
|               Passwords do not match | ||||
|             </div> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-options"> | ||||
|             <label class="checkbox-wrapper"> | ||||
|               <input type="checkbox" v-model="agreeToTerms" class="checkbox-input" required> | ||||
|               <span class="checkbox-custom"></span> | ||||
|               <span class="checkbox-label"> | ||||
|                 I agree to the <a href="/terms" class="link">Terms of Service</a>  | ||||
|                 and <a href="/privacy" class="link">Privacy Policy</a> | ||||
|               </span> | ||||
|             </label> | ||||
|           </div> | ||||
| 
 | ||||
|           <div class="form-options"> | ||||
|             <label class="checkbox-wrapper"> | ||||
|               <input type="checkbox" v-model="subscribeNewsletter" class="checkbox-input"> | ||||
|               <span class="checkbox-custom"></span> | ||||
|               <span class="checkbox-label"> | ||||
|                 Subscribe to newsletter for cycling tips and updates | ||||
|               </span> | ||||
|             </label> | ||||
|           </div> | ||||
| 
 | ||||
|           <button  | ||||
|             type="submit"  | ||||
|             class="signup-button" | ||||
|             :class="{ 'loading': isLoading }" | ||||
|             :disabled="isLoading || !isFormValid" | ||||
|           > | ||||
|             <span v-if="!isLoading">Create Account</span> | ||||
|             <span v-else class="loading-text"> | ||||
|               <i class="fas fa-spinner fa-spin"></i> | ||||
|               Creating Account... | ||||
|             </span> | ||||
|           </button> | ||||
| 
 | ||||
|           <div class="divider"> | ||||
|             <span>or</span> | ||||
|           </div> | ||||
| 
 | ||||
|           <button type="button" class="social-signup google"> | ||||
|             <i class="fab fa-google"></i> | ||||
|             Sign up with Google | ||||
|           </button> | ||||
| 
 | ||||
|           <button type="button" class="social-signup github"> | ||||
|             <i class="fab fa-github"></i> | ||||
|             Sign up with GitHub | ||||
|           </button> | ||||
|         </form> | ||||
| 
 | ||||
|         <div v-if="error" class="error-message"> | ||||
|           <i class="fas fa-exclamation-triangle"></i> | ||||
|           {{ error }} | ||||
|         </div> | ||||
| 
 | ||||
|         <div v-if="successMessage" class="success-message"> | ||||
|           <i class="fas fa-check-circle"></i> | ||||
|           {{ successMessage }} | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="login-prompt"> | ||||
|           <p>Already have an account? <a href="/login" class="login-link">Sign in</a></p> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import axios from 'axios'; | ||||
| import '@/assets/css/components/signup.css'; | ||||
| 
 | ||||
| export default { | ||||
|   name: 'UserSignup', | ||||
|   data() { | ||||
|     return { | ||||
|       firstName: '', | ||||
|       lastName: '', | ||||
|       username: '', | ||||
|       email: '', | ||||
|       password: '', | ||||
|       confirmPassword: '', | ||||
|       error: null, | ||||
|       successMessage: null, | ||||
|       isLoading: false, | ||||
|       showPassword: false, | ||||
|       showConfirmPassword: false, | ||||
|       agreeToTerms: false, | ||||
|       subscribeNewsletter: false, | ||||
|       floatingElements: [] | ||||
|     }; | ||||
|   }, | ||||
|   computed: { | ||||
|     passwordMismatch() { | ||||
|       return this.confirmPassword && this.password !== this.confirmPassword; | ||||
|     }, | ||||
|     passwordStrength() { | ||||
|       if (!this.password) return { width: '0%', class: '', text: '' }; | ||||
|        | ||||
|       let score = 0; | ||||
|       let feedback = []; | ||||
|        | ||||
|       if (this.password.length >= 8) score += 1; | ||||
|       else feedback.push('at least 8 characters'); | ||||
|        | ||||
|       if (/[A-Z]/.test(this.password)) score += 1; | ||||
|       else feedback.push('uppercase letter'); | ||||
|        | ||||
|       if (/[a-z]/.test(this.password)) score += 1; | ||||
|       else feedback.push('lowercase letter'); | ||||
| 
 | ||||
|       if (/\d/.test(this.password)) score += 1; | ||||
|       else feedback.push('number'); | ||||
|        | ||||
|       if (/[!@#$%^&*(),.?":{}|<>]/.test(this.password)) score += 1; | ||||
|       else feedback.push('special character'); | ||||
|        | ||||
|       if (score < 2) { | ||||
|         return { width: '25%', class: 'weak', text: 'Weak' }; | ||||
|       } else if (score < 4) { | ||||
|         return { width: '50%', class: 'fair', text: 'Fair' }; | ||||
|       } else if (score < 5) { | ||||
|         return { width: '75%', class: 'good', text: 'Good' }; | ||||
|       } else { | ||||
|         return { width: '100%', class: 'strong', text: 'Strong' }; | ||||
|       } | ||||
|     }, | ||||
|     isFormValid() { | ||||
|       return this.firstName &&  | ||||
|              this.lastName &&  | ||||
|              this.username &&  | ||||
|              this.email &&  | ||||
|              this.password &&  | ||||
|              this.confirmPassword &&  | ||||
|              !this.passwordMismatch &&  | ||||
|              this.agreeToTerms; | ||||
|     } | ||||
|   }, | ||||
|   mounted() { | ||||
|     this.generateFloatingElements(); | ||||
|   }, | ||||
|   methods: { | ||||
|     async signup() { | ||||
|       if (!this.isFormValid) { | ||||
|         this.error = 'Please fill in all required fields correctly'; | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       this.isLoading = true; | ||||
|       this.error = null; | ||||
|       this.successMessage = null; | ||||
| 
 | ||||
|       try { | ||||
|         const response = await axios.post('http://localhost:5000/api/signup', { | ||||
|           firstName: this.firstName, | ||||
|           lastName: this.lastName, | ||||
|           username: this.username, | ||||
|           email: this.email, | ||||
|           password: this.password, | ||||
|           subscribeNewsletter: this.subscribeNewsletter | ||||
|         }); | ||||
|          | ||||
|         console.log('Signup successful:', response.data); | ||||
|          | ||||
|         this.successMessage = 'Account created successfully! Please check your email to verify your account.'; | ||||
|          | ||||
|         setTimeout(() => { | ||||
|           this.$router.push('/login'); | ||||
|         }, 2000); | ||||
|          | ||||
|       } catch (error) { | ||||
|         console.error('Signup failed:', error.response?.data); | ||||
|         this.error = error.response?.data?.error || 'Account creation failed. Please try again.'; | ||||
|       } finally { | ||||
|         this.isLoading = false; | ||||
|       } | ||||
|     }, | ||||
|      | ||||
|     generateFloatingElements() { | ||||
|       for (let i = 0; i < 12; i++) { | ||||
|         this.floatingElements.push({ | ||||
|           style: { | ||||
|             left: Math.random() * 100 + '%', | ||||
|             top: Math.random() * 100 + '%', | ||||
|             animationDelay: Math.random() * 8 + 's', | ||||
|             animationDuration: (6 + Math.random() * 4) + 's' | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|  | @ -1,16 +1,46 @@ | |||
| import { createRouter, createWebHistory } from 'vue-router'; | ||||
| import Login from '../components/UserLogin.vue'; | ||||
| import LoggedinPage from '@/components/LoggedinPage.vue'; | ||||
| import { createRouter, createWebHistory } from 'vue-router' | ||||
| 
 | ||||
| const routes = [ | ||||
|   { path: '/', component: Login }, | ||||
|   { path: '/logged-in', component: LoggedinPage} | ||||
|   //{ path: '/dashboard', component: () => import('../components/Dashboard.vue') }, // Placeholder for a dashboard page
 | ||||
| ]; | ||||
|   {  | ||||
|     path: '/',  | ||||
|     name: 'Home', | ||||
|     component: () => import('../components/Home.vue'), | ||||
|     meta: { title: 'RideAware – Home' }  | ||||
|   }, | ||||
|   {  | ||||
|     path: '/login',  | ||||
|     name: 'Login', | ||||
|     component: () => import('../components/UserLogin.vue'), | ||||
|     meta: { title: 'RideAware – Sign In' }  | ||||
|   }, | ||||
|   { | ||||
|     path: '/signup', | ||||
|     name: 'SignUp', | ||||
|     component: () => import('../components/UserSignup.vue'), | ||||
|     meta: { title: 'RideAware – Sign Up' } | ||||
|   }, | ||||
|    | ||||
| ] | ||||
| 
 | ||||
| const router = createRouter({ | ||||
|   history: createWebHistory(), | ||||
|   routes, | ||||
| }); | ||||
| }) | ||||
| 
 | ||||
| export default router; | ||||
| router.beforeEach((to, from, next) => { | ||||
|   if (to.meta.requiresAuth) { | ||||
|     const isLoggedIn = !!(localStorage.getItem('user') || sessionStorage.getItem('user')) | ||||
|     if (!isLoggedIn) { | ||||
|       next({ name: 'Login' }) | ||||
|       return | ||||
|     } | ||||
|   } | ||||
|   next() | ||||
| }) | ||||
| 
 | ||||
| router.afterEach((to) => { | ||||
|   const defaultTitle = 'RideAware' | ||||
|   document.title = to.meta.title || defaultTitle | ||||
| }) | ||||
| 
 | ||||
| export default router | ||||
|  | @ -1,4 +1,5 @@ | |||
| const { defineConfig } = require('@vue/cli-service') | ||||
| module.exports = defineConfig({ | ||||
|   transpileDependencies: true | ||||
|   transpileDependencies: true, | ||||
|   lintOnSave: true | ||||
| }) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue