feat: add dynamic years calculation and complete CV content migration
- Add dynamic years of experience calculation from April 1, 2005 - Update professional title badges: ANALYST | TECHNICAL CONSULTANT - Add all missing skill categories from React CV (Programming Languages, JavaScript Frameworks, PHP Frameworks, Java Frameworks, Application Servers, CMS, Design Tools, Team Management) - Add complete References section with LinkedIn, Behance, portfolios, and recommendation letters - Refine years-of-experience subtitle styling to match original design - Achieve 100% content parity with old React CV (English: 7→14 skill categories)
This commit is contained in:
+136
-14
@@ -339,25 +339,72 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"category": "JavaScript Ecosystem",
|
"category": "Programming Languages",
|
||||||
"proficiency": 5,
|
"proficiency": 4,
|
||||||
"items": [
|
"items": [
|
||||||
"Advanced JavaScript (ES6+)",
|
"JAVA/Groovy",
|
||||||
"React & React Ecosystem",
|
"PHP",
|
||||||
"Node.js & Express",
|
"Scala",
|
||||||
"Webpack, Vite, Modern Build Tools"
|
"XML/XSLT",
|
||||||
|
"Action Script",
|
||||||
|
"Shell Scripts (Unix)",
|
||||||
|
"C/C++"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"category": "Web Development",
|
"category": "Web Development",
|
||||||
"proficiency": 5,
|
"proficiency": 5,
|
||||||
"items": [
|
"items": [
|
||||||
"HTML5, CSS3, Semantic Web",
|
"JSP/PHP",
|
||||||
|
"HTML(5)/XHTML/Handlebars/Moustache/Velocity/Freemarker",
|
||||||
|
"CSS/Less/Sass/Javascript/jQuery/mooTools",
|
||||||
|
"DOM/Ajax/SEO/WebServices",
|
||||||
"REST API Design & Development",
|
"REST API Design & Development",
|
||||||
"LESS, SASS, CSS Preprocessors",
|
|
||||||
"Responsive & Mobile-First Design"
|
"Responsive & Mobile-First Design"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"category": "JavaScript Frameworks",
|
||||||
|
"proficiency": 5,
|
||||||
|
"items": [
|
||||||
|
"Node & ReactJS",
|
||||||
|
"Redux/Flux",
|
||||||
|
"Webpack2/Express",
|
||||||
|
"Gulp/Grunt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "PHP Frameworks",
|
||||||
|
"proficiency": 4,
|
||||||
|
"items": [
|
||||||
|
"Yii Framework",
|
||||||
|
"Zend Framework",
|
||||||
|
"Wordpress API",
|
||||||
|
"Joomla API"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Java Frameworks",
|
||||||
|
"proficiency": 4,
|
||||||
|
"items": [
|
||||||
|
"Play! Framework",
|
||||||
|
"Struts",
|
||||||
|
"Spring",
|
||||||
|
"Hibernate",
|
||||||
|
"Ibatis",
|
||||||
|
"Magnolia CMS",
|
||||||
|
"XWiki",
|
||||||
|
"TESEO Framework"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Application Servers",
|
||||||
|
"proficiency": 4,
|
||||||
|
"items": [
|
||||||
|
"Apache/WAMP/MAMP",
|
||||||
|
"Tomcat/JBoss/Resin/Jetty/Websphere/Weblogic"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"category": "Backend Technologies",
|
"category": "Backend Technologies",
|
||||||
"proficiency": 4,
|
"proficiency": 4,
|
||||||
@@ -373,11 +420,44 @@
|
|||||||
"category": "Databases",
|
"category": "Databases",
|
||||||
"proficiency": 4,
|
"proficiency": 4,
|
||||||
"items": [
|
"items": [
|
||||||
"PostgreSQL",
|
|
||||||
"MySQL",
|
|
||||||
"Oracle",
|
"Oracle",
|
||||||
"MongoDB (NoSQL)",
|
"MySql",
|
||||||
"Database Design & Optimization"
|
"Postgresql",
|
||||||
|
"Hypersonic",
|
||||||
|
"SQL Knowledge"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "CMS's and Web Production Environments",
|
||||||
|
"proficiency": 4,
|
||||||
|
"items": [
|
||||||
|
"Joomla",
|
||||||
|
"Wordpress",
|
||||||
|
"RapidWeaver",
|
||||||
|
"Servoy",
|
||||||
|
"WebRatio",
|
||||||
|
"Play! Framework"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Design Tools",
|
||||||
|
"proficiency": 3,
|
||||||
|
"items": [
|
||||||
|
"Corel Draw",
|
||||||
|
"Adobe PhotoShop",
|
||||||
|
"Adobe Illustrator",
|
||||||
|
"GIMP"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Team Management",
|
||||||
|
"proficiency": 4,
|
||||||
|
"items": [
|
||||||
|
"Preparation and projects startup",
|
||||||
|
"Fluid communication with clients",
|
||||||
|
"Recruitment",
|
||||||
|
"Tasks management",
|
||||||
|
"Monthly reports"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -472,7 +552,7 @@
|
|||||||
"name": "AI-Powered Development Workflows",
|
"name": "AI-Powered Development Workflows",
|
||||||
"role": "Independent Research & Development",
|
"role": "Independent Research & Development",
|
||||||
"period": "2023 - Present",
|
"period": "2023 - Present",
|
||||||
"description": "Pioneered AI-assisted development workflows using Claude Code and modern tools. Successfully experimented with migrating projects from React to HTMX+Go architecture, reducing complexity while maintaining functionality.",
|
"description": "Pioneered AI-assisted development workflows using Claude Code and modern tools. Successfully experimented with migrating projects from React to HTMX + Go architecture, reducing complexity while maintaining functionality.",
|
||||||
"technologies": [
|
"technologies": [
|
||||||
"Claude Code",
|
"Claude Code",
|
||||||
"HTMX",
|
"HTMX",
|
||||||
@@ -564,8 +644,50 @@
|
|||||||
"description": "Data protection and GDPR compliance certification"
|
"description": "Data protection and GDPR compliance certification"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"title": "Recommendations Letter from TwenTIC",
|
||||||
|
"url": "http://www.drolosoft.com/2010/downloads/recomendacion.pdf",
|
||||||
|
"type": "recommendation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Presentation Letter",
|
||||||
|
"url": "http://www.domestika.org/empleo/demanda/txeo",
|
||||||
|
"type": "presentation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Complete Portfolio (English)",
|
||||||
|
"url": "http://www.behance.net/txeo",
|
||||||
|
"type": "portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Complete Portfolio (Spanish)",
|
||||||
|
"url": "http://www.domestika.org/portfolios/txeo",
|
||||||
|
"type": "portfolio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "LinkedIn Profile",
|
||||||
|
"url": "https://www.linkedin.com/in/juan-andr%C3%A9s-moreno-rubio-3277729/",
|
||||||
|
"type": "profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Tecnoempleo Profile",
|
||||||
|
"url": "https://www.tecnoempleo.com/juan-andres-moreno-rubio.mpt",
|
||||||
|
"type": "profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Currículum Vitae in PDF (Spanish)",
|
||||||
|
"url": "http://www.morenoyrubio.com/cv/cv_jamr_2021_es.pdf",
|
||||||
|
"type": "cv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Currículum Vitae chronological (7 pages)",
|
||||||
|
"url": "http://morenoyrubio.com/cv/cv_cronologico_jamr_2021_en.pdf",
|
||||||
|
"type": "cv"
|
||||||
|
}
|
||||||
|
],
|
||||||
"other": {
|
"other": {
|
||||||
"driverLicense": "Type C"
|
"driverLicense": "Type B"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": "2024",
|
"version": "2024",
|
||||||
|
|||||||
+12
-1
@@ -495,6 +495,17 @@
|
|||||||
"Adobe Illustrator",
|
"Adobe Illustrator",
|
||||||
"GIMP"
|
"GIMP"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Gestión de Equipos",
|
||||||
|
"proficiency": 4,
|
||||||
|
"items": [
|
||||||
|
"Preparación y puesta en marcha de proyectos",
|
||||||
|
"Comunicación fluida con los clientes",
|
||||||
|
"Contratación de personal",
|
||||||
|
"Gestión de tareas",
|
||||||
|
"Reportes mensuales"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"soft_skills": [
|
"soft_skills": [
|
||||||
@@ -582,7 +593,7 @@
|
|||||||
"name": "Flujos de Trabajo de Desarrollo Potenciados por IA",
|
"name": "Flujos de Trabajo de Desarrollo Potenciados por IA",
|
||||||
"role": "Investigación y Desarrollo Independiente",
|
"role": "Investigación y Desarrollo Independiente",
|
||||||
"period": "2023 - Presente",
|
"period": "2023 - Presente",
|
||||||
"description": "Desarrollo pionero de flujos de trabajo asistidos por IA usando Claude Code y herramientas modernas. Experimentación exitosa con migración de proyectos de arquitectura React a HTMX+Go, reduciendo complejidad mientras se mantiene funcionalidad.",
|
"description": "Desarrollo pionero de flujos de trabajo asistidos por IA usando Claude Code y herramientas modernas. Experimentación exitosa con migración de proyectos de arquitectura React a HTMX + Go, reduciendo complejidad mientras se mantiene funcionalidad.",
|
||||||
"technologies": [
|
"technologies": [
|
||||||
"Claude Code",
|
"Claude Code",
|
||||||
"HTMX",
|
"HTMX",
|
||||||
|
|||||||
+37
-8
@@ -51,12 +51,16 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Split skills between left and right sidebars
|
// Split skills between left and right sidebars
|
||||||
skillsLeft, skillsRight := splitSkills(cv.Skills.Technical)
|
skillsLeft, skillsRight := splitSkills(cv.Skills.Technical)
|
||||||
|
|
||||||
|
// Calculate years of experience
|
||||||
|
yearsOfExperience := calculateYearsOfExperience()
|
||||||
|
|
||||||
// Prepare template data
|
// Prepare template data
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"CV": cv,
|
"CV": cv,
|
||||||
"Lang": lang,
|
"Lang": lang,
|
||||||
"SkillsLeft": skillsLeft,
|
"SkillsLeft": skillsLeft,
|
||||||
"SkillsRight": skillsRight,
|
"SkillsRight": skillsRight,
|
||||||
|
"YearsOfExperience": yearsOfExperience,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render template
|
// Render template
|
||||||
@@ -97,12 +101,16 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Split skills between left and right sidebars
|
// Split skills between left and right sidebars
|
||||||
skillsLeft, skillsRight := splitSkills(cv.Skills.Technical)
|
skillsLeft, skillsRight := splitSkills(cv.Skills.Technical)
|
||||||
|
|
||||||
|
// Calculate years of experience
|
||||||
|
yearsOfExperience := calculateYearsOfExperience()
|
||||||
|
|
||||||
// Prepare template data
|
// Prepare template data
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"CV": cv,
|
"CV": cv,
|
||||||
"Lang": lang,
|
"Lang": lang,
|
||||||
"SkillsLeft": skillsLeft,
|
"SkillsLeft": skillsLeft,
|
||||||
"SkillsRight": skillsRight,
|
"SkillsRight": skillsRight,
|
||||||
|
"YearsOfExperience": yearsOfExperience,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render template
|
// Render template
|
||||||
@@ -178,3 +186,24 @@ func splitSkills(skills []models.SkillCategory) (left, right []models.SkillCateg
|
|||||||
|
|
||||||
return left, right
|
return left, right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculateYearsOfExperience calculates years of experience since April 1, 2005
|
||||||
|
// This matches the original React implementation that calculated from 01/04/2005
|
||||||
|
func calculateYearsOfExperience() int {
|
||||||
|
// First day at work: April 1, 2005
|
||||||
|
firstDay := time.Date(2005, time.April, 1, 9, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
// Current date
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
// Calculate the difference in years
|
||||||
|
years := now.Year() - firstDay.Year()
|
||||||
|
|
||||||
|
// Adjust if we haven't reached the anniversary this year yet
|
||||||
|
if now.Month() < firstDay.Month() ||
|
||||||
|
(now.Month() == firstDay.Month() && now.Day() < firstDay.Day()) {
|
||||||
|
years--
|
||||||
|
}
|
||||||
|
|
||||||
|
return years
|
||||||
|
}
|
||||||
|
|||||||
+35
-15
@@ -1,16 +1,19 @@
|
|||||||
/* Logo Toggle Component */
|
/* Toggle Components - Unified Design */
|
||||||
|
.language-toggle,
|
||||||
|
.cv-length-toggle,
|
||||||
.logo-toggle {
|
.logo-toggle {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch {
|
.toggle-switch {
|
||||||
display: flex;
|
display: inline-block;
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch input[type="checkbox"] {
|
.toggle-switch input[type="checkbox"] {
|
||||||
@@ -23,23 +26,24 @@
|
|||||||
.toggle-slider {
|
.toggle-slider {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 44px;
|
width: 50px;
|
||||||
height: 24px;
|
height: 26px;
|
||||||
background-color: #ccc;
|
background-color: #555;
|
||||||
border-radius: 24px;
|
border-radius: 26px;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-slider::after {
|
.toggle-slider::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 18px;
|
width: 20px;
|
||||||
height: 18px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
left: 3px;
|
left: 3px;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch input[type="checkbox"]:checked + .toggle-slider {
|
.toggle-switch input[type="checkbox"]:checked + .toggle-slider {
|
||||||
@@ -47,17 +51,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch input[type="checkbox"]:checked + .toggle-slider::after {
|
.toggle-switch input[type="checkbox"]:checked + .toggle-slider::after {
|
||||||
transform: translateX(20px);
|
transform: translateX(24px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch input[type="checkbox"]:focus + .toggle-slider {
|
.toggle-switch input[type="checkbox"]:focus + .toggle-slider {
|
||||||
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.2);
|
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-label {
|
.toggle-label-left,
|
||||||
font-size: 0.9rem;
|
.toggle-label-right {
|
||||||
|
font-size: 0.8rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #ccc;
|
color: #999;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Highlight active label based on parent container state */
|
||||||
|
.language-toggle:has(#langToggle:not(:checked)) .toggle-label-left,
|
||||||
|
.cv-length-toggle:has(#lengthToggle:not(:checked)) .toggle-label-left,
|
||||||
|
.logo-toggle:has(#logoToggle:not(:checked)) .toggle-label-left {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-toggle:has(#langToggle:checked) .toggle-label-right,
|
||||||
|
.cv-length-toggle:has(#lengthToggle:checked) .toggle-label-right,
|
||||||
|
.logo-toggle:has(#logoToggle:checked) .toggle-label-right {
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Experience Item with Logo Support */
|
/* Experience Item with Logo Support */
|
||||||
|
|||||||
+63
-12
@@ -49,19 +49,61 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-bar-content {
|
.action-bar-content {
|
||||||
max-width: 1200px;
|
max-width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 1rem 2rem;
|
padding: 0.75rem 1.5rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto auto 1fr;
|
grid-template-columns: 1fr auto 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-toggle {
|
/* Left: Site Title */
|
||||||
|
.site-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
justify-self: start;
|
justify-self: start;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-icon {
|
||||||
|
color: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title-text {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center: Toggle controls */
|
||||||
|
.toggle-controls-center {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
justify-self: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-toggle,
|
||||||
|
.cv-length-toggle,
|
||||||
|
.logo-toggle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right: Action buttons */
|
||||||
|
.action-buttons {
|
||||||
|
justify-self: end;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htmx-indicator {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-btn {
|
.lang-btn {
|
||||||
@@ -89,19 +131,20 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.export-btn {
|
.export-btn {
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 0.75rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: white;
|
color: white;
|
||||||
border: 1px solid rgba(255,255,255,0.3);
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.875rem;
|
font-size: 0.8rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.4rem;
|
||||||
text-decoration: none; /* For anchor tags */
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-btn:hover {
|
.export-btn:hover {
|
||||||
@@ -141,12 +184,11 @@ a:hover {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Action buttons on right */
|
/* Action buttons styling (already positioned by grid) */
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-self: end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loading Indicator */
|
/* Loading Indicator */
|
||||||
@@ -177,7 +219,7 @@ a:hover {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%; /* Full width to accommodate pages */
|
max-width: 100%; /* Full width to accommodate pages */
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 100px 0 0 0; /* Top padding to prevent sticky action bar overlap */
|
padding: 20px 0 0 0; /* Top padding to prevent sticky action bar overlap */
|
||||||
display: block; /* Changed from flex */
|
display: block; /* Changed from flex */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +362,15 @@ a:hover {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.years-experience {
|
||||||
|
font-family: 'Quicksand', sans-serif;
|
||||||
|
font-size: 0.85em;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #666;
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
/* Intro/Excerpt Text - Positioned inside header, matching old React CV */
|
/* Intro/Excerpt Text - Positioned inside header, matching old React CV */
|
||||||
.intro-text {
|
.intro-text {
|
||||||
font-family: 'Quicksand', sans-serif;
|
font-family: 'Quicksand', sans-serif;
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
<div class="cv-page page-1">
|
<div class="cv-page page-1">
|
||||||
<!-- Professional Title Badges - Full Width Top Bar -->
|
<!-- Professional Title Badges - Full Width Top Bar -->
|
||||||
<div class="cv-title-badges-header">
|
<div class="cv-title-badges-header">
|
||||||
<span class="title-badge">{{if eq .Lang "es"}}ANALISTA PROGRAMADOR{{else}}ANALYST PROGRAMMER{{end}}</span>
|
<span class="title-badge">{{if eq .Lang "es"}}ANALISTA{{else}}ANALYST{{end}}</span>
|
||||||
|
<span class="badge-separator">|</span>
|
||||||
|
<span class="title-badge">{{if eq .Lang "es"}}CONSULTOR TÉCNICO{{else}}TECHNICAL CONSULTANT{{end}}</span>
|
||||||
<span class="badge-separator">|</span>
|
<span class="badge-separator">|</span>
|
||||||
<span class="title-badge">NODEJS + REACTJS {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
<span class="title-badge">NODEJS + REACTJS {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
||||||
<span class="badge-separator">|</span>
|
<span class="badge-separator">|</span>
|
||||||
<span class="title-badge">WEB {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
<span class="title-badge">WEB {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
||||||
<span class="badge-separator">|</span>
|
<span class="badge-separator">|</span>
|
||||||
<span class="title-badge">GO+HTMX {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
<span class="title-badge">GO + HTMX {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
||||||
<span class="badge-separator">|</span>
|
<span class="badge-separator">|</span>
|
||||||
<span class="title-badge">PHP {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
<span class="title-badge">PHP {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
<div class="cv-header-content">
|
<div class="cv-header-content">
|
||||||
<div class="cv-header-left">
|
<div class="cv-header-left">
|
||||||
<h1 class="cv-name">{{.CV.Personal.Name}}</h1>
|
<h1 class="cv-name">{{.CV.Personal.Name}}</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 -->
|
<!-- Intro/Excerpt Text - No section heading, just the text -->
|
||||||
<div class="intro-text">{{.CV.Summary}}</div>
|
<div class="intro-text">{{.CV.Summary}}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +127,7 @@
|
|||||||
<span class="badge-separator">|</span>
|
<span class="badge-separator">|</span>
|
||||||
<span class="title-badge">WEB {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
<span class="title-badge">WEB {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
||||||
<span class="badge-separator">|</span>
|
<span class="badge-separator">|</span>
|
||||||
<span class="title-badge">GO+HTMX {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
<span class="title-badge">GO + HTMX {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
||||||
<span class="badge-separator">|</span>
|
<span class="badge-separator">|</span>
|
||||||
<span class="title-badge">PHP {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
<span class="title-badge">PHP {{if eq .Lang "es"}}DESARROLLADOR{{else}}DEVELOPER{{end}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -241,6 +244,13 @@
|
|||||||
<a href="mailto:{{.CV.Personal.Email}}" target="_blank" rel="noopener noreferrer">{{.CV.Personal.Email}}</a>
|
<a href="mailto:{{.CV.Personal.Email}}" target="_blank" rel="noopener noreferrer">{{.CV.Personal.Email}}</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+53
-48
@@ -94,55 +94,45 @@
|
|||||||
<!-- Single Black Bar with Everything -->
|
<!-- Single Black Bar with Everything -->
|
||||||
<div class="action-bar no-print" role="navigation" aria-label="Language and export controls">
|
<div class="action-bar no-print" role="navigation" aria-label="Language and export controls">
|
||||||
<div class="action-bar-content">
|
<div class="action-bar-content">
|
||||||
<!-- Left: Language buttons -->
|
<!-- Left: Site Title -->
|
||||||
<div class="language-toggle" role="group" aria-label="Language selection">
|
<div class="site-title">
|
||||||
<button
|
<svg class="site-icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||||
class="lang-btn {{if eq .Lang "en"}}active{{end}}"
|
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20M10,19H8V14H10V19M14,19H12V12H14V19M10,11H8V9H10V11Z"/>
|
||||||
hx-get="/cv?lang=en"
|
</svg>
|
||||||
hx-target="#cv-content"
|
<span class="site-title-text">{{if eq .Lang "es"}}Curriculum Vitae 2025{{else}}Curriculum Vitae 2025{{end}}</span>
|
||||||
hx-swap="innerHTML swap:200ms settle:200ms"
|
</div>
|
||||||
hx-push-url="/?lang=en"
|
|
||||||
hx-indicator="#loading"
|
<!-- Center: Toggle controls -->
|
||||||
aria-label="Switch to English"
|
<div class="toggle-controls-center">
|
||||||
aria-pressed="{{if eq .Lang "en"}}true{{else}}false{{end}}">
|
<!-- Language toggle -->
|
||||||
English
|
<div class="language-toggle">
|
||||||
</button>
|
<span class="toggle-label-left">EN</span>
|
||||||
<button
|
<label class="toggle-switch">
|
||||||
class="lang-btn {{if eq .Lang "es"}}active{{end}}"
|
<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}}">
|
||||||
hx-get="/cv?lang=es"
|
<span class="toggle-slider"></span>
|
||||||
hx-target="#cv-content"
|
</label>
|
||||||
hx-swap="innerHTML swap:200ms settle:200ms"
|
<span class="toggle-label-right">ES</span>
|
||||||
hx-push-url="/?lang=es"
|
|
||||||
hx-indicator="#loading"
|
|
||||||
aria-label="Switch to Spanish"
|
|
||||||
aria-pressed="{{if eq .Lang "es"}}true{{else}}false{{end}}">
|
|
||||||
Español
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Center: CV Length Toggle -->
|
<!-- Center: CV Length Toggle -->
|
||||||
<div class="cv-length-toggle">
|
<div class="cv-length-toggle">
|
||||||
<button
|
<span class="toggle-label-left">{{if eq .Lang "es"}}Corto{{else}}Short{{end}}</span>
|
||||||
class="length-btn active"
|
<label class="toggle-switch">
|
||||||
onclick="toggleCVLength('short')"
|
<input type="checkbox" id="lengthToggle" onclick="toggleCVLength()" aria-label="{{if eq .Lang "es"}}Cambiar longitud del CV{{else}}Toggle CV length{{end}}">
|
||||||
aria-label="{{if eq .Lang "es"}}Ver CV corto{{else}}View short CV{{end}}">
|
<span class="toggle-slider"></span>
|
||||||
{{if eq .Lang "es"}}Corto{{else}}Short{{end}}
|
</label>
|
||||||
</button>
|
<span class="toggle-label-right">{{if eq .Lang "es"}}Largo{{else}}Long{{end}}</span>
|
||||||
<button
|
|
||||||
class="length-btn"
|
|
||||||
onclick="toggleCVLength('long')"
|
|
||||||
aria-label="{{if eq .Lang "es"}}Ver CV largo{{else}}View long CV{{end}}">
|
|
||||||
{{if eq .Lang "es"}}Largo{{else}}Long{{end}}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Center Right: Logo Toggle -->
|
<!-- Center Right: Logo Toggle -->
|
||||||
<div class="logo-toggle">
|
<div class="logo-toggle">
|
||||||
|
<span class="toggle-label-left">{{if eq .Lang "es"}}Sin logos{{else}}No logos{{end}}</span>
|
||||||
<label class="toggle-switch">
|
<label class="toggle-switch">
|
||||||
<input type="checkbox" id="logoToggle" onclick="toggleLogos()" aria-label="{{if eq .Lang "es"}}Mostrar logos de empresas{{else}}Show company logos{{end}}">
|
<input type="checkbox" id="logoToggle" onclick="toggleLogos()" aria-label="{{if eq .Lang "es"}}Mostrar logos de empresas{{else}}Show company logos{{end}}">
|
||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
<span class="toggle-label">{{if eq .Lang "es"}}Mostrar logos{{else}}Show logos{{end}}</span>
|
|
||||||
</label>
|
</label>
|
||||||
|
<span class="toggle-label-right">{{if eq .Lang "es"}}Logos{{else}}Logos{{end}}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: Action buttons -->
|
<!-- Right: Action buttons -->
|
||||||
@@ -202,21 +192,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function toggleCVLength(length) {
|
function toggleLanguage() {
|
||||||
// Update button states
|
const checkbox = document.getElementById('langToggle');
|
||||||
document.querySelectorAll('.length-btn').forEach(btn => {
|
const lang = checkbox.checked ? 'es' : 'en';
|
||||||
btn.classList.remove('active');
|
|
||||||
});
|
|
||||||
event.target.classList.add('active');
|
|
||||||
|
|
||||||
// Toggle visibility
|
// Use HTMX to load new content
|
||||||
|
htmx.ajax('GET', `/cv?lang=${lang}`, {
|
||||||
|
target: '#cv-content',
|
||||||
|
swap: 'innerHTML swap:200ms settle:200ms',
|
||||||
|
indicator: '#loading'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update URL
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('lang', lang);
|
||||||
|
window.history.pushState({}, '', url);
|
||||||
|
|
||||||
|
// Update html lang attribute
|
||||||
|
document.documentElement.lang = lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCVLength() {
|
||||||
|
const checkbox = document.getElementById('lengthToggle');
|
||||||
const paper = document.querySelector('.cv-paper');
|
const paper = document.querySelector('.cv-paper');
|
||||||
if (length === 'short') {
|
|
||||||
paper.classList.add('cv-short');
|
if (checkbox.checked) {
|
||||||
paper.classList.remove('cv-long');
|
|
||||||
} else {
|
|
||||||
paper.classList.add('cv-long');
|
paper.classList.add('cv-long');
|
||||||
paper.classList.remove('cv-short');
|
paper.classList.remove('cv-short');
|
||||||
|
} else {
|
||||||
|
paper.classList.add('cv-short');
|
||||||
|
paper.classList.remove('cv-long');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user