feat: page transitions, scroll progress bar, and staggered value cards
This commit is contained in:
@@ -42,7 +42,11 @@ const {
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<body class="page-ready">
|
||||
|
||||
<!-- Scroll progress bar -->
|
||||
<div class="scroll-progress" id="scrollProgress" role="progressbar" aria-label="Progreso de lectura" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
|
||||
<SiteHeader activeNav={activeNav} />
|
||||
<main>
|
||||
<slot />
|
||||
@@ -68,17 +72,49 @@ const {
|
||||
{ threshold: 0.1, rootMargin: '0px 0px -48px 0px' }
|
||||
);
|
||||
|
||||
document.querySelectorAll('.reveal, .reveal-left, .reveal-scale, .gold-line, .section-eyebrow').forEach((el) => {
|
||||
document.querySelectorAll('.reveal, .reveal-left, .reveal-scale, .gold-line, .section-eyebrow, .value-card-animate').forEach((el) => {
|
||||
io.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── HEADER SCROLL ELEVATION ─────────────────
|
||||
const header = document.querySelector('.site-header') as HTMLElement | null;
|
||||
if (header) {
|
||||
window.addEventListener('scroll', () => {
|
||||
header.classList.toggle('is-scrolled', window.scrollY > 16);
|
||||
}, { passive: true });
|
||||
// ─── SCROLL PROGRESS ────────────────────────
|
||||
const progressBar = document.getElementById('scrollProgress') as HTMLElement | null;
|
||||
if (progressBar) {
|
||||
const updateProgress = () => {
|
||||
const scrollTop = window.scrollY;
|
||||
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
|
||||
progressBar.style.width = `${progress}%`;
|
||||
progressBar.setAttribute('aria-valuenow', String(Math.round(progress)));
|
||||
};
|
||||
window.addEventListener('scroll', updateProgress, { passive: true });
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
// ─── PAGE TRANSITIONS ───────────────────────
|
||||
// Fade IN: page loads → body gets .page-ready → CSS handles fade-in
|
||||
document.body.classList.add('page-ready');
|
||||
|
||||
// Fade OUT: intercept internal navigation links
|
||||
const handleNavClick = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const anchor = target.closest('a');
|
||||
if (!anchor) return;
|
||||
|
||||
const href = anchor.getAttribute('href');
|
||||
if (!href || href.startsWith('#') || href.startsWith('http') || href.startsWith('mailto') || href.startsWith('tel') || anchor.target === '_blank') return;
|
||||
|
||||
e.preventDefault();
|
||||
document.body.classList.remove('page-ready');
|
||||
setTimeout(() => {
|
||||
window.location.href = href;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleNavClick);
|
||||
|
||||
} else {
|
||||
// Reduced motion: show immediately, no transitions
|
||||
document.body.classList.add('page-ready');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -63,17 +63,17 @@ import MainLayout from '../layouts/MainLayout.astro';
|
||||
<h2 class="text-headline-lg reveal" style="--reveal-delay: 150;">Valores que nos definen</h2>
|
||||
</div>
|
||||
<div class="grid-3">
|
||||
<div class="value-card">
|
||||
<div class="value-card value-card-animate" data-value-delay="0">
|
||||
<span class="material-symbols-outlined text-primary icon-lg">verified</span>
|
||||
<h3 class="text-headline-md">Calidad</h3>
|
||||
<p class="text-body-sm text-muted leading-relaxed">Innegociable. Desde la frescura del producto hasta el último detalle del servicio de sala.</p>
|
||||
</div>
|
||||
<div class="value-card">
|
||||
<div class="value-card value-card-animate" data-value-delay="150">
|
||||
<span class="material-symbols-outlined text-primary icon-lg">history_edu</span>
|
||||
<h3 class="text-headline-md">Tradición</h3>
|
||||
<p class="text-body-sm text-muted leading-relaxed">Honramos las técnicas clásicas que han forjado la gran cocina, aplicándolas con respeto.</p>
|
||||
</div>
|
||||
<div class="value-card">
|
||||
<div class="value-card value-card-animate" data-value-delay="300">
|
||||
<span class="material-symbols-outlined text-primary icon-lg">architecture</span>
|
||||
<h3 class="text-headline-md">Vanguardia</h3>
|
||||
<p class="text-body-sm text-muted leading-relaxed">Innovamos en presentaciones y texturas para sorprender a los paladares contemporáneos.</p>
|
||||
@@ -147,4 +147,39 @@ import MainLayout from '../layouts/MainLayout.astro';
|
||||
}
|
||||
.quote-card-desktop { display: block !important; }
|
||||
}
|
||||
|
||||
/* ─── VALUE CARD TIMELINE ANIMATION ──────────── */
|
||||
.value-card-animate {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) rotate(var(--card-tilt, 0deg));
|
||||
transition:
|
||||
opacity 600ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
transition-delay: calc(var(--value-delay, 0) * 1ms);
|
||||
}
|
||||
|
||||
.value-card-animate.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0) rotate(0deg);
|
||||
}
|
||||
|
||||
.value-card-animate:nth-child(1) { --card-tilt: 0deg; }
|
||||
.value-card-animate:nth-child(2) { --card-tilt: 0deg; }
|
||||
.value-card-animate:nth-child(3) { --card-tilt: 0deg; }
|
||||
|
||||
/* subtle stacking effect */
|
||||
@media (min-width: 48rem) {
|
||||
.value-card-animate:nth-child(1) { --card-tilt: -1.5deg; }
|
||||
.value-card-animate:nth-child(2) { --card-tilt: 0deg; }
|
||||
.value-card-animate:nth-child(3) { --card-tilt: 1.5deg; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Override IntersectionObserver to also handle value-card timing
|
||||
const valueCards = document.querySelectorAll('.value-card-animate');
|
||||
valueCards.forEach((card) => {
|
||||
const delay = (card as HTMLElement).dataset.valueDelay || '0';
|
||||
(card as HTMLElement).style.setProperty('--value-delay', delay);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -255,3 +255,33 @@
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── PAGE TRANSITIONS ────────────────────────── */
|
||||
|
||||
body {
|
||||
opacity: 0;
|
||||
transition: opacity 400ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
body.page-ready {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ─── SCROLL PROGRESS ────────────────────────── */
|
||||
|
||||
.scroll-progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, var(--color-primary) 0%, var(--color-primary-container) 100%);
|
||||
z-index: 9999;
|
||||
transition: width 100ms linear;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.scroll-progress {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user