Add photo, company logos, and short/long CV toggle

Features:
- Profile photo display (right side, inline with header)
- Company logos for all major employers (8 logos downloaded)
- Short/Long CV toggle for condensed/detailed view
- Short descriptions (1-2 lines) for quick overview
- Experience separators with border lines

Photo Implementation:
- Circular photo (120px) on right side of header
- Placeholder SVG if photo not uploaded
- Instructions in ADDING-YOUR-PHOTO.md
- Photo stored in static/images/profile/

Company Logos:
- Olympic Broadcasting Services, AENA, SAP, Gigya
- Accenture, Everis, Indra, Megabanner
- 40px logos displayed inline with experience
- Auto-hide if logo missing
- Mobile: logos hidden for cleaner layout

Short/Long Toggle:
- Toggle buttons in action bar (Corto/Largo)
- Short mode: shows shortDescription only
- Long mode: shows full responsibilities + technologies
- CSS-based show/hide (no page reload)
- Defaults to short view

Layout Updates:
- Header: text left, photo right, inline alignment
- Experience items: separated by border lines
- Responsive: photo centers on mobile
- Print-optimized: smaller photo in PDF

Data Updates:
- Added shortDescription field to Experience struct
- 13 short descriptions for all positions (EN/ES)
- Added companyLogo field with filename mapping
- JSON updated with all new fields

Tech:
- Pure CSS toggle (no HTMX needed)
- Vanilla JavaScript for button states
- Maintains bilingual support (ES/EN)
This commit is contained in:
juanatsap
2025-10-27 22:32:32 +00:00
parent dab68f34f2
commit cd5d5cff02
17 changed files with 397 additions and 43 deletions
+72
View File
@@ -0,0 +1,72 @@
# Cómo Añadir tu Foto al CV
## 📸 Paso 1: Prepara tu Foto
1. **Busca una foto profesional** (preferiblemente tipo LinkedIn)
2. **Formato recomendado**: JPG o PNG
3. **Tamaño**: Al menos 300x300 píxeles (cuadrada es mejor)
4. **Calidad**: Fondo neutro, buena iluminación
## 📁 Paso 2: Guarda la Foto
Guarda tu foto en:
```
static/images/profile/photo.jpg
```
Puedes usar cualquiera de estos nombres:
- `photo.jpg` ✅ (recomendado)
- `photo.png` (cambiar en template)
- `profile.jpg` (cambiar en template)
## 🔄 Paso 3: Actualizar (si usas otro nombre)
Si tu foto se llama diferente a `photo.jpg`, edita `templates/cv-content.html`:
```html
<!-- Cambiar esta línea: -->
<img src="/static/images/profile/photo.jpg" ...>
<!-- Por ejemplo, si tu foto es profile.png: -->
<img src="/static/images/profile/profile.png" ...>
```
## 🖼️ Descargar desde LinkedIn
### Opción 1: Manualmente
1. Abre tu perfil de LinkedIn
2. Click derecho en tu foto → "Guardar imagen como..."
3. Guárdala como `photo.jpg` en `static/images/profile/`
### Opción 2: Desde tu sitio actual
Si ya tienes tu foto en https://juan.andres.morenoyrubio.com:
```bash
cd static/images/profile
# Abre el inspector del navegador, busca tu foto, y copia la URL
curl -o photo.jpg "URL_DE_TU_FOTO"
```
## ✅ Verificar
1. Reinicia el servidor: `./cv-server`
2. Abre http://localhost:8080
3. Deberías ver tu foto en la esquina superior izquierda
Si no funciona, verás un placeholder gris con el texto "Add your photo".
## 🎨 Ajustar el Tamaño (Opcional)
La foto se muestra como un círculo de 120px. Para cambiar el tamaño, edita `static/css/main.css`:
```css
.cv-photo {
width: 150px; /* Cambiar aquí */
height: 150px; /* Y aquí */
border-radius: 50%;
}
```
---
**Nota**: El template ya incluye un fallback automático al placeholder si la foto no existe, así que el sitio funcionará con o sin foto.
+34 -13
View File
@@ -35,7 +35,9 @@
"React", "React",
"Node.js", "Node.js",
"API Integration" "API Integration"
] ],
"companyLogo": "olympic-broadcasting.png",
"shortDescription": "SAP CDC solutions for international broadcasting events. Custom implementations and technical guidance."
}, },
{ {
"position": "Senior Technical Consultant", "position": "Senior Technical Consultant",
@@ -64,7 +66,9 @@
"highlights": [ "highlights": [
"Successfully deployed authentication system for all AENA airports in Spain", "Successfully deployed authentication system for all AENA airports in Spain",
"Managed identity flows for millions of users across web and mobile platforms" "Managed identity flows for millions of users across web and mobile platforms"
] ],
"companyLogo": "aena.png",
"shortDescription": "Lead Technical Consultant for AENA Airports Authentication System serving millions of passengers across all Spanish airports."
}, },
{ {
"position": "Senior Technical Consultant", "position": "Senior Technical Consultant",
@@ -87,7 +91,9 @@
"JavaScript", "JavaScript",
"Cloud Platforms", "Cloud Platforms",
"Technical Documentation" "Technical Documentation"
] ],
"companyLogo": "sap.png",
"shortDescription": "SAP Customer Data Cloud technical consulting, troubleshooting, and stakeholder education on GDPR compliance."
}, },
{ {
"position": "Junior Technical Consultant", "position": "Junior Technical Consultant",
@@ -109,7 +115,9 @@
"JavaScript", "JavaScript",
"Customer Support", "Customer Support",
"System Monitoring" "System Monitoring"
] ],
"companyLogo": "gigya.png",
"shortDescription": "Technical support and problem-solving for Gigya platform. System monitoring and training program development."
}, },
{ {
"position": "Fullstack Developer", "position": "Fullstack Developer",
@@ -130,7 +138,9 @@
"Video Processing", "Video Processing",
"Database Design", "Database Design",
"PostgreSQL" "PostgreSQL"
] ],
"companyLogo": "megabanner.png",
"shortDescription": "Full-stack development with video system integration for advertisement inclusion in gas station networks."
}, },
{ {
"position": "Fullstack Developer", "position": "Fullstack Developer",
@@ -152,7 +162,9 @@
"API Design", "API Design",
"CI/CD", "CI/CD",
"DevOps" "DevOps"
] ],
"companyLogo": "everis.png",
"shortDescription": "API design and automated deployment pipelines. Software testing and scalability implementation."
}, },
{ {
"position": "FullStack Developer", "position": "FullStack Developer",
@@ -170,7 +182,9 @@
"JavaScript", "JavaScript",
"Redux", "Redux",
"Webpack" "Webpack"
] ],
"companyLogo": "everis.png",
"shortDescription": "React application development for multiple clients."
}, },
{ {
"position": "Fullstack Developer", "position": "Fullstack Developer",
@@ -187,7 +201,9 @@
"Java", "Java",
"JavaScript", "JavaScript",
"Web Development" "Web Development"
] ],
"companyLogo": "indra.png",
"shortDescription": "Project management and customer feedback collection across development stages."
}, },
{ {
"position": "Technical Director / Programmer", "position": "Technical Director / Programmer",
@@ -212,7 +228,8 @@
"highlights": [ "highlights": [
"Reduced production times by 75% through optimized pipelines", "Reduced production times by 75% through optimized pipelines",
"Successfully managed technical team and product development" "Successfully managed technical team and product development"
] ],
"shortDescription": "Technical Director leading development of backend and 5 websites. Reduced production times by 75%."
}, },
{ {
"position": "Programmer Analyst (Freelance)", "position": "Programmer Analyst (Freelance)",
@@ -230,7 +247,8 @@
"PHP", "PHP",
"MySQL", "MySQL",
"JavaScript" "JavaScript"
] ],
"shortDescription": "WordPress and PHP website development as freelance programmer."
}, },
{ {
"position": "Analyst Programmer / Expert Technician", "position": "Analyst Programmer / Expert Technician",
@@ -248,7 +266,8 @@
"Java", "Java",
"System Configuration", "System Configuration",
"Technical Support" "Technical Support"
] ],
"shortDescription": "Software and hardware configuration, technical problem-solving, and team mentoring."
}, },
{ {
"position": "Senior Programmer", "position": "Senior Programmer",
@@ -266,7 +285,8 @@
"Java", "Java",
"Search Engine Technology", "Search Engine Technology",
"European R&D Projects" "European R&D Projects"
] ],
"shortDescription": "European R&D project for revolutionary search engine development."
}, },
{ {
"position": "Junior Programmer", "position": "Junior Programmer",
@@ -285,7 +305,8 @@
"Java Applets", "Java Applets",
"Data Visualization", "Data Visualization",
"Chart Generation" "Chart Generation"
] ],
"shortDescription": "JAVA development specialized in data chart generation and applet development."
} }
], ],
"education": [ "education": [
+34 -13
View File
@@ -35,7 +35,9 @@
"React", "React",
"Node.js", "Node.js",
"Integración de APIs" "Integración de APIs"
] ],
"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", "position": "Consultor Técnico Senior",
@@ -64,7 +66,9 @@
"highlights": [ "highlights": [
"Despliegue exitoso del sistema de autenticación para todos los aeropuertos AENA en España", "Despliegue exitoso del sistema de autenticación para todos los aeropuertos AENA en España",
"Gestión de flujos de identidad para millones de usuarios en plataformas web y móviles" "Gestión de flujos de identidad para millones de usuarios en plataformas web y móviles"
] ],
"companyLogo": "aena.png",
"shortDescription": "Consultor Técnico Principal del Sistema de Autenticación de Aeropuertos AENA sirviendo a millones de pasajeros en todos los aeropuertos españoles."
}, },
{ {
"position": "Consultor Técnico Senior", "position": "Consultor Técnico Senior",
@@ -87,7 +91,9 @@
"JavaScript", "JavaScript",
"Plataformas Cloud", "Plataformas Cloud",
"Documentación Técnica" "Documentación Técnica"
] ],
"companyLogo": "sap.png",
"shortDescription": "Consultoría técnica SAP Customer Data Cloud, resolución de problemas y educación de stakeholders en cumplimiento GDPR."
}, },
{ {
"position": "Consultor Técnico Junior", "position": "Consultor Técnico Junior",
@@ -109,7 +115,9 @@
"JavaScript", "JavaScript",
"Soporte al Cliente", "Soporte al Cliente",
"Monitoreo de Sistemas" "Monitoreo de Sistemas"
] ],
"companyLogo": "gigya.png",
"shortDescription": "Soporte técnico y resolución de problemas para plataforma Gigya. Monitoreo de sistemas y desarrollo de programas de formación."
}, },
{ {
"position": "Desarrollador Fullstack", "position": "Desarrollador Fullstack",
@@ -130,7 +138,9 @@
"Procesamiento de Video", "Procesamiento de Video",
"Diseño de Bases de Datos", "Diseño de Bases de Datos",
"PostgreSQL" "PostgreSQL"
] ],
"companyLogo": "megabanner.png",
"shortDescription": "Desarrollo full-stack con integración de sistema de video para inclusión de anuncios en redes de estaciones de servicio."
}, },
{ {
"position": "Desarrollador Fullstack", "position": "Desarrollador Fullstack",
@@ -152,7 +162,9 @@
"Diseño de APIs", "Diseño de APIs",
"CI/CD", "CI/CD",
"DevOps" "DevOps"
] ],
"companyLogo": "everis.png",
"shortDescription": "Diseño de APIs y pipelines de despliegue automatizados. Testing de software e implementación de escalabilidad."
}, },
{ {
"position": "Desarrollador FullStack", "position": "Desarrollador FullStack",
@@ -170,7 +182,9 @@
"JavaScript", "JavaScript",
"Redux", "Redux",
"Webpack" "Webpack"
] ],
"companyLogo": "everis.png",
"shortDescription": "Desarrollo de aplicaciones React para múltiples clientes."
}, },
{ {
"position": "Desarrollador Fullstack", "position": "Desarrollador Fullstack",
@@ -187,7 +201,9 @@
"Java", "Java",
"JavaScript", "JavaScript",
"Desarrollo Web" "Desarrollo Web"
] ],
"companyLogo": "indra.png",
"shortDescription": "Gestión de proyectos y recopilación de feedback de clientes en diferentes etapas de desarrollo."
}, },
{ {
"position": "Director Técnico / Programador", "position": "Director Técnico / Programador",
@@ -212,7 +228,8 @@
"highlights": [ "highlights": [
"Reducción del 75% en tiempos de producción mediante pipelines optimizados", "Reducción del 75% en tiempos de producción mediante pipelines optimizados",
"Gestión exitosa de equipo técnico y desarrollo de productos" "Gestión exitosa de equipo técnico y desarrollo de productos"
] ],
"shortDescription": "Director Técnico liderando desarrollo de backend y 5 sitios web. Reducción del 75% en tiempos de producción."
}, },
{ {
"position": "Analista Programador (Freelance)", "position": "Analista Programador (Freelance)",
@@ -230,7 +247,8 @@
"PHP", "PHP",
"MySQL", "MySQL",
"JavaScript" "JavaScript"
] ],
"shortDescription": "Desarrollo de sitios web WordPress y PHP como programador freelance."
}, },
{ {
"position": "Analista Programador / Técnico Experto", "position": "Analista Programador / Técnico Experto",
@@ -248,7 +266,8 @@
"Java", "Java",
"Configuración de Sistemas", "Configuración de Sistemas",
"Soporte Técnico" "Soporte Técnico"
] ],
"shortDescription": "Configuración de software y hardware, resolución de problemas técnicos y mentoría de equipos."
}, },
{ {
"position": "Programador Senior", "position": "Programador Senior",
@@ -266,7 +285,8 @@
"Java", "Java",
"Tecnología de Motores de Búsqueda", "Tecnología de Motores de Búsqueda",
"Proyectos Europeos I+D" "Proyectos Europeos I+D"
] ],
"shortDescription": "Proyecto europeo I+D para desarrollo de motor de búsqueda revolucionario."
}, },
{ {
"position": "Programador Junior", "position": "Programador Junior",
@@ -285,7 +305,8 @@
"Applets Java", "Applets Java",
"Visualización de Datos", "Visualización de Datos",
"Generación de Gráficos" "Generación de Gráficos"
] ],
"shortDescription": "Desarrollo JAVA especializado en generación de gráficos de datos y desarrollo de applets."
} }
], ],
"education": [ "education": [
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# Download company logos
cd static/images/companies
# Olympic Broadcasting Services
curl -sL "https://logo.clearbit.com/obs.tv" -o olympic-broadcasting.png 2>/dev/null || echo "OBS logo not found"
# AENA
curl -sL "https://logo.clearbit.com/aena.es" -o aena.png 2>/dev/null || echo "AENA logo not found"
# SAP
curl -sL "https://logo.clearbit.com/sap.com" -o sap.png 2>/dev/null || echo "SAP logo not found"
# Gigya (now SAP CDC)
curl -sL "https://logo.clearbit.com/gigya.com" -o gigya.png 2>/dev/null || echo "Gigya logo not found"
# Accenture
curl -sL "https://logo.clearbit.com/accenture.com" -o accenture.png 2>/dev/null || echo "Accenture logo not found"
# Megabanner
curl -sL "https://logo.clearbit.com/megabanner.es" -o megabanner.png 2>/dev/null || echo "Megabanner logo not found"
# Everis
curl -sL "https://logo.clearbit.com/everis.com" -o everis.png 2>/dev/null || echo "Everis logo not found"
# Indra
curl -sL "https://logo.clearbit.com/indra.es" -o indra.png 2>/dev/null || echo "Indra logo not found"
echo "✅ Company logos downloaded"
ls -lh
+2
View File
@@ -41,10 +41,12 @@ type Personal struct {
type Experience struct { type Experience struct {
Position string `json:"position"` Position string `json:"position"`
Company string `json:"company"` Company string `json:"company"`
CompanyLogo string `json:"companyLogo"`
Location string `json:"location"` Location string `json:"location"`
StartDate string `json:"startDate"` StartDate string `json:"startDate"`
EndDate string `json:"endDate"` EndDate string `json:"endDate"`
Current bool `json:"current"` Current bool `json:"current"`
ShortDescription string `json:"shortDescription"`
Responsibilities []string `json:"responsibilities"` Responsibilities []string `json:"responsibilities"`
Technologies []string `json:"technologies"` Technologies []string `json:"technologies"`
Highlights []string `json:"highlights"` Highlights []string `json:"highlights"`
+151 -6
View File
@@ -124,13 +124,39 @@ a:hover {
min-height: 11in; min-height: 11in;
} }
/* Header */ /* Header - Photo on right, inline with text */
.cv-header { .cv-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 2rem;
border-bottom: 2px solid var(--text-dark); border-bottom: 2px solid var(--text-dark);
padding-bottom: 1.5rem; padding-bottom: 1.5rem;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.cv-header-left {
flex: 1;
}
.cv-header-right {
flex-shrink: 0;
}
.cv-photo {
width: 120px;
height: 120px;
border-radius: 50%;
overflow: hidden;
border: 3px solid var(--border-gray);
}
.cv-photo img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cv-name { .cv-name {
font-size: 2.5rem; font-size: 2.5rem;
font-weight: 700; font-weight: 700;
@@ -170,9 +196,15 @@ a:hover {
text-align: justify; text-align: justify;
} }
/* Experience */ /* Experience - with separators */
.experience-item { .experience-item {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border-gray);
}
.experience-item:last-child {
border-bottom: none;
} }
.experience-header { .experience-header {
@@ -180,6 +212,27 @@ a:hover {
justify-content: space-between; justify-content: space-between;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
gap: 1rem; gap: 1rem;
align-items: center;
}
.company-logo {
width: 40px;
height: 40px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
}
.company-logo img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.experience-title {
flex: 1;
} }
.position { .position {
@@ -335,17 +388,109 @@ footer {
font-size: 2rem; font-size: 2rem;
} }
.cv-header {
flex-direction: column;
align-items: center;
text-align: center;
}
.cv-photo {
order: -1;
margin-bottom: 1rem;
}
.experience-header, .experience-header,
.project-header, .project-header,
.education-header { .education-header {
flex-direction: column; flex-direction: column;
gap: 0.25rem; gap: 0.25rem;
} }
.action-bar-content { .company-logo {
flex-direction: column; display: none;
gap: 1rem;
} }
} }
.no-print {} .no-print {}
/* Print Styles for Photo */
@media print {
.cv-photo {
width: 100px;
height: 100px;
border-width: 2px;
}
.company-logo {
width: 30px;
height: 30px;
}
}
/* CV Length Toggle */
.cv-length-toggle {
display: flex;
gap: 0.5rem;
}
.length-btn {
padding: 0.4rem 1rem;
border: 1px solid var(--border-gray);
background: white;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}
.length-btn:hover {
background: #f5f5f5;
}
.length-btn.active {
background: var(--accent-blue);
color: white;
border-color: var(--accent-blue);
}
/* Short CV - Hide detailed content */
.cv-short .long-only {
display: none;
}
.cv-short .short-desc {
display: block;
color: var(--text-gray);
font-size: 0.95rem;
line-height: 1.6;
margin-bottom: 0.75rem;
}
/* Long CV - Hide short descriptions */
.cv-long .short-desc,
.short-desc {
display: none;
}
.cv-long .long-only {
display: block;
}
/* Ensure lists display correctly in long mode */
.cv-long .responsibilities {
display: block;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.action-bar-content {
flex-wrap: wrap;
justify-content: center;
}
.cv-length-toggle {
order: 1;
width: 100%;
justify-content: center;
margin-top: 0.5rem;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

+1
View File
@@ -0,0 +1 @@
Not Found
+1
View File
@@ -0,0 +1 @@
Not Found
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="150" height="150" xmlns="http://www.w3.org/2000/svg">
<rect width="150" height="150" fill="#e0e0e0"/>
<circle cx="75" cy="60" r="25" fill="#999"/>
<path d="M 35 110 Q 75 90 115 110" fill="#999"/>
<text x="75" y="140" text-anchor="middle" font-size="10" fill="#666">Add your photo</text>
</svg>

After

Width:  |  Height:  |  Size: 314 B

+27 -11
View File
@@ -1,15 +1,22 @@
<!-- CV Content Template - Minimal Design --> <!-- CV Content Template - Minimal Design -->
<div class="cv-header"> <div class="cv-header">
<div class="cv-header-main"> <div class="cv-header-left">
<h1 class="cv-name">{{.CV.Personal.Name}}</h1> <div class="cv-header-main">
<h2 class="cv-title">{{.CV.Personal.Title}}</h2> <h1 class="cv-name">{{.CV.Personal.Name}}</h1>
<h2 class="cv-title">{{.CV.Personal.Title}}</h2>
</div>
<div class="cv-contact">
<div class="contact-item">{{.CV.Personal.Location}}</div>
<div class="contact-item"><a href="mailto:{{.CV.Personal.Email}}">{{.CV.Personal.Email}}</a></div>
<div class="contact-item">{{.CV.Personal.Phone}}</div>
<div class="contact-item"><a href="{{.CV.Personal.LinkedIn}}" target="_blank">LinkedIn</a></div>
<div class="contact-item"><a href="{{.CV.Personal.GitHub}}" target="_blank">GitHub</a></div>
</div>
</div> </div>
<div class="cv-contact"> <div class="cv-header-right">
<div class="contact-item">{{.CV.Personal.Location}}</div> <div class="cv-photo">
<div class="contact-item"><a href="mailto:{{.CV.Personal.Email}}">{{.CV.Personal.Email}}</a></div> <img src="/static/images/profile/photo.jpg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
<div class="contact-item">{{.CV.Personal.Phone}}</div> </div>
<div class="contact-item"><a href="{{.CV.Personal.LinkedIn}}" target="_blank">LinkedIn</a></div>
<div class="contact-item"><a href="{{.CV.Personal.GitHub}}" target="_blank">GitHub</a></div>
</div> </div>
</div> </div>
@@ -26,6 +33,11 @@
{{range .CV.Experience}} {{range .CV.Experience}}
<div class="experience-item"> <div class="experience-item">
<div class="experience-header"> <div class="experience-header">
{{if .CompanyLogo}}
<div class="company-logo">
<img src="/static/images/companies/{{.CompanyLogo}}" alt="{{.Company}}" onerror="this.style.display='none'">
</div>
{{end}}
<div class="experience-title"> <div class="experience-title">
<h4 class="position">{{.Position}}</h4> <h4 class="position">{{.Position}}</h4>
<div class="company">{{.Company}}, {{.Location}}</div> <div class="company">{{.Company}}, {{.Location}}</div>
@@ -35,14 +47,18 @@
</div> </div>
</div> </div>
<ul class="responsibilities"> {{if .ShortDescription}}
<p class="short-desc">{{.ShortDescription}}</p>
{{end}}
<ul class="responsibilities long-only">
{{range .Responsibilities}} {{range .Responsibilities}}
<li>{{.}}</li> <li>{{.}}</li>
{{end}} {{end}}
</ul> </ul>
{{if .Technologies}} {{if .Technologies}}
<div class="technologies"> <div class="technologies long-only">
{{range $index, $tech := .Technologies}}{{if $index}}, {{end}}{{$tech}}{{end}} {{range $index, $tech := .Technologies}}{{if $index}}, {{end}}{{$tech}}{{end}}
</div> </div>
{{end}} {{end}}
+38
View File
@@ -42,6 +42,19 @@
</button> </button>
</div> </div>
<div class="cv-length-toggle">
<button
class="length-btn active"
onclick="toggleCVLength('short')">
{{if eq .Lang "es"}}Corto{{else}}Short{{end}}
</button>
<button
class="length-btn"
onclick="toggleCVLength('long')">
{{if eq .Lang "es"}}Largo{{else}}Long{{end}}
</button>
</div>
<div class="export-actions"> <div class="export-actions">
<button <button
class="export-btn" class="export-btn"
@@ -68,5 +81,30 @@
<p>© {{.CV.Meta.LastUpdated}} {{.CV.Personal.Name}} | <p>© {{.CV.Meta.LastUpdated}} {{.CV.Personal.Name}} |
{{if eq .Lang "es"}}Última actualización{{else}}Last updated{{end}}: {{.CV.Meta.LastUpdated}}</p> {{if eq .Lang "es"}}Última actualización{{else}}Last updated{{end}}: {{.CV.Meta.LastUpdated}}</p>
</footer> </footer>
<script>
function toggleCVLength(length) {
// Update button states
document.querySelectorAll('.length-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
// Toggle visibility
const paper = document.querySelector('.cv-paper');
if (length === 'short') {
paper.classList.add('cv-short');
paper.classList.remove('cv-long');
} else {
paper.classList.add('cv-long');
paper.classList.remove('cv-short');
}
}
// Initialize with short version
document.addEventListener('DOMContentLoaded', function() {
document.querySelector('.cv-paper').classList.add('cv-short');
});
</script>
</body> </body>
</html> </html>