11import os
2+ import html
23import htmlmin
34import json
45
@@ -347,11 +348,11 @@ def generate_skill_bar(skill):
347348 return f"""
348349 <div class="mb-4 skill-item group">
349350 <div class="flex justify-between mb-1 cursor-pointer items-center skill-header p-1 rounded hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors">
350- <div class="flex items-center pointer-events-none ">
351+ <div class="flex items-center">
351352 <span class="text-base font-medium text-gray-700 dark:text-gray-300">{ skill ['name' ]} </span>
352353 <ion-icon name="chevron-down-outline" class="ml-2 text-gray-400 group-hover:text-blue-500 transition-transform duration-300 skill-chevron"></ion-icon>
353354 </div>
354- <span class="text-sm font-medium text-blue-700 dark:text-blue-400 pointer-events-none ">{ skill ['level' ]} </span>
355+ <span class="text-sm font-medium text-blue-700 dark:text-blue-400">{ skill ['level' ]} </span>
355356 </div>
356357 <div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700 overflow-hidden">
357358 <div class="bg-blue-500 h-2.5 rounded-full skill-bar-fill transition-all duration-1000" style="width: { skill ['percentage' ]} %"></div>
@@ -385,6 +386,8 @@ def generate_project_card(project):
385386 "link" : project .get ("link" ),
386387 "skills" : project .get ("skills" , [])
387388 })
389+ # Escape JSON for safe embedding in an HTML attribute.
390+ project_json_attr = html .escape (project_json , quote = True )
388391
389392 # Generate HTML for skill pills if they exist
390393 skills_html = ""
@@ -402,7 +405,7 @@ def generate_project_card(project):
402405
403406 # Return the complete card HTML using an f-string
404407 return f"""
405- <div class="project-card { project .get ('category' , 'other' )} group relative hover:z-10" data-project=' { project_json } ' >
408+ <div class="project-card { project .get ('category' , 'other' )} group relative hover:z-10" data-project=" { project_json_attr } " >
406409 <div class="bg-gray-50 dark:bg-gray-700/50 rounded-lg overflow-hidden transition-all duration-300 shadow-md hover:shadow-xl flex flex-col h-full">
407410
408411 <div class="relative h-48 transition-all duration-300 overflow-hidden bg-gray-100 dark:bg-gray-800">
@@ -528,12 +531,6 @@ def create_html_structure():
528531 opacity: 1;
529532 visibility: visible;
530533 }}
531-
532- /* Ensure icon doesn't block clicks in the button */
533- #modal-close ion-icon {{
534- pointer-events: none;
535- }}
536-
537534 .project-modal-content {{
538535 background: #f9fafb;
539536 border-radius: 1rem;
@@ -814,7 +811,13 @@ def create_html_structure():
814811 // Get project data from the data attribute
815812 const projectDataStr = card.getAttribute('data-project');
816813 if (projectDataStr) {{
817- const projectData = JSON.parse(projectDataStr);
814+ let projectData;
815+ try {{
816+ projectData = JSON.parse(projectDataStr);
817+ }} catch (err) {{
818+ console.error('Invalid project data for card:', card, err);
819+ return;
820+ }}
818821
819822 // Add click indicator icon
820823 const indicator = document.createElement('div');
@@ -845,7 +848,7 @@ def create_html_structure():
845848
846849 // Skill expansion logic
847850 document.querySelectorAll('.skill-header').forEach(header => {{
848- header.addEventListener('click', (e ) => {{
851+ header.addEventListener('click', () => {{
849852 const skillItem = header.closest('.skill-item');
850853 const details = skillItem.querySelector('.skill-details');
851854 const chevron = skillItem.querySelector('.skill-chevron');
@@ -864,10 +867,10 @@ def create_html_structure():
864867
865868 if (isHidden) {{
866869 details.classList.remove('hidden');
867- if (chevron) chevron.style.transform = 'rotate(180deg)';
870+ chevron.style.transform = 'rotate(180deg)';
868871 }} else {{
869872 details.classList.add('hidden');
870- if (chevron) chevron.style.transform = 'rotate(0deg)';
873+ chevron.style.transform = 'rotate(0deg)';
871874 }}
872875 }});
873876 }});
0 commit comments