## Documentation Updates (doc/11-PDF-EXPORT.md)
Added "References Section Integration" section with:
- Template code examples for shortcut URL usage
- Data configuration structure
- Key features and use cases
- Security attributes documentation
- Test command reference
## Test Updates (tests/mjs/28-references-pdf-download.test.mjs)
Completely rewrote test to match new direct link behavior:
- ❌ OLD: Tested modal opening, onclick handlers
- ✅ NEW: Tests direct shortcut URLs
New test coverage (5 tests):
1. English direct shortcut URL validation
2. Spanish direct shortcut URL validation
3. HTTP 301 redirect verification
4. Year validation (404 for invalid years)
5. Works without JavaScript (PDF export context)
Verifies:
- Correct href: /cv-jamr-{year}-{lang}.pdf
- No onclick handlers (pure HTML link)
- Security attributes (target="_blank", rel="noopener noreferrer")
- Year-aware dynamic URLs
- Language-aware (es/en)
- Backend 301 redirects working
- 404 for past/future years
## Benefits
- Comprehensive test coverage for new behavior
- Complete documentation with examples
- Easy to maintain and understand
- Validates entire shortcut URL feature end-to-end
## 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
## Merged Content
- Merged LONG-PDF-GENERATION.md technical details into doc/11-PDF-EXPORT.md
- Removed duplicate documentation file
- Single source of truth for all PDF export documentation
## Added Technical Details
- PDF generation flow diagram
- 2-column vs 3-column layout architecture decision
- Sidebar width configuration guide (18%-30% analysis)
- Detailed CSS injection strategy with code examples
- HTML structure examples for page layouts
- Expanded compact sidebar fonts implementation details
- Font size breakdown table (2-6% reduction)
## Result
- Comprehensive PDF documentation: 966 lines (was 793)
- Added 173 lines of technical implementation details
- Removed 411-line duplicate file
- Better organized, single reference for developers and users
Benefits:
- Single source of truth for PDF features
- No duplication between docs
- Easier maintenance
- Complete technical and user-facing documentation in one place
## 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
Changed PDF filename format to use hyphens instead of underscores for
consistency with other filename components, while keeping API parameter
as `version=with_skills`.
## Changes
**Backend:**
- internal/handlers/cv.go: Add underscore-to-hyphen conversion in filename generation
- New logic: `strings.ReplaceAll(version, "_", "-")` for filename only
- API parameter unchanged: still accepts `version=with_skills`
**Tests:**
- internal/handlers/pdf_test.go: Update expected filenames to use hyphens
- cv-*-with_skills-*.pdf → cv-*-with-skills-*.pdf
**Documentation:**
- Updated all PDF filename references to use hyphens
- PDF-EXPORT-FEATURE.md
- doc/LONG-PDF-GENERATION.md
- PDF-VALIDATION-REPORT.md (new validation report)
**PDFs:**
- Regenerated all 8 PDFs with new naming convention
- Old: cv-short-with_skills-jamr-2025-en.pdf
- New: cv-short-with-skills-jamr-2025-en.pdf
## Examples
API calls unchanged:
- GET /export/pdf?version=with_skills (still works)
Generated filenames:
- cv-short-with-skills-jamr-2025-en.pdf ✓
- cv-long-with-skills-jamr-2025-es.pdf ✓
**Tests:** All passing ✓
**API:** Backwards compatible ✓
- 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
- Changed from 24px back to 60px (matching projects structure)
- Used color: unset !important to reset CSS color override
- Allows inline color styles (style='color: #9333EA') to work
- Matches the project list icon styling exactly
- Reduced course responsibility icons to 24px (was 80px)
- Added color: inherit !important to try preserving inline color styles
- Removed borders, backgrounds, and padding for cleaner inline appearance
Note: color: inherit may inherit from parent theme color rather than
preserving individual icon colors. May need adjustment.
Removed overly broad selector that was shrinking ALL course
responsibility icons to 1.2em. Now only targets truly inline
icons within .course-desc text, not the main 60px list icons.
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)
Calculation: #5e5e5e inverted = #a1a1a1
- Set LIV Golf border to #a1a1a1 in dark theme
- After invert(1) filter → appears as #5e5e5e (correct gray)
- Light theme: resets to normal var(--icon-border)
Filter inverts ENTIRE element including borders, so we
pre-compensate by using the inverse color.
- Changed from transparent to #5e5e5e (rgb(94,94,94))
- Visible but subtle medium gray border in dark theme
- Updated test to verify new border color
- Note: CSS filter on LIV Golf only affects image pixels, not border
- Changed from rgba(255,255,255,0.05) to 'transparent'
- Previous attempt used WHITE transparent which was still visible
- Now borders are rgba(0,0,0,0) - completely invisible
- Verified with Playwright test showing transparent borders
- All icons (OBS, LIV Golf, etc.) now have no visible borders
- Changed from #1e1e1e to rgba(255, 255, 255, 0.05)
- Borders now 95% transparent, virtually invisible
- Applies to ALL icon borders (OBS, LIV Golf, etc.)
- Invert filter remains ONLY for LIV Golf logo
- Changed --icon-border from #2a2a2a to #1e1e1e (rgb(30,30,30))
- Background is #1a1a1a (rgb(26,26,26)), only 4 RGB units difference
- Borders now barely visible, creating cleaner dark theme appearance
- Add --icon-border variable: #ddd (light) / #2a2a2a (dark)
- Add --item-separator variable: rgba(0,0,0,0.1) / rgba(255,255,255,0.05)
- Replace 6 hardcoded icon borders with var(--icon-border)
- Replace 3 hardcoded separators with var(--item-separator)
- Add CSS filter to invert LIV Golf logo in dark themes for visibility
- LIV Golf logo filter: invert(1) in dark/auto(dark), none in light
- Auto theme dark mode now uses #3a3d3e (matches explicit dark)
- Previously used #2a2a2a causing visible difference when switching
- Ensures only two visual states (light/dark) regardless of theme mode
Light theme sidebar:
- Background: #d1d4d2 (light gray)
- Text: dark colors
Dark theme sidebar:
- Background: #3a3d3e (darker gray, between page bg and content)
- Text: light colors (using CSS variables)
- Titles: light colors
Changes:
- Revert sidebar bg to use var(--sidebar-bg) for theme switching
- Update dark theme sidebar color to #3a3d3e
- Replace all hardcoded text colors with CSS variables
- Sidebar titles, content, and arrows now adapt to theme
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.
Change .cv-main background from var(--paper-white) to var(--paper-bg)
so it adapts to theme:
- Light theme: white (#ffffff)
- Dark theme: cool dark gray (#1a1a1a)
Now the entire CV paper is dark in dark theme, not just the page background.
The toggleTheme() function targets .cv-container but initPreferences()
was adding theme-clean to document.body, causing state mismatch.
First click would do nothing because toggle state was inverted.
- Change initPreferences() to target .cv-container
- Fix HTMX swap handler to check .cv-container instead of body
- Now first click works immediately in both directions
Add detailed, user-friendly documentation for PDF export feature in
CUSTOMIZATION.md to help template users understand and customize
PDF generation.
New Documentation:
- Clear explanation of how PDF export works
- All 4 parameters (lang, length, icons, version) with examples
- Filename convention and patterns
- Frontend integration examples (HTML, HTMX)
- Tips for best PDF results
- Troubleshooting common issues
- Advanced customization guidance
Audience:
- Template users who want to customize their own CV
- Non-technical users who need to understand PDF options
- Developers who want to extend PDF functionality
Examples include:
- Complete URL patterns for all parameter combinations
- Code snippets for frontend buttons
- CSS customizations for print appearance
- Common troubleshooting solutions
**Issue**: Color theme button colors were too similar to other buttons
- Light mode (sun): Orange #f39c12 - same as keyboard button
- Dark mode (moon): Blue #3498db - same as zoom button
**Fix**: Updated to more distinctive colors
Light mode (sun):
- Was: Orange #f39c12 (same as keyboard)
- Now: Gold/Bright Yellow #ffd700 (distinct sun color)
Dark mode (moon):
- Was: Bright Blue #3498db (same as zoom)
- Now: Dark Slate Blue #2c3e50 (darker "nighty" blue)
Auto mode:
- Kept: Purple #9b59b6 (already distinctive)
**Result**: Color theme button now has unique, semantic colors that
clearly represent sun (bright yellow) and moon (dark night blue) without
conflicting with other buttons.
Updated both hover and at-bottom states for consistency.
**Bug 1: Wrong at-bottom colors**
- All buttons were showing orange (#f39c12) when at bottom
- User wanted each button to use its own hover color
**Bug 2: Zoom button missing at-bottom state**
- Zoom/search button wasn't changing color at bottom
**Fix - Updated .at-bottom styles to match hover colors:**
Download PDF button:
- Was: Orange (#f39c12)
- Now: PDF Red (#cd6060) - matches hover
Print Friendly button:
- Was: Orange (#f39c12)
- Now: White background + Green icon (#27ae60) - matches hover
Shortcuts button:
- Kept: Orange (#f39c12) - already correct
Color Theme Switcher:
- Was: Uniform orange
- Now: Dynamic colors based on theme mode
- Light mode: Yellow (#f39c12)
- Dark mode: Blue (#3498db)
- Auto mode: Purple (#9b59b6)
Info button:
- Already correct: Green (#27ae60)
Back-to-top button:
- Already correct: Green (#27ae60)
Zoom button:
- Added: Blue (#3498db) - matches hover
- Added to scroll handler in utils._hs
**Result**: Each button now shows its characteristic color when page
is scrolled to bottom, providing consistent visual feedback that matches
the hover state.
Files changed:
- static/hyperscript/utils._hs: Added zoom button
- static/css/main.css: Updated 3 button colors
- static/css/color-theme.css: Dynamic theme colors
**Issue**: Only info and shortcuts buttons were showing orange color when
scrolled to bottom of page. Download PDF, Print Friendly, and Color Theme
buttons were missing this visual feedback.
**Fix**:
1. Added all buttons to at-bottom class logic in utils._hs:
- #download-button
- #print-friendly-button
- .color-theme-switcher
2. Added CSS .at-bottom styles for missing buttons:
- .download-btn.at-bottom
- .print-friendly-btn.at-bottom
- .color-theme-switcher.at-bottom
**Result**: All fixed buttons now show unified orange (#f39c12) background
when page is scrolled to bottom (within 50px), providing consistent visual
feedback across all controls.
Files changed:
- static/hyperscript/utils._hs: Added 3 buttons to scroll handler
- static/css/main.css: Added 2 .at-bottom styles
- static/css/color-theme.css: Added 1 .at-bottom style
**Bug 1: Bidirectional Hover Sync**
- Fixed hover sync to work in BOTH directions
- Hovering left buttons → highlights menu buttons (was working)
- Hovering menu buttons → highlights left buttons (NOW WORKING)
- Added .download-btn and .print-friendly-btn to syncPdfHover/syncPrintHover selectors
**Bug 2: Theme-Aware CV Text Colors**
- CV content text now adapts to light/dark themes
- Light theme: Dark text (#1a1a1a, #333333) for readability
- Dark theme: Light text (#e0e0e0, #d0d0d0) for readability
- Added theme-specific overrides for --text-dark and --text-gray variables
- Solution uses CSS variables (not hardcoded) for future flexibility
Changes:
- static/hyperscript/hover-sync._hs: Added left-side button selectors
- static/css/color-theme.css: Added legacy variable overrides for all themes
Validates button positioning and responsive behavior across all viewports:
Desktop (>900px):
- Left side buttons (download, print, shortcuts, info) vertically stacked
- Back-to-top button on right side (intentional design)
- Zoom button visible
- Different bottom values verify vertical stacking
Wide Mobile (483-900px):
- Horizontal layout at bottom center
- Back-to-top remains on right side
- Zoom button hidden
Narrow Mobile (<483px):
- Back-to-top moved UP (5.5rem) to avoid overlap
- Still positioned on right side
- Horizontal button layout maintained
Accessibility:
- All buttons present and clickable
- Proper visibility checks
This test caught and validates the recent fixes:
1. Back-to-top on RIGHT (not left) in all mobile viewports
2. Narrow mobile positioning to prevent button overlap
3. Consistent hover effects across all buttons
Test results: 4/4 passed
- Desktop layout verification
- Wide mobile responsive layout
- Narrow mobile overlap prevention
- Button accessibility validation
Corrected positioning - button now stays on RIGHT side, just moved UP:
- right: 1.5rem (stays on right)
- bottom: 5.5rem (moved higher to avoid overlap)
This prevents overlap with bottom button row while maintaining
right-side positioning as requested.
**Problem:**
On screens narrower than 483px, the horizontal button layout caused overlap
between back-to-top button and other buttons due to limited width.
**Solution:**
Added media query for max-width: 483px to vertically stack back-to-top button:
- Moved back-to-top to LEFT side (left: 1.5rem)
- Positioned ABOVE info button (bottom: 5.5rem)
- Maintains 4rem vertical spacing between buttons (same as desktop)
**Layout behavior:**
- Above 900px: All buttons vertical on left side (desktop)
- 483px - 900px: Buttons horizontal at bottom center + back-to-top at bottom-right
- Below 483px: Buttons horizontal at bottom center + back-to-top stacked above info button (left side)
This prevents button overlap on narrow mobile devices while maintaining
consistent spacing and positioning.
Files modified: static/css/main.css
Fixed two UI bugs in button styling:
**1. Back-to-top button position in mobile:**
- BEFORE: Button was included in flexbox layout but not positioned (defaulted to left)
- AFTER: Excluded from flexbox layout, positioned at bottom-right (1.5rem) - same as desktop
- Mobile flexbox layout now has 5 buttons (download, print, shortcuts, theme, info)
- Back-to-top stays independently positioned at bottom-right corner
**2. Consistent hover effects for all buttons:**
- BEFORE: Only info and shortcuts buttons had enhanced box-shadow on hover
- AFTER: All buttons (download, print, shortcuts, info, back-to-top) have consistent hover effects
- Added `box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4) !important` to mobile hover states
- Creates unified "edge glow" effect across all button hovers
**Technical changes:**
- Split mobile button reset into two groups (flexbox buttons vs back-to-top)
- Enhanced mobile hover effect with box-shadow for visual consistency
- Maintains desktop behavior (back-to-top at bottom-right: 2rem)
Files modified: static/css/main.css
ISSUE: Matomo analytics not loading in production
ROOT CAUSE:
- Matomo script had nonce="{{.CSPNonce}}" attribute
- Go backend doesn't generate CSPNonce template variable
- Empty nonce attribute caused CSP to block the script in production
FIX:
- Removed nonce attribute from Matomo script tag
- CSP header already includes 'unsafe-inline' so nonces not needed
- Script now loads correctly in both localhost and production
VERIFICATION:
- Matomo will now load and track pageviews in production
- Check browser console for _paq object
- Verify tracking in Matomo dashboard
Remove SKELETON-LOADER-DEBUG-STATUS.md (318 lines) - debugging document no longer needed after successful resolution.
Feature status:
- Implementation complete (JavaScript migration from hyperscript)
- All 7 automated tests passing
- Manual testing verified working
- Documented in 2-MODERN-WEB-TECHNIQUES.md Section 11
This debug log served its purpose during troubleshooting but is now redundant with:
- Production code documentation
MIGRATION SUMMARY:
- Moved skeleton loader logic from hyperscript to JavaScript (main.js)
- Changed from htmx:oobAfterSwap to htmx:afterSettle event
- Changed OOB swap from innerHTML to outerHTML for proper element replacement
- Added languageSwitching flag for state tracking
- Added 100ms delay after afterSettle for final render completion
DOCUMENTATION UPDATES:
- 2-MODERN-WEB-TECHNIQUES.md: Updated skeleton loader section with