phase iv - i

This commit is contained in:
juanatsap
2025-11-12 18:59:48 +00:00
parent 8f2704e10a
commit d35a1decc7
9 changed files with 29 additions and 975 deletions
+10 -16
View File
@@ -65,22 +65,6 @@
// Flag to keep header visible after navigation
let keepHeaderVisible = false;
// Toggle sidebar accordion (mobile only)
window.toggleSidebar = function(header) {
const content = header.nextElementSibling;
const isActive = header.classList.contains('active');
if (isActive) {
// Close
header.classList.remove('active');
content.classList.remove('active');
} else {
// Open
header.classList.add('active');
content.classList.add('active');
}
};
// Expand all sections
window.expandAllSections = function(event) {
event.preventDefault();
@@ -725,6 +709,16 @@
}, 5000);
};
// Add close button handler for error toast
document.addEventListener('click', function(e) {
if (e.target.closest('.error-close')) {
const errorToast = document.getElementById('error-toast');
if (errorToast) {
errorToast.style.display = 'none';
}
}
});
// =============================================================================
// HTMX EVENT HANDLERS
// =============================================================================
+18 -14
View File
@@ -10,12 +10,13 @@
<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">
<details class="sidebar-accordion" open>
<summary class="sidebar-accordion-header">
<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>
</summary>
<div class="sidebar-accordion-content">
{{range .SkillsLeft}}
<section class="sidebar-section">
<details open>
@@ -28,7 +29,8 @@
</details>
</section>
{{end}}
</div>
</div>
</details>
</aside>
<!-- Main Content Area - Page 1 -->
@@ -59,12 +61,13 @@
<!-- 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">
<details class="sidebar-accordion" open>
<summary class="sidebar-accordion-header">
<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>
</summary>
<div class="sidebar-accordion-content">
{{range .SkillsRight}}
<section class="sidebar-section">
<details open>
@@ -77,7 +80,8 @@
</details>
</section>
{{end}}
</div>
</div>
</details>
</aside>
</div>
-423
View File
@@ -1,423 +0,0 @@
<!-- 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>{{. | safeHTML}}</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>{{. | safeHTML}}</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>{{. | 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>
</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>{{. | safeHTML}}</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>
-521
View File
@@ -1,521 +0,0 @@
<!DOCTYPE html>
<html lang="{{if eq .Lang "es"}}es{{else}}en{{end}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Primary Meta Tags -->
<title>{{.CV.Personal.Name}} - {{if eq .Lang "es"}}Curriculum Vitae{{else}}Curriculum Vitae{{end}}</title>
<meta name="title" content="{{.CV.Personal.Name}} - {{if eq .Lang "es"}}CV Profesional{{else}}Professional CV{{end}}">
<meta name="description" content="{{.CV.Personal.Title}} | {{if eq .Lang "es"}}18 años de experiencia en desarrollo web, SAP CDC, React, Node.js, Go, HTMX y desarrollo asistido por IA{{else}}18 years of experience in web development, SAP CDC, React, Node.js, Go, HTMX and AI-assisted development{{end}}">
<meta name="keywords" content="{{if eq .Lang "es"}}CV, Curriculum Vitae, {{.CV.Personal.Name}}, Desarrollador FullStack, SAP CDC, React, Node.js, Go, HTMX, IA, Desarrollo Web, Consultor Técnico{{else}}CV, Resume, {{.CV.Personal.Name}}, FullStack Developer, SAP CDC, React, Node.js, Go, HTMX, AI, Web Development, Technical Consultant{{end}}">
<meta name="author" content="{{.CV.Personal.Name}}">
<meta name="robots" content="index, follow">
<link rel="canonical" href="{{.CanonicalURL}}">
<!-- Hreflang tags for international SEO -->
<link rel="alternate" hreflang="en" href="{{.AlternateEN}}">
<link rel="alternate" hreflang="es" href="{{.AlternateES}}">
<link rel="alternate" hreflang="x-default" href="https://juan.andres.morenorub.io/?lang=en">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="profile">
<meta property="og:url" content="{{.CV.Personal.Website}}">
<meta property="og:title" content="{{.CV.Personal.Name}} - {{if eq .Lang "es"}}CV Profesional{{else}}Professional CV{{end}}">
<meta property="og:description" content="{{.CV.Personal.Title}} | {{if eq .Lang "es"}}Consultor Técnico Senior con 18 años de experiencia{{else}}Senior Technical Consultant with 18 years of experience{{end}}">
<meta property="og:image" content="{{.CV.Personal.Website}}/static/images/profile.jpg">
<meta property="og:locale" content="{{if eq .Lang "es"}}es_ES{{else}}en_US{{end}}">
<meta property="og:site_name" content="{{.CV.Personal.Name}}">
<meta property="profile:first_name" content="Juan Andrés">
<meta property="profile:last_name" content="Moreno Rubio">
<meta property="profile:username" content="txeo">
<!-- Social Media Card (Generic) -->
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{.CV.Personal.Name}} - {{if eq .Lang "es"}}CV Profesional{{else}}Professional CV{{end}}">
<meta name="twitter:description" content="{{.CV.Personal.Title}}">
<meta name="twitter:image" content="{{.CV.Personal.Website}}/static/images/profile.jpg">
<!-- HTMX Configuration -->
<meta name="htmx-config" content='{"timeout":5000,"defaultSwapStyle":"innerHTML","defaultSwapDelay":0,"defaultSettleDelay":20}'>
<!-- HTMX with SRI (Subresource Integrity) -->
<script src="https://unpkg.com/htmx.org@1.9.10"
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
<!-- Iconify - Load synchronously for immediate rendering -->
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
<!-- CSS -->
<link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="/static/css/logo-toggle.css">
<link rel="stylesheet" href="/static/css/print.css" media="print">
<!-- Fonts with Preload -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Structured Data (JSON-LD) -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "{{.CV.Personal.Name}}",
"jobTitle": "{{.CV.Personal.Title}}",
"url": "{{.CV.Personal.Website}}",
"email": "{{.CV.Personal.Email}}",
"telephone": "{{.CV.Personal.Phone}}",
"address": {
"@type": "PostalAddress",
"addressLocality": "{{.CV.Personal.Location}}"
},
"sameAs": [
"{{.CV.Personal.LinkedIn}}",
"{{.CV.Personal.GitHub}}",
"{{.CV.Personal.Domestika}}"
],
"alumniOf": {
"@type": "EducationalOrganization",
"name": "Universidad de Extremadura"
},
"knowsAbout": [
"Web Development",
"SAP Customer Data Cloud",
"React",
"Node.js",
"Go",
"HTMX",
"AI-Assisted Development",
"Full Stack Development"
],
"worksFor": {
"@type": "Organization",
"name": "Olympic Broadcasting Services"
}
}
</script>
</head>
<body>
<!-- Single Black Bar with Everything -->
<div class="action-bar no-print" role="navigation" aria-label="Language and export controls">
<div class="action-bar-content">
<!-- Left: Site Title + Hamburger Menu + Language -->
<div class="site-title">
<!-- Left group: Logo + Hamburger -->
<div class="site-title-left">
<a href="/" class="site-logo-link" id="logoLink" aria-label="Home">
<iconify-icon icon="mdi:file-account" width="24" height="24" class="site-icon"></iconify-icon>
</a>
<!-- Hamburger Menu Button -->
<button class="hamburger-btn" aria-label="Toggle navigation menu">
<iconify-icon icon="mdi:menu" width="24" height="24"></iconify-icon>
</button>
</div>
<!-- Center: Title -->
<a href="/" class="site-title-link" id="titleLink">
<iconify-icon icon="mdi:file-account" width="20" height="20" class="site-icon-mobile"></iconify-icon>
<span class="site-title-text">CV JAMR<span class="site-title-year"> - {{.CurrentYear}}</span></span>
</a>
<!-- Right: Language selector -->
<div class="language-selector">
<button class="selector-btn {{if eq .Lang "en"}}active{{end}}" data-short="EN" onclick="selectLanguage('en')" aria-label="English">
English
</button>
<button class="selector-btn {{if eq .Lang "es"}}active{{end}}" data-short="ES" onclick="selectLanguage('es')" aria-label="Español">
Español
</button>
</div>
</div>
<!-- Center: View controls with labels -->
<div class="view-controls-center">
<!-- CV Length toggle -->
<div class="selector-group">
<label class="selector-label">{{if eq .Lang "es"}}Longitud{{else}}Length{{end}}:</label>
<label class="icon-toggle">
<input type="checkbox" id="lengthToggle" onchange="toggleCVLength()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:file-document-outline" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:file-document-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
<!-- Logo toggle -->
<div class="selector-group">
<label class="selector-label">{{if eq .Lang "es"}}Logos{{else}}Logos{{end}}:</label>
<label class="icon-toggle">
<input type="checkbox" id="logoToggle" checked onchange="toggleLogos()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:image-off-outline" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:image-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
<!-- Theme toggle -->
<div class="selector-group">
<label class="selector-label">{{if eq .Lang "es"}}Vista{{else}}View{{end}}:</label>
<label class="icon-toggle">
<input type="checkbox" id="themeToggle" onchange="toggleTheme()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:page-layout-sidebar-left" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:page-layout-body" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
</div>
<!-- Right: Action buttons -->
<div class="action-buttons-right">
<button
class="action-btn pdf-btn"
onclick="openPdfModal()"
aria-label="{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}">
<iconify-icon icon="mdi:download" width="18" height="18"></iconify-icon>
{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}
</button>
<button
class="action-btn print-btn"
onclick="printFriendly()"
aria-label="{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}">
<iconify-icon icon="mdi:leaf" width="18" height="18"></iconify-icon>
{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}
</button>
</div>
<span id="loading"
class="htmx-indicator"
role="status"
aria-live="polite"
aria-label="Loading">
<span class="loader"></span>
</span>
</div>
</div>
<!-- Navigation Menu (Hidden by default) -->
<nav id="navigation-menu" class="navigation-menu no-print" role="navigation" aria-label="CV sections">
<div class="menu-content">
<!-- CV Sections - Quick Navigation -->
<div class="menu-item-submenu">
<a href="#" class="menu-item has-submenu">
<iconify-icon icon="mdi:menu" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Secciones CV{{else}}CV Sections{{end}}</span>
<iconify-icon icon="mdi:chevron-right" width="16" height="16" class="submenu-arrow"></iconify-icon>
</a>
<div class="submenu-content">
<a href="#education" class="menu-item" onclick="scrollToSection('education')">
<iconify-icon icon="mdi:school" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Formación{{else}}Training{{end}}</span>
</a>
<a href="#skills" class="menu-item" onclick="scrollToSection('skills')">
<iconify-icon icon="mdi:brain" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Competencias{{else}}Skills{{end}}</span>
</a>
<a href="#experience" class="menu-item" onclick="scrollToSection('experience')">
<iconify-icon icon="mdi:office-building" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Experiencia{{else}}Experience{{end}}</span>
</a>
<a href="#awards" class="menu-item" onclick="scrollToSection('awards')">
<iconify-icon icon="mdi:trophy" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Premios y Reconocimientos{{else}}Awards{{end}}</span>
</a>
<a href="#projects" class="menu-item" onclick="scrollToSection('projects')">
<iconify-icon icon="mdi:web" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Proyectos Personales / Freelance{{else}}Personal / Freelance Projects{{end}}</span>
</a>
<a href="#courses" class="menu-item" onclick="scrollToSection('courses')">
<iconify-icon icon="mdi:school" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Cursos Realizados{{else}}Courses{{end}}</span>
</a>
<a href="#languages" class="menu-item" onclick="scrollToSection('languages')">
<iconify-icon icon="mdi:translate" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Idiomas{{else}}Languages{{end}}</span>
</a>
<a href="#references" class="menu-item" onclick="scrollToSection('references')">
<iconify-icon icon="mdi:link-variant" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Referencias{{else}}References{{end}}</span>
</a>
<a href="#other" class="menu-item" onclick="scrollToSection('other')">
<iconify-icon icon="mdi:information" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Otros{{else}}Other{{end}}</span>
</a>
</div>
</div>
<!-- Quick Actions Section -->
<div class="menu-section-wrapper">
<div class="menu-item menu-item-header">
<iconify-icon icon="mdi:cog-outline" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Acciones Rápidas{{else}}Quick Actions{{end}}</span>
</div>
<a href="#" class="menu-item menu-item-action" onclick="collapseAllSections(event)">
<iconify-icon icon="mdi:arrow-collapse-all" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Colapsar Todo{{else}}Collapse All{{end}}</span>
</a>
<a href="#" class="menu-item menu-item-action" onclick="expandAllSections(event)">
<iconify-icon icon="mdi:arrow-expand-all" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Expandir Todo{{else}}Expand All{{end}}</span>
</a>
<a href="#" id="show-zoom-menu-btn" class="menu-item menu-item-action" onclick="showZoomControl(event)" style="display: none;">
<iconify-icon icon="mdi:magnify" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Zoom{{else}}Zoom{{end}}</span>
</a>
</div>
<!-- View Controls in menu (visible only on mobile < 900px) -->
<div class="menu-controls-section">
<div class="menu-item menu-item-header">
<iconify-icon icon="mdi:tune-variant" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Controles de Vista{{else}}View Controls{{end}}</span>
</div>
<!-- CV Length toggle -->
<div class="menu-control-item">
<label class="menu-control-label">
<iconify-icon icon="mdi:file-document-outline" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Longitud{{else}}Length{{end}}</span>
</label>
<label class="icon-toggle">
<input type="checkbox" id="lengthToggleMenu" onchange="toggleCVLength()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:file-document-outline" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:file-document-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
<!-- Logo toggle -->
<div class="menu-control-item">
<label class="menu-control-label">
<iconify-icon icon="mdi:image-multiple-outline" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Logos{{else}}Logos{{end}}</span>
</label>
<label class="icon-toggle">
<input type="checkbox" id="logoToggleMenu" checked onchange="toggleLogos()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:image-off-outline" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:image-multiple-outline" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
<!-- Theme toggle -->
<div class="menu-control-item">
<label class="menu-control-label">
<iconify-icon icon="mdi:page-layout-sidebar-left" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Vista{{else}}View{{end}}</span>
</label>
<label class="icon-toggle">
<input type="checkbox" id="themeToggleMenu" onchange="toggleTheme()">
<span class="icon-toggle-slider">
<iconify-icon icon="mdi:page-layout-sidebar-left" width="16" height="16" class="icon-left"></iconify-icon>
<iconify-icon icon="mdi:page-layout-body" width="16" height="16" class="icon-right"></iconify-icon>
</span>
</label>
</div>
</div>
<!-- Action Buttons in menu (visible only on mobile < 900px) -->
<div class="menu-actions-section">
<div class="menu-item menu-item-header">
<iconify-icon icon="mdi:lightning-bolt" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Acciones{{else}}Actions{{end}}</span>
</div>
<button class="menu-action-btn" onclick="openPdfModal()">
<iconify-icon icon="mdi:download" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Descargar como PDF{{else}}Download as PDF{{end}}</span>
</button>
<button class="menu-action-btn" onclick="printFriendly()">
<iconify-icon icon="mdi:leaf" width="20" height="20"></iconify-icon>
<span>{{if eq .Lang "es"}}Imprimir amigable{{else}}Print Friendly{{end}}</span>
</button>
</div>
</div>
</nav>
<!-- Zoom Wrapper (for zoom functionality) -->
<div id="zoom-wrapper" class="zoom-wrapper">
<!-- CV Content Container -->
<div class="cv-container">
<main id="cv-content"
class="cv-paper"
role="main"
aria-live="polite">
{{template "cv-content.html" .}}
</main>
</div>
<!-- Footer (hidden in print) -->
<footer class="no-print">
<p style="text-align: center; margin-bottom: 0.5rem;">
<a href="https://github.com/juanatsap/cv-site" target="_blank" rel="noopener noreferrer" class="github-repo-link" style="text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem;">
<iconify-icon icon="mdi:github" width="20" height="20"></iconify-icon>
{{if eq .Lang "es"}}Ver este proyecto en GitHub{{else}}View this project on GitHub{{end}}
</a>
</p>
<p>© {{.CV.Meta.LastUpdated}} {{.CV.Personal.Name}} |
{{if eq .Lang "es"}}Última actualización{{else}}Last updated{{end}}: {{.CV.Meta.LastUpdated}}</p>
</footer>
<!-- Error Toast -->
<div id="error-toast" class="error-toast no-print" role="alert" aria-live="assertive" style="display: none;">
<span class="error-icon">⚠️</span>
<span id="error-message"></span>
<button onclick="this.parentElement.style.display='none'" aria-label="Close error message" class="error-close">×</button>
</div>
<!-- Back to Top Button -->
<button id="back-to-top" class="back-to-top no-print" aria-label="{{if eq .Lang "es"}}Volver arriba{{else}}Back to top{{end}}" style="display: none;">
<iconify-icon icon="mdi:arrow-up" width="24" height="24"></iconify-icon>
</button>
<!-- Info Button (Bottom Left) -->
<button id="info-button" class="info-button no-print" aria-label="{{if eq .Lang "es"}}Información{{else}}Information{{end}}" onclick="openInfoModal()">
<iconify-icon icon="mdi:information-outline" width="24" height="24"></iconify-icon>
</button>
<!-- Info Modal -->
<div id="info-modal" class="info-modal no-print" onclick="closeInfoModalOnBackdrop(event)">
<div class="info-modal-content" onclick="event.stopPropagation()">
<button class="info-modal-close" onclick="closeInfoModal()" aria-label="Close">
<iconify-icon icon="mdi:close" width="24" height="24"></iconify-icon>
</button>
<div class="info-modal-header">
<h2>{{.UI.InfoModal.Title}}</h2>
<div class="info-modal-cv-title">
CV {{.CurrentYear}} JAMR -
<span class="photo-bracket-wrapper">
<img src="/static/images/profile/dni.jpeg" alt="JAMR" class="info-modal-photo">
</span>
</div>
</div>
<div class="info-modal-body">
<p class="info-modal-description">
{{.UI.InfoModal.Description}}
</p>
<div class="info-modal-tech">
<div class="info-tech-item">
<iconify-icon icon="mdi:language-go" width="32" height="32"></iconify-icon>
<span>{{.UI.InfoModal.TechStack.GoHono}}</span>
</div>
<div class="info-tech-item">
<iconify-icon icon="mdi:lightning-bolt" width="32" height="32"></iconify-icon>
<span>{{.UI.InfoModal.TechStack.HTMX}}</span>
</div>
<div class="info-tech-item">
<iconify-icon icon="mdi:language-html5" width="32" height="32"></iconify-icon>
<span>{{.UI.InfoModal.TechStack.HTML5}}</span>
</div>
<div class="info-tech-item">
<iconify-icon icon="mdi:language-css3" width="32" height="32"></iconify-icon>
<span>{{.UI.InfoModal.TechStack.CSS3}}</span>
</div>
</div>
<p class="info-modal-github-subtext">{{.UI.InfoModal.ViewSourceSubtext}}</p>
<a href="https://github.com/juanatsap/cv-site" target="_blank" rel="noopener noreferrer" class="info-modal-github">
<iconify-icon icon="mdi:github" width="24" height="24"></iconify-icon>
<span>{{.UI.InfoModal.ViewSource}}</span>
<iconify-icon icon="mdi:arrow-right" width="20" height="20"></iconify-icon>
</a>
</div>
</div>
</div> <!-- End cv-container -->
</div> <!-- End zoom-wrapper -->
<!-- PDF Export Modal -->
<div id="pdf-modal" class="info-modal no-print" onclick="closePdfModalOnBackdrop(event)">
<div class="info-modal-content" onclick="event.stopPropagation()">
<button class="info-modal-close" onclick="closePdfModal()" aria-label="Close">
<iconify-icon icon="mdi:close" width="24" height="24"></iconify-icon>
</button>
<div class="info-modal-header">
<div class="icon" style="font-size: 3rem; margin-bottom: 1rem;">🚧</div>
<h2>{{if eq .Lang "es"}}Exportación PDF - En Desarrollo{{else}}PDF Export - Work in Progress{{end}}</h2>
</div>
<div class="info-modal-body">
<p class="info-modal-description">
{{if eq .Lang "es"}}
La función de exportación a PDF está siendo mejorada. Por favor, usa el botón <strong>Imprimir Amigable</strong> en su lugar (Ctrl+P o Cmd+P para guardar como PDF).
{{else}}
The PDF export feature is currently being improved. Please use the <strong>Print Friendly</strong> button instead (Ctrl+P or Cmd+P to save as PDF).
{{end}}
</p>
</div>
</div>
</div>
<!-- Zoom Control (Fixed Bottom Center, Draggable) -->
<div id="zoom-control" class="zoom-control no-print" role="group" aria-label="{{if eq .Lang "es"}}Control de zoom{{else}}Zoom control{{end}}">
<button
id="zoom-close"
class="zoom-close-btn"
aria-label="{{if eq .Lang "es"}}Cerrar control de zoom{{else}}Close zoom control{{end}}"
title="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}">
<iconify-icon icon="mdi:close" width="16" height="16"></iconify-icon>
</button>
<span class="zoom-value zoom-value-min" aria-hidden="true">25</span>
<input
type="range"
id="zoom-slider"
class="zoom-slider"
min="25"
max="175"
step="1"
value="100"
aria-label="{{if eq .Lang "es"}}Ajustar nivel de zoom del CV{{else}}Adjust CV zoom level{{end}}"
aria-valuemin="25"
aria-valuemax="175"
aria-valuenow="100"
aria-valuetext="100%">
<span class="zoom-value zoom-value-max" aria-hidden="true">175</span>
<button
id="zoom-reset"
class="zoom-reset-btn"
aria-label="{{if eq .Lang "es"}}Restablecer zoom al 100%{{else}}Reset zoom to 100%{{end}}"
title="{{if eq .Lang "es"}}Restablecer{{else}}Reset{{end}}"
aria-live="polite">
<span id="zoom-value-current">100</span>
</button>
</div>
<!-- External JavaScript - CSP Compliant -->
<script src="/static/js/main.js"></script>
<!-- Matomo Analytics - Nonce-based CSP -->
<script nonce="{{.CSPNonce}}">
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://matomo.drolo.club/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</body>
</html>
+1 -1
View File
@@ -3,6 +3,6 @@
<div id="error-toast" class="error-toast no-print" role="alert" aria-live="assertive" style="display: none;">
<span class="error-icon">⚠️</span>
<span id="error-message"></span>
<button onclick="this.parentElement.style.display='none'" aria-label="Close error message" class="error-close">×</button>
<button aria-label="Close error message" class="error-close">×</button>
</div>
{{end}}