feat: Enhance animations for feature and value icons, and update platform row stagger logic

This commit is contained in:
Matt Batchelder
2026-04-05 20:09:21 -04:00
parent 2548179a8d
commit 4e833fd836
3 changed files with 49 additions and 174 deletions

View File

@@ -125,7 +125,8 @@ a:hover { color: var(--color-primary); }
60% { transform: scale(1.2); opacity: 1; } 60% { transform: scale(1.2); opacity: 1; }
100% { transform: scale(1); } 100% { transform: scale(1); }
} }
.feature-icon.icon-pop { .feature-icon.icon-pop,
.value-icon.icon-pop {
animation: icon-pop 0.4s ease-out backwards; animation: icon-pop 0.4s ease-out backwards;
} }
@@ -146,15 +147,17 @@ a:hover { color: var(--color-primary); }
from { transform: rotate(0deg); } from { transform: rotate(0deg); }
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
/* Float + ring on all feature icons (but only once the card has /* Float + ring on all feature/value icons (but only once the card has
scroll-revealed — the .is-animated class is set by demo-animator.js). */ scroll-revealed — the .is-animated class is set by demo-animator.js). */
.oribi-card.is-animated .feature-icon { .oribi-card.is-animated .feature-icon,
.oribi-card.is-animated .value-icon {
animation: icon-float 3.6s ease-in-out infinite, animation: icon-float 3.6s ease-in-out infinite,
icon-ring 3.6s ease-out infinite; icon-ring 3.6s ease-out infinite;
} }
/* Pause ambient animation during hover so the scale transform /* Pause ambient animation during hover so the scale transform
from .oribi-card:hover .feature-icon takes over cleanly. */ from .oribi-card:hover .feature-icon / .value-icon takes over cleanly. */
.oribi-card:hover .feature-icon { .oribi-card:hover .feature-icon,
.oribi-card:hover .value-icon {
animation: none; animation: none;
} }
/* Cog icons spin slowly — contextually appropriate regardless of card. */ /* Cog icons spin slowly — contextually appropriate regardless of card. */
@@ -164,7 +167,8 @@ a:hover { color: var(--color-primary); }
} }
/* Respect reduced-motion preference. */ /* Respect reduced-motion preference. */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.oribi-card.is-animated .feature-icon { animation: none; } .oribi-card.is-animated .feature-icon,
.oribi-card.is-animated .value-icon { animation: none; }
.feature-icon .fa-cog, .feature-icon .fa-cog,
.feature-icon .fa-users-cog { animation: none; } .feature-icon .fa-users-cog { animation: none; }
} }
@@ -3888,13 +3892,12 @@ p:last-child { margin-bottom: 0; }
.pkg-stage { .pkg-stage {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
gap: 2rem;
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
margin: 0 auto; margin: 0 auto;
padding: 2rem 0; padding: 2rem 0;
position: relative;
color: var(--color-primary);
} }
.pkg-node { .pkg-node {
@@ -3903,7 +3906,6 @@ p:last-child { margin-bottom: 0; }
align-items: center; align-items: center;
gap: 0.45rem; gap: 0.45rem;
flex-shrink: 0; flex-shrink: 0;
z-index: 2;
} }
.pkg-node__label { .pkg-node__label {
@@ -3914,6 +3916,15 @@ p:last-child { margin-bottom: 0; }
text-transform: uppercase; text-transform: uppercase;
} }
.pkg-plus {
font-size: 2rem;
font-weight: 300;
color: var(--color-primary);
line-height: 1;
flex-shrink: 0;
margin-bottom: 1.1rem; /* align with device bodies above label */
}
/* ── Player device ────────────────────────────────────────── */ /* ── Player device ────────────────────────────────────────── */
.pkg-player { .pkg-player {
display: flex; display: flex;
@@ -3987,85 +3998,6 @@ p:last-child { margin-bottom: 0; }
40%, 60% { opacity: 0.25; box-shadow: none; } 40%, 60% { opacity: 0.25; box-shadow: none; }
} }
/* ── Bundle box ───────────────────────────────────────────── */
.pkg-box {
display: flex;
flex-direction: column;
align-items: center;
}
.pkg-box__lids {
display: flex;
width: 76px;
gap: 3px;
perspective: 80px;
}
.pkg-box__flap {
flex: 1;
height: 14px;
background: var(--color-primary-dk, #A22702);
border-radius: 3px 3px 0 0;
}
.pkg-box__flap--l {
transform-origin: bottom center;
animation: pkg-flap-l 6s ease-in-out infinite;
}
.pkg-box__flap--r {
transform-origin: bottom center;
animation: pkg-flap-r 6s ease-in-out infinite;
}
@keyframes pkg-flap-l {
0%, 10% { transform: rotateX(0deg); }
28%, 72% { transform: rotateX(-110deg); }
90%, 100% { transform: rotateX(0deg); }
}
@keyframes pkg-flap-r {
0%, 12% { transform: rotateX(0deg); }
30%, 74% { transform: rotateX(-110deg); }
92%, 100% { transform: rotateX(0deg); }
}
.pkg-box__body {
width: 76px;
height: 68px;
background: var(--color-primary);
border-radius: 0 0 6px 6px;
position: relative;
overflow: hidden;
animation: pkg-box-glow 6s ease-in-out infinite;
}
/* Tape stripe */
.pkg-box__body::before {
content: '';
position: absolute;
top: 0; bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 13px;
background: rgba(255,255,255,.18);
}
/* Horizontal fold line */
.pkg-box__body::after {
content: '';
position: absolute;
top: 32px; left: 8px; right: 8px;
height: 1px;
background: rgba(0,0,0,.15);
}
@keyframes pkg-box-glow {
0%, 25% { box-shadow: 0 6px 18px rgba(var(--color-primary-rgb), .25); }
45%, 65% { box-shadow: 0 6px 34px rgba(var(--color-primary-rgb), .55); }
85%, 100% { box-shadow: 0 6px 18px rgba(var(--color-primary-rgb), .25); }
}
/* ── TV / Display ─────────────────────────────────────────── */ /* ── TV / Display ─────────────────────────────────────────── */
.pkg-tv { .pkg-tv {
display: flex; display: flex;
@@ -4114,81 +4046,13 @@ p:last-child { margin-bottom: 0; }
border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px;
} }
/* ── Connecting lines ─────────────────────────────────────── */
.pkg-line {
flex-grow: 1;
height: 2px;
background: rgba(var(--color-primary-rgb), .18);
margin: 0 1rem;
position: relative;
display: flex;
align-items: center;
overflow: hidden;
border-radius: 2px;
}
[data-theme="dark"] .pkg-line {
background: rgba(255,255,255,.1);
}
.pkg-pkt {
width: 10px;
height: 4px;
background: var(--color-primary);
border-radius: 4px;
position: absolute;
left: -12px;
box-shadow: 0 0 6px var(--color-primary);
}
[data-theme="dark"] .pkg-pkt {
background: #fff;
box-shadow: 0 0 6px rgba(255,255,255,.7);
}
/* LTR packets (player → box) */
.pkg-pkt--ltr-1 { animation: pkg-travel-ltr 2s linear infinite; }
.pkg-pkt--ltr-2 { animation: pkg-travel-ltr 2s linear infinite 0.65s; }
.pkg-pkt--ltr-3 { animation: pkg-travel-ltr 2s linear infinite 1.3s; }
@keyframes pkg-travel-ltr {
0% { left: -12px; opacity: 0; }
8% { opacity: 1; }
92% { opacity: 1; }
100% { left: 100%; opacity: 0; }
}
/* RTL packets (display → box) */
.pkg-pkt--rtl-1 { animation: pkg-travel-rtl 2s linear infinite; }
.pkg-pkt--rtl-2 { animation: pkg-travel-rtl 2s linear infinite 0.65s; }
.pkg-pkt--rtl-3 { animation: pkg-travel-rtl 2s linear infinite 1.3s; }
@keyframes pkg-travel-rtl {
0% { left: calc(100% + 12px); opacity: 0; }
8% { opacity: 1; }
92% { opacity: 1; }
100% { left: -12px; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.pkg-player__led, .pkg-player__led,
.pkg-box__flap--l,
.pkg-box__flap--r,
.pkg-box__body,
.pkg-pkt,
.pkg-tv__screen::after { animation: none; } .pkg-tv__screen::after { animation: none; }
.pkg-box__flap--l,
.pkg-box__flap--r { transform: rotateX(-90deg); }
.pkg-pkt--ltr-1 { left: 15%; opacity: 1; }
.pkg-pkt--ltr-2 { left: 50%; opacity: 1; }
.pkg-pkt--ltr-3 { left: 75%; opacity: 1; }
.pkg-pkt--rtl-1 { left: 75%; opacity: 1; }
.pkg-pkt--rtl-2 { left: 50%; opacity: 1; }
.pkg-pkt--rtl-3 { left: 15%; opacity: 1; }
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.pkg-stage { max-width: 300px; } .pkg-stage { max-width: 300px; gap: 1.5rem; }
} }

View File

@@ -1,22 +1,24 @@
/** /**
* Feature Section Card Animator * Feature Section Card Animator
* Staggered scroll-reveal entrance + icon pop-in for .feature-section cards. * Staggered scroll-reveal entrance + icon pop-in for .feature-section and
* .value-section cards, plus stagger for .platform-row elements.
* Coordinates with the main.js scroll-hidden/visible system: * Coordinates with the main.js scroll-hidden/visible system:
* 1. Sets --scroll-delay CSS custom property on each card so main.js's * 1. Sets --scroll-delay CSS custom property on each card/row so main.js's
* scroll-visible transition fires at staggered intervals. * scroll-visible transition fires at staggered intervals.
* 2. Uses MutationObserver to detect when scroll-visible is applied, then * 2. Uses MutationObserver to detect when scroll-visible is applied, then
* triggers the icon-pop animation on the card's .feature-icon. * triggers the icon-pop animation on the card's .feature-icon or .value-icon.
* 3. Resets the delay to 0s after the entrance so hover interactions stay snappy. * 3. Resets the delay to 0s after the entrance so hover interactions stay snappy.
*/ */
(function () { (function () {
'use strict'; 'use strict';
var STAGGER = 0.08; // seconds between each card's entrance var STAGGER = 0.08; // seconds between each card's entrance
var ROW_STAGGER = 0.12; // slightly longer stagger for platform-row blocks
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
// 1. Assign incrementing --scroll-delay to every card within a feature-section. // 1. Assign incrementing --scroll-delay to every card within a feature/value-section.
// The block renders .grid-2/.grid-3/.grid-4 wrappers (no .feature-section class). // The block renders .grid-2/.grid-3/.grid-4 wrappers (no .feature-section class).
// main.js's .scroll-visible rule reads this via var(--scroll-delay, 0s). // main.js's .scroll-visible rule reads this via var(--scroll-delay, 0s).
document.querySelectorAll('.grid-2, .grid-3, .grid-4').forEach(function (grid) { document.querySelectorAll('.grid-2, .grid-3, .grid-4').forEach(function (grid) {
@@ -25,6 +27,21 @@
}); });
}); });
// 1b. Assign incrementing --scroll-delay to .platform-row elements grouped
// by their parent container, so rows in the same section stagger in sequence.
var rowGroups = new Map();
document.querySelectorAll('.platform-row').forEach(function (row) {
var parent = row.parentElement;
if (!parent) return;
if (!rowGroups.has(parent)) rowGroups.set(parent, []);
rowGroups.get(parent).push(row);
});
rowGroups.forEach(function (rows) {
rows.forEach(function (row, i) {
row.style.setProperty('--scroll-delay', (i * ROW_STAGGER).toFixed(2) + 's');
});
});
// 2. Watch for scroll-visible being added to each card. // 2. Watch for scroll-visible being added to each card.
var cards = document.querySelectorAll('.grid-2 .oribi-card, .grid-3 .oribi-card, .grid-4 .oribi-card'); var cards = document.querySelectorAll('.grid-2 .oribi-card, .grid-3 .oribi-card, .grid-4 .oribi-card');
if (!cards.length) return; if (!cards.length) return;
@@ -37,7 +54,8 @@
mo.unobserve(card); mo.unobserve(card);
// Trigger icon pop ~150ms after the card itself starts fading in. // Trigger icon pop ~150ms after the card itself starts fading in.
var icon = card.querySelector('.feature-icon'); // Supports both .feature-icon (feature-cards) and .value-icon (value-cards).
var icon = card.querySelector('.feature-icon') || card.querySelector('.value-icon');
if (icon) { if (icon) {
var cardDelay = parseFloat(card.style.getPropertyValue('--scroll-delay')) || 0; var cardDelay = parseFloat(card.style.getPropertyValue('--scroll-delay')) || 0;
icon.style.animationDelay = (cardDelay + 0.15).toFixed(2) + 's'; icon.style.animationDelay = (cardDelay + 0.15).toFixed(2) + 's';

View File

@@ -1023,15 +1023,8 @@ function oribi_render_intro_section($a)
$pkg .= '<div class="pkg-player"><div class="pkg-player__body"><div class="pkg-player__led"></div></div><div class="pkg-player__hdmi"></div></div>'; $pkg .= '<div class="pkg-player"><div class="pkg-player__body"><div class="pkg-player__led"></div></div><div class="pkg-player__hdmi"></div></div>';
$pkg .= '<span class="pkg-node__label">Player</span>'; $pkg .= '<span class="pkg-node__label">Player</span>';
$pkg .= '</div>'; $pkg .= '</div>';
// LTR line (player → box) // Plus separator
$pkg .= '<div class="pkg-line pkg-line--ltr"><div class="pkg-pkt pkg-pkt--ltr-1"></div><div class="pkg-pkt pkg-pkt--ltr-2"></div><div class="pkg-pkt pkg-pkt--ltr-3"></div></div>'; $pkg .= '<div class="pkg-plus">+</div>';
// Box node
$pkg .= '<div class="pkg-node pkg-node--box">';
$pkg .= '<div class="pkg-box"><div class="pkg-box__lids"><div class="pkg-box__flap pkg-box__flap--l"></div><div class="pkg-box__flap pkg-box__flap--r"></div></div><div class="pkg-box__body"></div></div>';
$pkg .= '<span class="pkg-node__label">Bundle</span>';
$pkg .= '</div>';
// RTL line (display → box)
$pkg .= '<div class="pkg-line pkg-line--rtl"><div class="pkg-pkt pkg-pkt--rtl-1"></div><div class="pkg-pkt pkg-pkt--rtl-2"></div><div class="pkg-pkt pkg-pkt--rtl-3"></div></div>';
// TV node // TV node
$pkg .= '<div class="pkg-node pkg-node--tv">'; $pkg .= '<div class="pkg-node pkg-node--tv">';
$pkg .= '<div class="pkg-tv"><div class="pkg-tv__body"><div class="pkg-tv__screen"></div></div><div class="pkg-tv__feet"><div class="pkg-tv__foot"></div><div class="pkg-tv__foot"></div></div></div>'; $pkg .= '<div class="pkg-tv"><div class="pkg-tv__body"><div class="pkg-tv__screen"></div></div><div class="pkg-tv__feet"><div class="pkg-tv__foot"></div><div class="pkg-tv__foot"></div></div></div>';
@@ -2783,7 +2776,7 @@ function oribi_render_platform_row($a)
$br .= '<div class="bright-panel__screen bright-panel__screen--dim">'; $br .= '<div class="bright-panel__screen bright-panel__screen--dim">';
$br .= '<div class="bright-panel__content">'; $br .= '<div class="bright-panel__content">';
$br .= '<div class="bright-panel__eyebrow">Today\'s Special</div>'; $br .= '<div class="bright-panel__eyebrow">Today\'s Special</div>';
$br .= '<div class="bright-panel__title">Fish &amp; Chips<br>&pound;8.99</div>'; $br .= '<div class="bright-panel__title">Burger &amp; Fries<br>$9.99</div>';
$br .= '<div class="bright-panel__sub">Available until 3pm</div>'; $br .= '<div class="bright-panel__sub">Available until 3pm</div>';
$br .= '</div>'; $br .= '</div>';
$br .= '<div class="bright-panel__glare"></div>'; $br .= '<div class="bright-panel__glare"></div>';
@@ -2799,7 +2792,7 @@ function oribi_render_platform_row($a)
$br .= '<div class="bright-panel__screen bright-panel__screen--vivid">'; $br .= '<div class="bright-panel__screen bright-panel__screen--vivid">';
$br .= '<div class="bright-panel__content">'; $br .= '<div class="bright-panel__content">';
$br .= '<div class="bright-panel__eyebrow">Today\'s Special</div>'; $br .= '<div class="bright-panel__eyebrow">Today\'s Special</div>';
$br .= '<div class="bright-panel__title">Fish &amp; Chips<br>&pound;8.99</div>'; $br .= '<div class="bright-panel__title">Burger &amp; Fries<br>$9.99</div>';
$br .= '<div class="bright-panel__sub">Available until 3pm</div>'; $br .= '<div class="bright-panel__sub">Available until 3pm</div>';
$br .= '</div>'; $br .= '</div>';
$br .= '</div>'; $br .= '</div>';