Light theme:
- Change background from #b8bbbe to #d6d6d6 (light gray)
- Add concentric squares pattern with 4 gradient layers
- Creates subtle geometric texture on gray background
Dark theme:
- Change background from rgb(82, 86, 89) to #3a3a3a (medium gray)
- Add diagonal grid pattern with green glow (rgba(0, 255, 128, 0.15))
- 45deg and -45deg crosshatch creates retro tech aesthetic
Both patterns use CSS custom properties (--page-bg-pattern) and are
applied via background-image in _reset.css. Patterns are fixed and
maintain visibility across theme switches.
- Configure chromedp with headless mode and CI-friendly flags
- Add --no-sandbox flag (required for Docker/CI environments)
- Add --disable-gpu and --disable-dev-shm-usage for stability
- Use NewExecAllocator with DefaultExecAllocatorOptions
This fixes 'chrome failed to start' errors in GitHub Actions CI where
Chrome needs to run without a display and with relaxed sandbox restrictions.
- Add findDataFile() helper to search up directory tree for data files
- Fixes tests running from subdirectories (internal/handlers)
- Install Chrome in GitHub Actions for PDF generation tests
This resolves test failures that have existed since PDF tests were introduced:
- Error: 'open data/cv-es.json: no such file or directory'
- Error: 'chrome failed to start'
Tests now properly locate data files from any working directory and
have Chrome available for PDF generation in CI environment.
- 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
**TESTING** - These patterns can be easily reverted or modified
Light Theme (Woven Fabric Pattern):
- Subtle gray crosshatch pattern
- Multiple gradients creating woven texture
- Professional and clean appearance
Dark Theme (Diagonal Grid with Green Glow):
- 45° and -45° diagonal lines
- Green glow (rgba(0, 255, 128, 0.1))
- Cyberpunk/tech aesthetic
- 40px grid spacing
Implementation:
- Added --page-bg-pattern CSS variables to color-theme.css
- Applied patterns via body background-image in _reset.css
- OLD pattern commented out and preserved for easy restoration
- Patterns work with light, dark, and auto themes
Files changed:
- static/css/color-theme.css: Added pattern variables
- static/css/01-foundation/_reset.css: Applied patterns to body
To revert: Uncomment old pattern and remove --page-bg-pattern usage
**Test 3 Updated**: Shortcut URL behavior changed
- OLD: Expected HTTP 301 redirect to /export/pdf
- NEW: Expects HTTP 200 with direct PDF and Content-Disposition header
- Verifies correct filename: cv-jamr-2025-{lang}.pdf
**Test 6 Added**: Language switching links
- Tests "See this CV in Spanish/English" links
- Verifies URL correctness (/?lang=es and /?lang=en)
- Ensures no URL corruption from replaceYearPlaceholder fix
- Checks target="_blank" attribute
- Tests both English → Spanish and Spanish → English links
All 6 tests passing:
✅ EN Direct Shortcut URL
✅ ES Direct Shortcut URL
✅ Shortcut URL Direct Download (updated)
✅ Year Validation
✅ No JavaScript Required
✅ Language Switching Links (new)
**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
## 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