366 lines
14 KiB
Markdown
366 lines
14 KiB
Markdown
|
|
# Implement Skeleton Loader Transitions for Language Switching
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
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.
|
||
|
|
</objective>
|
||
|
|
|
||
|
|
<context>
|
||
|
|
**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 the layout structure
|
||
|
|
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
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<requirements>
|
||
|
|
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
|
||
|
|
|
||
|
|
2. **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)
|
||
|
|
|
||
|
|
3. **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
|
||
|
|
|
||
|
|
4. **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)
|
||
|
|
|
||
|
|
5. **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
|
||
|
|
|
||
|
|
6. **Performance:**
|
||
|
|
- Skeleton rendering should be instant (<16ms)
|
||
|
|
- Animations GPU-accelerated for smooth 60fps
|
||
|
|
- No layout thrashing during transitions
|
||
|
|
- Total transition under 700ms
|
||
|
|
|
||
|
|
7. **Accessibility:**
|
||
|
|
- Respect `prefers-reduced-motion` - disable pulse animation
|
||
|
|
- Add `aria-busy="true"` during loading states
|
||
|
|
- Ensure screen readers announce loading state
|
||
|
|
|
||
|
|
8. **Consistency:**
|
||
|
|
- Use existing project timing patterns (0.2s-0.3s)
|
||
|
|
- Match existing design language and color palette
|
||
|
|
- Work on mobile and desktop viewports
|
||
|
|
</requirements>
|
||
|
|
|
||
|
|
<implementation>
|
||
|
|
**Recommended Implementation Strategy:**
|
||
|
|
|
||
|
|
**Step 1: Create Skeleton Loader HTML Structure**
|
||
|
|
|
||
|
|
Create a new template or partial: `templates/partials/skeleton-loader.html`
|
||
|
|
|
||
|
|
This should contain a simplified version of your CV layout with placeholder boxes:
|
||
|
|
- Header skeleton (circular avatar placeholder, horizontal bars for name/title)
|
||
|
|
- Left sidebar skeleton (rectangular blocks for skills)
|
||
|
|
- Main content skeleton (blocks for experience entries)
|
||
|
|
- Right sidebar skeleton (blocks for additional skills)
|
||
|
|
|
||
|
|
**Step 2: Create Skeleton CSS Animations**
|
||
|
|
|
||
|
|
Add to `static/css/main.css` or create `static/css/skeleton.css`:
|
||
|
|
|
||
|
|
```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; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Skeleton container - hidden by default */
|
||
|
|
.skeleton-overlay {
|
||
|
|
position: absolute;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
right: 0;
|
||
|
|
bottom: 0;
|
||
|
|
background: white;
|
||
|
|
z-index: 10;
|
||
|
|
opacity: 0;
|
||
|
|
pointer-events: none;
|
||
|
|
transition: opacity 250ms ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.skeleton-overlay.active {
|
||
|
|
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 `hx-indicator="#skeleton-loader"` to show skeleton during request
|
||
|
|
- Add hyperscript to manage skeleton visibility
|
||
|
|
|
||
|
|
**Step 4: Add Skeleton Loader to Main Template**
|
||
|
|
|
||
|
|
Insert the skeleton loader overlay into your main CV template:
|
||
|
|
- Position it absolutely over the content area
|
||
|
|
- Initially hidden with `opacity: 0`
|
||
|
|
- Activated via HTMX events or hyperscript
|
||
|
|
|
||
|
|
**Step 5: HTMX Event Handling with Hyperscript**
|
||
|
|
|
||
|
|
Add hyperscript behavior to show/hide skeleton:
|
||
|
|
|
||
|
|
```hyperscript
|
||
|
|
on htmx:beforeRequest from #language-selector
|
||
|
|
add .active to #skeleton-loader
|
||
|
|
end
|
||
|
|
|
||
|
|
on htmx:afterSwap from #language-selector
|
||
|
|
wait 100ms -- Brief delay to ensure content is rendered
|
||
|
|
remove .active from #skeleton-loader
|
||
|
|
end
|
||
|
|
```
|
||
|
|
|
||
|
|
**Alternative Approach (Pure HTMX):**
|
||
|
|
Use `hx-indicator` attribute with CSS to control visibility:
|
||
|
|
- `hx-indicator="#skeleton-loader"` on language buttons
|
||
|
|
- HTMX automatically adds `.htmx-request` class during requests
|
||
|
|
- CSS: `#skeleton-loader.htmx-request { opacity: 1; }`
|
||
|
|
|
||
|
|
**Step 6: Content Fade Transitions**
|
||
|
|
|
||
|
|
Add fade-in/out to actual content sections:
|
||
|
|
|
||
|
|
```css
|
||
|
|
.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. Get basic skeleton structure showing/hiding correctly
|
||
|
|
2. Add pulsing animation
|
||
|
|
3. Refine skeleton shapes to better match layout
|
||
|
|
4. Polish timing and transitions
|
||
|
|
5. Add accessibility features
|
||
|
|
|
||
|
|
**What to Avoid:**
|
||
|
|
- 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
|
||
|
|
</implementation>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
Create/modify the following files:
|
||
|
|
|
||
|
|
1. **`./templates/partials/skeleton-loader.html`** (NEW)
|
||
|
|
- Skeleton loader HTML structure
|
||
|
|
- Simplified CV layout with placeholder boxes
|
||
|
|
- Should mirror the structure of CV content sections
|
||
|
|
|
||
|
|
2. **`./static/css/skeleton.css`** (NEW)
|
||
|
|
- Skeleton loader styles
|
||
|
|
- Pulsing animation keyframes
|
||
|
|
- Skeleton overlay positioning and transitions
|
||
|
|
- Responsive skeleton layouts
|
||
|
|
- Accessibility overrides for `prefers-reduced-motion`
|
||
|
|
|
||
|
|
3. **`./templates/partials/navigation/language-selector.html`**
|
||
|
|
- Add HTMX swap timing modifiers
|
||
|
|
- Add skeleton loader indicator reference
|
||
|
|
- Add hyperscript for skeleton show/hide 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. **Include skeleton loader in main template** (wherever CV content lives)
|
||
|
|
- Add `<div id="skeleton-loader" class="skeleton-overlay">...</div>`
|
||
|
|
- Position over content area
|
||
|
|
- Initially hidden, shown during language switch
|
||
|
|
|
||
|
|
</output>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
Before declaring complete, perform these tests:
|
||
|
|
|
||
|
|
**1. Visual Verification:**
|
||
|
|
- [ ] Click EN button: Content fades out → Skeleton appears → New content fades in
|
||
|
|
- [ ] Click ES button: Same smooth transition with skeleton
|
||
|
|
- [ ] Skeleton boxes pulse/shimmer smoothly
|
||
|
|
- [ ] Skeleton roughly matches CV layout (recognizable structure)
|
||
|
|
- [ ] 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:**
|
||
|
|
✅ Skeleton loaders appear during language transitions
|
||
|
|
✅ 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
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
1. Skeleton loaders with pulsing animation appear during language switching
|
||
|
|
2. Three-phase transition: fade-out → skeleton → fade-in
|
||
|
|
3. Total transition time: 500-700ms (feels premium, not sluggish)
|
||
|
|
4. Skeleton structure roughly matches CV layout (recognizable)
|
||
|
|
5. Animations are GPU-accelerated and buttery smooth (60fps)
|
||
|
|
6. Respects `prefers-reduced-motion` accessibility setting
|
||
|
|
7. Handles rapid clicking without breaking or visual glitches
|
||
|
|
8. Works consistently across modern browsers and device sizes
|
||
|
|
9. Code follows existing project patterns and conventions
|
||
|
|
10. Implementation is maintainable and doesn't overcomplicate the codebase
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<references>
|
||
|
|
**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
|
||
|
|
</references>
|
||
|
|
|
||
|
|
<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>
|