Files
cv-site/prompts/done/002-animate-language-transitions.md
juanatsap 7b60fdcf9c refactor: Separate CV domain and UI presentation models into distinct packages
**Main Changes:**

1. **Package Restructuring** - Separated mixed concerns into focused packages:
   - Created `internal/models/cv/` for CV domain logic (CV, Personal, Experience, etc.)
   - Created `internal/models/ui/` for UI presentation logic (InfoModal, ShortcutsModal, etc.)
   - Removed monolithic `internal/models/cv.go` (300+ lines → organized packages)

2. **Testing** - Added comprehensive unit tests:
   - `internal/models/cv/loader_test.go` - CV data loading and validation
   - `internal/models/ui/loader_test.go` - UI translations loading
   - All tests passing 

3. **Documentation** - Added Go learning knowledge base:
   - `_go-learning/architecture/server-design.md` - Goroutines, graceful shutdown explained
   - `_go-learning/refactorings/001-cv-model-separation.md` - This refactoring documented
   - Public documentation showcasing Go expertise (README.md kept private)

4. **Handler Updates** - Updated imports to use new package structure:
   - `internal/handlers/cv.go` - Uses `cvmodel` and `uimodel` aliases

**Benefits:**
-  Clear separation of concerns (domain vs presentation)
-  Better testability (isolated package testing)
-  Improved maintainability (smaller, focused files)
-  Scalability (each domain can evolve independently)
-  Follows Go best practices (small, cohesive packages)

**Other Updates:**
- Updated middleware security checks
- Template improvements
- Organized completed prompts

**Testing:**
- All Go unit tests pass (cv, ui, handlers)
- Server verified with curl tests (English, Spanish, Health endpoints)
- Frontend functionality unchanged (refactoring is transparent to UI)
2025-11-20 16:17:56 +00:00

15 KiB

Implement Skeleton Loader Transitions for Language Switching

Implement professional skeleton loader animations when switching between English and Spanish languages in the CV application. The goal is to create a polished, modern user experience similar to FriendKit (reference screenshots provided) where content transitions through skeleton/placeholder states with pulsing animations.

Visual Reference: The user provided screenshots showing gray pulsing placeholder boxes that appear during page transitions - these skeleton loaders make the transition feel smooth and intentional while new content loads.

This will transform the current instant language switch into a premium, modern web app experience.

**Current Implementation:** - Language switching uses HTMX with `/switch-language?lang={en|es}` endpoint - HTMX performs out-of-band swaps (`hx-swap-oob="innerHTML"`) to update: - `#language-selector` (primary target) - `#cv-inner-content-page-1` - `#cv-inner-content-page-2` - Currently uses `hx-swap="outerHTML"` which causes instant, jarring content replacement - No intermediate loading state - content just "pops" from one language to another

Reference Files: @templates/language-switch.html - Server response template with OOB swaps @templates/partials/navigation/language-selector.html - Language selector buttons @static/css/main.css - Existing CSS with some transitions already in place

Tech Stack:

  • HTMX for dynamic content swapping
  • Hyperscript for custom behaviors (already in use)
  • Vanilla CSS for animations (no external animation libraries)
  • Go templates for server-side rendering

Desired User Experience (from screenshots):

  1. User clicks language button (EN or ES)
  2. Current content fades out (200-300ms)
  3. Skeleton loaders appear - gray pulsing boxes matching INSIDE of the CV (so, a toggle of the content)
  4. New language content loads from server
  5. Skeleton loaders fade out and new content fades in (200-300ms)
  6. Total smooth, professional transition feel

Why Skeleton Loaders Matter:

  • Perceived performance: Users perceive loading as faster when they see progressive feedback
  • Modern UX standard: Used by Facebook, LinkedIn, YouTube, and all modern web apps
  • Reduces cognitive load: Shows users "something is happening" vs blank screen or instant swap
  • Professional polish: Demonstrates attention to detail and quality
1. **Skeleton Loader Design:** - Gray placeholder boxes (`background: #e0e0e0` or similar) matching content structure - Pulsing/shimmer animation (subtle opacity or gradient shift) - Should roughly match the layout of CV sections (header, skills, experience, etc.) - Lightweight and fast to render
  1. Three-Phase Transition:

    • Phase 1 (Fade Out): Current content fades to opacity 0 (250ms)
    • Phase 2 (Skeleton Display): Show skeleton loaders with pulse animation
    • Phase 3 (Fade In): New content fades from opacity 0 to 1 (250ms)
    • Total transition: ~500-700ms (feels premium, not sluggish)
  2. HTMX Integration:

    • Use HTMX swap timing modifiers: swap:250ms settle:250ms
    • Leverage HTMX request lifecycle events for skeleton state management
    • Use htmx:beforeRequest to show skeleton
    • Use htmx:afterSwap to hide skeleton and fade in content
  3. CSS Animations:

    • Create .skeleton-loader component classes
    • Pulsing animation using CSS @keyframes (opacity or background gradient shift)
    • Animation should be subtle: 1.5s-2s loop, infinite
    • GPU-accelerated properties only (opacity, transform)
  4. Layout Matching:

    • Skeleton should mirror the actual CV layout structure
    • Consider creating skeleton variants for:
      • Header section (name, title, badges)
      • Skills sidebar sections
      • Experience entries
      • Education entries
    • Doesn't need to be pixel-perfect, just recognizable structure
  5. Performance:

    • Skeleton rendering should be instant (<16ms)
    • Animations GPU-accelerated for smooth 60fps
    • No layout thrashing during transitions
    • Total transition under 700ms
  6. Accessibility:

    • Respect prefers-reduced-motion - disable pulse animation
    • Add aria-busy="true" during loading states
    • Ensure screen readers announce loading state
  7. Consistency:

    • Use existing project timing patterns (0.2s-0.3s)
    • Match existing design language and color palette
    • Work on mobile and desktop viewports
**Recommended Implementation Strategy:**

Step 1: Add Skeleton States to Existing Components

Modify existing CV component templates to include built-in skeleton states:

  • templates/partials/cv-header.html - Add skeleton state markup inside the component
  • templates/partials/skills-*.html - Add skeleton placeholders for skill blocks
  • templates/partials/experience-entry.html - Add skeleton placeholders for experience blocks
  • Each component should have both actual content and skeleton markup, toggled via CSS classes

Example structure:

<div class="component-wrapper">
  <div class="actual-content"><!-- Real CV content --></div>
  <div class="skeleton-content hidden"><!-- Skeleton placeholders --></div>
</div>

Step 2: Create Skeleton CSS Animations

Add to static/css/main.css or create static/css/skeleton.css:

/* Skeleton loader base styles */
.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-pulse 1.5s ease-in-out infinite;
  border-radius: 4px;
}

@keyframes skeleton-pulse {
  0%, 100% { background-position: 200% 0; }
  50% { background-position: 0 0; }
}

/* Component state toggling */
.component-wrapper .actual-content {
  opacity: 1;
  transition: opacity 250ms ease;
}

.component-wrapper .skeleton-content {
  opacity: 0;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
  transition: opacity 250ms ease;
}

/* Loading state - toggle content visibility */
.component-wrapper.loading .actual-content {
  opacity: 0;
  pointer-events: none;
}

.component-wrapper.loading .skeleton-content {
  opacity: 1;
  pointer-events: all;
}

/* Skeleton shapes - create boxes matching your layout */
.skeleton-header { height: 120px; margin-bottom: 20px; }
.skeleton-sidebar-item { height: 60px; margin-bottom: 10px; }
.skeleton-content-item { height: 100px; margin-bottom: 15px; }

/* Reduce motion support */
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; background: #e0e0e0; }
}

Step 3: Update Language Selector with HTMX Timing

Modify templates/partials/navigation/language-selector.html:

  • Add hx-swap="outerHTML swap:250ms settle:250ms"
  • Add hyperscript to toggle .loading class on component wrappers
  • Coordinate timing with component fade-in/out transitions

Step 4: Ensure Component Wrappers Have Proper Classes

Verify that CV component wrappers in templates have:

  • .component-wrapper class for CSS targeting
  • Both .actual-content and .skeleton-content children
  • Proper positioning context (relative positioning on wrapper)

Step 5: HTMX Event Handling with Hyperscript

Add hyperscript behavior to toggle loading state on components:

on htmx:beforeRequest from #language-selector
  add .loading to .component-wrapper in #cv-inner-content-page-1
  add .loading to .component-wrapper in #cv-inner-content-page-2
end

on htmx:afterSwap from #language-selector
  wait 100ms  -- Brief delay to ensure content is rendered
  remove .loading from .component-wrapper in #cv-inner-content-page-1
  remove .loading from .component-wrapper in #cv-inner-content-page-2
end

Alternative Approach (CSS-based): Use HTMX's automatic class management:

  • Add .component-wrapper to elements receiving OOB swaps
  • Use HTMX classes: .htmx-swapping triggers skeleton state
  • CSS: .component-wrapper.htmx-swapping .actual-content { opacity: 0; }
  • CSS: .component-wrapper.htmx-swapping .skeleton-content { opacity: 1; }

Step 6: Content Fade Transitions

Add fade-in/out to actual content sections:

.cv-page-content-wrapper {
  transition: opacity 250ms ease;
}

.cv-page-content-wrapper.htmx-swapping {
  opacity: 0;
}

.cv-page-content-wrapper.htmx-settling {
  opacity: 1;
}

What to Prioritize:

  1. Add skeleton markup inside each existing component (dual-state structure)
  2. Implement CSS toggling between actual content and skeleton content
  3. Add pulsing animation to skeleton elements
  4. Refine skeleton shapes to better match layout
  5. Polish timing and transitions
  6. Add accessibility features

What to Avoid:

  • Don't create a separate overlay layer - skeleton states live inside components
  • Don't create overly complex skeleton markup - simple boxes are fine
  • Don't make the skeleton identical to real content - approximate is better
  • Don't use JavaScript animations - stick to CSS for performance
  • Don't make transitions too long - 500-700ms total maximum
  • Don't forget to test rapid clicking (skeleton should handle interruptions gracefully)

Why These Constraints Matter:

  • Simplicity: Complex skeletons are harder to maintain when layout changes
  • Performance: CSS animations are GPU-accelerated; JS animations cause jank
  • UX Research: 400-700ms is the sweet spot - longer feels broken, shorter feels pointless
  • Resilience: Users click fast - implementation must handle interruptions without breaking
Create/modify the following files:
  1. Modify existing component templates (UPDATE)

    • ./templates/partials/cv-header.html - Add skeleton state inside component
    • ./templates/partials/skills-*.html - Add skeleton placeholders
    • ./templates/partials/experience-entry.html - Add skeleton placeholders
    • Each component gets dual-state structure (actual content + skeleton content)
  2. ./static/css/skeleton.css (NEW)

    • Skeleton loader styles
    • Pulsing animation keyframes
    • Component state toggling styles (.loading class behavior)
    • Responsive skeleton layouts
    • Accessibility overrides for prefers-reduced-motion
  3. ./templates/partials/navigation/language-selector.html

    • Add HTMX swap timing modifiers
    • Add hyperscript to toggle .loading class on component wrappers
    • Coordinate skeleton state transitions with HTMX events
  4. ./templates/language-switch.html

    • May need to coordinate OOB swap timing
    • Ensure skeleton works with all content updates
  5. ./static/css/main.css

    • Add content fade-in/out transitions
    • Import skeleton.css if created separately
    • Add HTMX animation class styles
  6. Ensure component wrappers are properly structured

    • Each component should have .component-wrapper class
    • Components contain both .actual-content and .skeleton-content
    • Skeleton content is hidden by default, shown when .loading class is added
Before declaring complete, perform these tests:

1. Visual Verification:

  • Click EN button: Content fades out → Skeleton appears inside components → New content fades in
  • Click ES button: Same smooth transition with skeleton
  • Skeleton boxes pulse/shimmer smoothly inside each component
  • Skeleton roughly matches CV layout (recognizable structure)
  • Each component toggles between actual content and skeleton state
  • No flashing or jarring jumps during transition

2. Timing Verification:

  • Total transition feels responsive (<700ms)
  • Skeleton appears immediately when language button clicked
  • Content fade-out is smooth (not instant)
  • Content fade-in is smooth after skeleton disappears

3. Interaction Testing:

  • Rapidly click between EN and ES - no broken states
  • Click language button while skeleton is showing - handles gracefully
  • Mobile viewport - skeleton works correctly
  • Desktop viewport - skeleton works correctly

4. Performance Testing:

  • Open DevTools Performance tab
  • Record during language switch
  • Verify 60fps animation (no dropped frames)
  • No layout thrashing or long tasks
  • GPU acceleration active for animations

5. Accessibility Testing:

  • Enable "Reduce motion" in OS settings → Skeleton pulse disabled
  • Screen reader announces loading state
  • Keyboard navigation still works during transitions
  • Focus management doesn't break

6. Browser Compatibility:

  • Chrome: Smooth skeleton animations
  • Firefox: Smooth skeleton animations
  • Safari: Smooth skeleton animations
  • Mobile browsers: Touch interactions work correctly

7. Edge Cases:

  • Slow network simulation (throttle to 3G) - skeleton visible longer
  • Fast network - skeleton doesn't flash too quickly (minimum display time?)
  • First load vs subsequent switches - consistent behavior
  • Page refresh during skeleton display - recovers gracefully

Success Indicators: Components toggle between actual content and skeleton states during language transitions Skeleton states appear INSIDE each component (not as separate overlay) Pulsing animation is smooth and subtle Total transition time feels professional (500-700ms) No jarring content jumps or flashes Works consistently across browsers and devices Respects accessibility preferences Handles rapid interactions gracefully

<success_criteria>

  1. Each CV component toggles between actual content and skeleton state during language switching
  2. Skeleton loaders with pulsing animation appear INSIDE components (not as separate overlay)
  3. Three-phase transition: fade-out → skeleton → fade-in
  4. Total transition time: 500-700ms (feels premium, not sluggish)
  5. Skeleton structure roughly matches CV layout (recognizable)
  6. Animations are GPU-accelerated and buttery smooth (60fps)
  7. Respects prefers-reduced-motion accessibility setting
  8. Handles rapid clicking without breaking or visual glitches
  9. Works consistently across modern browsers and device sizes
  10. Code follows existing project patterns and conventions
  11. Implementation is maintainable and doesn't overcomplicate the codebase </success_criteria>
**HTMX Documentation:** - Swap timing: https://htmx.org/attributes/hx-swap/ - Request indicators: https://htmx.org/attributes/hx-indicator/ - CSS transitions: https://htmx.org/examples/animations/

Skeleton Loader Patterns:

  • Facebook-style skeleton screens
  • Modern progressive loading UX patterns
  • LinkedIn content placeholders

Visual Reference (provided by user):

  • FriendKit example pages showing gray pulsing placeholder boxes
  • Boxes animate with subtle shimmer/pulse effect
  • Content appears progressively after skeleton state

<additional_context> If you can provide the FriendKit code: If you have access to the actual CSS/HTML from the FriendKit examples, examining their skeleton loader implementation would be valuable for:

  • Exact timing values they use
  • Gradient/animation patterns for the pulse effect
  • How they structure skeleton markup
  • Any clever performance optimizations

However, we can implement an excellent skeleton loader without that code - the visual reference is sufficient to create a premium experience. </additional_context>