feat: comprehensive UI redesign and content updates

- Updated action bar with transparent buttons (colored on hover only)
- Repositioned language selector after CV title for better flow
- Simplified toggle labels (removed parentheses values)
- Changed button styling: transparent by default, green/gray on hover
- Updated name format to "Moreno Rubio, Juan Andrés" with right alignment
- Added LIVGolf experience (Apr 2024-present) with detailed responsibilities
- Updated profile photo to dni.jpeg
- Refined summary text focusing on consultant/analyst/developer roles
- Added award logos (clicplan.png, drolosoft.png, teseo.png)
- Implemented smooth logo animations (fade/scale transitions)
- Adjusted toggle dimensions (80px wide, 30px tall) with smaller icons (16x16)
- Added breathing room to title and icon with proper padding
- Removed italic styling from name per user preference
This commit is contained in:
juanatsap
2025-11-07 11:49:47 +00:00
parent 27c2f4b44f
commit b3e4976204
12 changed files with 618 additions and 128 deletions
+34 -4
View File
@@ -14,7 +14,7 @@
"website": "https://juan.andres.morenoyrubio.com",
"photo": "/static/images/profile.jpg"
},
"summary": "Technical Consultant, Fullstack Developer, and AI enthusiast with 18 years of experience in the IT industry, specializing in SAP Customer Data Cloud, web technologies (mainly React and Node ecosystem), and AI integrations. Proven track record of leading technical projects and providing guidance to over 35 international clients. Seeking opportunities to apply and expand my skills in a challenging and rewarding environment.",
"summary": "Technical Consultant, Analyst and Fullstack Developer. Specialist in SAP Customer Data Cloud. Passionate about web technologies (mainly Go and Node/React ecosystems). Proven track record of leading technical projects and providing guidance to over 40 international clients. Seeking opportunities to apply and expand my skills in a challenging and rewarding environment.",
"experience": [
{
"position": "Senior SAP Technical Consultant",
@@ -40,6 +40,33 @@
"companyLogo": "olympic-broadcasting.png",
"shortDescription": "SAP CDC solutions for international broadcasting events. Custom implementations and technical guidance."
},
{
"position": "Senior SAP/CDC Technical Consultant",
"company": "LIV Golf",
"companyURL": "https://www.livgolf.com/",
"location": "Remote",
"startDate": "2024-04",
"endDate": "present",
"current": true,
"responsibilities": [
"Technical consulting about SAP Customer Data Cloud implementation and architecture",
"Created authorization process screens and user interfaces",
"Provided support to all teams regarding authentication and authorization",
"Developed backend endpoints and processes for data processing",
"Created comprehensive documentation for processes and project specifications",
"Bug solving and troubleshooting across the authentication system"
],
"technologies": [
"SAP CDC",
"JavaScript",
"React",
"Node.js",
"API Development",
"Authentication Systems"
],
"companyLogo": "livgolf.png",
"shortDescription": "Technical consulting for SAP CDC implementation. Created authorization screens, backend endpoints, and comprehensive documentation."
},
{
"position": "Senior Technical Consultant",
"company": "AENA (via Accenture Spain)",
@@ -609,19 +636,22 @@
"title": "Best Comparison Service with Clicplan",
"issuer": "eAwards",
"date": "2013-05",
"description": "Recognition for excellence in comparison service development"
"description": "Recognition for excellence in comparison service development",
"awardLogo": "clicplan.png"
},
{
"title": "Project Construction Scholarship for drolosoft",
"issuer": "Junta de Extremadura",
"date": "2009-08",
"description": "Scholarship for innovative software project development"
"description": "Scholarship for innovative software project development",
"awardLogo": "drolosoft.png"
},
{
"title": "Scholarship to work at TESEO Software Factory",
"issuer": "Universidad de Extremadura",
"date": "2004-04",
"description": "Academic scholarship for software development work"
"description": "Academic scholarship for software development work",
"awardLogo": "teseo.png"
}
],
"certifications": [
+34 -4
View File
@@ -14,7 +14,7 @@
"website": "https://juan.andres.morenoyrubio.com",
"photo": "/static/images/profile.jpg"
},
"summary": "Consultor Técnico, Desarrollador Fullstack, y entusiasta de IA con 18 años de experiencia en la industria IT, especializado en SAP Customer Data Cloud, tecnologías web (principalmente React y el ecosistema Node), e integraciones de IA. Historial comprobado liderando proyectos técnicos y proporcionando orientación a más de 35 clientes internacionales. Buscando oportunidades para aplicar y expandir mis habilidades en un entorno desafiante y gratificante.",
"summary": "Consultor Técnico, Analista y Desarrollador Fullstack. Especialista en SAP Customer Data Cloud. Apasionado por las tecnologías web (principalmente ecosistemas Go y Node/React). Trayectoria demostrada liderando proyectos técnicos y asesorando a más de 40 clientes internacionales. Buscando oportunidades para aplicar y expandir mis habilidades en un entorno desafiante y gratificante.",
"experience": [
{
"position": "Consultor Técnico Senior SAP",
@@ -40,6 +40,33 @@
"companyLogo": "olympic-broadcasting.png",
"shortDescription": "Soluciones SAP CDC para eventos de transmisión internacional. Implementaciones personalizadas y orientación técnica."
},
{
"position": "Consultor Técnico Senior SAP/CDC",
"company": "LIV Golf",
"companyURL": "https://www.livgolf.com/",
"location": "Remoto",
"startDate": "2024-04",
"endDate": "presente",
"current": true,
"responsibilities": [
"Consultoría técnica sobre implementación y arquitectura de SAP Customer Data Cloud",
"Creación de pantallas de proceso de autorización e interfaces de usuario",
"Soporte a todos los equipos en temas de autenticación y autorización",
"Desarrollo de endpoints backend y procesos para procesamiento de datos",
"Creación de documentación completa para procesos y especificaciones del proyecto",
"Resolución de bugs y troubleshooting del sistema de autenticación"
],
"technologies": [
"SAP CDC",
"JavaScript",
"React",
"Node.js",
"Desarrollo de APIs",
"Sistemas de Autenticación"
],
"companyLogo": "livgolf.png",
"shortDescription": "Consultoría técnica para implementación SAP CDC. Creación de pantallas de autorización, endpoints backend y documentación completa."
},
{
"position": "Consultor Técnico Senior",
"company": "AENA (vía Accenture Spain)",
@@ -650,19 +677,22 @@
"title": "Mejor Servicio de Comparación con Clicplan",
"issuer": "eAwards",
"date": "2013-05",
"description": "Reconocimiento por excelencia en desarrollo de servicio de comparación"
"description": "Reconocimiento por excelencia en desarrollo de servicio de comparación",
"awardLogo": "clicplan.png"
},
{
"title": "Beca de Construcción de Proyecto para drolosoft",
"issuer": "Junta de Extremadura",
"date": "2009-08",
"description": "Beca para desarrollo de proyecto de software innovador"
"description": "Beca para desarrollo de proyecto de software innovador",
"awardLogo": "drolosoft.png"
},
{
"title": "Beca para trabajar en Fábrica de Software TESEO",
"issuer": "Universidad de Extremadura",
"date": "2004-04",
"description": "Beca académica para trabajo de desarrollo de software"
"description": "Beca académica para trabajo de desarrollo de software",
"awardLogo": "teseo.png"
}
],
"certifications": [
+8
View File
@@ -64,6 +64,9 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
// Calculate years of experience
yearsOfExperience := calculateYearsOfExperience()
// Get current year
currentYear := time.Now().Year()
// Prepare template data
data := map[string]interface{}{
"CV": cv,
@@ -71,6 +74,7 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
"SkillsLeft": skillsLeft,
"SkillsRight": skillsRight,
"YearsOfExperience": yearsOfExperience,
"CurrentYear": currentYear,
}
// Render template
@@ -124,6 +128,9 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
// Calculate years of experience
yearsOfExperience := calculateYearsOfExperience()
// Get current year
currentYear := time.Now().Year()
// Prepare template data
data := map[string]interface{}{
"CV": cv,
@@ -131,6 +138,7 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
"SkillsLeft": skillsLeft,
"SkillsRight": skillsRight,
"YearsOfExperience": yearsOfExperience,
"CurrentYear": currentYear,
}
// Render template
+2 -2
View File
@@ -27,11 +27,11 @@ func SecurityHeaders(next http.Handler) http.Handler {
// Content Security Policy (comprehensive)
csp := "default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://unpkg.com; " +
"script-src 'self' 'unsafe-inline' https://unpkg.com https://code.iconify.design; " +
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
"font-src 'self' https://fonts.gstatic.com; " +
"img-src 'self' data: https:; " +
"connect-src 'self'; " +
"connect-src 'self' https://api.iconify.design; " +
"frame-ancestors 'self'; " +
"base-uri 'self'; " +
"form-action 'self'"
+1
View File
@@ -112,6 +112,7 @@ type Award struct {
Issuer string `json:"issuer"`
Date string `json:"date"`
Description string `json:"description"`
AwardLogo string `json:"awardLogo"`
}
type Certification struct {
+43 -11
View File
@@ -67,6 +67,8 @@
white-space: nowrap;
display: flex;
align-items: center;
justify-content: center;
height: 28px;
}
/* Flag icons - special styling */
@@ -115,6 +117,7 @@
display: flex;
gap: 1.2rem;
position: relative;
transition: gap 0.3s ease-in-out;
}
.experience-item:last-child {
@@ -122,23 +125,29 @@
padding-bottom: 0;
}
/* Adjust gap when logos are hidden */
.cv-paper:not(.show-logos) .experience-item {
gap: 0;
}
.company-logo {
display: block;
flex-shrink: 0;
}
.company-logo img {
width: 64px;
height: 64px;
width: 80px;
height: 80px;
object-fit: contain;
border-radius: 4px;
border: 1px solid #ddd;
background: white;
padding: 10px;
}
.default-company-icon {
width: 64px;
height: 64px;
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
@@ -146,7 +155,7 @@
border: 1px solid #ddd;
background: #f5f5f5;
color: #999;
padding: 8px;
padding: 10px;
}
.experience-content {
@@ -154,14 +163,37 @@
min-width: 0; /* Prevents flex item from overflowing */
}
/* Hide logos when toggle is OFF */
.cv-paper:not(.show-logos) .company-logo {
display: none;
/* Animate logos with fade and scale */
.company-logo,
.award-logo {
overflow: hidden;
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out, width 0.3s ease-in-out, height 0.3s ease-in-out, margin 0.3s ease-in-out;
opacity: 1;
transform: scale(1);
width: auto;
height: auto;
}
/* Show logos when toggle is ON (default) */
.show-logos .company-logo {
display: block;
/* Hide logos when toggle is OFF - with animation */
.cv-paper:not(.show-logos) .company-logo,
.cv-paper:not(.show-logos) .award-logo {
opacity: 0;
transform: scale(0.8);
width: 0;
height: 0;
margin: 0;
padding: 0;
pointer-events: none;
overflow: hidden;
}
/* Show logos when toggle is ON (default) - with animation */
.show-logos .company-logo,
.show-logos .award-logo {
opacity: 1;
transform: scale(1);
width: auto;
height: auto;
}
/* Hide logos in print by default */
+358 -32
View File
@@ -1,7 +1,7 @@
/* CV Design - Original Style Recreation */
/* Import Quicksand Font */
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Source+Sans+Pro:wght@400;600&display=swap');
/* Import Fonts */
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&family=Source+Sans+Pro:wght@400;600&family=Inter:wght@400;500;600;700&display=swap');
:root {
--bg-gray: rgb(82, 86, 89);
@@ -46,16 +46,18 @@ a:hover {
top: 0;
z-index: 100;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
}
.action-bar-content {
max-width: 100%;
margin: 0 auto;
padding: 0.75rem 1.5rem;
padding: 0;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
align-items: stretch;
gap: 2rem;
height: 50px;
}
/* Left: Site Title */
@@ -65,29 +67,72 @@ a:hover {
gap: 0.75rem;
justify-self: start;
white-space: nowrap;
padding: 0;
height: 100%;
}
.site-icon {
color: #fff;
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
height: 36px;
padding: 0 1rem;
}
/* Ensure Iconify icons display properly */
.iconify,
iconify-icon {
display: inline-block;
vertical-align: middle;
}
.site-title-text {
font-size: 1.1rem;
font-size: 1.05rem;
font-weight: 600;
color: #fff;
letter-spacing: 0.5px;
letter-spacing: -0.01em;
line-height: 1;
display: flex;
align-items: center;
height: 36px;
padding: 0 1rem;
}
/* Center: Toggle controls */
.toggle-controls-center {
/* Center: View controls with labels */
.view-controls-center {
display: flex;
flex-direction: row;
align-items: center;
gap: 1.5rem;
gap: 2.5rem;
justify-self: center;
flex-shrink: 0;
white-space: nowrap;
height: 100%;
}
.selector-group {
display: flex;
align-items: center;
gap: 0.75rem;
}
.selector-label {
font-size: 0.875rem;
color: rgba(255,255,255,0.85);
font-weight: 500;
white-space: nowrap;
letter-spacing: -0.01em;
line-height: 1;
display: flex;
align-items: center;
height: 36px;
}
.selector-label span {
color: #27ae60;
font-weight: 600;
}
.language-toggle,
@@ -113,7 +158,7 @@ a:hover {
color: white;
border-radius: 3px;
cursor: pointer;
font-size: 0.875rem;
font-size: 1rem;
font-weight: 400;
text-transform: capitalize;
transition: all 0.2s ease;
@@ -130,27 +175,198 @@ a:hover {
font-weight: 500;
}
.export-btn {
padding: 0.4rem 0.75rem;
background: transparent;
color: white;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 3px;
/* Icon Toggle Switches */
.icon-toggle {
position: relative;
display: inline-block;
cursor: pointer;
font-size: 0.8rem;
font-weight: 400;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.4rem;
text-decoration: none;
white-space: nowrap;
}
.export-btn:hover {
.icon-toggle input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.icon-toggle-slider {
position: relative;
display: inline-flex;
align-items: center;
justify-content: space-between;
width: 80px;
height: 30px;
background: rgba(255,255,255,0.1);
border-color: rgba(255,255,255,0.5);
text-decoration: none; /* Override default anchor hover */
border-radius: 15px;
padding: 0 6px;
transition: all 0.3s ease;
}
.icon-toggle-slider::before {
content: '';
position: absolute;
width: 24px;
height: 24px;
left: 3px;
background: white;
border-radius: 50%;
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
z-index: 2;
pointer-events: none;
}
.icon-toggle input:checked + .icon-toggle-slider::before {
transform: translateX(50px);
}
.icon-toggle input:checked + .icon-toggle-slider {
background: #27ae60;
}
.icon-toggle-slider .icon-left,
.icon-toggle-slider .icon-right {
position: absolute;
z-index: 3;
transition: all 0.3s ease;
flex-shrink: 0;
pointer-events: none;
}
.icon-toggle-slider .icon-left {
left: 8px;
}
.icon-toggle-slider .icon-right {
right: 7px;
}
.icon-toggle input:not(:checked) + .icon-toggle-slider .icon-left {
color: #333;
}
.icon-toggle input:not(:checked) + .icon-toggle-slider .icon-right {
color: rgba(255,255,255,0.3);
}
.icon-toggle input:checked + .icon-toggle-slider .icon-left {
color: rgba(255,255,255,0.3);
}
.icon-toggle input:checked + .icon-toggle-slider .icon-right {
color: #333;
}
.icon-toggle input:focus + .icon-toggle-slider {
box-shadow: 0 0 0 3px rgba(39, 174, 96, 0.2);
}
/* Language selector - matching action button style */
.language-selector {
display: inline-flex;
gap: 0;
padding: 0;
background: transparent;
border-radius: 0;
height: 100%;
align-items: stretch;
}
.selector-btn {
padding: 0 1.5rem;
background: transparent;
color: white;
border: none;
border-radius: 0;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
letter-spacing: -0.01em;
height: 100%;
line-height: 1;
transition: all 0.2s ease;
outline: none !important;
box-shadow: none !important;
}
.selector-btn:focus,
.selector-btn:focus-visible,
.selector-btn:active {
outline: none !important;
box-shadow: none !important;
}
.selector-btn:hover {
background: #666;
}
.selector-btn:hover iconify-icon {
color: #27ae60;
}
.selector-btn.active {
background: #27ae60;
color: white;
}
.selector-btn:not(.active) {
background: transparent;
color: white;
}
/* Action buttons - transparent with white text */
.action-btn {
padding: 0 1.5rem;
background: transparent;
color: white;
border: none;
border-radius: 0;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
letter-spacing: -0.01em;
height: 100%;
line-height: 1;
}
.action-btn iconify-icon {
color: white;
}
.action-btn:hover {
background: #ddd;
color: #333;
text-decoration: none;
}
.action-btn:hover iconify-icon {
color: #27ae60;
}
/* Print Friendly button - transparent by default, green on hover */
.print-btn {
background: transparent;
}
.print-btn:hover {
background: #27ae60;
color: white;
}
.print-btn:hover iconify-icon {
color: white;
}
/* CV Length Toggle - Center of action bar */
@@ -185,10 +401,17 @@ a:hover {
}
/* Action buttons styling (already positioned by grid) */
.action-buttons {
.action-buttons,
.action-buttons-right {
display: flex;
gap: 0.5rem;
align-items: center;
gap: 0;
align-items: stretch;
height: 100%;
}
.action-buttons-right {
justify-self: end;
margin-left: auto;
}
/* Loading Indicator */
@@ -223,6 +446,46 @@ a:hover {
display: block; /* Changed from flex */
}
/* Clean theme - no sidebars, centered content */
.cv-container.theme-clean {
padding: 20px 0 0 0;
transition: all 0.3s ease-in-out;
}
.theme-clean .cv-page {
box-shadow: none;
border: none;
margin: 0 auto;
max-width: 900px;
transition: all 0.3s ease-in-out;
}
/* Animate sidebar, header, footer when hiding/showing */
.cv-sidebar,
.cv-title-badges-header,
.cv-footer {
overflow: hidden;
transition: all 0.3s ease-in-out;
}
.theme-clean .cv-sidebar,
.theme-clean .cv-title-badges-header,
.theme-clean .cv-footer {
display: none !important;
animation: fadeOutShrink 0.3s ease-in-out;
}
.theme-clean .page-content {
grid-template-columns: 1fr !important;
transition: grid-template-columns 0.3s ease-in-out;
}
.theme-clean .cv-main {
grid-column: 1 !important;
padding: 2rem !important;
transition: all 0.3s ease-in-out;
}
/* CV Paper - Container for two-page layout */
.cv-paper {
width: 100%;
@@ -281,7 +544,7 @@ a:hover {
/* Main Content - Right column */
.cv-main {
background: var(--paper-white);
padding: 2rem 2.5rem;
padding: 3rem 2.5rem;
}
/* Professional Title Badges - Spans Both Columns */
@@ -336,6 +599,12 @@ a:hover {
overflow: hidden;
border: 3px solid white;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
/* Colocación a manopla de la foto :) */
margin: 15px;
{{/* top: 25px; */}}
position: relative;
right: 5px;
}
.cv-photo img {
@@ -348,9 +617,11 @@ a:hover {
font-family: 'Quicksand', sans-serif;
font-size: 2.2em;
font-weight: 400;
{{/* font-style: italic; */}}
line-height: 1.1;
margin-bottom: 8px;
color: rgb(0, 0, 0);
text-align: right;
}
.cv-experience-years {
@@ -369,6 +640,7 @@ a:hover {
color: #666;
margin: 4px 0 0 0;
line-height: 1.4;
text-align: right;
}
/* Intro/Excerpt Text - Positioned inside header, matching old React CV */
@@ -393,7 +665,7 @@ a:hover {
font-size: 1.3em;
font-weight: 500;
line-height: 1.2em;
margin: 10px 0;
margin: 10px 0 20px 0;
padding: 0;
color: rgb(51, 51, 51);
}
@@ -981,6 +1253,60 @@ a:focus {
margin-bottom: 1em;
}
/* Award item with logo */
.award-item {
display: flex;
gap: 1.2rem;
align-items: flex-start;
margin-bottom: 2.5rem;
padding-bottom: 2rem;
border-bottom: 2px solid #ddd;
page-break-inside: avoid;
transition: gap 0.3s ease-in-out;
}
.award-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
/* Adjust gap when logos are hidden */
.cv-paper:not(.show-logos) .award-item {
gap: 0;
}
.award-logo {
flex-shrink: 0;
display: block;
}
.award-logo img {
width: 80px;
height: 80px;
object-fit: contain;
border-radius: 4px;
border: 1px solid #ddd;
background: white;
padding: 10px;
}
.default-award-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;
}
.award-content {
flex: 1;
}
.award-item strong,
.course-item strong,
.language-item strong {
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

+14 -7
View File
@@ -35,13 +35,13 @@
<div class="cv-header">
<div class="cv-header-content">
<div class="cv-header-left">
<h1 class="cv-name">{{.CV.Personal.Name}}</h1>
<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>
<!-- Intro/Excerpt Text - No section heading, just the text -->
<div class="intro-text">{{.CV.Summary}}</div>
</div>
<div class="cv-photo">
<img src="/static/images/profile/photo.jpg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
<img src="/static/images/profile/dni.jpeg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
</div>
</div>
</div>
@@ -76,9 +76,9 @@
<div class="experience-item">
<div class="company-logo">
{{if .CompanyLogo}}
<img src="/static/images/companies/{{.CompanyLogo}}" alt="{{.Company}} logo" onerror="this.parentElement.innerHTML='<span class=\'iconify default-company-icon\' data-icon=\'mdi:office-building\' data-width=\'48\' data-height=\'48\'></span>'">
<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}}
<span class="iconify default-company-icon" data-icon="mdi:office-building" data-width="48" data-height="48"></span>
<iconify-icon icon="mdi:office-building" width="60" height="60" class="default-company-icon"></iconify-icon>
{{end}}
</div>
<div class="experience-content">
@@ -148,9 +148,16 @@
<h3 class="section-title">{{if eq .Lang "es"}}Premios y Reconocimientos{{else}}Awards{{end}}</h3>
{{range .CV.Awards}}
<div class="award-item">
<strong>{{.Title}}</strong><br>
<small>{{.Issuer}} - {{.Date}}</small>
{{if .Description}}<p class="award-desc">{{.Description}}</p>{{end}}
{{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 .Description}}<p class="award-desc">{{.Description}}</p>{{end}}
</div>
</div>
{{end}}
</section>
+124 -68
View File
@@ -39,8 +39,8 @@
integrity="sha384-D1Kt99CQMDuVetoL1lrYwg5t+9QdHe7NLX/SoJYkXDFfX37iInKRy5xLSi8nO7UC"
crossorigin="anonymous"></script>
<!-- Iconify -->
<script src="https://code.iconify.design/3/3.1.1/iconify.min.js"></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">
@@ -97,77 +97,77 @@
<!-- 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 -->
<!-- Left: Site Title and Language -->
<div class="site-title">
<span class="iconify site-icon" data-icon="mdi:file-account" data-width="24" data-height="24"></span>
<span class="site-title-text">{{if eq .Lang "es"}}Curriculum Vitae 2025{{else}}Curriculum Vitae 2025{{end}}</span>
<iconify-icon icon="mdi:file-account" width="24" height="24" class="site-icon"></iconify-icon>
<span class="site-title-text">CV {{.CurrentYear}} - JAMR</span>
<!-- Language selector (after title) -->
<div class="language-selector">
<button class="selector-btn {{if eq .Lang "en"}}active{{end}}" onclick="selectLanguage('en')" aria-label="English">
English
</button>
<button class="selector-btn {{if eq .Lang "es"}}active{{end}}" onclick="selectLanguage('es')" aria-label="Español">
Español
</button>
</div>
</div>
<!-- Center: Toggle controls -->
<div class="toggle-controls-center">
<!-- Language toggle -->
<div class="language-toggle">
<span class="toggle-label-left flag-icon">
<span class="iconify" data-icon="circle-flags:us" data-width="28" data-height="28"></span>
</span>
<label class="toggle-switch">
<input type="checkbox" id="langToggle" {{if eq .Lang "es"}}checked{{end}} onclick="toggleLanguage()" aria-label="{{if eq .Lang "es"}}Switch to English{{else}}Switch to Spanish{{end}}">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label-right flag-icon">
<span class="iconify" data-icon="circle-flags:es" data-width="28" data-height="28"></span>
</span>
</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>
<!-- Center: CV Length Toggle -->
<div class="cv-length-toggle">
<span class="toggle-label-left">
<span class="iconify" data-icon="mdi:file-document-outline" data-width="24" data-height="24"></span>
</span>
<label class="toggle-switch">
<input type="checkbox" id="lengthToggle" onclick="toggleCVLength()" aria-label="{{if eq .Lang "es"}}Cambiar longitud del CV{{else}}Toggle CV length{{end}}">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label-right">
<span class="iconify" data-icon="mdi:file-document-multiple-outline" data-width="24" data-height="24"></span>
</span>
</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>
<!-- Center Right: Logo Toggle -->
<div class="logo-toggle">
<span class="toggle-label-left">
<span class="iconify" data-icon="mdi:image-off-outline" data-width="24" data-height="24"></span>
</span>
<label class="toggle-switch">
<input type="checkbox" id="logoToggle" checked onclick="toggleLogos()" aria-label="{{if eq .Lang "es"}}Mostrar logos de empresas{{else}}Show company logos{{end}}">
<span class="toggle-slider"></span>
</label>
<span class="toggle-label-right">
<span class="iconify" data-icon="mdi:image-multiple-outline" data-width="24" data-height="24"></span>
</span>
</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">
<div class="action-buttons-right">
<a
class="export-btn"
class="action-btn pdf-btn"
href="/export/pdf?lang={{.Lang}}"
download
aria-label="{{if eq .Lang "es"}}Descargar PDF del CV{{else}}Download CV as PDF{{end}}">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="display: inline-block; vertical-align: middle;">
<path d="M8.5 11.5l3.5-3.5h-2.5v-6h-2v6h-2.5l3.5 3.5zm-6.5 2.5v2h12v-2h-12z"/>
</svg>
{{if eq .Lang "es"}}Descargar PDF{{else}}Download as PDF{{end}}
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}}
</a>
<button
class="export-btn"
onclick="window.print()"
aria-label="{{if eq .Lang "es"}}Imprimir CV{{else}}Print CV{{end}}">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" style="display: inline-block; vertical-align: middle;">
<path d="M14 4h-3v-3h-6v3h-3c-1.1 0-2 0.9-2 2v5h3v4h8v-4h3v-5c0-1.1-0.9-2-2-2zm-7-2h2v2h-2v-2zm5 11h-8v-5h8v5zm2-7c-0.552 0-1-0.448-1-1s0.448-1 1-1 1 0.448 1 1-0.448 1-1 1z"/>
</svg>
{{if eq .Lang "es"}}Imprimir{{else}}Print Friendly{{end}}
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>
@@ -205,9 +205,12 @@
</div>
<script>
function toggleLanguage() {
const checkbox = document.getElementById('langToggle');
const lang = checkbox.checked ? 'es' : 'en';
function selectLanguage(lang) {
// Update button states
document.querySelectorAll('.language-selector .selector-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.closest('.selector-btn').classList.add('active');
// Use HTMX to load new content
htmx.ajax('GET', `/cv?lang=${lang}`, {
@@ -226,33 +229,86 @@
}
function toggleCVLength() {
const checkbox = document.getElementById('lengthToggle');
const toggle = document.getElementById('lengthToggle');
const paper = document.querySelector('.cv-paper');
const lang = document.documentElement.lang;
const label = document.getElementById('lengthLabel');
if (checkbox.checked) {
if (toggle.checked) {
label.textContent = lang === 'es' ? 'Largo' : 'Long';
paper.classList.add('cv-long');
paper.classList.remove('cv-short');
} else {
label.textContent = lang === 'es' ? 'Corto' : 'Short';
paper.classList.add('cv-short');
paper.classList.remove('cv-long');
}
}
function toggleLogos() {
const checkbox = document.getElementById('logoToggle');
const toggle = document.getElementById('logoToggle');
const paper = document.querySelector('.cv-paper');
const lang = document.documentElement.lang;
const label = document.getElementById('logoLabel');
if (checkbox.checked) {
if (toggle.checked) {
label.textContent = lang === 'es' ? 'Con' : 'On';
paper.classList.add('show-logos');
} else {
label.textContent = lang === 'es' ? 'Sin' : 'Off';
paper.classList.remove('show-logos');
}
}
// Initialize with short version and logos enabled
function toggleTheme() {
const toggle = document.getElementById('themeToggle');
const container = document.querySelector('.cv-container');
const lang = document.documentElement.lang;
const label = document.getElementById('themeLabel');
if (toggle.checked) {
label.textContent = lang === 'es' ? 'Limpia' : 'Clean';
container.classList.add('theme-clean');
localStorage.setItem('cv-theme', 'clean');
} else {
label.textContent = lang === 'es' ? 'Completa' : 'Full';
container.classList.remove('theme-clean');
localStorage.setItem('cv-theme', 'default');
}
}
// Print Friendly - applies Clean theme before printing
function printFriendly() {
const container = document.querySelector('.cv-container');
const wasClean = container.classList.contains('theme-clean');
// Apply clean theme for printing
if (!wasClean) {
container.classList.add('theme-clean');
}
// Print
window.print();
// Restore original theme after print dialog
setTimeout(() => {
if (!wasClean) {
container.classList.remove('theme-clean');
}
}, 100);
}
// Initialize with short version, logos enabled, and saved theme
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('.cv-paper').classList.add('cv-short');
document.querySelector('.cv-paper').classList.add('show-logos');
// Restore theme preference
const savedTheme = localStorage.getItem('cv-theme') || 'default';
if (savedTheme === 'clean') {
document.getElementById('themeToggle').checked = true;
toggleTheme();
}
});
// Error handling utility