a90d923956
Previously, HTML in short descriptions was being escaped and displayed as raw text instead of rendering properly. This happened because the safeHTML template function had been removed for security reasons. Changes: - Added safeHTML function back to template.FuncMap (template.go:53-55) - Updated three template locations to use safeHTML pipe: * Experience descriptions (cv-content.html:122) * Award descriptions (cv-content.html:180) * Project descriptions (cv-content.html:232) Security note: The safeHTML function is safe to use here because CV data comes from trusted YAML files controlled by the site owner, not user input. Clear documentation added to prevent misuse with untrusted content. Examples now rendering correctly: - Award: "Premio por excelencia en marketing B2B...con <a href=...>Clicplan</a>" - Projects: Links to Lidering, Jorpack, Delivery Bikes BCN, Mobbeel
424 lines
23 KiB
HTML
424 lines
23 KiB
HTML
<!-- PAGE 1 -->
|
|
<div class="cv-page page-1">
|
|
<!-- Professional Title Badges - Full Width Top Bar -->
|
|
<div class="cv-title-badges-header">
|
|
<span class="title-badge">{{if eq .Lang "es"}}CONSULTOR TÉCNICO{{else}}TECHNICAL CONSULTANT{{end}}</span>
|
|
<span class="badge-separator">|</span>
|
|
<span class="title-badge">{{if eq .Lang "es"}}INGENIERO FULL-STACK{{else}}FULL-STACK ENGINEER{{end}}</span>
|
|
<span class="badge-separator">|</span>
|
|
<span class="title-badge">{{if eq .Lang "es"}}ESPECIALISTA EN AUTENTICACIÓN{{else}}AUTHENTICATION SPECIALIST{{end}}</span>
|
|
<span class="badge-separator">|</span>
|
|
<span class="title-badge">{{if eq .Lang "es"}}ARQUITECTO DE SOLUCIONES{{else}}SOLUTION ARCHITECT{{end}}</span>
|
|
</div>
|
|
|
|
<!-- Page 1 Content Grid: Left Sidebar + Main Content -->
|
|
<div class="page-content">
|
|
<!-- Left Sidebar - Skills (first half) -->
|
|
<aside class="cv-sidebar cv-sidebar-left">
|
|
<div class="sidebar-accordion-header" onclick="toggleSidebar(this)">
|
|
<iconify-icon icon="mdi:brain" width="20" height="20"></iconify-icon>
|
|
<span>{{if eq .Lang "es"}}Competencias Técnicas{{else}}Technical Skills{{end}}</span>
|
|
<iconify-icon icon="mdi:chevron-down" width="20" height="20" class="chevron"></iconify-icon>
|
|
</div>
|
|
<div class="sidebar-accordion-content">
|
|
{{range $index, $category := .SkillsLeft}}
|
|
<section class="sidebar-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="sidebar-title">{{$category.Category}}</h3>
|
|
</summary>
|
|
<div class="sidebar-content">
|
|
{{range $category.Items}}<div class="skill-item">{{.}}</div>{{end}}
|
|
</div>
|
|
</details>
|
|
</section>
|
|
{{end}}
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content Area - Page 1 -->
|
|
<main class="cv-main">
|
|
<!-- Header with Name and Photo -->
|
|
<div class="cv-header">
|
|
<div class="cv-header-content">
|
|
<div class="cv-header-left">
|
|
<h1 class="cv-name">Moreno Rubio, Juan Andrés</h1>
|
|
<p class="years-experience">{{.YearsOfExperience}} {{if eq .Lang "es"}}años de experiencia{{else}}years of experience{{end}}</p>
|
|
|
|
<!-- Photo positioned for mobile (centered between name and intro) -->
|
|
<div class="cv-photo">
|
|
<img src="/static/images/profile/dni.jpeg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
|
|
</div>
|
|
|
|
<!-- Intro/Excerpt Text - No section heading, just the text -->
|
|
<div class="intro-text">{{.CV.Summary}}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Education -->
|
|
<section id="education" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:school" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Formación{{else}}Training{{end}}
|
|
</h3>
|
|
</summary>
|
|
{{range .CV.Education}}
|
|
<div class="education-item">
|
|
<strong>{{.Degree}}</strong> ({{.StartDate}}-{{.EndDate}}) {{if eq $.Lang "es"}}obtenido de{{else}}obtained from the{{end}} <strong>{{.Institution}}</strong> ({{.Location}})
|
|
</div>
|
|
{{end}}
|
|
</details>
|
|
</section>
|
|
|
|
<!-- Skills Summary -->
|
|
<section id="skills" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:brain" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Competencias{{else}}Skills{{end}}
|
|
</h3>
|
|
</summary>
|
|
<p class="summary-text">
|
|
{{if eq .Lang "es"}}
|
|
Desarrollador <strong>full-stack</strong> con experiencia en <strong>Go</strong>, <strong>Node.js</strong>, <strong>React</strong> y <strong>HTMX</strong> para <strong>aplicaciones modernas</strong>, además de conocimientos en Java y PHP para proyectos legacy. He trabajado en <strong>unos 20 sitios web</strong> y realizado <strong>consultoría para 35-40 clientes internacionales</strong>, desde e-commerce y plataformas empresariales hasta <strong>sistemas de autenticación</strong> que gestionan <strong>millones de usuarios</strong>. Familiarizado con flujos de trabajo asistidos por <strong>IA</strong> y gestión de infraestructura (<strong>Linux</strong>, <strong>Docker</strong>, <strong>CI/CD</strong>). Me adapto bien tanto al trabajo independiente como colaborativo en equipos internacionales.
|
|
{{else}}
|
|
<strong>Full-stack</strong> developer with experience in <strong>Go</strong>, <strong>Node.js</strong>, <strong>React</strong>, and <strong>HTMX</strong> for <strong>modern applications</strong>, plus Java and PHP knowledge for legacy projects. I've worked on <strong>around 20 websites</strong> and provided <strong>consulting for 35-40 international clients</strong>, from e-commerce and enterprise platforms to <strong>authentication systems</strong> managing <strong>millions of users</strong>. Familiar with <strong>AI-assisted development</strong> workflows and infrastructure management (<strong>Linux</strong>, <strong>Docker</strong>, <strong>CI/CD</strong>). I adapt well to both independent work and collaborative teams across different countries.
|
|
{{end}}
|
|
</p>
|
|
</details>
|
|
</section>
|
|
|
|
<!-- Experience -->
|
|
<section id="experience" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:office-building" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Experiencia{{else}}Experience{{end}}
|
|
</h3>
|
|
</summary>
|
|
|
|
{{range .CV.Experience}}
|
|
<div class="experience-item">
|
|
<div class="company-logo">
|
|
{{if .CompanyLogo}}
|
|
<img src="/static/images/companies/{{.CompanyLogo}}" alt="{{.Company}} logo" onerror="this.parentElement.innerHTML='<iconify-icon icon=\'mdi:office-building\' width=\'60\' height=\'60\' class=\'default-company-icon\'></iconify-icon>'">
|
|
{{else}}
|
|
<iconify-icon icon="mdi:office-building" width="60" height="60" class="default-company-icon"></iconify-icon>
|
|
{{end}}
|
|
</div>
|
|
<div class="experience-content">
|
|
<strong>{{.Position}}{{if .Company}} - {{if .CompanyURL}}<a href="{{.CompanyURL}}" target="_blank" rel="noopener noreferrer">{{.Company}}</a>{{else}}{{.Company}}{{end}}{{if .Duration}} - <span class="duration-text">{{.Duration}}</span>{{end}}{{end}}</strong>
|
|
{{if .Current}}<span class="current-badge">{{if eq $.Lang "es"}}ACTUAL{{else}}CURRENT{{end}}</span>{{end}}
|
|
{{if .Expired}}<span class="expired-badge">{{if eq $.Lang "es"}}EXPIRADO{{else}}EXPIRED{{end}}</span>{{end}}
|
|
<br>
|
|
<small>{{.StartDate}} / {{if .Current}}{{if eq $.Lang "es"}}presente{{else}}now{{end}}{{else}}{{.EndDate}}{{end}} - ({{.Location}})</small>
|
|
|
|
{{if .ShortDescription}}
|
|
<p class="experience-desc short-desc">{{.ShortDescription | safeHTML}}</p>
|
|
{{end}}
|
|
|
|
{{if .Responsibilities}}
|
|
<ul class="responsibilities long-only">
|
|
{{range .Responsibilities}}
|
|
<li>{{.}}</li>
|
|
{{end}}
|
|
</ul>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</details>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PAGE 2 -->
|
|
<div class="cv-page page-2">
|
|
<!-- Professional Title Badges - Same as Page 1 -->
|
|
<div class="cv-title-badges-header">
|
|
<span class="title-badge">{{if eq .Lang "es"}}CONSULTOR TÉCNICO{{else}}TECHNICAL CONSULTANT{{end}}</span>
|
|
<span class="badge-separator">|</span>
|
|
<span class="title-badge">{{if eq .Lang "es"}}INGENIERO FULL-STACK{{else}}FULL-STACK ENGINEER{{end}}</span>
|
|
<span class="badge-separator">|</span>
|
|
<span class="title-badge">{{if eq .Lang "es"}}ESPECIALISTA EN AUTENTICACIÓN{{else}}AUTHENTICATION SPECIALIST{{end}}</span>
|
|
<span class="badge-separator">|</span>
|
|
<span class="title-badge">{{if eq .Lang "es"}}ARQUITECTO DE SOLUCIONES{{else}}SOLUTION ARCHITECT{{end}}</span>
|
|
</div>
|
|
|
|
<!-- Page 2 Content Grid: Main Content + Right Sidebar -->
|
|
<div class="page-content">
|
|
<!-- Main Content Area - Page 2 -->
|
|
<main class="cv-main">
|
|
<!-- Awards Section -->
|
|
{{if .CV.Awards}}
|
|
<section id="awards" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<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>
|
|
</summary>
|
|
{{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>{{.}}</li>
|
|
{{end}}
|
|
</ul>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</details>
|
|
</section>
|
|
{{end}}
|
|
|
|
<!-- Projects Section -->
|
|
{{if .CV.Projects}}
|
|
<section id="projects" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:web" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Proyectos Personales / Freelance{{else}}Personal / Freelance Projects{{end}}
|
|
</h3>
|
|
</summary>
|
|
{{range .CV.Projects}}
|
|
<div class="project-item">
|
|
{{if .ProjectLogo}}
|
|
<div class="project-icon">
|
|
<img src="/static/images/projects/{{.ProjectLogo}}" alt="{{.Title}} logo" onerror="this.parentElement.innerHTML='<iconify-icon icon=\'mdi:web\' width=\'80\' height=\'80\' class=\'default-project-icon\'></iconify-icon>'">
|
|
</div>
|
|
{{else}}
|
|
<div class="project-icon">
|
|
<iconify-icon icon="mdi:web" width="80" height="80" class="default-project-icon"></iconify-icon>
|
|
</div>
|
|
{{end}}
|
|
<div class="project-content">
|
|
<strong>
|
|
{{if .ProjectName}}
|
|
{{if .URL}}<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.ProjectName}}</a>{{else}}{{.ProjectName}}{{end}}{{if .ProjectDesc}} - {{.ProjectDesc}}{{end}}
|
|
{{else}}
|
|
{{if .URL}}<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.Title}}</a>{{else}}{{.Title}}{{end}}
|
|
{{end}}
|
|
</strong>
|
|
{{if .Current}}<span class="live-badge"><iconify-icon icon="mdi:wifi" width="14" height="14"></iconify-icon>LIVE</span>{{end}}
|
|
{{if .MaintainedBy}}<span class="maintained-badge">{{if eq $.Lang "es"}}MANTENIDO POR{{else}}MAINTAINED BY{{end}} {{.MaintainedBy}}</span>{{end}}
|
|
<br>
|
|
<small>{{if .StartDate}}{{.StartDate}}{{if .Current}}{{if .DynamicDate}} / {{.DynamicDate}}{{else}} / {{if eq $.Lang "es"}}presente{{else}}ahora{{end}}{{end}}{{end}}{{end}} - ({{.Location}})</small>
|
|
|
|
{{if .ShortDescription}}
|
|
<p class="project-desc short-desc">{{.ShortDescription | safeHTML}}</p>
|
|
{{end}}
|
|
|
|
{{if .Responsibilities}}
|
|
<ul class="responsibilities long-only">
|
|
{{range .Responsibilities}}
|
|
<li>{{.}}</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>
|
|
</details>
|
|
</section>
|
|
{{end}}
|
|
|
|
<!-- Courses Section -->
|
|
{{if .CV.Courses}}
|
|
<section id="courses" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:school" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Cursos Realizados{{else}}Courses{{end}}
|
|
</h3>
|
|
</summary>
|
|
{{range .CV.Courses}}
|
|
<div class="course-item">
|
|
{{if .CourseLogo}}
|
|
<div class="course-icon">
|
|
<img src="/static/images/courses/{{.CourseLogo}}" alt="{{.Title}} logo" onerror="this.parentElement.innerHTML='<iconify-icon icon=\'mdi:school\' width=\'80\' height=\'80\' class=\'default-course-icon\'></iconify-icon>'">
|
|
</div>
|
|
{{else}}
|
|
<div class="course-icon">
|
|
<iconify-icon icon="mdi:school" width="80" height="80" class="default-course-icon"></iconify-icon>
|
|
</div>
|
|
{{end}}
|
|
<div class="course-content">
|
|
<strong>{{.Title}}</strong><br>
|
|
<small>{{.Institution}} - {{.Date}} - ({{.Location}})</small>
|
|
|
|
{{if .ShortDescription}}
|
|
<p class="course-desc short-desc">{{.ShortDescription}}</p>
|
|
{{end}}
|
|
|
|
{{if .Responsibilities}}
|
|
<ul class="responsibilities long-only">
|
|
{{range .Responsibilities}}
|
|
<li>{{.}}</li>
|
|
{{end}}
|
|
</ul>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
</details>
|
|
</section>
|
|
{{end}}
|
|
|
|
<!-- Languages Section -->
|
|
<section id="languages" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:translate" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Idiomas{{else}}Languages{{end}}
|
|
</h3>
|
|
</summary>
|
|
{{range .CV.Languages}}
|
|
<div class="language-item">
|
|
<strong>{{.Language}}:</strong> {{.Proficiency}}{{if .Detail}} {{.Detail}}{{end}}
|
|
</div>
|
|
{{end}}
|
|
</details>
|
|
</section>
|
|
|
|
<!-- References Section -->
|
|
{{if .CV.References}}
|
|
<section id="references" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:link-variant" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Referencias{{else}}References{{end}}
|
|
</h3>
|
|
</summary>
|
|
{{range .CV.References}}
|
|
<div class="reference-item">
|
|
{{if .TextBefore}}{{.TextBefore}} {{end}}{{if eq .Action "downloadPDF"}}<a href="{{.URL}}" onclick="event.preventDefault(); openPdfModal(); return false;"><strong>{{if .LinkText}}{{.LinkText}}{{else}}{{.Title}}{{end}}</strong></a>{{else}}<a href="{{.URL}}" {{if and (eq .Type "cv") (ne .URL "/?lang=en") (ne .URL "/?lang=es")}}download{{else}}target="_blank" rel="noopener noreferrer"{{end}}><strong>{{if .LinkText}}{{.LinkText}}{{else}}{{.Title}}{{end}}</strong></a>{{end}}{{if .TextAfter}} {{.TextAfter}}{{end}}
|
|
</div>
|
|
{{end}}
|
|
</details>
|
|
</section>
|
|
{{end}}
|
|
|
|
<!-- Other Section (Driver's License) -->
|
|
{{if .CV.Other.DriverLicense}}
|
|
<section id="other" class="cv-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="section-title">
|
|
<iconify-icon icon="mdi:information" width="24" height="24" class="section-icon"></iconify-icon>
|
|
{{if eq .Lang "es"}}Otros{{else}}Other{{end}}
|
|
</h3>
|
|
</summary>
|
|
<div class="other-content">
|
|
{{if eq .Lang "es"}}Carnet de conducir tipo <strong>{{.CV.Other.DriverLicense}}</strong>{{else}}Driving License type <strong>{{.CV.Other.DriverLicense}}</strong>{{end}}
|
|
</div>
|
|
</details>
|
|
</section>
|
|
{{end}}
|
|
</main>
|
|
|
|
<!-- Right Sidebar - Skills (second half) -->
|
|
<aside class="cv-sidebar cv-sidebar-right">
|
|
<div class="sidebar-accordion-header" onclick="toggleSidebar(this)">
|
|
<iconify-icon icon="mdi:brain" width="20" height="20"></iconify-icon>
|
|
<span>{{if eq .Lang "es"}}Más Competencias{{else}}More Skills{{end}}</span>
|
|
<iconify-icon icon="mdi:chevron-down" width="20" height="20" class="chevron"></iconify-icon>
|
|
</div>
|
|
<div class="sidebar-accordion-content">
|
|
{{range $index, $category := .SkillsRight}}
|
|
<section class="sidebar-section">
|
|
<details open>
|
|
<summary>
|
|
<h3 class="sidebar-title">{{$category.Category}}</h3>
|
|
</summary>
|
|
<div class="sidebar-content">
|
|
{{range $category.Items}}<div class="skill-item">{{.}}</div>{{end}}
|
|
</div>
|
|
</details>
|
|
</section>
|
|
{{end}}
|
|
</div>
|
|
</aside>
|
|
</div>
|
|
|
|
<!-- Footer - Only on Page 2 -->
|
|
<footer class="cv-footer">
|
|
<ul class="footer-content">
|
|
<li>
|
|
<div class="footer-label">linkedin_</div>
|
|
<div class="footer-separator"><i class="fa fa-circle"></i></div>
|
|
<div class="footer-value">
|
|
<a href="{{.CV.Personal.LinkedIn}}" target="_blank" rel="noopener noreferrer">{{.CV.Personal.LinkedIn}}</a>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="footer-label">github_</div>
|
|
<div class="footer-separator"><i class="fa fa-circle"></i></div>
|
|
<div class="footer-value">
|
|
<a href="{{.CV.Personal.GitHub}}" target="_blank" rel="noopener noreferrer">{{.CV.Personal.GitHub}}</a>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="footer-label">domestika_</div>
|
|
<div class="footer-separator"><i class="fa fa-circle"></i></div>
|
|
<div class="footer-value">
|
|
<a href="{{.CV.Personal.Domestika}}" target="_blank" rel="noopener noreferrer">{{.CV.Personal.Domestika}}</a>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="footer-label">email@</div>
|
|
<div class="footer-separator"><i class="fa fa-circle"></i></div>
|
|
<div class="footer-value">
|
|
<a href="mailto:{{.CV.Personal.Email}}" target="_blank" rel="noopener noreferrer">{{.CV.Personal.Email}}</a>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<div class="footer-label">phone#</div>
|
|
<div class="footer-separator"><i class="fa fa-circle"></i></div>
|
|
<div class="footer-value">
|
|
<a href="tel:+34676875420" target="_blank" rel="noopener noreferrer">+34 676 875 420</a>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</footer>
|
|
</div>
|