|
86 | 86 | "title": "PyAerial", |
87 | 87 | "description": "A better plane tracker for AERPAW, successor to airstrik.py.", |
88 | 88 | "link": "https://github.com/quantumbagel/PyAerial", |
89 | | - "skills": ["Python", "MavSDK", "Docker", "MongoDB", "ADS-B"] |
| 89 | + "skills": ["Python", "MavSDK", "Docker", "MongoDB", "ADS-B"], |
| 90 | + "hover_image": "https://quantumbagel.github.io/PyAerial/social-preview.png" |
90 | 91 | }, |
91 | 92 | { |
92 | 93 | "category": "internship", |
@@ -206,30 +207,56 @@ def generate_skill_bar(skill): |
206 | 207 |
|
207 | 208 |
|
208 | 209 | def generate_project_card(project): |
209 | | - """Generates a single project card.""" |
210 | | - # Generate skill pills |
| 210 | + """ |
| 211 | + Generates a single project card with advanced hover effects. |
| 212 | +
|
| 213 | + Expects a project dictionary with keys: 'category', 'image', |
| 214 | + 'hover_image', 'title', 'description', 'skills', and 'link'. |
| 215 | + """ |
| 216 | + # Gracefully handle projects that may not have a hover image |
| 217 | + # It will use the main image as a fallback to prevent errors |
| 218 | + hover_image_src = project.get('hover_image', project.get('image')) |
| 219 | + |
| 220 | + # Generate HTML for skill pills if they exist |
211 | 221 | skills_html = "" |
212 | 222 | if project.get("skills"): |
213 | 223 | pills_html = "".join([ |
214 | | - f'<span class="inline-block bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 text-xs font-medium mr-2 mb-2 px-3 py-1 rounded-full">{skill}</span>' |
215 | | - for skill in project['skills']]) |
| 224 | + f'<span class="inline-block bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 text-xs font-medium mr-2 mb-2 px-3 py-1 rounded-full">{skill}</span>' |
| 225 | + for skill in project['skills'] |
| 226 | + ]) |
216 | 227 | skills_html = f'<div class="flex flex-wrap mt-4">{pills_html}</div>' |
217 | 228 |
|
218 | | - return f""" |
219 | | - <div class="project-card {project['category']}"> |
220 | | - <div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg overflow-hidden group transform hover:scale-105 transition-transform duration-300 shadow-md hover:shadow-2xl flex flex-col h-full"> |
221 | | - <img src="{project['image']}" class="w-full h-48 object-cover" alt="{project['title']} Project"> |
222 | | - <div class="p-6 flex flex-col flex-grow"> |
223 | | - <h3 class="text-xl font-bold text-gray-900 dark:text-white mb-2">{project['title']}</h3> |
224 | | - <p class="text-sm text-gray-600 dark:text-gray-400 mb-4 flex-grow">{project['description']}</p> |
225 | | - <div class="mt-auto"> |
226 | | - {skills_html} |
227 | | - {'<a href="' + project['link'] + '" class="text-blue-500 font-semibold hover:underline mt-4 inline-block">View Project →</a>"' if project['link'] is not None else ""} |
228 | | - </div> |
229 | | - </div> |
230 | | - </div> |
231 | | - </div>""" |
| 229 | + # Generate HTML for the link if it exists |
| 230 | + link_html = "" |
| 231 | + if project.get("link"): |
| 232 | + link_html = f'<a href="{project["link"]}" class="text-blue-500 font-semibold hover:underline mt-4 inline-block">View Project →</a>' |
232 | 233 |
|
| 234 | + # Return the complete card HTML using an f-string |
| 235 | + return f""" |
| 236 | +<div class="project-card {project.get('category', 'other')} group relative hover:z-10"> |
| 237 | + <div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg overflow-hidden transform group-hover:scale-110 transition-transform duration-300 shadow-md hover:shadow-2xl flex flex-col h-full"> |
| 238 | +
|
| 239 | + <div class="relative h-48 transition-all duration-300 overflow-hidden bg-gray-100 dark:bg-gray-800"> |
| 240 | + <img src="{project.get('image')}" |
| 241 | + class="absolute w-full h-full object-cover transition-transform duration-300 group-hover:scale-110 flex items-center justify-center text-gray-500 dark:text-gray-400" |
| 242 | + alt="{project.get('title')} Project"> |
| 243 | +
|
| 244 | + <img src="{hover_image_src}" |
| 245 | + class="absolute w-full h-full object-cover opacity-0 transition-all duration-300 group-hover:opacity-100 group-hover:scale-110 flex items-center justify-center text-gray-500 dark:text-gray-400" |
| 246 | + alt="{project.get('title')} Preview"> |
| 247 | + </div> |
| 248 | +
|
| 249 | + <div class="p-6 flex flex-col flex-grow"> |
| 250 | + <h3 class="text-xl font-bold text-gray-900 dark:text-white mb-2">{project.get('title')}</h3> |
| 251 | + <p class="text-sm text-gray-600 dark:text-gray-400 mb-4 flex-grow">{project.get('description')}</p> |
| 252 | + <div class="mt-auto"> |
| 253 | + {skills_html} |
| 254 | + {link_html} |
| 255 | + </div> |
| 256 | + </div> |
| 257 | + </div> |
| 258 | +</div> |
| 259 | +""" |
233 | 260 |
|
234 | 261 | def create_html_structure(): |
235 | 262 | """Builds the final HTML file content by assembling all components.""" |
@@ -300,9 +327,6 @@ def create_html_structure(): |
300 | 327 | color: #000000 !important; |
301 | 328 | background-color: #EFF6FF; |
302 | 329 | }} |
303 | | - .skill-bar-fill {{ |
304 | | - transition: width 0.8s ease-in-out; |
305 | | - }} |
306 | 330 | </style> |
307 | 331 | </head> |
308 | 332 | <body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200"> |
@@ -502,21 +526,6 @@ def create_html_structure(): |
502 | 526 | |
503 | 527 | window.addEventListener('scroll', handleEdgeCases); |
504 | 528 | handleEdgeCases(); // Run once on load to set the correct initial state |
505 | | -
|
506 | | - // Animate skill bars on scroll (This part remains the same) |
507 | | - const skillsSection = document.getElementById('skills'); |
508 | | - const skillBars = document.querySelectorAll('.skill-bar-fill'); |
509 | | - const skillObserver = new IntersectionObserver((entries) => {{ entries.forEach(entry => {{ if (entry.isIntersecting) {{ skillBars.forEach(bar => {{ const width = bar.style.width; |
510 | | - bar.style.width = '0%'; |
511 | | - setTimeout(() => {{ bar.style.width = width; |
512 | | - }}, 100); |
513 | | - }}); |
514 | | - skillObserver.unobserve(skillsSection); |
515 | | - }} |
516 | | - }}); |
517 | | - }}, {{ threshold: 0.5 }}); |
518 | | - if (skillsSection) {{ skillObserver.observe(skillsSection); |
519 | | - }} |
520 | 529 | }}); |
521 | 530 | </script> |
522 | 531 | </body> |
|
0 commit comments