2026-03-22 01:40:10 -04:00
|
|
|
/**
|
|
|
|
|
* Feature Section Card Animator
|
|
|
|
|
* Staggered scroll-reveal entrance + icon pop-in for .feature-section cards.
|
|
|
|
|
* Coordinates with the main.js scroll-hidden/visible system:
|
|
|
|
|
* 1. Sets --scroll-delay CSS custom property on each card so main.js's
|
|
|
|
|
* scroll-visible transition fires at staggered intervals.
|
|
|
|
|
* 2. Uses MutationObserver to detect when scroll-visible is applied, then
|
|
|
|
|
* triggers the icon-pop animation on the card's .feature-icon.
|
|
|
|
|
* 3. Resets the delay to 0s after the entrance so hover interactions stay snappy.
|
|
|
|
|
*/
|
|
|
|
|
(function () {
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var STAGGER = 0.08; // seconds between each card's entrance
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
|
|
|
|
|
|
|
|
|
// 1. Assign incrementing --scroll-delay to every card within a feature-section.
|
2026-03-22 01:45:38 -04:00
|
|
|
// The block renders .grid-2/.grid-3/.grid-4 wrappers (no .feature-section class).
|
2026-03-22 01:40:10 -04:00
|
|
|
// main.js's .scroll-visible rule reads this via var(--scroll-delay, 0s).
|
2026-03-22 01:45:38 -04:00
|
|
|
document.querySelectorAll('.grid-2, .grid-3, .grid-4').forEach(function (grid) {
|
|
|
|
|
grid.querySelectorAll('.oribi-card').forEach(function (card, i) {
|
2026-03-22 01:40:10 -04:00
|
|
|
card.style.setProperty('--scroll-delay', (i * STAGGER).toFixed(2) + 's');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 2. Watch for scroll-visible being added to each card.
|
2026-03-22 01:45:38 -04:00
|
|
|
var cards = document.querySelectorAll('.grid-2 .oribi-card, .grid-3 .oribi-card, .grid-4 .oribi-card');
|
2026-03-22 01:40:10 -04:00
|
|
|
if (!cards.length) return;
|
|
|
|
|
|
|
|
|
|
var mo = new MutationObserver(function (mutations) {
|
|
|
|
|
mutations.forEach(function (m) {
|
|
|
|
|
var card = m.target;
|
|
|
|
|
if (!card.classList.contains('scroll-visible')) return;
|
|
|
|
|
|
|
|
|
|
mo.unobserve(card);
|
|
|
|
|
|
|
|
|
|
// Trigger icon pop ~150ms after the card itself starts fading in.
|
|
|
|
|
var icon = card.querySelector('.feature-icon');
|
|
|
|
|
if (icon) {
|
|
|
|
|
var cardDelay = parseFloat(card.style.getPropertyValue('--scroll-delay')) || 0;
|
|
|
|
|
icon.style.animationDelay = (cardDelay + 0.15).toFixed(2) + 's';
|
|
|
|
|
icon.classList.add('icon-pop');
|
|
|
|
|
icon.addEventListener('animationend', function () {
|
|
|
|
|
icon.classList.remove('icon-pop');
|
|
|
|
|
icon.style.animationDelay = '';
|
2026-03-22 01:45:38 -04:00
|
|
|
// Start ambient float+ring animation once the pop-in finishes.
|
|
|
|
|
card.classList.add('is-animated');
|
2026-03-22 01:40:10 -04:00
|
|
|
}, { once: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset stagger delay after entrance transition so hover isn't delayed.
|
|
|
|
|
card.addEventListener('transitionend', function (e) {
|
|
|
|
|
if (e.propertyName === 'opacity') {
|
|
|
|
|
card.style.setProperty('--scroll-delay', '0s');
|
|
|
|
|
}
|
|
|
|
|
}, { once: true });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
cards.forEach(function (card) {
|
|
|
|
|
mo.observe(card, { attributes: true, attributeFilter: ['class'] });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}());
|