From fc700f1f0eaa99db71a12c7a8f6410cd059c4bf0 Mon Sep 17 00:00:00 2001 From: juanatsap Date: Mon, 10 Nov 2025 15:45:55 +0000 Subject: [PATCH] feat: optimize mobile layout with unified design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive mobile optimization (≤768px) for improved consistency and readability: - Center profile photo between name and intro text on mobile - Unify all logo/icon sizes to 60×60px for visual consistency - Standardize spacing across experience, courses, projects, and awards - Reduce font sizes proportionally for better mobile readability - Remove justified text alignment (except intro/skills) to prevent awkward spacing - Apply consistent 1rem gaps and 1.5rem padding throughout - Optimize sidebar items with reduced margins and font sizes Technical improvements: - Add comprehensive mobile breakpoint rules at 768px - Implement flexible photo positioning (absolute on desktop, static on mobile) - Ensure uniform typography scale across all content types --- internal/handlers/cv.go | 100 ++++++++--- static/css/main.css | 365 ++++++++++++++++++++++++++++++++++---- static/css/print.css | 90 ++++++---- templates/cv-content.html | 9 +- templates/index.html | 23 ++- 5 files changed, 483 insertions(+), 104 deletions(-) diff --git a/internal/handlers/cv.go b/internal/handlers/cv.go index 1abc793..bae546a 100644 --- a/internal/handlers/cv.go +++ b/internal/handlers/cv.go @@ -185,6 +185,7 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) { } // ExportPDF handles PDF export requests using chromedp +// TEMPORARILY DISABLED - Work in progress func (h *CVHandler) ExportPDF(w http.ResponseWriter, r *http.Request) { // Get language from query parameter lang := r.URL.Query().Get("lang") @@ -198,33 +199,90 @@ func (h *CVHandler) ExportPDF(w http.ResponseWriter, r *http.Request) { return } - // Construct URL to generate PDF from - // Use localhost instead of the actual server address to avoid network overhead - url := fmt.Sprintf("http://%s/?lang=%s", h.serverAddr, lang) + log.Printf("PDF export requested but temporarily disabled (redirecting to print friendly)") - log.Printf("Generating PDF from URL: %s", url) - - // Generate PDF - pdfData, err := h.pdfGenerator.GenerateFromURL(r.Context(), url) - if err != nil { - log.Printf("PDF generation failed: %v", err) - HandleError(w, r, InternalError(err)) - return + // Return HTML with message and redirect to print friendly + message := "PDF Export - Work in Progress" + body := "The PDF export feature is currently being improved. Please use the Print Friendly button instead (Ctrl+P or Cmd+P to save as PDF)." + if lang == "es" { + message = "Exportación PDF - En Desarrollo" + body = "La función de exportación a PDF está siendo mejorada. Por favor, usa el botón Imprimir Amigable en su lugar (Ctrl+P o Cmd+P para guardar como PDF)." } - // Set response headers - filename := fmt.Sprintf("CV-Juan-Andres-Moreno-Rubio-%s.pdf", lang) - w.Header().Set("Content-Type", "application/pdf") - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) - w.Header().Set("Content-Length", fmt.Sprintf("%d", len(pdfData))) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) - // Write PDF data - if _, err := w.Write(pdfData); err != nil { - log.Printf("Failed to write PDF response: %v", err) - return + redirectMsg := "Redirecting in 5 seconds..." + if lang == "es" { + redirectMsg = "Redirigiendo en 5 segundos..." } - log.Printf("Successfully generated PDF: %s (%d bytes)", filename, len(pdfData)) + html := fmt.Sprintf(` + + + + + %s + + + + +
+
🚧
+

%s

+

%s

+
+ %s +
+
+ +`, lang, message, lang, message, body, redirectMsg) + + w.Write([]byte(html)) } // splitSkills splits skill categories between left (page 1) and right (page 2) sidebars diff --git a/static/css/main.css b/static/css/main.css index d52ef6c..30724a3 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -78,6 +78,12 @@ a:hover { height: 100%; } +.site-title-left { + display: flex; + align-items: center; + gap: 0.75rem; +} + .site-icon { color: #fff; flex-shrink: 0; @@ -88,6 +94,14 @@ a:hover { padding: 0 .5rem 0 1.5rem; } +/* Mobile icon hidden by default, shown only on mobile */ +.site-icon-mobile { + display: none; + color: #fff; + flex-shrink: 0; + margin-right: 0.5rem; +} + /* Site logo and title links */ .site-logo-link, .site-title-link { @@ -236,7 +250,7 @@ iconify-icon { position: absolute; width: 24px; height: 24px; - left: 3px; + left: 2px; background: white; border-radius: 50%; transition: transform 0.3s ease; @@ -246,7 +260,7 @@ iconify-icon { } .icon-toggle input:checked + .icon-toggle-slider::before { - transform: translateX(45px); + transform: translateX(43px); } .icon-toggle input:checked + .icon-toggle-slider { @@ -264,11 +278,11 @@ iconify-icon { } .icon-toggle-slider .icon-left { - left: 8px; + left: 6px; } .icon-toggle-slider .icon-right { - right: 7px; + right: 6px; } .icon-toggle input:not(:checked) + .icon-toggle-slider .icon-left { @@ -287,7 +301,7 @@ iconify-icon { } .icon-toggle input:checked + .icon-toggle-slider .icon-right { - color: #fff; + color: #333; font-weight: bold; } @@ -320,7 +334,8 @@ iconify-icon { display: inline-flex; align-items: center; justify-content: center; - gap: 0.5rem; + /* gap: 0.5rem; */ + gap: 0rem; text-decoration: none; white-space: nowrap; letter-spacing: -0.01em; @@ -329,6 +344,7 @@ iconify-icon { transition: all 0.2s ease; outline: none !important; box-shadow: none !important; + min-width: 50px!important; } .selector-btn:focus, @@ -520,7 +536,7 @@ iconify-icon { .theme-clean .cv-main { grid-column: 1 !important; - padding: 2rem !important; + padding: 2rem 3rem!important; transition: all 0.3s ease-in-out; } @@ -665,7 +681,7 @@ iconify-icon { .sidebar-content { font-family: 'Quicksand', sans-serif; - font-size: 0.95em; + font-size: 0.95rem; font-weight: 400; line-height: 1.5; } @@ -730,6 +746,9 @@ iconify-icon { .cv-header-left { flex: 1; + position: relative; + /* Desktop: Add right padding to make room for the photo */ + padding-right: 185px; /* Photo width (150px) + gap (35px) */ } .cv-photo { @@ -739,12 +758,11 @@ iconify-icon { 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; + + /* Desktop: Position photo in the right padding area */ + position: absolute; + top: 15px; + right: 15px; /* Margin from the right edge */ } .cv-photo img { @@ -973,7 +991,7 @@ iconify-icon { .short-desc { color: var(--text-dark); - font-size: 0.9rem; + font-size: 0.95rem; line-height: 1.6; margin-top: 0.5rem; } @@ -993,7 +1011,7 @@ iconify-icon { padding-left: 1.2rem; margin-bottom: 0.4rem; position: relative; - font-size: 0.9rem; + font-size: 0.95rem; color: var(--text-dark); line-height: 1.5; } @@ -1047,7 +1065,7 @@ iconify-icon { /* Education */ .education-item { margin-bottom: 1rem; - font-size: 0.9rem; + font-size: 0.95rem; line-height: 1.6; color: var(--text-dark); } @@ -1061,7 +1079,7 @@ iconify-icon { } .language-item { - font-size: 1.1rem!important; + font-size: 0.95rem!important; color: var(--text-dark); margin-bottom: 0.3rem!important; line-height: 1.4!important; @@ -1258,7 +1276,7 @@ iconify-icon { } .project-desc { - font-size: 0.9rem; + font-size: 0.95rem; color: var(--text-dark); margin-top: 0.5rem; line-height: 1.6; @@ -1276,7 +1294,7 @@ iconify-icon { margin-top: -1.5rem; padding-top: 0rem; text-align: center; - font-size: 0.95em; + font-size: 0.95rem; color: var(--text-gray); } @@ -1298,7 +1316,7 @@ iconify-icon { margin-bottom: 0!important; line-height: 1.4!important; margin-left: 2rem!important; - font-size: 1.1rem!important; + font-size: 0.95rem!important; } .reference-item a { @@ -1422,9 +1440,9 @@ footer { font-size: 1.8rem; } - .cv-photo { - width: 120px; - height: 150px; + .intro-text { + font-size: 0.9em; + margin-top: 15px; } .action-bar-content { @@ -1447,11 +1465,6 @@ footer { gap: 0.25rem; } - .intro-text { - font-size: 0.9em; - margin-top: 15px; - } - /* ========== Hide header controls, show in menu ========== */ /* Keep language selector visible in header */ .view-controls-center { @@ -1702,7 +1715,7 @@ a:focus { margin-bottom: 0!important; line-height: 1.4!important; margin-left: 2rem!important; - font-size: 1.1rem!important; + font-size: 0.95rem!important; } /* Award item with logo */ @@ -1839,7 +1852,7 @@ a:focus { .course-desc { margin-top: 0.5em; color: var(--text-gray); - font-size: 0.95em; + font-size: 0.95rem; } .reference-item { @@ -1929,6 +1942,11 @@ a:focus { margin-bottom: 0; padding: 0; } + + .site-title { + justify-content: space-between; + width: 100%; + } } /* =============================================== @@ -2082,7 +2100,7 @@ a:focus { .submenu-content { position: fixed; /* Changed from absolute to fixed to break out of parent overflow */ - left: 278px; /* Slight overlap with menu to eliminate any gap */ + left: 232px; /* Slight overlap with menu to eliminate any gap */ background: #ffffff; box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.15); border-radius: 8px; @@ -2279,6 +2297,11 @@ html { padding: 0.75rem 1rem; font-size: 0.9rem; } + + .site-title { + justify-content: space-between; + width: 100%; + } } /* Hide menu overlay on print */ @@ -2831,3 +2854,281 @@ html { margin-top: 10px !important; } } + +/* ======================================== + Responsive: Small Screens (up to 540px) + ======================================== */ + +/* ======================================== + Responsive: Tablet Screens - Center Photo (up to 768px) + ======================================== */ + +@media (max-width: 768px) { + /* ======================================== + TYPOGRAPHY - Subtle font size reductions + ======================================== */ + .cv-name { + text-align: center; + font-size: 1.6rem; + } + + .years-experience { + text-align: center; + font-size: 1.1em; + } + + .section-title { + font-size: 1.2em; + } + + .sidebar-title { + font-size: 1.2em; + } + + .experience-period, + .experience-separator, + .experience-location, + .experience-duration { + font-size: 0.95rem; + } + + .position { + font-size: 0.95rem; + } + + .short-desc, + .responsibilities li { + font-size: 0.85rem; + } + + /* ======================================== + TEXT ALIGNMENT FIXES - Selective alignment + ======================================== */ + /* Keep justified for intro and skills */ + .intro-text, + .summary-text { + text-align: justify; + font-size: 0.85rem; + line-height: 1.5; + } + + .intro-text { + margin-top: 0; + width: 100%; + } + + /* Left-align for course/project descriptions */ + .course-desc, + .project-desc { + text-align: left !important; + font-size: 0.85rem !important; + line-height: 1.5; + } + + /* ======================================== + HEADER LAYOUT - Centered photo + ======================================== */ + .cv-header-content { + flex-direction: column; + align-items: center; + gap: 1rem; + } + + .cv-header-left { + width: 100%; + position: static; + padding-right: 0; + } + + .cv-photo { + position: static; + width: auto; + height: auto; + max-width: 250px; + margin: 1.5rem auto; + text-align: center; + right: auto; + top: auto; + } + + .cv-photo img { + width: 100%; + height: auto; + max-height: none; + } + + /* ======================================== + UNIFIED LOGO/ICON SIZING - Consistent 60px + ======================================== */ + .company-logo, + .course-icon, + .project-icon, + .award-logo { + width: 60px !important; + height: 60px !important; + flex-shrink: 0; + } + + .company-logo img, + .course-icon img, + .project-icon img, + .award-logo img { + width: 60px !important; + height: 60px !important; + object-fit: contain; + } + + .default-company-icon, + .default-course-icon, + .default-project-icon, + .default-award-icon { + width: 60px !important; + height: 60px !important; + } + + /* ======================================== + CONSISTENT ITEM LAYOUT - Uniform spacing + ======================================== */ + .experience-item, + .course-item, + .project-item, + .award-item { + display: flex; + flex-direction: row; + gap: 1rem !important; + align-items: flex-start; + margin-bottom: 2rem !important; + padding-bottom: 1.5rem !important; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } + + .experience-item { + margin-bottom: 1.8rem !important; + } + + .experience-content, + .course-content, + .project-content, + .award-content { + flex: 1; + min-width: 0; + } + + /* ======================================== + FONT SIZE CONSISTENCY - Titles and descriptions + ======================================== */ + .course-title, + .project-title, + .award-item strong { + font-size: 0.95rem !important; + line-height: 1.4; + } + + .course-item small, + .project-item small, + .award-item small { + font-size: 0.8rem !important; + } + + .course-desc, + .project-desc, + .award-desc { + font-size: 0.85rem !important; + line-height: 1.5; + } + + /* ======================================== + RESPONSIBILITIES MOBILE OPTIMIZATION + ======================================== */ + .responsibilities li:has(img), + .responsibilities li:has(iconify-icon) { + grid-template-columns: 60px 1fr !important; + gap: 0.75rem !important; + margin-bottom: 0.75rem !important; + } + + .responsibilities li img, + .responsibilities li iconify-icon.default-company-icon { + width: 60px !important; + height: 60px !important; + } + + /* ======================================== + SIDEBAR ITEMS MOBILE OPTIMIZATION + ======================================== */ + .language-item, + .reference-item, + .other-content { + margin-bottom: 0 !important; + line-height: 1.4 !important; + margin-left: 1rem !important; + font-size: 0.85rem !important; + } +} + +/* ======================================== + Responsive: All Mobile Screens (up to 540px) + ======================================== */ + +@media (max-width: 540px) { + /* Hide year from title in mobile view */ + .site-title-year { + display: none; + } + + /* Hide desktop logo, show mobile icon in title */ + .site-logo-link { + display: none; + } + + .site-icon-mobile { + display: inline-flex; + } + + /* Align language selector to the right */ + .site-title { + justify-content: space-between; + width: 100%; + } + + .language-selector { + margin-left: auto; + padding-left: 0; + margin-right: 0rem; + } + + /* ========== Language Selector - Show Short Names Only ========== */ + .language-selector .selector-btn { + position: relative; + padding: 0.4rem 0.75rem; + min-width: 40px; + font-size: 0; /* Hide actual text */ + overflow: visible; + transition: font-size 0.3s ease; + display: inline-flex; + justify-content: center; + align-items: center; + } + + /* Show only short version (EN/ES) */ + .language-selector .selector-btn::before { + content: attr(data-short); + font-size: 0.95rem; + opacity: 1; + transition: opacity 0.3s ease; + display: block; + width: 100%; + text-align: center; + } + + /* Keep short names on hover (no expansion) */ + .language-selector .selector-btn:hover { + font-size: 0; + min-width: 40px; + } + + .language-selector .selector-btn:hover::before { + content: attr(data-short); + opacity: 1; + } +} diff --git a/static/css/print.css b/static/css/print.css index cbd4a3d..6b8f3b1 100644 --- a/static/css/print.css +++ b/static/css/print.css @@ -44,22 +44,52 @@ display: none !important; } - /* Hide ALL icons in print */ - iconify-icon, - .section-icon, - .default-company-icon, - .default-project-icon, - .default-course-icon, - .default-award-icon { - display: none !important; + /* =================================== + SHOW ALL ICONS, LOGOS, AND BADGES - Print Default + =================================== */ + + /* Section title icons - smaller for print */ + .section-icon { + width: 16px !important; + height: 16px !important; + vertical-align: middle !important; + margin-right: 4px !important; } - /* Hide company/project/course logos */ + /* Company/Project/Course logos - compact for print */ .company-logo, .project-icon, .course-icon, .award-logo { - display: none !important; + width: 40px !important; + height: 40px !important; + flex-shrink: 0 !important; + } + + .company-logo img, + .project-icon img, + .course-icon img, + .award-logo img { + width: 40px !important; + height: 40px !important; + object-fit: contain !important; + } + + /* Default fallback icons - show at print size */ + .default-company-icon, + .default-project-icon, + .default-course-icon, + .default-award-icon { + width: 40px !important; + height: 40px !important; + } + + /* Badges - keep visible for print */ + .current-badge, + .expired-badge, + .maintained-badge { + font-size: 7pt !important; + padding: 1px 4px !important; } /* =================================== @@ -191,12 +221,12 @@ PHOTO - FIXED ASPECT RATIO (3:4 Portrait) =================================== */ .cv-photo { - width: 60px !important; - height: 80px !important; /* Maintains 3:4 ratio */ + width: 90px !important; + height: 120px !important; /* Maintains 3:4 ratio */ object-fit: contain !important; /* Show full photo, no crop */ border: none !important; /* Remove border */ box-shadow: none !important; - margin: 10px 15px 10px 10px !important; + margin: 0px 0px 20px 20px !important; page-break-inside: avoid; } @@ -241,10 +271,6 @@ margin-bottom: 3mm !important; /* More space for lists */ } - .section-icon { - display: none; /* Hide section icons in print */ - } - .summary-text { font-size: 9pt; line-height: 1.5; @@ -254,6 +280,8 @@ EXPERIENCE - REDUCED SPACING (60px → 26px) =================================== */ .experience-item { + display: flex !important; /* Show logos side-by-side with content */ + gap: 12px !important; margin-bottom: 4mm !important; /* ~15px, down from 40px */ padding-bottom: 3mm !important; /* ~11px, down from 32px */ border-bottom: 0.5pt solid #dddddd !important; @@ -305,7 +333,7 @@ line-height: 1.4 !important; } - /* All logos already hidden above - no exceptions */ + /* Logos are visible at 40x40 for compact print layout */ /* =================================== PROJECTS & COURSES @@ -313,7 +341,8 @@ .project-item, .course-item, .award-item { - display: block !important; /* Remove flex/grid layouts for print */ + display: flex !important; /* Show logos side-by-side with content */ + gap: 12px !important; margin-bottom: 4mm !important; padding-bottom: 3mm !important; border-bottom: 0.5pt solid #dddddd !important; @@ -345,13 +374,13 @@ font-weight: 600 !important; } - /* Icons/logos already hidden - display: none above */ + /* Logos visible at 40x40 - default icons remain hidden */ /* Consistent item titles */ .project-title, .course-title, - .award-item strong, - .course-item strong { + .experience-title, + .award-title { font-size: 10pt !important; font-weight: 600 !important; line-height: 1.3 !important; @@ -382,14 +411,6 @@ margin-left: 0.5mm !important; } - /* Course title styling to match experience */ - .course-title { - font-size: 10pt !important; - font-weight: 600 !important; - line-height: 1.3 !important; - margin-bottom: 0.5mm !important; - } - /* Course metadata (date, location) - match experience style */ .course-period, .course-location, @@ -531,15 +552,6 @@ font-weight: 600; } - /* =================================== - BADGES - Hidden for minimal print - =================================== */ - .current-badge, - .expired-badge, - .maintained-badge { - display: none !important; - } - /* =================================== CV LENGTH TOGGLE - Force Short Version for Print Friendly =================================== */ diff --git a/templates/cv-content.html b/templates/cv-content.html index eaa9f65..2b06446 100644 --- a/templates/cv-content.html +++ b/templates/cv-content.html @@ -41,12 +41,15 @@

Moreno Rubio, Juan Andrés

{{.YearsOfExperience}} {{if eq .Lang "es"}}años de experiencia{{else}}years of experience{{end}}

+ + +
+ {{.CV.Personal.Name}} +
+
{{.CV.Summary}}
-
- {{.CV.Personal.Name}} -
diff --git a/templates/index.html b/templates/index.html index e53effa..5c1f27b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -99,20 +99,25 @@