- Add green background (#27ae60) to info and back-to-top buttons when at page bottom
- Implement bottom detection (within 50px threshold) in scroll handler
- Add conditional green hover to zoom reset button (only when zoom ≠ 100%)
- Enhance UX with visual feedback for scroll position and zoom state
Switched from transform: scale() to CSS zoom property on zoom-wrapper.
CSS zoom changes actual layout space, not just visual rendering:
- At 50% zoom, wrapper takes 50% space (no reserved empty space)
- Footer naturally follows right after zoomed content
- At 200% zoom, content extends beyond viewport with scrolling
- Fixes the large gray gap between content and footer
- Created zoom-wrapper div around cv-container
- Zoom now applies to wrapper only, footer adjusts naturally below
- Footer no longer scaled, stays at normal size
- Fixes gap between content and footer at low zoom levels
- Reduced back-to-top button size (35px default, grows to 50px on hover)
- Cleaner separation of concerns for zoom functionality
- Moved "Zoom" button from action bar to hamburger menu under "Acciones Rápidas"
- Close button (X) now grey/subtle by default (opacity: 0.7)
- Close button turns red only on hover for clear indication
- Updated JavaScript to reference show-zoom-menu-btn instead of show-zoom-btn
- Added preventDefault to showZoomControl to prevent link navigation
CSS zoom property had constraints preventing proper zoom beyond 100%.
Switched back to transform: scale() which provides true visual zoom
that can extend beyond viewport bounds at 100-200% range.
- Add close button (X) to zoom control widget
- Make zoom control draggable anywhere on screen
- Persist dragged position in localStorage (cv-zoom-position)
- Add "Zoom" button to action bar to show control when hidden
- Persist visibility state in localStorage (cv-zoom-visible)
- Cursor changes to "move" to indicate draggability
- Interactive elements (slider, buttons) don't trigger drag
- Position stays within viewport bounds
- All features work on desktop only (zoom hidden on mobile)
- Hide zoom control on mobile viewports (≤768px) via CSS
- Skip loading saved zoom on mobile, always use 100%
- Add resize handler to reset zoom when switching to mobile view
- Clean up unnecessary mobile-specific zoom styles
- Prevents confusion on touch devices where zoom is less useful
Changed from transform: scale() to CSS zoom property which affects actual
document layout space. At 50% zoom, content now takes 50% space instead of
reserving full 100% height. This eliminates the large gray empty space issue.
Removed proportional scroll adjustment that was causing page to jump down
when zooming. The transform-origin: top center property keeps content
anchored at top naturally without needing scroll compensation.
Changed zoom target from .cv-paper to .cv-container and .cv-footer to fix issue
where 50% zoom still reserved 100% document space creating empty areas. Both
elements now scale together, properly reducing space at low zoom levels while
keeping main action bar unaffected.
- Modified applyZoom() to target .cv-container and .cv-footer
- Added transform-origin, transition, and will-change to both CSS selectors
- Maintains proportional scroll compensation for smooth visual experience
The dynamic height adjustment was setting a fixed height on .cv-paper,
but the footer is a sibling element OUTSIDE .cv-paper in the DOM.
This caused the footer to be positioned incorrectly and hidden.
Removed:
- dataset.originalHeight storage
- height calculation and setting
- All dynamic height adjustment logic
The CSS transform: scale() will handle the visual zoom without
affecting the natural document flow, allowing the footer to
appear properly after the content.
Result: Footer should now be visible at all zoom levels
Increased .cv-main bottom padding from 1rem to 8rem to ensure:
- Footer is fully visible and not hidden behind content
- Adequate clearance for zoom control (positioned at bottom: 100px)
- Proper breathing room between content and footer
Result: Footer now properly visible in normal view
The margin-top: -60px on .cv-footer was pulling the footer up too much,
hiding the GitHub link and other footer content.
Removed the negative margin - the reduced .cv-main bottom padding
(1rem instead of 3rem) already provides better spacing without hiding content.
Result:
- Footer content (GitHub, LinkedIn links) now visible
- Still has better spacing than before
- Zoom control at 100px bottom clears footer properly
Fixed two issues with zoom and footer:
1. Dynamic height adjustment based on zoom level:
- Store original height on first zoom (dataset.originalHeight)
- Calculate scaled height: originalHeight × scale factor
- Set container height dynamically to match visual content
- Example: 50% zoom → height = originalHeight × 0.5
- Prevents empty space below content at low zoom
- Prevents overflow at high zoom
2. Footer spacing and positioning:
- Reduced .cv-main bottom padding: 3rem → 1rem
- Added .cv-footer margin-top: -60px to pull footer up
- Increased .zoom-control bottom: 70px → 100px
- Zoom control now clears footer GitHub link
Result:
- Footer appears immediately after content at any zoom level
- No wasted empty space below content
- Zoom control doesn't overlap GitHub link
- Page height dynamically adapts to zoom level
When zooming, the page was moving up/down because the scroll position
wasn't being adjusted to account for the scale change.
Solution: Proportional scroll compensation
- Track old scale before applying new scale
- Calculate scale ratio (newScale / oldScale)
- Adjust scroll position: newScrollTop = oldScrollTop * scaleRatio
Example:
- At 100% zoom, scrolled to pixel 1000
- Zoom to 50%: content at pixel 1000 is now at pixel 500
- Adjust scroll to 500 to keep same content visible
Result:
- Page stays visually in the same place when zooming
- Content you're viewing remains stable
- No jumping or movement during zoom transitions
- Works at all zoom levels (50%-200%)
The viewport-centered zoom approach was creating empty space above
the content when zooming to 50%, causing the CV to move down.
Solution: Simple top-anchored zoom
- Removed all complex scroll compensation logic
- Set transform-origin to "top center" (fixed position)
- Page now scales naturally from the top without movement
- No dynamic transform-origin calculations
- No scroll position adjustments
Result:
- Page stays anchored at top during zoom
- No empty space created above content
- Clean, predictable zoom behavior
- Works correctly at all zoom levels (50%-200%)
The page simply scales up/down from the top center point,
maintaining its position without any jumping or space issues.
Changed zoom behavior from "page-relative" to "viewport-relative":
Before:
- Zoomed from top of page causing perspective/depth effect
- Content appeared to move "through" the viewer
- Disorienting experience when scrolled down
After:
- Zooms from center of YOUR viewport (where you're looking)
- Content expands both above and below your position
- You stay at your original viewing point - no movement
- Like pinch-to-zoom: magnifies what you're currently viewing
Technical implementation:
1. Calculate viewport center relative to page (scrollTop + viewportHeight/2)
2. Convert to percentage of page height
3. Set transform-origin dynamically to that percentage
4. Apply scale transform from your viewing position
5. Adjust scroll to keep same content at viewport center
CSS changes:
- Default transform-origin: center center (was top center)
- Added transition for transform-origin: 0s (instant, no animation)
- Maintains smooth 0.08s linear transform transition
Result: Natural, stable zoom that feels like magnifying glass
When zooming, the page was scaling from a fixed point causing content
to appear to "move through" the viewport like a 3D perspective effect.
This made the zoom feel disorienting as the visible content would shift.
Solution: Proportional scroll compensation
- Track current scale before applying new scale
- Calculate scale ratio (newScale / currentScale)
- Adjust scroll position by multiplying by scale ratio
- Example: scrollTop 1000px at 100% → 1500px at 150%
Benefits:
- Content you're viewing stays in the same visual position
- Zoom feels more like "magnification" than "perspective"
- Smooth, stable zoom experience without content shifting
- Eliminates the "going through me and getting farther" effect
Technical changes:
- Extract current scale from transform property via regex
- Calculate proportional scroll adjustment
- Apply synchronized transform + scroll in requestAnimationFrame
Previously, HTML in short descriptions was being escaped and displayed
as raw text instead of rendering properly. This happened because the
safeHTML template function had been removed for security reasons.
Changes:
- Added safeHTML function back to template.FuncMap (template.go:53-55)
- Updated three template locations to use safeHTML pipe:
* Experience descriptions (cv-content.html:122)
* Award descriptions (cv-content.html:180)
* Project descriptions (cv-content.html:232)
Security note:
The safeHTML function is safe to use here because CV data comes from
trusted YAML files controlled by the site owner, not user input.
Clear documentation added to prevent misuse with untrusted content.
Examples now rendering correctly:
- Award: "Premio por excelencia en marketing B2B...con <a href=...>Clicplan</a>"
- Projects: Links to Lidering, Jorpack, Delivery Bikes BCN, Mobbeel
Design improvements based on user feedback:
- Move current zoom value inside circular reset button
- Add colorful gradient (red→orange→blue→green) to slider on hover
- Make reset button perfectly circular (44px diameter)
- Dynamic value display updates in real-time inside button
- Maintains gray monochrome when not hovering
- Enhanced visual feedback with smooth color transitions
- Mobile responsive with smaller circular button (38px)
Technical changes:
- HTML: Moved #zoom-value-current span inside button
- CSS: border-radius 50%, min-width/min-height for perfect circle
- CSS: Gradient hover for both WebKit and Firefox sliders
- JavaScript: Already compatible (targets same element ID)
Visibility Improvements:
- Background: darker gray rgba(128, 128, 128, 0.7) instead of light transparent
- Base opacity: 0.7 (was 0.3) - much more visible by default
- Text: white/light gray instead of dark gray (better contrast on gray bg)
- Slider track: lighter gray rgba(200, 200, 200, 0.5) - more visible
- Slider thumb: bright white with light border - stands out clearly
- Reset button: lighter background with white text - easier to see
- Larger padding and sizing for better visibility
- Hover state: full opacity with darker background
Result: Control is clearly visible while maintaining elegant gray aesthetic
- Move <strong> tags from data to template for proper HTML rendering
- Update both English and Spanish data files to use plain text
- Template now wraps license type in <strong> tags
- Enhanced CI/CD pipeline with coverage reporting, benchmarks, and artifact uploads
- Implemented rate limiter IP validation with proxy support and spoofing protection
- Added extensive Makefile test targets for coverage, benchmarks, and continuous testing
- Expanded middleware chain with request validation, size limits, and suspicious activity logging
- Changed /?lang=en and /?lang=es to full https://juan.andres.morenorub.io URLs
- Changed /static/pdf paths to absolute URLs with production domain
- Prevents localhost URLs from appearing in downloaded/shared CVs
- Affects both English and Spanish versions
- Changed 'Olympic platforms' to 'Olympic Games platforms'
- Added 'Certified SAP Customer Data Cloud consultant' specialization
- Emphasized digital identity solutions for 35-40 international clients
- More specific positioning as CDC expert
PROBLEM:
- Previous grid approach (50% 30%) didn't work correctly
- Language selector is INSIDE .site-title div, not a separate grid column
- Grid structure: .site-title | .view-controls-center | .action-buttons-right
SOLUTION @ 540px:
Grid Structure:
- Changed .action-bar-content to single column (1fr)
- Hide .view-controls-center and .action-buttons-right
- .site-title becomes the only visible element
Flexbox Inside .site-title:
- .site-title: display: flex, justify-content: space-between
- .site-title-left: flex: 1 1 55% (hamburger + title area)
* Contains hamburger button and CV JAMR title
* flex-grow allows expansion, flex-shrink allows contraction
- .language-selector: flex: 0 0 35% (language buttons)
* Fixed at ~35% width, doesn't grow or shrink
* justify-content: flex-end (align buttons to right)
Text Overflow Protection:
- .site-title-link: overflow: hidden
- .site-title-text: white-space: nowrap, text-overflow: ellipsis
- Prevents title from breaking layout
RESULT:
Mobile distribution:
✓ Hamburger + Title area: ~55% (flexible)
✓ Language selector (EN/ES): ~35% (fixed)
✓ Remaining ~10%: gaps and padding
✓ Visual result: approximately 50% / 30% with breathing room
BENEFITS:
- Proper space distribution matching internal HTML structure
- Language buttons have adequate touch targets
- Title can truncate gracefully if needed
- All controls still accessible via hamburger menu
PROBLEM:
- Mobile action bar using equal width distribution (1fr auto 1fr)
- Not optimal space utilization on small screens
- Action buttons and center controls taking unnecessary space
SOLUTION @ 540px breakpoint:
.action-bar-content:
- Changed grid: 1fr auto 1fr → 50% 30%
- Hamburger menu + title area: 50% width
- Language selector area: 30% width
- Reduced gap: 2rem → 0.5rem
- Reduced padding: auto → 0.5rem horizontal
Hidden elements on mobile:
- .view-controls-center: display: none (moved to hamburger menu)
- .action-buttons-right: display: none (PDF/Print in hamburger menu)
RESULT:
Mobile layout distribution:
✓ Hamburger + CV title: ~50% (more breathing room)
✓ Language selector (EN/ES): ~30% (adequate space)
✓ Remaining 20%: natural spacing/padding
✓ Center controls hidden (accessible via hamburger menu)
✓ Action buttons hidden (accessible via hamburger menu)
BENEFITS:
- Better space utilization on small screens
- Clearer visual hierarchy
- More touch-friendly target areas
- All functionality still accessible via hamburger menu
- Change from HTMX partial update to full page redirect
- Ensures modal and all UI elements render in correct language
- Uses window.location.replace() to avoid history pollution
- Changed selectLanguage() to reload full page instead of HTMX partial update
- Fixes issue where info modal content stayed in original language
- Ensures all UI elements (including modals) update correctly on language change
- Added missing ViewSourceSubtext field to models.InfoModal
- Resolves template rendering error that was preventing JavaScript from loading
- Fixes toggleTheme is not defined error
- Replace blue border with green curly brackets { } using CSS pseudo-elements
- Add proper spacing and vertical centering with inline-flex
- Add engaging subtext below GitHub button: "Want to know how it's built?"
- Bilingual support for subtext (EN/ES)
- Change sidebar titles from center to left alignment for better readability
- Update badge from "Senior Consultant" to "Technical Consultant"
- Applies to both English and Spanish versions
- Update summary to be concise and focus on high-availability systems
- Replace technology-specific badges with professional role titles
- Add emphasis (bold) to key concepts in Skills section
- Update project metrics (20 websites, 35-40 consulting clients)
- Change "EN VIVO" to universal "LIVE" badge for consistency
- Improve professional positioning: Senior Consultant, Full-Stack Engineer, Authentication Specialist, Solution Architect
- Changed right sidebar title alignment from center to right for consistency
- Added center alignment to sidebar accordion headers
- Fixed mobile sidebar alignments to respect left/right positioning (left sidebar: left-aligned, right sidebar: right-aligned)
PROBLEM:
- Right sidebar title was right-aligned instead of centered
- Inconsistent title alignment between left and right sidebars
SOLUTION:
All Sidebars (Desktop):
- .sidebar-title: text-align: center (all section titles centered)
Left Sidebar Content:
- .cv-sidebar-left .sidebar-content: text-align: left
- .cv-sidebar-left .skill-item: text-align: left
Right Sidebar Content:
- .cv-sidebar-right .sidebar-content: text-align: right
- .cv-sidebar-right .skill-item: text-align: right
Mobile @ 768px:
- Both sidebars content: text-align: left !important (unchanged)
RESULT:
Desktop:
✓ Left sidebar: title centered, content left-aligned
✓ Right sidebar: title centered, content right-aligned
✓ Visual consistency with centered titles
✓ Content flows naturally from title position
Mobile:
✓ Both sidebars: content left-aligned for readability
✓ No changes to mobile behavior
VISUAL HIERARCHY:
- Titles centered = visual anchors
- Content aligned to sidebar position = natural reading flow