- 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.
**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
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
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)
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