Visitors can ask questions about the CV via a floating chat panel.
The agent uses Gemini to answer questions about experience, projects,
skills, and education by querying the cached CV JSON data.
- internal/chat/agent.go: LLM agent with query_cv tool that searches
CV data by section (experience, projects, skills, etc.) with keyword filtering
- internal/chat/handler.go: POST /api/chat endpoint with session management,
graceful degradation when GOOGLE_API_KEY is not set
- chat-widget.html: HTMX-powered floating chat panel with Hyperscript toggle
- _chat.css: Responsive chat UI with dark theme support
- Wired into existing architecture via dependency injection (CVHandler,
routes, main.go) — zero breaking changes, all existing tests pass
Reduces HTTP requests from 44+ individual images to 3 sprite sheets
(~93% reduction). Includes Go sprite generator tool, CSS classes,
template integration, and E2E tests.
- Add cmd/sprites/main.go for sprite generation (60x60px + 120x120px @2x)
- Add _sprites.css with responsive sizing and retina support
- Update templates to use sprites with logoIndex fallback
- Add Makefile targets: sprites, sprites-clean
- Add 9-test E2E suite for sprite functionality
- Add doc/22-SPRITES.md with usage documentation
- Delete orphaned CSS files (color-theme.css, logo-toggle.css,
skeleton.css, main.new.css) - replaced by modular equivalents
- Delete 08-contexts/_print.css - wrongly created during modularization
- Remove 08-contexts folder (now empty)
- Add print.css as standalone with media="print" in HTML
- Update stale comments referencing old file names
- Remove _print.css import from main.css
print.css remains standalone and will NOT be bundled, as it's a
special case loaded only when printing (media="print").
**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 ✓
**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
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
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
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.
Switched from Flexbox to CSS Grid to enable true staggered animations.
Why Grid works better:
- Grid children maintain their grid cell during layout changes
- Buttons can transition individually within their cells
- grid-auto-flow change doesn't force simultaneous repositioning
Changes:
- Container: display: grid with grid-auto-flow
- Desktop: grid-auto-flow: row (vertical stack)
- Mobile: grid-auto-flow: column (horizontal row)
- Buttons: transition: all 0.5s with staggered delays (0s, 0.08s, 0.16s, 0.24s, 0.32s)
Result: Each button now cascades independently when resizing between
desktop and mobile layouts, creating the smooth wave effect.
The issue was that 'transition: all' on the container was forcing all
buttons to move together. Fixed by:
1. Container now transitions only: left, bottom, gap, transform
(NOT flex-direction or all properties)
2. Buttons transition only: transform, opacity, background-color, box-shadow, color
(NOT all properties)
3. Increased stagger delays for better visual effect:
- Button 1: 0s (instant)
- Button 2: 0.08s
- Button 3: 0.16s
- Button 4: 0.24s
- Button 5: 0.32s
4. Added flex-direction: 0s transition in mobile to instant switch direction
while buttons still cascade to new positions
Result: Each button now animates independently with cascading wave effect
when switching between desktop (vertical) and mobile (horizontal) layouts.
Implemented beautiful cascading animation effect where each button
animates individually with a staggered delay.
Changes:
- Each button now has transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1)
- Added nth-child selectors with progressive delays:
* Button 1: 0.05s delay
* Button 2: 0.10s delay
* Button 3: 0.15s delay
* Button 4: 0.20s delay
* Button 5: 0.25s delay
Effect:
When viewport changes (desktop ↔ mobile), buttons don't move as a group.
Instead, they cascade one by one creating a wave-like animation:
- Desktop→Mobile: Buttons flow from vertical to horizontal sequentially
- Mobile→Desktop: Buttons stack from horizontal to vertical one by one
This creates a much cooler, more polished animation than moving all
buttons simultaneously. The 50ms stagger creates perfect visual rhythm.
Fixes:
1. ✅ Reversed button order - Info at bottom, Download at top
- Desktop (bottom→top): Info → Shortcuts → Zoom → Print → Download
- Mobile (left→right): Download → Print → Shortcuts → Info
2. ✅ Restored smooth morphing transitions
- Added transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1)
- Container smoothly transforms from vertical to horizontal
- Buttons smoothly reposition during responsive transition
3. ✅ Preserved all special hover behaviors
- Zoom button: Blue hover + highlights zoom control
- Print button: White bg + green icon on hover (synchronized)
- Download button: Red bg on hover (synchronized)
- All sync functions working: syncPdfHover, syncPrintHover, highlightZoomControl
The flexbox container now has the correct button order AND smooth animations!
Major architectural improvement for button management:
Desktop Layout (left side, vertical):
- Created .fixed-buttons-container positioned left: 2rem, bottom: 2rem
- Buttons stack vertically with flex-direction: column
- Order (bottom to top): Info → Shortcuts → Zoom → Print → Download
- 1rem (16px) gap between buttons
- All buttons 50×50px with consistent styling
- Easy to add/remove buttons - just add template to container
Mobile Layout (bottom center, horizontal):
- Container switches to flex-direction: row at @media (max-width: 900px)
- Centered with left: 50% and transform: translateX(-50%)
- 10px gap between buttons
- Zoom button hidden on mobile (display: none)
- 4 visible buttons: Info, Shortcuts, Print, Download
Benefits:
✅ Single source of truth - container manages all buttons
✅ Easy to add/remove buttons - no manual position calculations
✅ Responsive - automatic reflow from vertical to horizontal
✅ Maintainable - changes in one place affect all buttons
✅ Consistent spacing - gap property ensures even distribution
✅ Future-proof - adding button = adding template to container
Technical Implementation:
- Container uses position: fixed with flexbox
- Buttons use position: relative inside container (not fixed individually)
- All positioning managed by flexbox (gap, flex-direction, align-items)
- Hover states preserved with color-coded backgrounds per button
- Back-to-top button remains separate (not in container)
This replaces individual fixed positioning with a scalable flex layout.
Changes:
- Replaced fixed positioning with flexible layout for mobile buttons
- Now shows 4 buttons on mobile: Download, Print, Shortcuts, Info
- Hide only Zoom button on mobile (accessible via hamburger menu)
- All buttons now 50×50px (consistent size, info button was smaller)
- Even 10px spacing between buttons
- Perfectly centered as a group in viewport
- Future-proof: easy to add/remove buttons without breaking layout
Layout details:
- Total width: 4 buttons × 50px + 3 gaps × 10px = 230px
- Centered using calc(50% - offset) for each button
- Maintains smooth hover effects with translateY only
- All buttons at same vertical position (bottom: 1.5rem)
Benefits:
- Consistent button sizes across mobile/desktop
- Maintainable: adding/removing buttons only requires adjusting calculations
- Better UX: users can access all main actions from bottom bar
- Shortcuts and info buttons now easily accessible on mobile
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
- Added 0.3s ease transitions for background-color and color
- Icons now smoothly transition color as well
- Matches the smooth animations on fixed buttons
- Applies to PDF and Print Friendly buttons in top action bar
- 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
- 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
- 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 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