Update Playwright tests to use new 'icons' terminology:
- Update class name checks: show-logos → show-icons
- Update localStorage keys: cv-logos → cv-icons
- Update UI text references: Logos → Icons
- Update test output messages
Files modified:
- tests/test-all-features.js - Icon class and persistence checks
- tests/test-all-toggles.js - Icon toggle selector
- tests/test-toggle-complete.js - Icon localStorage key
No functional changes, only terminology updates to match
current codebase naming conventions.
PROBLEM:
- htmx:swapError with "Cannot read properties of null (reading 'insertBefore')" on double-click
- Toggle animations were "digital" (instant snap) instead of "analogical" (smooth slide)
- Conflict between server templates with hx-swap-oob and client-side hyperscript
ROOT CAUSE:
- Server templates returned HTML with hx-swap="outerHTML" + hx-swap-oob="true"
- This destroyed and recreated DOM elements during swap
- Second click tried to insert into null parent (element was destroyed)
- CSS transitions broke because element was destroyed mid-animation
SOLUTION:
- Remove all HTML from toggle templates (length-toggle.html, logo-toggle.html, theme-toggle.html)
- Templates now return empty comment: "<!-- Template not used - toggles use hx-swap="none" with inline hyperscript -->"
- Toggles use hx-swap="none" to prevent any DOM replacement
- All visual updates handled client-side via inline hyperscript
- Server only saves cookies in background (no HTML returned)
BENEFITS:
- ✅ No more null reference errors (no DOM destruction)
- ✅ Smooth CSS transitions work perfectly (element preserved)
- ✅ Desktop/mobile toggles sync via direct ID manipulation
- ✅ Zero HTMX swap conflicts
- ✅ Clean separation: client handles visuals, server persists state
DOCUMENTATION:
- Updated MODERN-WEB-TECHNIQUES.md with Phase 8
- Documented the complete debug journey and solution
- Added architecture pattern for client-first toggles
- Moved menu hover logic from JavaScript to CSS selectors, reducing JS to minimal bridge code
- Replaced JavaScript-based toast timing with pure CSS animation lifecycle (slide in → stay → fade out)
- Removed unnecessary event handlers and legacy compatibility code for cleaner implementation
- Removed over-engineered cache system for static CV data that only changes on deployment
- Extracted all route configuration to internal/routes/routes.go for better organization
- Implemented rate limiting and cache control middleware for PDF endpoint protection
- Replace CSS zoom with transform: scale() for proper viewport extension
- Add dynamic margin-bottom to position footer correctly
- Remove zoom: 1 reset from fixed buttons (no longer needed)
- Enables true zoom from 10% to 500% that extends beyond viewport
- 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