diff --git a/theme/assets/css/main.css b/theme/assets/css/main.css index 27c82b4..6288c28 100644 --- a/theme/assets/css/main.css +++ b/theme/assets/css/main.css @@ -112,8 +112,21 @@ a:hover { color: var(--color-primary); } transform: translateY(0); /* !important ensures this wins over transition: all on .oribi-card (same selector specificity, but .oribi-card appears later in the - stylesheet and would otherwise override this). */ - transition: opacity .5s ease, transform .5s ease !important; + stylesheet and would otherwise override this). + var(--scroll-delay) is set per-card by demo-animator.js to produce + a staggered entrance when multiple cards reveal at once. */ + transition: opacity .5s ease var(--scroll-delay, 0s), transform .5s ease var(--scroll-delay, 0s) !important; +} + +/* Icon pop-in animation — triggered by demo-animator.js when a + feature-section card enters the viewport. */ +@keyframes icon-pop { + 0% { transform: scale(0.5); opacity: 0; } + 60% { transform: scale(1.2); opacity: 1; } + 100% { transform: scale(1); } +} +.feature-icon.icon-pop { + animation: icon-pop 0.4s ease-out backwards; } /* Smooth theme transition */ diff --git a/theme/assets/js/demo-animator.js b/theme/assets/js/demo-animator.js new file mode 100644 index 0000000..fbafe12 --- /dev/null +++ b/theme/assets/js/demo-animator.js @@ -0,0 +1,63 @@ +/** + * 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. + // main.js's .scroll-visible rule reads this via var(--scroll-delay, 0s). + document.querySelectorAll('.feature-section').forEach(function (section) { + section.querySelectorAll('.oribi-card').forEach(function (card, i) { + card.style.setProperty('--scroll-delay', (i * STAGGER).toFixed(2) + 's'); + }); + }); + + // 2. Watch for scroll-visible being added to each card. + var cards = document.querySelectorAll('.feature-section .oribi-card'); + 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 = ''; + }, { 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'] }); + }); + }); +}()); diff --git a/theme/inc/enqueue.php b/theme/inc/enqueue.php index 3b1593d..2c319c4 100644 --- a/theme/inc/enqueue.php +++ b/theme/inc/enqueue.php @@ -65,6 +65,15 @@ add_action( 'wp_enqueue_scripts', function () { true ); + // Feature section card animator - staggered entrance and icon pop-in + wp_enqueue_script( + 'oribi-demo-animator', + ORIBI_URI . '/assets/js/demo-animator.js', + [], + ORIBI_VERSION . '.' . filemtime( ORIBI_DIR . '/assets/js/demo-animator.js' ), + true + ); + // Localize AJAX endpoint for the contact form wp_localize_script( 'oribi-main', 'oribiAjax', [ 'url' => admin_url( 'admin-ajax.php' ),