Fixed two critical mobile view issues:
1. Extended CV Sidebar Accordion:
- Updated sidebar.html to use native <details> element (was div with onclick)
- Styled accordion header to match CV title badges dark theme (#303030)
- Applied consistent styling: dark gray background, light text, uppercase, no spacing
- Result: Sidebars now collapse/expand properly with native HTML functionality
2. PDF Download Modal Centering:
- Added JavaScript-based centering for mobile viewports (≤768px)
- Uses inline styles with !important flag to override browser defaults
- Updated download button to call openPdfModal() function
- Result: Modal is perfectly centered on mobile (0px offset)
Technical notes:
- Modal centering required setProperty() with 'important' flag
- Accordion matches cv-title-badges-header style exactly
- All tests passing: accordion toggle, modal centering
Files modified:
- templates/partials/cv/sidebar.html
- static/css/05-responsive/_breakpoints.css
- static/js/main.js
- templates/partials/widgets/download-button.html
Tests added:
- tests/mjs/43-mobile-accordion-and-modal-test.mjs
- tests/mjs/46-visual-accordion-style-test.mjs
Changed tooltip text for fixed buttons to match action bar wording:
- Print button: "Print CV" → "Print Friendly"
- Download button: "Download PDF" → "Download as PDF"
This ensures consistency across all button locations (fixed left buttons,
action bar, and hamburger menu).
Changes:
- templates/partials/widgets/print-friendly-button.html: Updated tooltip text
- templates/partials/widgets/download-button.html: Updated tooltip text
- Add has-tooltip class and data-tooltip to info button
- Add has-tooltip class and data-tooltip to color theme switcher
- Both buttons on LEFT side show tooltips on RIGHT
- Mobile: tooltips appear on TOP (like macOS Dock)
- Complete tooltip coverage for all action buttons
- Remove tooltip-left class from zoom and shortcuts buttons (all left-side buttons show tooltips on RIGHT)
- Add mobile CSS rules for fixed-btn tooltips to appear on TOP (like macOS Dock)
- Update button template comments to reflect correct positioning
- Mobile: All fixed buttons now show tooltips above (top position)
- Desktop: All left-side fixed buttons show tooltips on right
Simplified PDF download UX to use only the modal loading overlay,
removing the redundant toast notification that appeared when the modal
was closed during download. Updated tests to reflect the new behavior.
Changes:
- Removed toast trigger logic from PDF modal download function
- Removed modal close event listener for toast display
- Updated toast notification test expectations
- Fixed recommended card outline styling
- Reorder cards: Short → Default ⭐ → Extended (was Short → Extended → Default)
- Add cleanup to prevent stuck blur effect on modal reopen
- Use MutationObserver to reset loading state when modal opens
- Add close event listener to clear loading-active class
Fixes:
1. Default CV now displays in center position as intended
2. Modal no longer shows grey/faded content due to stuck loading-active class
**Issue 1: URL corruption in "See this CV in..." links**
- Bug: replaceYearPlaceholder used fmt.Sprintf on ALL URLs
- URLs like "/?lang=es" were corrupted to "/?lang=es%!(EXTRA string=2025)"
- Fix: Changed to strings.ReplaceAll("{{YEAR}}", year)
- Result: Only replaces actual {{YEAR}} placeholders, leaves other URLs intact
**Issue 2: Download filename not respected**
- Bug: Shortcut URLs (cv-jamr-2025-en.pdf) redirected with HTTP 301
- Browsers used original URL filename instead of Content-Disposition header
- Fix: Generate PDF directly in DefaultCVShortcut handler
- Result: Returns PDF with correct filename in Content-Disposition header
Files changed:
- internal/models/cv.go: Fixed replaceYearPlaceholder function
- internal/handlers/cv.go: Changed redirect to direct PDF generation
Both fixes verified:
- "See this CV in Spanish" link: href="/?lang=es" ✓
- Download link: filename=cv-jamr-2025-en.pdf ✓
UX Improvement:
- Added visual feedback during PDF generation process
- Users now see immediate response when clicking download button
- Clear communication about what's happening during the wait
New Features:
- Loading overlay with animated spinner
- Format-specific estimated generation times (Short: ~3s, Default: ~4s, Long: ~8s)
- Blur effect on modal background during loading
- Bilingual support (English/Spanish)
- Automatic modal close after download completes
CSS Updates (static/css/04-interactive/_modals.css):
- Added .pdf-loading-overlay with glassmorphism effect
- Spinning animation for loader (1s linear infinite)
- Fade-in animation (300ms)
- Accessibility: respects prefers-reduced-motion
- Background blur when loading active
HTML Updates (templates/partials/modals/pdf-modal.html):
- Loading overlay structure with spinner
- Dynamic loading messages based on selected format
- Enhanced downloadPDF() function with timing logic
Before: Click → silence → download appears
After: Click → overlay + spinner + estimate → download appears
## Problem
- References section PDF link opened modal dialog
- Modal doesn't work in PDF or non-browser environments
- Link was not usable when CV exported as PDF
## Solution
- Changed from `onclick="openPdfModal()"` to direct shortcut URL
- Spanish CV: `/cv-jamr-2025-es.pdf`
- English CV: `/cv-jamr-2025-en.pdf`
- Language-aware: Uses {{$.Lang}} template variable
- Year-aware: Uses {{$.CurrentYear}} for auto-updates
## Benefits
- ✅ Works in any environment (PDF, browser, external links)
- ✅ Direct download without JavaScript/modal
- ✅ Shareable, permanent URLs
- ✅ Auto-updates yearly with no code changes
Technical details:
- File: templates/partials/sections/references.html:16
- Uses shortcut URLs created in previous feature
- Maintains target="_blank" and security attributes
## Shortcut URLs
- New routes: /cv-jamr-{year}-{lang}.pdf (e.g., /cv-jamr-2025-en.pdf)
- Year validation: Only current year accepted, returns 404 for past/future
- Auto-redirects (301) to: /export/pdf?lang={lang}&length=short&icons=show&version=with_skills
- Both languages supported: en and es
## PDF Modal Updates
- Replaced "Current View" option with "Default CV (Recommended)"
- Visual highlighting: purple gradient badge, star emoji ⭐, bold text
- Uses shortcut URL with dynamic year detection
- Clear recommendation for users (5 pages, short with skills)
## Technical Details
- Handler: DefaultCVShortcut() in internal/handlers/cv.go
- Pattern check in Home() handler for proper routing
- Helper function: window.openPdfModal() for references section
- Documentation: PDF-SHORTCUT-IMPLEMENTATION.md
Benefits:
- Memorable, shareable URLs (juan.andres.morenorub.io/cv-jamr-2025-en.pdf)
- Auto-updates yearly without code changes
- Clear user guidance for recommended CV format
- Created keyboard._hs as reference documentation (inline handler in body tag)
- Externalized 9 hamburger menu navigation links to scrollToSection()
- Added scrollToSection() as JavaScript function (CSP-safe, no eval needed)
- Restored original keyboard handler format in body tag (working correctly)
- Removed problematic navigation._hs (had syntax/CSP issues)
- Added Rule 4 to HYPERSCRIPT-RULES.md on event handler externalization
- Updated PROJECT-MEMORY.md with externalization guidelines
Key learnings:
- Complex event handlers that inspect event properties must stay inline
- JavaScript functions avoid CSP unsafe-eval restrictions
- Navigation successfully externalized: 9 links → 1 function (91% reduction)
Changed PDF export naming convention from 'detailed' to 'short' for better clarity and contrast with 'extended'. Updated:
- Documentation: All references from 'detailed' → 'short'
- JSON data files: Static PDF URLs now use cv-short-jamr-{{YEAR}}-{lang}.pdf
- Frontend modal: Removed 'short' → 'detailed' mapping (now stays as 'short')
- Static PDFs: Renamed cv-detailed-* to cv-short-* (deleted old files)
- Backend validation: Change
Problem: Inline icons embedded in responsibilities, courses, and
projects had explicit width='60' height='60' attributes that made
them too large (60px instead of ~16px).
Solution:
- Added CSS with !important to override inline width/height attributes
- Targeted inline icons in:
* Course responsibilities and descriptions
* Project descriptions and technologies
* Experience responsibilities (within divs)
- Preserved large icons (80px) for main company/course/project logos
Changes:
- static/css/03-components/_courses.css: Override to 1.2em
- static/css/03-components/_projects.css: Override to 1.2em
- static/css/03-components/_cv-section.css: Override to 1.2em
Test Results:
✅ 7 course inline icons: 16px × 16px
✅ Main company icons: 80px × 80px (preserved)
Change sidebar background from var(--sidebar-bg) to fixed #d1d4d2
so it remains the same light gray color in both light and dark themes.
The sidebar should provide a consistent visual anchor regardless
of the main content theme.
Complete color theme system (light/dark/auto) with dynamic UI:
Features:
- Color theme switcher with auto/light/dark modes
- Dynamic button colors on hover (purple/yellow/blue per theme)
- localStorage persistence across sessions
- Proper button positioning (desktop and mobile)
- Mobile: 5-button layout with theme before info button
Fixes:
- CSP updated to allow jsDelivr CDN for iconify icons
- Button repositioning: Download PDF and Print Friendly at top
- Hover-only colors (not persistent)
- Mobile button order corrected
Files:
- static/css/color-theme.css - Theme system with CSS variables
- static/js/color-theme.js - Theme switching logic
- templates/partials/color-theme-switcher.html - Button component
- internal/middleware/security.go - CSP fix for jsDelivr
- tests/mjs/13-color-theme-switcher.test.mjs - Comprehensive test
- tests/TEST-SUMMARY.md - Updated test documentation
Updates skeleton header to exactly match the actual CV header layout with
photo absolutely positioned on the right side.
**Layout Changes:**
- Photo: 150x200px positioned absolutely (top: 15px, right: 15px)
- Name: 40px height (h1 size)
- Subtitle: 24px height (larger)
- Intro text: 90px height (3-4 lines)
- Header has padding-right: 185px to accommodate photo
- Removed flex layout, using absolute positioning like actual
**Visual Improvements:**
- Photo border: 3px solid #e8e8e8 (matches actual white border)
- No border-radius on photo (matches actual)
- Larger text blocks for better visual match
- Proper spacing between elements
**Tests:**
- test-skeleton-verify.mjs: ✅ All 4 language switches pass
- Skeleton displays correctly on every transition
Implements component-level skeleton loaders that display during language
transitions, providing visual feedback while content swaps.
**Implementation:**
- Dual-state structure: each component has actual-content + skeleton-content
- CSS toggles visibility via opacity transitions (250ms)
- Global hyperscript listener on <body> for language button clicks
- Adds .loading class to parent containers that persist across OOB swaps
- Skeleton shapes mimic actual components (header, name, photo, intro)
**Key Technical Solutions:**
- Event delegation using event.target.matches('.selector-btn')
- Parent container targeting to survive HTMX OOB innerHTML swaps
- CSS descendant selector .loading .component-wrapper for triggering
- Shimmer animation with GPU acceleration (1.8s infinite)
**Files Modified:**
- static/css/skeleton.css: Complete skeleton system with shimmer animation
- templates/index.html: Global hyperscript for .loading class management
- templates/partials/sections/header.html: Dual-state component structure
- templates/partials/navigation/language-selector.html: Removed local hyperscript
**Tests:**
- test-skeleton-verify.mjs: Validates skeleton across 4 language switches
- All tests passing: ✅ Consistent activation on every language change
Implemented dual-state skeleton system as specified in prompt:
- Each component has actual-content + skeleton-content structure
- CSS toggles visibility via .loading class on component-wrapper
- Individual skeleton boxes (name, photo, intro, etc.) with shimmer animation
- Hyperscript triggers loading state during language switch
Changes:
- skeleton.css: Complete component-level skeleton CSS system with shimmer
- language-selector.html: Hyperscript to add/remove .loading class
- header.html: Dual-state structure with skeleton placeholders
Behavior:
- Click language button → .loading class added to components
- Actual content fades out (opacity → 0) in 250ms
- Skeleton boxes appear and shimmer
- After HTMX swap + 100ms delay → .loading class removed
- New content fades in (opacity → 1) in 250ms
Test Results:
✅ Component wrapper structure verified
✅ Dual-state toggle working correctly
✅ Skeleton elements present and animated
✅ Shimmer animation active (1.8s infinite loop)
✅ Accessibility: respects prefers-reduced-motion
✅ Print: skeletons hidden, content always visible
Next: Add skeleton structure to remaining components (experience, education, skills, projects)
Previously, the .htmx-swapping class was only applied to the language
selector button target, not to the actual CV content containers that
were being swapped via hx-swap-oob.
Solution:
- Added hyperscript event handlers to language selector buttons
- On htmx:beforeRequest → add .htmx-swapping to both content wrappers
- On htmx:afterSwap → remove .htmx-swapping from both wrappers
Effect:
- Skeleton shimmer (opacity 0.3 + animated ::before overlay) now
appears on CV content during language switch (~500ms duration)
- Smooth transition with shimmer effect inside content area
- No external overlay layer - effect is within existing DOM structure
Verified with Playwright test showing:
✅ .htmx-swapping class applied during swap
✅ Opacity changes to 0.3
✅ ::before pseudo-element with shimmer animation active
User can now zoom up to 300% for better readability.
Changes:
- Update zoom slider max from 175 to 300
- Update aria-valuemax to 300
- Update display label to show 300
- Update README to reflect new range (25%-300%)
Tested with curl - verified max="300" in HTML output
Zoom level persistence was broken because hyperscript was setting the
container's value instead of the slider's value on page load.
Changes:
- Fix zoom-control.html line 10: set #zoom-slider's value (not 'my value')
- Add comprehensive zoom persistence test (10-zoom-persistence.test.mjs)
- Update cv-functions.js documentation to clarify hyperscript interop
- Add zoom control feature to README
Test results: 5/5 tests pass
- Zoom saves to localStorage when changed ✅
- Zoom restores correctly on page reload ✅
- Reset to 100% works and persists ✅
Architecture note:
- Hyperscript 'call' within _="" attributes requires global JS scope
- JavaScript wrappers bridge window exposure to hyperscript evaluate()
- Pattern: window.fn() → _hyperscript.evaluate('hyperscriptFn()')
CRITICAL BUG FIX: Hover states now sync between action bar and hamburger menu
Changes:
1. Added mouseenter/mouseleave handlers to menu PDF button
- templates/partials/navigation/hamburger-menu.html:178-181
- Added .menu-pdf-btn class for targeting
- Added hyperscript hover sync events
2. Updated syncPdfHover() function
- static/js/cv-functions.js:71-82
- Now selects both .pdf-btn and .menu-pdf-btn
- Both buttons get .pdf-hover-sync class on hover
3. Updated syncPrintHover() function
- static/js/cv-functions.js:88-99
- Now selects both .print-btn and .menu-print-btn
- Both buttons get .print-hover-sync class on hover
4. Added CSS for menu PDF button hover sync
- static/css/main.css:2690-2700
- .menu-pdf-btn.pdf-hover-sync styling (white bg, red icon)
- Matches action bar PDF button hover state
5. Created comprehensive hover sync test
- tests/mjs/8-hover-sync.test.mjs
- Tests all 4 hover scenarios (bar→menu, menu→bar for both buttons)
- Validates event handlers and CSS class application
- Manual verification instructions included
Behavior now correct:
✅ Hovering action bar PDF button highlights menu PDF button
✅ Hovering action bar Print button highlights menu Print button
✅ Hovering menu PDF button highlights action bar PDF button
✅ Hovering menu Print button highlights action bar Print button
Fixes documented bug from PROJECT-MEMORY.md Section 3.
This commit includes two major improvements:
1. Hyperscript Code Organization:
- Extracted all hyperscript blocks >3 lines into reusable functions
- Created 6 new functions in functions._hs:
* toggleCVLength(isLong) - CV length toggle sync
* toggleIcons(showIcons) - Icons toggle sync
* toggleTheme(isClean) - Theme toggle sync
* syncPdfHover(show) - PDF button synchronized hover
* syncPrintHover(show) - Print button synchronized hover
* highlightZoomControl(show) - Zoom control highlight effect
- Reduced inline hyperscript from 11+ lines to 1-2 lines per element
- Updated 8 template files to use function calls:
* hamburger-menu.html
* view-controls.html
* action-buttons.html
* download-button.html
* print-friendly-button.html
* zoom-toggle-button.html
2. Mobile Button Layout (max-width: 900px):
- Repositioned three fixed buttons (PDF, Print, Info) horizontally at bottom center
- Print button perfectly centered in viewport
- Download button on left, Info button on right
- Hidden zoom and shortcuts buttons on mobile (available in hamburger menu)
- Removed conflicting old mobile styles that were hiding print button
- Smooth hover transitions maintained with proper transform calculations
Technical details:
- Used CSS transform with calc() for precise horizontal positioning
- Maintained hover effects with combined translateX/translateY transforms
- Ensured accessibility with proper ARIA labels and spacing
- All functions check element existence before manipulation
- LocalStorage sync maintained across desktop/mobile toggles
- Removed .zoom-active CSS class and JavaScript logic
- Zoom button stays same gray color whether zoom is on or off
- Fixed Show Zoom menu button visibility (changed inline style to zoom-hidden class)
- Menu item now correctly appears when zoom is hidden
Print Button Improvements:
- Hover shows white background with green icon (was green bg + white icon)
- Applied to all print buttons: fixed, action bar, and menu
- Default state remains dark gray with white icon
Zoom Control Improvements:
- Text now 100% white (was 70-95% opacity) for better readability
- Increased font sizes: labels 0.95rem (was 0.8rem), current value 1.05rem (was 0.9rem)
- Hover zoom toggle button highlights zoom control with blue glow shadow
- Active zoom button uses semi-transparent blue rgba(52, 152, 219, 0.5) instead of solid
Z-Index Fix:
- Hamburger menu now appears above fixed buttons (z-index: 1000 vs 999)
- PDF buttons (fixed, action bar, menu) now sync hover states across all instances
- Print-friendly buttons (fixed, action bar, menu) sync green hover states
- White PDF icon with red hover background (#cd6060)
- Green print button hover (#27ae60)
- Implemented using hyperscript with .pdf-hover-sync and .print-hover-sync classes
- Creates cool visual feedback showing all related buttons simultaneously
- 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
- Minimal, professional CV design with paper-on-gray layout
- Bilingual support (Spanish/English) with HTMX language switching
- JSON-based content management (cv-en.json, cv-es.json)
- Print-optimized CSS for PDF export
- Responsive design for all devices
- Go backend with stdlib net/http
- Clean, maintainable codebase
Features:
- 18+ years professional experience
- SAP CDC expertise
- Complete project history
- Education, certifications, awards
- Multi-language support
Tech stack: Go, HTMX, vanilla CSS