feat: convert projects section to collapsible accordion

Project cards now show a compact summary row (name, status badge, date
range, chevron) that expands on click to reveal description and tech
tags. Uses Alpine.js with independent toggles and smooth transitions.
This commit is contained in:
Ricardo
2026-02-24 15:37:19 +01:00
parent 0279cfa977
commit c66a46e349
+42 -8
View File
@@ -1,5 +1,5 @@
{#
CV Projects Section - project cards
CV Projects Section - collapsible project cards (accordion)
Data fetched from /cv/data.json via homepage plugin
#}
@@ -8,18 +8,24 @@
{% set showTechnologies = sectionConfig.showTechnologies if sectionConfig.showTechnologies is defined else true %}
{% if cv and cv.projects and cv.projects.length %}
<section class="mb-8 sm:mb-12" id="projects">
<section class="mb-8 sm:mb-12" id="projects" x-data="{ expanded: {} }">
<h2 class="text-xl sm:text-2xl font-bold text-surface-900 dark:text-surface-100 mb-4 sm:mb-6">
{{ sectionConfig.title or "Projects" }}
</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{% for item in cv.projects | head(maxItems) %}
<div class="p-4 bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors">
<div class="flex items-start justify-between gap-2 mb-1">
<h3 class="font-semibold text-surface-900 dark:text-surface-100">
<div class="bg-white dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-600 transition-colors overflow-hidden">
{# Summary row — always visible, clickable #}
<button
class="w-full p-4 flex items-center justify-between gap-2 cursor-pointer text-left hover:bg-surface-50 dark:hover:bg-surface-700/50 transition-colors"
@click="expanded[{{ loop.index0 }}] = !expanded[{{ loop.index0 }}]"
:aria-expanded="expanded[{{ loop.index0 }}] ? 'true' : 'false'"
>
<div class="flex items-center gap-2 min-w-0 flex-1">
<h3 class="font-semibold text-surface-900 dark:text-surface-100 truncate">
{% if item.url %}
<a href="{{ item.url }}" class="hover:text-primary-600 dark:hover:text-primary-400">{{ item.name }}</a>
<a href="{{ item.url }}" class="hover:text-primary-600 dark:hover:text-primary-400" @click.stop>{{ item.name }}</a>
{% else %}
{{ item.name }}
{% endif %}
@@ -34,9 +40,36 @@
</span>
{% endif %}
</div>
<div class="flex items-center gap-2 shrink-0">
{% if item.startDate %}
<p class="text-xs text-surface-500 mb-1">
<span class="text-xs text-surface-500 hidden sm:inline">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</span>
{% endif %}
<svg
class="w-4 h-4 text-surface-400 transition-transform duration-200"
:class="expanded[{{ loop.index0 }}] && 'rotate-180'"
fill="none" stroke="currentColor" viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
</button>
{# Detail section — collapsible #}
<div
x-show="expanded[{{ loop.index0 }}]"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-1"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-1"
x-cloak
class="px-4 pb-4"
>
{% if item.startDate %}
<p class="text-xs text-surface-500 mb-1 sm:hidden">
{{ item.startDate }}{% if item.endDate %} {{ item.endDate }}{% else %} Present{% endif %}
</p>
{% endif %}
@@ -55,6 +88,7 @@
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</section>