feat: add Projects section between Courses and Awards

Added new Projects section with two initial projects:
- Somos Una Ola - Beach cleaning initiative website (Node.js/Express/HTMX)
- Herrumbre Vivo Arte - Artist portfolio for recycled art

Changes:
- Added projects data to cv-en.json and cv-es.json
- Updated Project struct in models/cv.go with all required fields
- Added Projects section CSS matching Awards/Courses styling (80×80px icons)
- Added Projects template with icons, current badges, and Domestika link
- Reordered sections: Courses → Projects → Awards (as requested)

Features:
- Clickable project titles linking to websites
- Current badge for ongoing projects
- Period and location display
- Short descriptions (always visible)
- Responsibilities list (long version only)
- Technologies list (long version only)
- Footer with link to Domestika portfolio
This commit is contained in:
juanatsap
2025-11-08 10:52:06 +00:00
parent 229769f288
commit fa4996709d
5 changed files with 274 additions and 42 deletions
+30
View File
@@ -702,6 +702,36 @@
] ]
} }
], ],
"projects": [
{
"title": "Somos Una Ola - Beach Cleaning Initiative",
"url": "https://somosunaola.org",
"location": "La Palma, Canary Islands",
"startDate": "2023-07",
"current": true,
"technologies": ["Node.js", "Express.js", "HTMX"],
"shortDescription": "Volunteer project promoting beach cleaning on La Palma island. Created their website to publish completed cleanings and schedule future events.",
"responsibilities": [
"Designed and developed full-stack website using Node.js Express and HTMX",
"Implemented event publishing system for completed and upcoming beach cleanings",
"Supported environmental initiative that has completed 18 cleanings across 12 beaches"
]
},
{
"title": "Herrumbre Vivo Arte - Artist Portfolio Website",
"url": "https://herrumbrevivoarte.com",
"location": "Fuencaliente, La Palma",
"startDate": "2024",
"current": true,
"technologies": ["Web Development", "Portfolio Design"],
"shortDescription": "Portfolio website for Gustavo Díaz, artisan who transforms recycled materials into sculptures. Promotes environmental art and sustainable creativity.",
"responsibilities": [
"Created online presence for recycled art project focused on sustainability",
"Showcased sculptures made from metal, plastic, glass, and wood waste",
"Highlighted environmental workshops and educational mission aligned with Sustainable Development Goals"
]
}
],
"references": [ "references": [
{ {
"title": "Recommendations Letter from TwenTIC", "title": "Recommendations Letter from TwenTIC",
+30
View File
@@ -734,6 +734,36 @@
] ]
} }
], ],
"projects": [
{
"title": "Somos Una Ola - Iniciativa de Limpieza de Playas",
"url": "https://somosunaola.org",
"location": "La Palma, Islas Canarias",
"startDate": "2023-07",
"current": true,
"technologies": ["Node.js", "Express.js", "HTMX"],
"shortDescription": "Proyecto de voluntariado que promueve la limpieza de playas en la isla de La Palma. Creación de su sitio web para publicar limpiezas realizadas y programar eventos futuros.",
"responsibilities": [
"Diseñé y desarrollé sitio web full-stack usando Node.js Express y HTMX",
"Implementé sistema de publicación de eventos para limpiezas realizadas y futuras",
"Apoyé iniciativa ambiental que ha completado 18 limpiezas en 12 playas diferentes"
]
},
{
"title": "Herrumbre Vivo Arte - Sitio Web Portfolio de Artista",
"url": "https://herrumbrevivoarte.com",
"location": "Fuencaliente, La Palma",
"startDate": "2024",
"current": true,
"technologies": ["Desarrollo Web", "Diseño de Portfolio"],
"shortDescription": "Sitio web portfolio para Gustavo Díaz, artesano que transforma materiales reciclados en esculturas. Promueve arte ambiental y creatividad sostenible.",
"responsibilities": [
"Creé presencia online para proyecto de arte reciclado enfocado en sostenibilidad",
"Mostré esculturas hechas de desechos metálicos, plásticos, vidrio y madera",
"Destaqué talleres ambientales y misión educativa alineada con Objetivos de Desarrollo Sostenible"
]
}
],
"references": [ "references": [
{ {
"title": "Cartas de recomendación de TwenTiC", "title": "Cartas de recomendación de TwenTiC",
+8 -7
View File
@@ -99,13 +99,14 @@ type Language struct {
} }
type Project struct { type Project struct {
Name string `json:"name"` Title string `json:"title"`
Role string `json:"role"` URL string `json:"url"`
URL string `json:"url"` Location string `json:"location"`
Period string `json:"period"` StartDate string `json:"startDate"`
Description string `json:"description"` Current bool `json:"current"`
Technologies []string `json:"technologies"` Technologies []string `json:"technologies"`
Highlights []string `json:"highlights"` ShortDescription string `json:"shortDescription"`
Responsibilities []string `json:"responsibilities"`
} }
type Award struct { type Award struct {
+110
View File
@@ -935,6 +935,116 @@ iconify-icon {
text-align: justify; text-align: justify;
} }
/* Projects */
.project-item {
display: flex;
gap: 1.2rem;
align-items: flex-start;
margin-bottom: 2.5rem;
padding-bottom: 2rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.project-icon {
flex-shrink: 0;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
}
.default-project-icon {
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
border: 1px solid #ddd;
background: #f5f5f5;
color: #999;
padding: 10px;
}
.project-content {
flex: 1;
}
.project-header {
margin-bottom: 0.5rem;
}
.project-title {
font-size: 1em;
font-weight: 600;
margin: 0 0 0.3rem 0;
line-height: 1.4;
color: var(--text-dark);
}
.project-title-text {
display: inline;
}
.project-title-text a {
color: var(--accent-blue);
text-decoration: none;
}
.project-title-text a:hover {
text-decoration: underline;
}
.project-period,
.project-separator,
.project-location {
color: #555;
font-size: 0.9em;
font-weight: 600;
}
.project-separator {
color: #999;
}
.project-desc {
font-size: 0.9rem;
color: var(--text-dark);
margin-top: 0.5rem;
line-height: 1.6;
text-align: justify;
}
.project-technologies {
font-size: 0.85em;
color: var(--text-gray);
margin-top: 0.5rem;
line-height: 1.4;
}
.projects-footer {
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 2px solid #ddd;
text-align: center;
font-size: 0.95em;
color: var(--text-gray);
}
.projects-footer p {
margin: 0;
}
.projects-footer a {
color: var(--accent-blue);
text-decoration: none;
}
.projects-footer a:hover {
text-decoration: underline;
}
/* References */ /* References */
.reference-item { .reference-item {
margin-bottom: 0.6rem; margin-bottom: 0.6rem;
+96 -35
View File
@@ -157,41 +157,6 @@
<div class="page-content"> <div class="page-content">
<!-- Main Content Area - Page 2 --> <!-- Main Content Area - Page 2 -->
<main class="cv-main"> <main class="cv-main">
<!-- Awards Section -->
{{if .CV.Awards}}
<section id="awards" class="cv-section">
<h3 class="section-title">
<iconify-icon icon="mdi:trophy" width="24" height="24" class="section-icon"></iconify-icon>
{{if eq .Lang "es"}}Premios y Reconocimientos{{else}}Awards{{end}}
</h3>
{{range .CV.Awards}}
<div class="award-item">
{{if .AwardLogo}}
<div class="award-logo">
<img src="/static/images/companies/{{.AwardLogo}}" alt="{{.Title}} logo" onerror="this.parentElement.innerHTML='<iconify-icon icon=\'mdi:trophy\' width=\'60\' height=\'60\' class=\'default-award-icon\'></iconify-icon>'">
</div>
{{end}}
<div class="award-content">
<strong>{{.Title}}</strong><br>
<small>{{.Issuer}} - {{.Date}}</small>
{{if .ShortDescription}}
<p class="award-desc short-desc">{{.ShortDescription | safeHTML}}</p>
{{end}}
{{if .Responsibilities}}
<ul class="responsibilities long-only">
{{range .Responsibilities}}
<li>{{. | safeHTML}}</li>
{{end}}
</ul>
{{end}}
</div>
</div>
{{end}}
</section>
{{end}}
<!-- Courses Section --> <!-- Courses Section -->
{{if .CV.Courses}} {{if .CV.Courses}}
<section id="courses" class="cv-section"> <section id="courses" class="cv-section">
@@ -236,6 +201,102 @@
</section> </section>
{{end}} {{end}}
<!-- Projects Section -->
{{if .CV.Projects}}
<section id="projects" class="cv-section">
<h3 class="section-title">
<iconify-icon icon="mdi:folder-multiple" width="24" height="24" class="section-icon"></iconify-icon>
{{if eq .Lang "es"}}Proyectos{{else}}Projects{{end}}
</h3>
{{range .CV.Projects}}
<div class="project-item">
<div class="project-icon">
<iconify-icon icon="mdi:web" width="80" height="80" class="default-project-icon"></iconify-icon>
</div>
<div class="project-content">
<div class="project-header">
<h4 class="project-title">
<span class="project-title-text">
{{if .URL}}
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.Title}}</a>
{{else}}
{{.Title}}
{{end}}
</span>
{{if .Current}}
<span class="current-badge">{{if eq $.Lang "es"}}ACTUAL{{else}}CURRENT{{end}}</span>
{{end}}
</h4>
<span class="project-period">{{.StartDate}} {{if .Current}}/ {{if eq $.Lang "es"}}presente{{else}}now{{end}}{{end}}</span>
<span class="project-separator">&nbsp;-&nbsp;</span>
<span class="project-location">({{.Location}})</span>
</div>
{{if .ShortDescription}}
<p class="project-desc short-desc">{{.ShortDescription | safeHTML}}</p>
{{end}}
{{if .Responsibilities}}
<ul class="responsibilities long-only">
{{range .Responsibilities}}
<li>{{. | safeHTML}}</li>
{{end}}
</ul>
{{end}}
{{if .Technologies}}
<div class="project-technologies long-only">
<strong>{{if eq $.Lang "es"}}Tecnologías:{{else}}Technologies:{{end}}</strong>
{{range $index, $tech := .Technologies}}{{if $index}}, {{end}}{{$tech}}{{end}}
</div>
{{end}}
</div>
</div>
{{end}}
<!-- Link to full portfolio -->
<div class="projects-footer">
<p>{{if eq .Lang "es"}}Ver todos los proyectos en mi{{else}}See all projects on my{{end}}
<a href="{{.CV.Personal.Domestika}}" target="_blank" rel="noopener noreferrer"><strong>{{if eq .Lang "es"}}portfolio de Domestika{{else}}Domestika portfolio{{end}}</strong></a></p>
</div>
</section>
{{end}}
<!-- Awards Section -->
{{if .CV.Awards}}
<section id="awards" class="cv-section">
<h3 class="section-title">
<iconify-icon icon="mdi:trophy" width="24" height="24" class="section-icon"></iconify-icon>
{{if eq .Lang "es"}}Premios y Reconocimientos{{else}}Awards{{end}}
</h3>
{{range .CV.Awards}}
<div class="award-item">
{{if .AwardLogo}}
<div class="award-logo">
<img src="/static/images/companies/{{.AwardLogo}}" alt="{{.Title}} logo" onerror="this.parentElement.innerHTML='<iconify-icon icon=\'mdi:trophy\' width=\'60\' height=\'60\' class=\'default-award-icon\'></iconify-icon>'">
</div>
{{end}}
<div class="award-content">
<strong>{{.Title}}</strong><br>
<small>{{.Issuer}} - {{.Date}}</small>
{{if .ShortDescription}}
<p class="award-desc short-desc">{{.ShortDescription | safeHTML}}</p>
{{end}}
{{if .Responsibilities}}
<ul class="responsibilities long-only">
{{range .Responsibilities}}
<li>{{. | safeHTML}}</li>
{{end}}
</ul>
{{end}}
</div>
</div>
{{end}}
</section>
{{end}}
<!-- Languages Section --> <!-- Languages Section -->
<section id="languages" class="cv-section"> <section id="languages" class="cv-section">
<h3 class="section-title"> <h3 class="section-title">