Plain text endpoint:
- Add /text route for plain text CV (for curl/AI crawlers)
- Use k3a/html2text library for HTML-to-text conversion
- Add Plain Text button to hamburger menu with UI translations
Contact form feature:
- Add ContactHandler with proper email service integration
- Add CSRF protection middleware
- Add rate limiting (5 submissions/hour per IP)
- Add honeypot and timing-based bot protection
- Add input validation with detailed error messages
- Add security logging middleware
- Add browser-only middleware for API protection
Code quality:
- Fix all golangci-lint errcheck warnings for w.Write calls
- Remove duplicate getClientIP functions
- Wire up ContactHandler in routes.Setup
- Add llms.txt file for AI crawlers (llmstxt.org standard)
- Enhance robots.txt with 15+ AI bot rules (GPTBot, ClaudeBot, etc.)
- Expand JSON-LD structured data from 1 to 12+ schema blocks:
- Person (enhanced with occupations, languages, employers)
- WebSite, BreadcrumbList, ProfilePage
- EducationalOccupationalCredential (dynamic per education)
- Course (dynamic per certification)
- Create doc/15-SEO.md with comprehensive SEO documentation
- Update MODERN-WEB-TECHNIQUES.md with SEO section (techniques 11-13)
Based on WPBeginner 2025 SEO recommendations for AI Overviews,
structured data, and E-E-A-T signals.
- Fix golangci-lint errcheck errors by using t.Setenv() instead of os.Setenv()
- Add CSS bundle build step to deploy workflow for production
- Add graceful fallback to modular CSS if bundle doesn't exist
- Remove unused os import from preferences_test.go
- 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").
- Move all bilingual text from templates to UI JSON (labels, buttons, modals)
- Move skills summary paragraph to CV JSON with HTML support
- Add new UI sections: navigation, viewControls, sections, footer, portfolio,
pdfModal, shortcutsModal, infoModal, widgets
- Update Go structs to match expanded JSON structure
- Add template.HTML type for CV.SkillsSummary field
- Add JSON content validation test (70-json-content-validation.test.mjs)
Templates now contain only structural logic (CSS classes, HTML attributes)
while all user-visible text loads from JSON files for proper i18n support.
First-time visitors now always see light theme (paper aesthetic)
regardless of their system dark mode preference.
Users can still switch to dark or auto mode, and their preference
is saved to localStorage for future visits.
This maintains the professional CV paper appearance as the default
experience while giving users full control over their preference.
- Add closeOnBackdrop(modal, evt) to utils._hs for modal backdrop clicks
- Add scrollToTop(evt) to utils._hs for smooth scroll to top
- Simplify 3 modal templates (shortcuts, info, pdf) from 4 lines to 1
- Simplify back-to-top button from 3 lines to 1
- Move inline drag handling (~35 lines) to external functions (~4 lines)
- Add isZoomDragTarget(el), startZoomDrag(), moveZoomDrag(), endZoomDrag()
- Note: 'target' is reserved in hyperscript, use 'el' for parameters
- Drag state stored on element (_isDragging, _initialX, _initialY)
Zoom control HTML now has clean, minimal hyperscript handlers.
- Add scrollToSection() to utils._hs (was missing after cv-functions.js removal)
- Move error toast close handler to inline hyperscript
- Remove initMenuCloseOnClick() - now integrated into scrollToSection()
- Remove initErrorToastClose() - now hyperscript inline handler
- Remove unused initScrollBehaviorJS() fallback (~70 lines dead code)
This fixes the navigation menu scroll functionality and eliminates
more JavaScript in favor of hyperscript.
- Move initZoomControlButtons() from main.js to hyperscript handlers
- zoom-toggle-button: on click call toggleZoomControl()
- zoom-close: on click call hideZoomControl()
- show-zoom-menu-btn: on click call showZoomControl()
- Move expandAllSections/collapseAllSections from JS to utils._hs
- Add zoom visibility functions to zoom._hs:
- showZoomControl(), hideZoomControl(), toggleZoomControl()
- Update hamburger menu links to use hyperscript calls
Eliminates ~75 more lines of JavaScript in favor of declarative
hyperscript, continuing the pattern of moving behavior to ._hs files.
Root cause: overflow-x: hidden on html/body elements breaks position: sticky
on descendant elements. This is a known CSS behavior.
Changes:
- _reset.css: Changed overflow-x from 'hidden' to 'clip' on html and body
- 'clip' prevents horizontal scrolling WITHOUT breaking sticky positioning
- index.html: Restored hyperscript scroll handlers (initScrollBehavior, handleScroll)
- main.js: Disabled JavaScript scroll fallback in favor of hyperscript
Behavior:
- Desktop: Action bar hides on scroll down, reappears on scroll up
- Mobile (≤900px): Action bar stays visible at all times (CSS override)
Tested: Both desktop and mobile scroll behaviors work correctly
The hyperscript-based scroll behavior was not working reliably across all browsers.
Replaced with a pure JavaScript implementation that:
Desktop (>900px):
- Hides action bar on scroll down (past 100px threshold)
- Shows action bar on scroll up
- Shows action bar at top of page
Mobile (≤900px):
- Always keeps action bar visible
- Actively removes header-hidden class on mobile
- Handles viewport resize for responsive testing
Changes:
- Added initScrollBehaviorJS() function to main.js
- Removed hyperscript scroll handlers from body tag in index.html
- Kept keyboard shortcut handlers in hyperscript (still working)
- Uses passive scroll listener for better performance
This fixes the bug where:
- Desktop: bar would hide but not show again on scroll up
- Mobile: bar was incorrectly hiding despite CSS override
On mobile, the info modal fonts were too large and causing content overflow.
All text elements have been proportionally reduced for better mobile UX.
Changes for mobile (≤768px):
- Modal padding: 2rem → 1.5rem (vertical), 1.5rem → 1rem (horizontal)
- Close button: 40px → 32px, icon 24px → 20px
- Title: 1.5rem → 1.05rem (30% reduction)
- CV title: 0.95rem
- Photo: 50px × 67px → 30px × 40px
- Description: 0.95rem → 0.85rem
- Tech items: 0.9rem → 0.8rem, icons 32px → 24px
- GitHub button: 0.875rem, icon 24px → 20px
- Tech grid: Single column layout
- Reduced spacing throughout
Result:
- All content fits comfortably within mobile viewport
- No text overflow or coverage issues
- Improved readability and visual hierarchy
- Better use of limited mobile screen space
Tests created but have Playwright API compatibility issues (non-blocking)
The menu was showing light gray text in dark theme, creating poor contrast
against the white menu background. Menu text must always be dark since
the menu background is always white regardless of theme.
Changes:
- Added dark theme overrides for .navigation-menu and .submenu-content
- Force --text-dark to #1a1a1a (dark text) in dark theme for menu
- Force --text-gray to #333333 (dark gray) in dark theme for menu icons
- Applied same fix for auto theme when system preference is dark
Result:
- Menu text: Dark/black (rgb(26, 26, 26)) in all themes
- Menu icons: Dark gray (rgb(51, 51, 51)) in all themes
- Menu background: White (rgb(255, 255, 255)) in all themes
- Proper contrast and readability restored
Test created: 68-menu-colors-dark-theme-test.mjs
Screenshots: menu-light-theme.png, menu-dark-theme.png
Changed zoom button color from #9b59b6 to #5c59b6 for better visual appeal.
The new shade is more blue-tinted, creating a vibrant indigo/periwinkle appearance
that remains distinct from the info button blue.
Changes:
- Updated zoom-toggle-btn background: rgba(92, 89, 182, 0.7)
- Updated hover state: #5c59b6
- Updated at-bottom state: #5c59b6
- Updated test output to reflect correct hex code
Test verified: All buttons visible and colors distinct at all viewports.
Issues fixed:
1. Zoom button now uses purple color (rgba(155, 89, 182, 0.7)) instead of blue
2. Info button keeps blue color (rgba(52, 152, 219, 0.7))
3. Both buttons now show distinct colors in default state, not just on hover
4. Device detection now considers viewport width, not just user agent
5. Buttons no longer hide in responsive mode at desktop viewport sizes
Changes:
- Updated zoom-toggle-btn to use purple background color
- Updated info-button to use blue background color (explicit, not var)
- Modified device-detection.js to check viewport width (≤900px) in addition to UA
- Added resize listener to update device class dynamically
- Created test (67-button-colors-and-visibility-test.mjs) to verify fixes
Testing:
- Desktop (1278px): All buttons visible with distinct colors
- Mobile (375px): Zoom/shortcuts hidden, core buttons visible
- Device detection now viewport-aware (prevents hiding at desktop sizes)
PDF generation tests require a running HTTP server for chromedp to connect to.
This is not available in CI environment, causing tests to fail with ERR_CONNECTION_REFUSED.
Changes:
- Added skip condition to TestDefaultCVShortcut when running in short mode
- Updated CI workflow to use -short flag for tests and benchmarks
- Removed Chrome installation from CI (not needed for unit tests)
- Integration tests can still run locally without -short flag
The back-to-top button is intentionally hidden on page load and only
appears after scrolling down. This is expected behavior, not a bug.
Updated test to not flag this as an issue.
1. Removed unused getPreferenceCookie and setPreferenceCookie functions
- These were flagged by golangci-lint as unused
- Cookie preferences now handled client-side via localStorage
- Removed unused net/http import
2. Fixed desktop sidebar accordion auto-opening
- Updated handleLandscapeAccordions() to open accordions in desktop view (≥769px)
- Sidebars now show content in desktop, landscape mobile, and portrait mobile
- Only keep accordions collapsed in portrait mobile for space saving
3. Created comprehensive multi-viewport test (66-comprehensive-all-viewports-test.mjs)
- Tests desktop (1278px), portrait mobile (375×667), landscape mobile (667×375)
- Validates sidebars, accordion state, content visibility, AND all buttons
- Checks button backdrop visibility in mobile views
- Every feature now has corresponding test coverage
Fixes golangci-lint errors:
- internal/handlers/cv_helpers.go:366: func getPreferenceCookie is unused
- internal/handlers/cv_helpers.go:375: func setPreferenceCookie is unused
- internal/handlers/cv_helpers.go:7: net/http imported and not used
Fixed two critical landscape mode issues on mobile devices:
1. Button backdrop blur bar now shows in landscape mode
- Added iOS-style blur backdrop with 70px height for landscape
- Consistent visual experience between portrait and landscape
- Supports dark mode with appropriate theming
2. Photo positioned on the right in landscape with better sizing
- Changed from stacked single-column to two-column grid layout
- Photo now positioned on right side (180px vs previous 120px)
- Text (name, experience, intro) on left, photo on right
- Better use of horizontal space in landscape orientation
- Left-aligned text for cleaner layout with photo on right
Test results (iPhone SE & iPhone 12 landscape):
✅ Two-column layout with photo on right
✅ Photo properly sized and positioned (180px)
✅ Button backdrop visible with blur effect
✅ No horizontal scroll
✅ All landscape tests passing
Test: tests/mjs/59-landscape-photo-and-backdrop-test.mjs
Fixes info modal positioning to be perfectly centered on mobile devices
in all orientations.
ISSUE:
- Info modal was not centered on mobile viewports
- User reported "pop-up of information in mobile it is not centered"
- Modal positioning relied on inset:0 + margin:auto which doesn't
work consistently on mobile devices
FIX:
- Added explicit mobile centering using transform translate(-50%, -50%)
- Position: top: 50%, left: 50% with transform centering
- Applied to all modals: info, keyboard shortcuts, and PDF download
- Added mobile-specific fade-in animation preserving centering
- Constrained modal to viewport with calc(100vw - 2rem) width/height
Files modified:
- static/css/04-interactive/_modals.css - Mobile centering for all modals
- tests/mjs/58-modal-centering-test.mjs - Validation test
Test results:
✅ Portrait (375×667): Perfect center - 0px offset
✅ Landscape (667×375): Perfect center - 0px offset
✅ Modal center matches viewport center exactly
Fixes three critical mobile UI issues:
1. Theme Button Transparency (FIXED)
- Changed theme button from 50% to full opacity on mobile
- Updated _themes.css with rgba(x, y, z, 1) for all theme modes
- Added opacity: 1 !important to mobile media query
2. Info Button Color Differentiation (FIXED)
- Changed info button from green (#27ae60) to blue (#3498db)
- Now visually distinct from green back-to-top button
- Updated all states: default, hover, at-bottom
3. Button Layout Reorganization (FIXED)
- Added .is-mobile-device rules for 5-button layout (no shortcuts)
- Buttons centered without gap: Download, Print, Theme, Info, Back-to-top
- Total width: 290px (5 buttons + 4 gaps) vs 350px (6 buttons)
Files modified:
- static/css/01-foundation/_themes.css - Primary theme button fix
- static/css/04-interactive/_scroll-behavior.css - Info color + layout
- static/css/color-theme.css - Mobile device positioning sync
- tests/mjs/53-final-mobile-fixes-test.mjs - Comprehensive validation
Test results:
✅ Shortcuts hidden on real mobile (iPhone user agent)
✅ 5 buttons evenly spaced with no gap (60px spacing)
✅ Info button blue (52, 152, 219) vs back-to-top green (39, 174, 96)
✅ Theme button full opacity (alpha: 1, opacity: 1)
Added comprehensive test coverage for mobile fixes:
1. test 50: Landscape Layout Diagnostic (50-landscape-layout-check.mjs)
Purpose: Verify single-column layout in landscape orientation
Tests:
- Grid template columns detection
- Sidebar and main content widths
- 2-column vs 1-column layout verification
Viewport: 844x390 (iPhone 14 Pro landscape)
Expected: Single column (1fr) grid layout
2. test 51: Mobile Button Opacity Test (51-mobile-button-opacity-test.mjs)
Purpose: Verify all mobile buttons have full opacity (no transparency)
Tests:
- Background color alpha channel (should be 1.0)
- CSS opacity property (should be 1.0)
- Checks all 6 buttons: download, print, shortcuts, info, back-to-top, theme
Viewport: 375x667 (iPhone SE portrait)
Expected: All buttons at full opacity with blur bar backdrop
Test Organization:
- Numbered sequence: 48-52 (continuing from existing tests)
- Test 48: Mobile landscape and blur bar
- Test 49: Mobile light theme default
- Test 50: Landscape layout verification (NEW)
- Test 51: Button opacity verification (NEW)
- Test 52: Device detection and shortcuts visibility
All tests are executable with proper shebang (#!/usr/bin/env node)
Run with: node tests/mjs/[test-number]-[test-name].mjs
Issue 1: Blur bar compatibility (Android doesn't always show at bottom)
✅ Solution: Wrap blur bar in @supports query for backdrop-filter
- Only shows on devices that support backdrop-filter (primarily iOS)
- Android devices without support won't see the bar
- Prevents layout issues on non-iOS devices
Issue 2: Keyboard shortcuts button on real mobile (no physical keyboard)
✅ Solution: Device detection + conditional hiding
- Added device-detection.js: Detects real mobile vs desktop browser
- Checks user agent (Android, iPhone, iPad, etc.) + touch support
- Adds 'is-mobile-device' or 'is-desktop' class to <html>
- CSS hides shortcuts button only on real mobile devices
- Desktop browser in mobile view: shortcuts button still visible (for testing)
Implementation Details:
1. Device Detection (static/js/device-detection.js):
- User agent detection: /Android|iPhone|iPad|etc./
- Touch support check: ontouchstart + maxTouchPoints
- Class added to <html>: is-mobile-device or is-desktop
2. Blur Bar (@supports query):
- Detects backdrop-filter support before applying
- iOS: Shows blur bar with backdrop-filter
- Android (most): No blur bar (no backdrop-filter support)
- Prevents empty/broken bar on incompatible devices
3. CSS Hiding Rules:
- .is-mobile-device .shortcuts-btn { display: none !important; }
- Also hides zoom-toggle-btn and zoom-control on real mobile
- Desktop mobile view: shortcuts button remains visible
Files Modified:
- static/js/device-detection.js: NEW - Device detection logic
- templates/index.html: Load device-detection.js early
- static/css/05-responsive/_breakpoints.css: @supports wrapper for blur bar
- static/css/04-interactive/_scroll-behavior.css: Hide shortcuts on real mobile
- tests/mjs/52-mobile-device-detection-test.mjs: Comprehensive device detection test
Test Results:
✅ iPhone (real mobile): is-mobile-device class, shortcuts hidden
✅ Desktop browser (mobile view): is-desktop class, shortcuts visible
✅ Blur bar: Only shows on devices with backdrop-filter support
Changed landscape orientation behavior to display profile photo
at reduced size (50% width, max 80px) instead of hiding it completely.
Changes:
- static/css/05-responsive/_breakpoints.css: Changed from display:none to width:50%
- tests/mjs/48-mobile-landscape-and-blur-test.mjs: Updated test to verify photo visibility
Test Results:
✅ Photo visible in landscape: YES
✅ Photo width: 80px (max: 80px)
✅ Landscape mode optimized with visible photo
Screenshot: tests/screenshots/mobile-landscape-optimized.png
Mobile Portrait Enhancements:
- Added iOS-style blur backdrop behind fixed buttons
- Frosted glass effect with backdrop-filter: blur(20px) saturate(180%)
- Semi-transparent background with border-top separator
- Dark mode variant for theme consistency
- Z-index 98 (below buttons at 99)
- pointer-events: none to maintain button animations and clicks
Landscape Orientation Fixes:
- Hide profile photo to maximize vertical space
- Compact header with reduced font sizes (1.2rem)
- Reduced padding on action bar, sidebar, and sections
- Optimized button sizes (40x40px) and positions
- Fixed hamburger menu positioning in landscape
Files Modified:
- static/css/05-responsive/_breakpoints.css: Added blur backdrop and landscape styles
- templates/index.html: Added fixed-buttons-backdrop element
- tests/mjs/48-mobile-landscape-and-blur-test.mjs: Comprehensive test suite
Test Results:
✅ Blur backdrop exists with correct blur effect
✅ Fixed position at bottom with 90px height
✅ Border separator visible (0.5px)
✅ Photo hidden in landscape mode
✅ Compact sizing applied in landscape
✅ All animations maintained (backdrop separate from buttons)
Screenshots:
- tests/screenshots/mobile-portrait-blur-bar.png
- tests/screenshots/mobile-landscape-optimized.png
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
Created detailed ASCII diagrams documenting the entire system architecture:
1. System Architecture (_go-learning/diagrams/01-system-architecture.md)
- Overall system architecture with client/server/storage layers
- Layered architecture (Presentation → Application → Business → Data)
- Component interaction and HTTP request flow
- Data flow from app start through per-request lifecycle
- Package dependencies and file organization
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
The theme switcher and info button tooltips were appearing on the RIGHT
in mobile view instead of on TOP (like macOS Dock style) because they
didn't have the .fixed-btn class and weren't included in the mobile
media query rules.
Changes:
- static/css/04-interactive/_tooltips.css: Add .color-theme-switcher and
.info-button selectors to mobile @media (max-width: 900px) rules
- Now tooltips appear ABOVE these buttons on mobile with bottom: calc(100% + 8px)
Testing:
- Added mobile tooltip position test
- Verified theme switcher and info tooltips now positioned on top on mobile
- Desktop behavior unchanged (tooltips still on right)
Mobile tooltip positioning now consistent across ALL buttons:
✅ All bottom dock buttons show tooltips on TOP (macOS Dock style)
The theme switcher button was hidden above the viewport on desktop because
.has-tooltip's position: relative was overriding the button's position: fixed
due to CSS cascade order (_tooltips.css loaded after _themes.css).
Fixed by adding !important to .color-theme-switcher position: fixed rule.
Changes:
- static/css/01-foundation/_themes.css: Add !important to position: fixed
to override .has-tooltip position: relative cascade
Testing:
- Added comprehensive tooltip tests for all buttons (desktop & mobile)
- Verified theme switcher visible on desktop at correct position
- Verified all tooltips working on hover (desktop only, hidden on mobile touch)
- Verified button positioning in mobile bottom dock
All buttons now display correctly:
✅ Desktop: All 6 buttons with working tooltips
✅ Mobile: All 5 buttons in bottom dock
- 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