7b60fdcf9c
**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)
390 lines
15 KiB
Markdown
390 lines
15 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 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
|
|
</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: 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:
|
|
```html
|
|
<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`:
|
|
|
|
```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:
|
|
|
|
```hyperscript
|
|
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:
|
|
|
|
```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. 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
|
|
</implementation>
|
|
|
|
<output>
|
|
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
|
|
|
|
</output>
|
|
|
|
<verification>
|
|
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
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<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>
|