@@ -82,7 +82,7 @@ def render_experience_item(item: dict[str, Any]) -> str:
8282 if item .get ("link" ):
8383 title_html += ' cursor-pointer hover:text-accent dark:hover:text-accent transition-colors"'
8484 title_html += f' onclick="window.location.href=\' { item ["link" ]} \' ">{ item ["title" ]} </h3>'
85- title_html += f'<button class="experience-copy-link hidden group-hover:block text-zinc-400 hover:text-accent dark:hover:text-accent transition-colors" data-link="{ item ["link" ]} " title="Copy link"><ion-icon name=" link" class="text-sm "></ion-icon ></button>'
85+ title_html += f'<button class="experience-copy-link hidden group-hover:block text-zinc-400 hover:text-accent dark:hover:text-accent transition-colors" data-link="{ item ["link" ]} " title="Copy link"><i class="bi bi- link-45deg inline-block align-middle "></i ></button>'
8686 else :
8787 title_html += '">' + item ["title" ] + '</h3>'
8888
@@ -152,7 +152,14 @@ def render_skill_item(skill: dict[str, Any], project_ids: dict[str, str]) -> str
152152
153153 icon_html = ""
154154 if skill .get ("icon" ):
155- icon_html = f'<ion-icon name="{ skill ["icon" ]} " class="text-lg mr-2"></ion-icon>'
155+ icon_val = skill ["icon" ]
156+ if "devicon" in icon_val :
157+ icon_class = icon_val
158+ elif icon_val .startswith ("bi bi-" ) or icon_val .startswith ("bi-" ):
159+ icon_class = icon_val if icon_val .startswith ("bi bi-" ) else f"bi { icon_val } "
160+ else :
161+ icon_class = f"bi bi-{ icon_val } "
162+ icon_html = f'<i class="{ icon_class } text-lg mr-2 inline-block align-middle"></i>'
156163
157164 duration_html = ""
158165 duration = skill .get ("duration" )
@@ -218,7 +225,7 @@ def render_project_card(project: dict[str, Any], category_map: dict[str, str] |
218225 category_label = category .replace ("-" , " " ).title ()
219226 date_html = (
220227 '<span class="text-xs font-medium font-mono text-zinc-500 dark:text-zinc-400 mb-2 block flex items-center">'
221- f'<ion-icon name="time-outline" class=" mr-1"></ion-icon >{ project ["date" ]} \u2022 { category_label } </span>'
228+ f'<i class="bi bi-clock mr-1 inline-block align-middle "></i >{ project ["date" ]} \u2022 { category_label } </span>'
222229 )
223230
224231 details_button_html = ""
@@ -313,12 +320,12 @@ def render_project_modal(project: dict[str, Any]) -> str:
313320 if len (slides ) > 1 :
314321 prev_btn = (
315322 f'<button class="carousel-prev absolute left-2 top-1/2 -translate-y-1/2 bg-black/40 hover:bg-black/70 text-white font-semibold rounded-none p-2 border border-white/20 z-10 transition-all opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 md:focus:opacity-100 flex items-center justify-center" aria-label="Previous slide">'
316- f'<ion-icon name=" chevron-back" class="text-xl" ></ion-icon >'
323+ f'<i class="bi bi- chevron-left" ></i >'
317324 f'</button>'
318325 )
319326 next_btn = (
320327 f'<button class="carousel-next absolute right-2 top-1/2 -translate-y-1/2 bg-black/40 hover:bg-black/70 text-white font-semibold rounded-none p-2 border border-white/20 z-10 transition-all opacity-100 md:opacity-0 md:group-hover/carousel:opacity-100 md:focus:opacity-100 flex items-center justify-center" aria-label="Next slide">'
321- f'<ion-icon name=" chevron-forward" class="text-xl" ></ion-icon >'
328+ f'<i class="bi bi- chevron-right" ></i >'
322329 f'</button>'
323330 )
324331
@@ -441,6 +448,7 @@ def build_index_context(config: dict[str, Any]) -> dict[str, Any]:
441448 "languages_title" : "Languages" ,
442449 "technologies_title" : "Technologies" ,
443450 "contact_title" : "Contact" ,
451+ "no_projects" : "No projects found matching your criteria." ,
444452 ** config .get ("sections" , {}),
445453 }
446454
@@ -474,17 +482,72 @@ def build_index_context(config: dict[str, Any]) -> dict[str, Any]:
474482 filters = config .get ("project_filters" ) or default_project_filters (projects )
475483 category_map = {f ["key" ]: f ["label" ] for f in filters if "key" in f and "label" in f }
476484
485+ skills_categories_config = config .get ("skills_categories" , {})
486+ skills_categories = []
487+
488+ if isinstance (skills_categories_config , dict ):
489+ for key , val in skills_categories_config .items ():
490+ skills_categories .append ({"key" : key , "title" : val })
491+ elif isinstance (skills_categories_config , list ):
492+ for item in skills_categories_config :
493+ if isinstance (item , dict ) and "key" in item and "title" in item :
494+ skills_categories .append ({"key" : item ["key" ], "title" : item ["title" ]})
495+
496+ if not skills_categories :
497+ skills_categories = [
498+ {"key" : "languages" , "title" : sections .get ("languages_title" , "Languages" )},
499+ {"key" : "technologies" , "title" : sections .get ("technologies_title" , "Technologies" )},
500+ ]
501+
502+ existing_keys = {cat ["key" ] for cat in skills_categories }
503+ if isinstance (skills , dict ):
504+ for key in skills .keys ():
505+ if key not in existing_keys :
506+ title = key .replace ("_" , " " ).title ()
507+ skills_categories .append ({"key" : key , "title" : title })
508+
509+ skills_columns = []
510+ for cat in skills_categories :
511+ cat_key = cat ["key" ]
512+ cat_title = cat ["title" ]
513+ items = skills .get (cat_key , []) if isinstance (skills , dict ) else []
514+ if not items :
515+ continue
516+
517+ items_html = "" .join (render_skill_item (skill , project_ids ) for skill in items )
518+ column_html = (
519+ f' <div>\n '
520+ f' <h3 class="text-base font-semibold font-mono text-accent-2 mb-4">{ cat_title } </h3>\n '
521+ f' { items_html } \n '
522+ f' </div>'
523+ )
524+ skills_columns .append (column_html )
525+
526+ num_columns = len (skills_columns )
527+ if num_columns == 1 :
528+ skills_grid_class = "grid grid-cols-1 gap-8"
529+ elif num_columns == 2 :
530+ skills_grid_class = "grid grid-cols-1 md:grid-cols-2 gap-8"
531+ elif num_columns == 3 :
532+ skills_grid_class = "grid grid-cols-1 md:grid-cols-3 gap-8"
533+ else :
534+ skills_grid_class = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8"
535+
536+ skills_html = "\n " .join (skills_columns )
537+
477538 content = {
478539 "first_tagline" : taglines [0 ],
479540 "taglines_json" : json .dumps (taglines ),
480541 "about_html" : render_about (about_paragraphs ),
481542 "timeline_items_html" : "" .join (render_experience_item (item ) for item in experiences ),
482543 "programming_skills_html" : "" .join (
483- render_skill_item (skill , project_ids ) for skill in skills .get ("languages" , [])
544+ render_skill_item (skill , project_ids ) for skill in ( skills .get ("languages" , []) if isinstance ( skills , dict ) else [])
484545 ),
485546 "tech_skills_html" : "" .join (
486- render_skill_item (skill , project_ids ) for skill in skills .get ("technologies" , [])
547+ render_skill_item (skill , project_ids ) for skill in ( skills .get ("technologies" , []) if isinstance ( skills , dict ) else [])
487548 ),
549+ "skills_html" : skills_html ,
550+ "skills_grid_class" : skills_grid_class ,
488551 "project_filters_html" : render_filter_buttons (filters ),
489552 "project_cards_html" : "" .join (render_project_card (project , category_map ) for project in projects ),
490553 "project_modals_html" : "" .join (render_project_modal (project ) for project in projects ),
0 commit comments