merge in new changes
This commit is contained in:
		
							
								
								
									
										250
									
								
								src/components/ui/sections/Skills.astro
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								src/components/ui/sections/Skills.astro
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | ||||
| --- | ||||
| import { Icon } from 'astro-icon/components'; | ||||
| import { readItems } from '@directus/sdk'; | ||||
|  | ||||
| import type { Skill } from '@lib/directusTypes'; | ||||
|  | ||||
| import directus from '@lib/directus'; | ||||
|  | ||||
| const skills = await directus.request( | ||||
|   readItems('site_skills', { | ||||
|     fields: ['*'], | ||||
|     sort: ['-date_created'], | ||||
|   }) | ||||
| ); | ||||
|  | ||||
| const baseClasses = 'mx-2 min-w-[220px] sm:mx-4 sm:min-w-[280px]'; | ||||
| const borderClasses = | ||||
|   'border border-neutral-100 hover:border-neutral-200 dark:border-stone-500/20 dark:hover:border-neutral-800'; | ||||
| const bgColorClasses = 'bg-neutral-100/80 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90'; | ||||
| const hoverClasses = 'hover:-translate-y-2 hover:scale-105 '; | ||||
| const shadowClasses = 'shadow-xs hover:shadow-lg'; | ||||
| --- | ||||
|  | ||||
| <section class:list={['flex flex-col gap-4', Astro.props.className]}> | ||||
|   <h3 | ||||
|     class="relative flex w-full items-center gap-3 pb-4 text-5xl text-neutral-800 dark:text-neutral-200" | ||||
|   > | ||||
|     Skills | ||||
|   </h3> | ||||
|   <div class=""> | ||||
|     <div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8"> | ||||
|       <!-- Main slider container --> | ||||
|       <div class="slider-track animate-slide flex"> | ||||
|         { | ||||
|           [...skills, ...skills, ...skills].map((skill: Skill) => { | ||||
|             return ( | ||||
|               <div | ||||
|                 class={`skill-card transform rounded-xl transition-all duration-300 ${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${shadowClasses}`} | ||||
|               > | ||||
|                 <div class="p-4 sm:p-6"> | ||||
|                   <div class="mb-4 flex items-center justify-between sm:mb-6"> | ||||
|                     <div class="flex items-center gap-2 sm:gap-4"> | ||||
|                       <div class="flex transform items-center justify-center rounded-lg text-neutral-800 transition-transform group-hover:rotate-12 dark:text-neutral-200"> | ||||
|                         <Icon name={skill.icon} class="h-8 w-8 sm:h-12 sm:w-12" /> | ||||
|                       </div> | ||||
|                       <h3 class="text-base font-semibold text-neutral-900 sm:text-xl dark:text-neutral-100"> | ||||
|                         {skill.title} | ||||
|                       </h3> | ||||
|                     </div> | ||||
|                     <span class="rounded-full bg-neutral-200 px-2 py-0.5 font-mono text-xs text-neutral-700 sm:px-2.5 sm:py-1 sm:text-sm dark:bg-neutral-800 dark:text-neutral-300"> | ||||
|                       {skill.level}% | ||||
|                     </span> | ||||
|                   </div> | ||||
|  | ||||
|                   <div class="relative h-1.5 w-full overflow-hidden rounded-full bg-stone-500/20 sm:h-2 dark:bg-stone-500/20"> | ||||
|                     <div | ||||
|                       class="progress-bar-animate from-steel via-bermuda to-steel absolute top-0 left-0 h-full rounded-full bg-gradient-to-r transition-all duration-1000" | ||||
|                       style={`width: ${skill.level}%`} | ||||
|                     /> | ||||
|                   </div> | ||||
|  | ||||
|                   <div class="mt-1 flex justify-between font-mono text-[10px] text-neutral-600 sm:mt-2 sm:text-xs dark:text-neutral-400"> | ||||
|                     <span>Beginner</span> | ||||
|                     <span>Advanced</span> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </div> | ||||
|             ); | ||||
|           }) | ||||
|         } | ||||
|       </div> | ||||
|  | ||||
|       <!-- Gradient overlays for smooth fade effect --> | ||||
|       <div | ||||
|         class="absolute top-0 bottom-0 left-0 z-10 w-12 bg-gradient-to-r from-neutral-200 to-transparent sm:w-24 dark:from-stone-700" | ||||
|       > | ||||
|       </div> | ||||
|       <div | ||||
|         class="absolute top-0 right-0 bottom-0 z-10 w-12 bg-gradient-to-l from-neutral-200 to-transparent sm:w-24 dark:from-stone-700" | ||||
|       > | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </section> | ||||
|  | ||||
| <script> | ||||
|   document.addEventListener('astro:page-load', () => { | ||||
|     // Create seamless infinite scrolling effect | ||||
|     function setupInfiniteScroll() { | ||||
|       const cards = document.querySelectorAll('.skill-card'); | ||||
|       if (!cards.length) return; | ||||
|     } | ||||
|  | ||||
|     setupInfiniteScroll(); | ||||
|  | ||||
|     // Add hover effects to cards - only on non-touch devices | ||||
|     const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; | ||||
|     const cards = document.querySelectorAll('.skill-card'); | ||||
|  | ||||
|     if (!isTouchDevice) { | ||||
|       cards.forEach((card) => { | ||||
|         card.addEventListener('mousemove', (e) => { | ||||
|           const rect = card.getBoundingClientRect(); | ||||
|           const x = e.clientX - rect.left; | ||||
|           const y = e.clientY - rect.top; | ||||
|  | ||||
|           const centerX = rect.width / 2; | ||||
|           const centerY = rect.height / 2; | ||||
|  | ||||
|           const angleX = (y - centerY) / 15; | ||||
|           const angleY = (centerX - x) / 15; | ||||
|  | ||||
|           card.style.transform = `perspective(1000px) rotateX(${angleX}deg) rotateY(${angleY}deg) scale(1.08) translateZ(20px)`; | ||||
|  | ||||
|           // Dynamic shadow based on tilt | ||||
|           const shadowX = (x - centerX) / 25; | ||||
|           const shadowY = (y - centerY) / 25; | ||||
|           card.style.boxShadow = ` | ||||
|             ${shadowX}px ${shadowY}px 20px rgba(0, 0, 0, 0.1), | ||||
|             0 10px 20px rgba(0, 0, 0, 0.05) | ||||
|           `; | ||||
|         }); | ||||
|  | ||||
|         card.addEventListener('mouseleave', () => { | ||||
|           card.style.transform = ''; | ||||
|           card.style.boxShadow = ''; | ||||
|         }); | ||||
|       }); | ||||
|     } else { | ||||
|       // Simpler effects for touch devices | ||||
|       cards.forEach((card) => { | ||||
|         card.addEventListener('touchstart', () => { | ||||
|           card.classList.add('is-touched'); | ||||
|         }); | ||||
|  | ||||
|         card.addEventListener('touchend', () => { | ||||
|           setTimeout(() => { | ||||
|             card.classList.remove('is-touched'); | ||||
|           }, 300); | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| </script> | ||||
|  | ||||
| <style> | ||||
|   /* Tech Stack Slider */ | ||||
|   .slider-track { | ||||
|     width: fit-content; | ||||
|     animation: scroll 40s linear infinite; | ||||
|   } | ||||
|  | ||||
|   @keyframes scroll { | ||||
|     0% { | ||||
|       transform: translateX(0); | ||||
|     } | ||||
|     100% { | ||||
|       transform: translateX(calc(-220px * 6 - 16px * 6)); /* Card width + margin for mobile */ | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @media (min-width: 640px) { | ||||
|     .slider-track { | ||||
|       animation: scroll 80s linear infinite; | ||||
|     } | ||||
|  | ||||
|     @keyframes scroll { | ||||
|       0% { | ||||
|         transform: translateX(0); | ||||
|       } | ||||
|       100% { | ||||
|         transform: translateX(calc(-280px * 6 - 32px * 6)); /* Card width + margin for desktop */ | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .tech-stack-slider:hover .slider-track { | ||||
|     animation-play-state: paused; | ||||
|   } | ||||
|  | ||||
|   .skill-card { | ||||
|     transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|   } | ||||
|  | ||||
|   .skill-card:hover { | ||||
|     z-index: 10; | ||||
|   } | ||||
|  | ||||
|   /* Reduce animation complexity on mobile */ | ||||
|   @media (max-width: 640px) { | ||||
|     .skill-card { | ||||
|       transition: | ||||
|         transform 0.3s ease, | ||||
|         box-shadow 0.3s ease; | ||||
|     } | ||||
|  | ||||
|     .skill-card:hover { | ||||
|       transform: translateY(-5px) !important; | ||||
|       box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   .skill-card:before { | ||||
|     content: ''; | ||||
|     position: absolute; | ||||
|     top: -10%; | ||||
|     left: -10%; | ||||
|     width: 120%; | ||||
|     height: 120%; | ||||
|     background: radial-gradient( | ||||
|       circle at center, | ||||
|       rgba(255, 255, 255, 0.1) 0%, | ||||
|       rgba(255, 255, 255, 0) 70% | ||||
|     ); | ||||
|     opacity: 0; | ||||
|     transition: opacity 0.5s ease; | ||||
|     pointer-events: none; | ||||
|   } | ||||
|  | ||||
|   .skill-card:hover:before { | ||||
|     opacity: 1; | ||||
|   } | ||||
|  | ||||
|   .progress-bar-animate { | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|   } | ||||
|  | ||||
|   .progress-bar-animate:after { | ||||
|     content: ''; | ||||
|     position: absolute; | ||||
|     top: 0; | ||||
|     left: -100%; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); | ||||
|     animation: progress-shine 2s infinite; | ||||
|   } | ||||
|  | ||||
|   @keyframes progress-shine { | ||||
|     0% { | ||||
|       left: -100%; | ||||
|     } | ||||
|     100% { | ||||
|       left: 100%; | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
		Reference in New Issue
	
	Block a user