feat: add explicit sidebar placement control and responsive design (1024-1280px)

- Add sidebar field to SkillCategory model for explicit left/right control
- Update splitSkills to respect sidebar field instead of automatic splitting
- Add responsive CSS for 1024-1280px: collapse labels, icons-only buttons, EN/ES language selector
- Remove language switcher animations
- Ensure desktop view (>1280px) always shows full sidebar content
- Move Databases and Infrastructure to right sidebar
- Reduce font sizes in responsive range
- Update project logos (Lidering, Jorpack, Delivery Bikes)
This commit is contained in:
juanatsap
2025-11-08 15:05:54 +00:00
parent 4761145ad8
commit 286d0d0e3e
9 changed files with 215 additions and 38 deletions
+24 -14
View File
@@ -350,6 +350,7 @@
{ {
"category": "Go Ecosystem", "category": "Go Ecosystem",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"Hono - High-Performance Web Framework", "Hono - High-Performance Web Framework",
"Gin - Web Framework", "Gin - Web Framework",
@@ -363,6 +364,7 @@
{ {
"category": "JavaScript Ecosystem", "category": "JavaScript Ecosystem",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"Node.js & Express", "Node.js & Express",
"React & React Ecosystem", "React & React Ecosystem",
@@ -375,6 +377,7 @@
{ {
"category": "Frontend Technologies", "category": "Frontend Technologies",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"HTMX - Hypermedia-Driven Applications", "HTMX - Hypermedia-Driven Applications",
"HTML5 & Semantic Web", "HTML5 & Semantic Web",
@@ -386,20 +389,10 @@
"Template Engines (Handlebars, Panini, Mustache)" "Template Engines (Handlebars, Panini, Mustache)"
] ]
}, },
{
"category": "Legacy Enterprise Technologies",
"proficiency": 3,
"items": [
"Java & J2EE",
"Spring Framework, Struts, Hibernate",
"PHP & WordPress",
"Yii Framework, Zend Framework",
"Enterprise Application Servers (Tomcat, JBoss, WebLogic)"
]
},
{ {
"category": "Backend Technologies", "category": "Backend Technologies",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"Go - Current Primary Stack", "Go - Current Primary Stack",
"Hono Framework - High-Performance Web Server", "Hono Framework - High-Performance Web Server",
@@ -409,9 +402,22 @@
"Database Design & Optimization" "Database Design & Optimization"
] ]
}, },
{
"category": "Legacy Enterprise Technologies",
"proficiency": 3,
"sidebar": "left",
"items": [
"Java & J2EE",
"Spring Framework, Struts, Hibernate",
"PHP & WordPress",
"Yii Framework, Zend Framework",
"Enterprise Application Servers (Tomcat, JBoss, WebLogic)"
]
},
{ {
"category": "Databases", "category": "Databases",
"proficiency": 4, "proficiency": 4,
"sidebar": "right",
"items": [ "items": [
"PostgreSQL", "PostgreSQL",
"MySQL", "MySQL",
@@ -425,6 +431,7 @@
{ {
"category": "Infrastructure & Servers", "category": "Infrastructure & Servers",
"proficiency": 5, "proficiency": 5,
"sidebar": "right",
"items": [ "items": [
"Linux Server Administration", "Linux Server Administration",
"VPS Deployment & Configuration", "VPS Deployment & Configuration",
@@ -436,6 +443,7 @@
{ {
"category": "DevOps & CI/CD", "category": "DevOps & CI/CD",
"proficiency": 5, "proficiency": 5,
"sidebar": "right",
"items": [ "items": [
"CI/CD Pipeline Design & Implementation", "CI/CD Pipeline Design & Implementation",
"Custom Deployment Solutions", "Custom Deployment Solutions",
@@ -447,6 +455,7 @@
{ {
"category": "Team Management", "category": "Team Management",
"proficiency": 4, "proficiency": 4,
"sidebar": "right",
"items": [ "items": [
"Preparation and projects startup", "Preparation and projects startup",
"Fluid communication with clients", "Fluid communication with clients",
@@ -458,6 +467,7 @@
{ {
"category": "Design Tools", "category": "Design Tools",
"proficiency": 3, "proficiency": 3,
"sidebar": "right",
"items": [ "items": [
"Corel Draw", "Corel Draw",
"Adobe PhotoShop", "Adobe PhotoShop",
@@ -793,9 +803,9 @@
"technologies": ["JavaScript", "React", "Node.js", "PHP", "WordPress", "Web Development"], "technologies": ["JavaScript", "React", "Node.js", "PHP", "WordPress", "Web Development"],
"shortDescription": "Collection of client projects and websites where I contributed to development, implementation, and technical solutions across various industries.", "shortDescription": "Collection of client projects and websites where I contributed to development, implementation, and technical solutions across various industries.",
"responsibilities": [ "responsibilities": [
"<img src='/static/images/projects/twentic.png' alt='Lidering'><div><strong><a href='https://lidering.com' target='_blank' rel='noopener noreferrer'>Lidering</a></strong> (via Twentic): Real estate and property management platform development</div>", "<img src='/static/images/projects/lidering.png' alt='Lidering'><div><strong><a href='https://lidering.com' target='_blank' rel='noopener noreferrer'>Lidering</a></strong> (via Twentic): Real estate and property management platform development</div>",
"<img src='/static/images/projects/twentic.png' alt='Jorpack'><div><strong><a href='https://jorpack.com' target='_blank' rel='noopener noreferrer'>Jorpack</a></strong> (via Twentic): Industrial packaging solutions and corporate website</div>", "<img src='/static/images/projects/jorpack.png' alt='Jorpack'><div><strong><a href='https://jorpack.com' target='_blank' rel='noopener noreferrer'>Jorpack</a></strong> (via Twentic): Industrial packaging solutions and corporate website</div>",
"<iconify-icon icon='mdi:bike' width='60' height='60' class='default-company-icon'></iconify-icon><div><strong><a href='https://deliverybikesbcn.com/' target='_blank' rel='noopener noreferrer'>Delivery Bikes BCN</a></strong>: Bicycle delivery service platform for Barcelona</div>", "<img src='/static/images/projects/deliverybikes.png' alt='Delivery Bikes BCN'><div><strong><a href='https://deliverybikesbcn.com/' target='_blank' rel='noopener noreferrer'>Delivery Bikes BCN</a></strong>: Bicycle delivery service platform for Barcelona</div>",
"<iconify-icon icon='mdi:security' width='60' height='60' class='default-company-icon'></iconify-icon><div><strong><a href='https://mobbeel.com' target='_blank' rel='noopener noreferrer'>Mobbeel</a></strong>: Biometric authentication and identity verification solutions website</div>" "<iconify-icon icon='mdi:security' width='60' height='60' class='default-company-icon'></iconify-icon><div><strong><a href='https://mobbeel.com' target='_blank' rel='noopener noreferrer'>Mobbeel</a></strong>: Biometric authentication and identity verification solutions website</div>"
] ]
} }
+24 -14
View File
@@ -350,6 +350,7 @@
{ {
"category": "Ecosistema Go", "category": "Ecosistema Go",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"Hono - Framework Web de Alto Rendimiento", "Hono - Framework Web de Alto Rendimiento",
"Gin - Framework Web", "Gin - Framework Web",
@@ -363,6 +364,7 @@
{ {
"category": "Ecosistema JavaScript", "category": "Ecosistema JavaScript",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"Node.js y Express", "Node.js y Express",
"React y Ecosistema React", "React y Ecosistema React",
@@ -375,6 +377,7 @@
{ {
"category": "Tecnologías Frontend", "category": "Tecnologías Frontend",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"HTMX - Aplicaciones Basadas en Hipermedia", "HTMX - Aplicaciones Basadas en Hipermedia",
"HTML5 y Web Semántica", "HTML5 y Web Semántica",
@@ -386,20 +389,10 @@
"Motores de Plantillas (Handlebars, Panini, Mustache)" "Motores de Plantillas (Handlebars, Panini, Mustache)"
] ]
}, },
{
"category": "Tecnologías Enterprise Anteriores",
"proficiency": 3,
"items": [
"Java y J2EE",
"Spring Framework, Struts, Hibernate",
"PHP y WordPress",
"Yii Framework, Zend Framework",
"Servidores de Aplicaciones Enterprise (Tomcat, JBoss, WebLogic)"
]
},
{ {
"category": "Tecnologías Backend", "category": "Tecnologías Backend",
"proficiency": 5, "proficiency": 5,
"sidebar": "left",
"items": [ "items": [
"Go - Stack Principal Actual", "Go - Stack Principal Actual",
"Hono Framework - Servidor Web de Alto Rendimiento", "Hono Framework - Servidor Web de Alto Rendimiento",
@@ -409,9 +402,22 @@
"Diseño y Optimización de Bases de Datos" "Diseño y Optimización de Bases de Datos"
] ]
}, },
{
"category": "Tecnologías Enterprise Anteriores",
"proficiency": 3,
"sidebar": "left",
"items": [
"Java y J2EE",
"Spring Framework, Struts, Hibernate",
"PHP y WordPress",
"Yii Framework, Zend Framework",
"Servidores de Aplicaciones Enterprise (Tomcat, JBoss, WebLogic)"
]
},
{ {
"category": "Bases de Datos", "category": "Bases de Datos",
"proficiency": 4, "proficiency": 4,
"sidebar": "right",
"items": [ "items": [
"PostgreSQL", "PostgreSQL",
"MySQL", "MySQL",
@@ -425,6 +431,7 @@
{ {
"category": "Infraestructura y Servidores", "category": "Infraestructura y Servidores",
"proficiency": 5, "proficiency": 5,
"sidebar": "right",
"items": [ "items": [
"Administración de Servidores Linux", "Administración de Servidores Linux",
"Despliegue y Configuración de VPS", "Despliegue y Configuración de VPS",
@@ -436,6 +443,7 @@
{ {
"category": "DevOps y CI/CD", "category": "DevOps y CI/CD",
"proficiency": 5, "proficiency": 5,
"sidebar": "right",
"items": [ "items": [
"Diseño e Implementación de Pipelines CI/CD", "Diseño e Implementación de Pipelines CI/CD",
"Soluciones de Despliegue Personalizadas", "Soluciones de Despliegue Personalizadas",
@@ -447,6 +455,7 @@
{ {
"category": "Gestión de Equipos", "category": "Gestión de Equipos",
"proficiency": 4, "proficiency": 4,
"sidebar": "right",
"items": [ "items": [
"Preparación y puesta en marcha de proyectos", "Preparación y puesta en marcha de proyectos",
"Comunicación fluida con los clientes", "Comunicación fluida con los clientes",
@@ -458,6 +467,7 @@
{ {
"category": "Herramientas de Diseño", "category": "Herramientas de Diseño",
"proficiency": 3, "proficiency": 3,
"sidebar": "right",
"items": [ "items": [
"Corel Draw", "Corel Draw",
"Adobe PhotoShop", "Adobe PhotoShop",
@@ -798,9 +808,9 @@
"technologies": ["JavaScript", "React", "Node.js", "PHP", "WordPress", "Desarrollo Web"], "technologies": ["JavaScript", "React", "Node.js", "PHP", "WordPress", "Desarrollo Web"],
"shortDescription": "Colección de proyectos de clientes y sitios web donde contribuí al desarrollo, implementación y soluciones técnicas en diversas industrias.", "shortDescription": "Colección de proyectos de clientes y sitios web donde contribuí al desarrollo, implementación y soluciones técnicas en diversas industrias.",
"responsibilities": [ "responsibilities": [
"<img src='/static/images/projects/twentic.png' alt='Lidering'><div><strong><a href='https://lidering.com' target='_blank' rel='noopener noreferrer'>Lidering</a></strong> (a través de Twentic): Desarrollo de plataforma de gestión inmobiliaria y propiedades</div>", "<img src='/static/images/projects/lidering.png' alt='Lidering'><div><strong><a href='https://lidering.com' target='_blank' rel='noopener noreferrer'>Lidering</a></strong> (a través de Twentic): Desarrollo de plataforma de gestión inmobiliaria y propiedades</div>",
"<img src='/static/images/projects/twentic.png' alt='Jorpack'><div><strong><a href='https://jorpack.com' target='_blank' rel='noopener noreferrer'>Jorpack</a></strong> (a través de Twentic): Soluciones de embalaje industrial y sitio web corporativo</div>", "<img src='/static/images/projects/jorpack.png' alt='Jorpack'><div><strong><a href='https://jorpack.com' target='_blank' rel='noopener noreferrer'>Jorpack</a></strong> (a través de Twentic): Soluciones de embalaje industrial y sitio web corporativo</div>",
"<iconify-icon icon='mdi:bike' width='60' height='60' class='default-company-icon'></iconify-icon><div><strong><a href='https://deliverybikesbcn.com/' target='_blank' rel='noopener noreferrer'>Delivery Bikes BCN</a></strong>: Plataforma de servicio de entrega en bicicleta para Barcelona</div>", "<img src='/static/images/projects/deliverybikes.png' alt='Delivery Bikes BCN'><div><strong><a href='https://deliverybikesbcn.com/' target='_blank' rel='noopener noreferrer'>Delivery Bikes BCN</a></strong>: Plataforma de servicio de entrega en bicicleta para Barcelona</div>",
"<iconify-icon icon='mdi:security' width='60' height='60' class='default-company-icon'></iconify-icon><div><strong><a href='https://mobbeel.com' target='_blank' rel='noopener noreferrer'>Mobbeel</a></strong>: Sitio web de soluciones de autenticación biométrica y verificación de identidad</div>" "<iconify-icon icon='mdi:security' width='60' height='60' class='default-company-icon'></iconify-icon><div><strong><a href='https://mobbeel.com' target='_blank' rel='noopener noreferrer'>Mobbeel</a></strong>: Sitio web de soluciones de autenticación biométrica y verificación de identidad</div>"
] ]
} }
+9 -8
View File
@@ -199,20 +199,21 @@ func (h *CVHandler) ExportPDF(w http.ResponseWriter, r *http.Request) {
} }
// splitSkills splits skill categories between left (page 1) and right (page 2) sidebars // splitSkills splits skill categories between left (page 1) and right (page 2) sidebars
// Left sidebar shows first 7 categories, right sidebar shows remaining categories // Each category explicitly specifies which sidebar it belongs to via the "sidebar" field
func splitSkills(skills []models.SkillCategory) (left, right []models.SkillCategory) { func splitSkills(skills []models.SkillCategory) (left, right []models.SkillCategory) {
if len(skills) == 0 { if len(skills) == 0 {
return nil, nil return nil, nil
} }
// Split at index 7 (first 7 items on left) // Filter by sidebar field
splitIndex := 7 for _, skill := range skills {
if len(skills) < splitIndex { if skill.Sidebar == "right" {
return skills, nil right = append(right, skill)
} else {
// Default to left if not specified or if set to "left"
left = append(left, skill)
}
} }
left = skills[:splitIndex]
right = skills[splitIndex:]
return left, right return left, right
} }
+1
View File
@@ -89,6 +89,7 @@ type SkillCategory struct {
Category string `json:"category"` Category string `json:"category"`
Proficiency int `json:"proficiency"` Proficiency int `json:"proficiency"`
Items []string `json:"items"` Items []string `json:"items"`
Sidebar string `json:"sidebar"` // "left" or "right"
} }
type Language struct { type Language struct {
+155
View File
@@ -1899,3 +1899,158 @@ html {
height: 45px; height: 45px;
} }
} }
/* ========================================
Desktop: Ensure Sidebar Content Visible (>1280px)
======================================== */
@media (min-width: 1281px) {
/* Ensure sidebar content is always visible in desktop view */
.sidebar-content {
max-height: none !important;
opacity: 1 !important;
overflow: visible !important;
display: block !important;
margin-top: 10px !important;
}
.skill-category .sidebar-content,
.cv-sidebar-section .sidebar-content {
max-height: none !important;
opacity: 1 !important;
overflow: visible !important;
}
.sidebar-title::after {
display: none !important;
}
.sidebar-title {
cursor: default !important;
}
}
/* ========================================
Responsive: Medium Screens (1024px - 1280px)
======================================== */
@media (min-width: 1024px) and (max-width: 1280px) {
/* ========== Global Font Size Reduction ========== */
html {
font-size: 14px; /* Reduced from default 16px */
}
.cv-name {
font-size: 1.8em; /* Reduced from 2.2em */
}
.sidebar-title {
font-size: 0.95rem;
}
.sidebar-content {
font-size: 0.9rem;
}
.experience-item h3,
.project-item h3 {
font-size: 1rem;
}
.experience-item p,
.project-item p,
.experience-item li,
.project-item li {
font-size: 0.85rem;
}
/* ========== Selector Labels - Hide, Show on Hover ========== */
.selector-label {
max-width: 0;
overflow: hidden;
opacity: 0;
transition: all 0.3s ease;
white-space: nowrap;
}
.selector-group:hover .selector-label {
max-width: 200px;
opacity: 1;
margin-right: 0.75rem;
}
/* ========== Language Selector - Collapse to EN/ES ========== */
.language-selector .selector-btn {
position: relative;
padding: 0.4rem 0.8rem;
min-width: 50px;
font-size: 0; /* Hide actual text */
overflow: visible;
}
/* Show only short version (EN/ES) */
.language-selector .selector-btn::before {
content: attr(data-short);
font-size: 1rem; /* Restore font size for pseudo-element */
opacity: 1;
}
/* On hover, show full text */
.language-selector:hover .selector-btn,
.language-selector .selector-btn:hover {
font-size: 1rem; /* Restore font size */
padding: 0.4rem 1rem;
min-width: auto;
}
.language-selector:hover .selector-btn::before,
.language-selector .selector-btn:hover::before {
content: ''; /* Hide short version */
opacity: 0;
}
/* ========== Action Buttons - Icon Only, Expand on Hover ========== */
.action-btn {
position: relative;
width: 45px;
overflow: hidden;
transition: width 0.3s ease, padding 0.3s ease;
white-space: nowrap;
text-indent: 0;
}
/* Hide button text, keep icon */
.action-btn iconify-icon {
flex-shrink: 0;
}
.action-btn {
font-size: 0;
padding: 0 0.65rem;
justify-content: center;
}
/* On hover, show text */
.action-btn:hover {
width: auto;
font-size: 0.95rem;
padding: 0.65rem 1.5rem;
gap: 0.5rem;
}
/* ========== Sidebar Content - Hide Text, Show on Hover ========== */
.sidebar-content {
max-height: 0;
overflow: hidden;
opacity: 0;
}
/* Show sidebar content on hover */
.skill-category:hover .sidebar-content,
.cv-sidebar-section:hover .sidebar-content {
max-height: 1000px;
opacity: 1;
margin-top: 10px;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

+2 -2
View File
@@ -110,10 +110,10 @@
<!-- Language selector (after title) --> <!-- Language selector (after title) -->
<div class="language-selector"> <div class="language-selector">
<button class="selector-btn {{if eq .Lang "en"}}active{{end}}" onclick="selectLanguage('en')" aria-label="English"> <button class="selector-btn {{if eq .Lang "en"}}active{{end}}" data-short="EN" onclick="selectLanguage('en')" aria-label="English">
English English
</button> </button>
<button class="selector-btn {{if eq .Lang "es"}}active{{end}}" onclick="selectLanguage('es')" aria-label="Español"> <button class="selector-btn {{if eq .Lang "es"}}active{{end}}" data-short="ES" onclick="selectLanguage('es')" aria-label="Español">
Español Español
</button> </button>
</div> </div>