feat: implement component-level skeleton loaders for language transitions
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)
This commit is contained in:
@@ -28,11 +28,12 @@
|
||||
hx-push-url="/?lang=en"
|
||||
aria-label="English"
|
||||
_="on htmx:beforeRequest
|
||||
add .htmx-swapping to #cv-inner-content-page-1
|
||||
add .htmx-swapping to #cv-inner-content-page-2
|
||||
add .loading to .component-wrapper in #cv-inner-content-page-1
|
||||
add .loading to .component-wrapper in #cv-inner-content-page-2
|
||||
on htmx:afterSwap
|
||||
remove .htmx-swapping from #cv-inner-content-page-1
|
||||
remove .htmx-swapping from #cv-inner-content-page-2">
|
||||
wait 100ms
|
||||
remove .loading from .component-wrapper in #cv-inner-content-page-1
|
||||
remove .loading from .component-wrapper in #cv-inner-content-page-2">
|
||||
<span>English</span>
|
||||
</button>
|
||||
<button class="selector-btn {{if eq .Lang "es"}}active{{end}}"
|
||||
@@ -44,11 +45,12 @@
|
||||
hx-push-url="/?lang=es"
|
||||
aria-label="Español"
|
||||
_="on htmx:beforeRequest
|
||||
add .htmx-swapping to #cv-inner-content-page-1
|
||||
add .htmx-swapping to #cv-inner-content-page-2
|
||||
add .loading to .component-wrapper in #cv-inner-content-page-1
|
||||
add .loading to .component-wrapper in #cv-inner-content-page-2
|
||||
on htmx:afterSwap
|
||||
remove .htmx-swapping from #cv-inner-content-page-1
|
||||
remove .htmx-swapping from #cv-inner-content-page-2">
|
||||
wait 100ms
|
||||
remove .loading from .component-wrapper in #cv-inner-content-page-1
|
||||
remove .loading from .component-wrapper in #cv-inner-content-page-2">
|
||||
<span>Español</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
{{define "section-header"}}
|
||||
<!-- Header with Name and Photo -->
|
||||
<div class="cv-header">
|
||||
<div class="cv-header-content">
|
||||
<div class="cv-header-left">
|
||||
<h1 class="cv-name">Moreno Rubio, Juan Andrés</h1>
|
||||
<p class="years-experience">{{.YearsOfExperience}} {{if eq .Lang "es"}}años de experiencia{{else}}years of experience{{end}}</p>
|
||||
<div class="cv-header component-wrapper">
|
||||
<!-- Actual Content -->
|
||||
<div class="actual-content">
|
||||
<div class="cv-header-content">
|
||||
<div class="cv-header-left">
|
||||
<h1 class="cv-name">Moreno Rubio, Juan Andrés</h1>
|
||||
<p class="years-experience">{{.YearsOfExperience}} {{if eq .Lang "es"}}años de experiencia{{else}}years of experience{{end}}</p>
|
||||
|
||||
<!-- Photo positioned for mobile (centered between name and intro) -->
|
||||
<div class="cv-photo">
|
||||
<img src="/static/images/profile/dni.jpeg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
|
||||
<!-- Photo positioned for mobile (centered between name and intro) -->
|
||||
<div class="cv-photo">
|
||||
<img src="/static/images/profile/dni.jpeg" alt="{{.CV.Personal.Name}}" onerror="this.src='/static/images/profile/placeholder.svg'">
|
||||
</div>
|
||||
|
||||
<!-- Intro/Excerpt Text - No section heading, just the text -->
|
||||
<div class="intro-text">{{.CV.Summary}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Intro/Excerpt Text - No section heading, just the text -->
|
||||
<div class="intro-text">{{.CV.Summary}}</div>
|
||||
<!-- Skeleton Content -->
|
||||
<div class="skeleton-content">
|
||||
<div class="skeleton-header">
|
||||
<div class="skeleton-header-text">
|
||||
<div class="skeleton skeleton-name"></div>
|
||||
<div class="skeleton skeleton-experience-years"></div>
|
||||
<div class="skeleton skeleton-intro"></div>
|
||||
</div>
|
||||
<div class="skeleton skeleton-photo"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user