Add device animation functionality to platform row and enhance CSS for visual effects

This commit is contained in:
Matt Batchelder
2026-02-21 01:33:52 -05:00
parent 3100a93d9a
commit 618ba6ded4
4 changed files with 420 additions and 1 deletions

View File

@@ -1984,6 +1984,348 @@ p:last-child { margin-bottom: 0; }
.platform-row.reverse .platform-visual { order: unset; }
}
/* ── 10b. Device Animator ───────────────────────────────────── */
.platform-visual.has-anim {
padding: 0;
overflow: hidden;
position: relative;
font-size: inherit;
}
.da-stage {
position: absolute;
inset: 0;
}
/* Each device panel hidden by default, centred in stage */
.da-device {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.88);
opacity: 0;
display: flex;
flex-direction: column;
align-items: center;
transition: opacity 0.55s cubic-bezier(0.4,0,0.2,1),
transform 0.55s cubic-bezier(0.4,0,0.2,1);
will-change: opacity, transform;
}
.da-device.is-active {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
.da-device.is-leaving {
opacity: 0;
transform: translate(-50%, -50%) scale(1.07);
}
/* Screen surface */
.da-screen {
width: 100%;
height: 100%;
border-radius: 2px;
position: relative;
overflow: hidden;
background:
repeating-linear-gradient(
180deg,
transparent,
transparent 3px,
rgba(0,0,0,0.10) 3px,
rgba(0,0,0,0.10) 4px
),
linear-gradient(135deg, #0c1016 0%, #151c28 60%, #0c1016 100%);
}
.da-screen::before {
content: '';
position: absolute;
top: -20%;
left: -10%;
width: 60%;
height: 60%;
background: radial-gradient(ellipse, rgba(74,222,128,0.12) 0%, transparent 70%);
pointer-events: none;
}
@keyframes da-scan {
0% { top: -6%; opacity: 0; }
5% { opacity: 1; }
95% { opacity: 1; }
100% { top: 106%; opacity: 0; }
}
.da-screen::after {
content: '';
position: absolute;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, transparent, rgba(74,222,128,0.28), transparent);
animation: da-scan 3s linear infinite;
pointer-events: none;
}
/* Device label */
.da-label {
display: block;
margin-top: 11px;
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--color-text-muted);
text-align: center;
}
/* ── Tablet ────────────────────────────────────── */
.da-tablet .da-body {
width: 128px;
height: 194px;
background: var(--color-bg-alt);
border: 2px solid var(--color-border);
border-radius: 14px;
padding: 10px 8px 14px;
display: flex;
align-items: stretch;
position: relative;
box-shadow: 0 16px 48px rgba(0,0,0,0.50);
}
.da-tablet .da-body::before {
content: '';
position: absolute;
top: 5px;
left: 50%;
transform: translateX(-50%);
width: 6px;
height: 6px;
background: var(--color-border);
border-radius: 50%;
}
.da-tablet .da-body::after {
content: '';
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
width: 36px;
height: 3px;
background: var(--color-border);
border-radius: 2px;
}
/* ── Small Monitor ─────────────────────────────── */
.da-monitor-sm .da-body {
width: 236px;
height: 146px;
background: var(--color-bg-alt);
border: 5px solid var(--color-bg-alt);
border-radius: 6px;
outline: 1px solid var(--color-border);
padding: 3px;
display: flex;
align-items: stretch;
position: relative;
box-shadow: 0 10px 36px rgba(0,0,0,0.50);
}
.da-monitor-sm .da-body::after {
content: '';
position: absolute;
bottom: -9px;
right: 8px;
width: 5px;
height: 5px;
background: var(--color-primary);
border-radius: 50%;
box-shadow: 0 0 5px var(--color-primary);
}
.da-monitor-sm .da-stand,
.da-monitor-lg .da-stand { display: flex; flex-direction: column; align-items: center; }
.da-monitor-sm .da-stem {
width: 14px;
height: 20px;
background: var(--color-bg-alt);
border-left: 1px solid var(--color-border);
border-right: 1px solid var(--color-border);
}
.da-monitor-sm .da-base {
width: 68px;
height: 5px;
background: var(--color-bg-alt);
border: 1px solid var(--color-border);
border-radius: 3px;
}
/* ── Large Monitor ─────────────────────────────── */
.da-monitor-lg .da-body {
width: 298px;
height: 177px;
background: var(--color-bg-alt);
border: 4px solid var(--color-bg-alt);
border-radius: 6px;
outline: 1px solid var(--color-border);
padding: 3px;
display: flex;
align-items: stretch;
position: relative;
box-shadow: 0 12px 40px rgba(0,0,0,0.50);
}
.da-monitor-lg .da-stem {
width: 16px;
height: 26px;
background: var(--color-bg-alt);
border-left: 1px solid var(--color-border);
border-right: 1px solid var(--color-border);
}
.da-monitor-lg .da-base {
width: 88px;
height: 5px;
background: var(--color-bg-alt);
border: 1px solid var(--color-border);
border-radius: 3px;
}
/* ── TV ────────────────────────────────────────── */
.da-tv .da-body {
width: 320px;
height: 188px;
background: var(--color-bg-alt);
border: 4px solid var(--color-bg-alt);
border-radius: 6px 6px 4px 4px;
outline: 1px solid var(--color-border);
padding: 3px;
display: flex;
align-items: stretch;
position: relative;
box-shadow: 0 14px 48px rgba(0,0,0,0.55);
}
.da-tv .da-body::after {
content: '\25B6';
position: absolute;
bottom: -13px;
left: 50%;
transform: translateX(-50%);
font-size: 8px;
color: rgba(74,222,128,0.7);
}
.da-tv .da-feet { display: flex; justify-content: space-between; width: 180px; }
.da-tv .da-foot {
width: 12px;
height: 8px;
background: var(--color-bg-alt);
border: 1px solid var(--color-border);
border-radius: 0 0 4px 4px;
}
/* ── Projector ─────────────────────────────────── */
.da-projector .da-proj-layout { display: flex; flex-direction: column; align-items: center; }
.da-projector .da-proj-body {
width: 156px;
height: 62px;
background: var(--color-bg-alt);
border: 1px solid var(--color-border);
border-radius: 10px 10px 8px 8px;
display: flex;
align-items: center;
padding: 0 14px;
gap: 10px;
box-shadow: 0 6px 20px rgba(0,0,0,0.45);
position: relative;
}
.da-projector .da-proj-body::after {
content: '';
position: absolute;
top: 8px;
right: 10px;
width: 7px;
height: 7px;
background: var(--color-primary);
border-radius: 50%;
box-shadow: 0 0 6px var(--color-primary);
}
.da-projector .da-proj-body::before {
content: '';
position: absolute;
right: 10px;
bottom: 8px;
width: 28px;
height: 8px;
background: repeating-linear-gradient(
90deg,
var(--color-border) 0px,
var(--color-border) 2px,
transparent 2px,
transparent 5px
);
border-radius: 1px;
}
.da-projector .da-lens {
width: 38px;
height: 38px;
background: #080c12;
border: 2px solid var(--color-border);
border-radius: 50%;
flex-shrink: 0;
position: relative;
box-shadow: inset 0 0 8px rgba(0,0,0,0.8);
}
.da-projector .da-lens::after {
content: '';
position: absolute;
inset: 5px;
background: radial-gradient(circle at 35% 35%, rgba(74,222,128,0.30) 0%, #080c12 65%);
border-radius: 50%;
}
.da-projector .da-beam {
width: 240px;
height: 50px;
clip-path: polygon(31% 0%, 69% 0%, 100% 100%, 0% 100%);
background: linear-gradient(
180deg,
rgba(74,222,128,0.07) 0%,
rgba(74,222,128,0.02) 100%
);
}
.da-projector .da-proj-screen {
width: 240px;
height: 72px;
background: var(--color-bg-alt);
border: 1px solid var(--color-border);
border-radius: 3px;
overflow: hidden;
padding: 3px;
box-shadow: 0 4px 14px rgba(0,0,0,0.40);
}
/* ── Video Wall (2×2) ──────────────────────────── */
.da-vwall .da-vwall-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 5px;
background: #0a0d12;
padding: 5px;
border: 1px solid var(--color-border);
border-radius: 4px;
box-shadow: 0 14px 48px rgba(0,0,0,0.60);
}
.da-vwall .da-panel {
width: 148px;
height: 90px;
background: var(--color-bg-alt);
border: 2px solid var(--color-bg-alt);
padding: 2px;
display: flex;
align-items: stretch;
overflow: hidden;
}
.da-vwall .da-panel:nth-child(2) .da-screen::after { animation-delay: -0.75s; }
.da-vwall .da-panel:nth-child(3) .da-screen::after { animation-delay: -1.5s; }
.da-vwall .da-panel:nth-child(4) .da-screen::after { animation-delay: -2.25s; }
/* ── Responsive scale-down ─────────────────────── */
@media (max-width: 900px) {
.da-device { transform: translate(-50%,-50%) scale(0.76); }
.da-device.is-active { transform: translate(-50%,-50%) scale(0.84); }
.da-device.is-leaving { transform: translate(-50%,-50%) scale(0.91); }
}
@media (max-width: 640px) {
.da-device { transform: translate(-50%,-50%) scale(0.56); }
.da-device.is-active { transform: translate(-50%,-50%) scale(0.64); }
.da-device.is-leaving { transform: translate(-50%,-50%) scale(0.70); }
}
@media (prefers-reduced-motion: reduce) {
.da-device { transition: none; }
.da-screen::after { animation: none; }
}
/* ── 11. Page Hero (inner pages) ───────────────────────────── */
.page-hero {
background: #111111;

View File

@@ -610,3 +610,68 @@ document.addEventListener('DOMContentLoaded', () => {
paintBg();
start();
})();
/* ── Device Animator ──────────────────────────────────────────────────────── */
(function () {
const DEVICES = [
'da-tablet', 'da-monitor-sm', 'da-monitor-lg',
'da-tv', 'da-projector', 'da-vwall'
];
const DWELL = 2500; // ms each device is shown
document.querySelectorAll('.da-stage').forEach(function (stage) {
let current = 0;
let timer = null;
// Collect the 6 device panels
const panels = DEVICES.map(function (cls) {
return stage.querySelector('.' + cls);
});
function show(idx) {
panels.forEach(function (el, i) {
if (!el) return;
el.classList.toggle('is-active', i === idx);
el.classList.remove('is-leaving');
});
}
function advance() {
const leaving = current;
current = (current + 1) % DEVICES.length;
if (panels[leaving]) panels[leaving].classList.add('is-leaving');
show(current);
setTimeout(function () {
if (panels[leaving]) panels[leaving].classList.remove('is-leaving');
}, 600);
}
function startCycle() {
if (timer) return;
timer = setInterval(advance, DWELL);
}
function stopCycle() {
clearInterval(timer);
timer = null;
}
// Honour reduced-motion preference: show first device statically
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
show(0);
return;
}
show(0);
startCycle();
// Pause when scrolled out of view to save resources
if ('IntersectionObserver' in window) {
new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
e.isIntersecting ? startCycle() : stopCycle();
});
}, { threshold: 0.2 }).observe(stage);
}
});
})();

View File

@@ -553,6 +553,7 @@ add_action( 'init', function () {
'imgUrl' => [ 'type' => 'string', 'default' => '' ],
'imgAlt' => [ 'type' => 'string', 'default' => '' ],
'imgWidth' => [ 'type' => 'number', 'default' => 300 ],
'deviceAnim' => [ 'type' => 'boolean', 'default' => false ],
],
'supports' => $block_supports,
'render_callback' => 'oribi_render_platform_row',
@@ -1346,6 +1347,17 @@ function oribi_render_platform_row( $a ) {
$visual_html = '<img src="' . esc_url( $img_url ) . '" alt="' . esc_attr( $img_alt ) . '" style="' . esc_attr( $img_style ) . '">';
}
$visual_cls = 'platform-visual has-img';
} elseif ( ! empty( $a['deviceAnim'] ) ) {
$da = '<div class="da-stage" aria-hidden="true">';
$da .= '<div class="da-device da-tablet"><div class="da-body"><div class="da-screen"></div></div><span class="da-label">Tablet</span></div>';
$da .= '<div class="da-device da-monitor-sm"><div class="da-body"><div class="da-screen"></div></div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Small Monitor</span></div>';
$da .= '<div class="da-device da-monitor-lg"><div class="da-body"><div class="da-screen"></div></div><div class="da-stand"><div class="da-stem"></div><div class="da-base"></div></div><span class="da-label">Large Monitor</span></div>';
$da .= '<div class="da-device da-tv"><div class="da-body"><div class="da-screen"></div></div><div class="da-feet"><div class="da-foot"></div><div class="da-foot"></div></div><span class="da-label">TV</span></div>';
$da .= '<div class="da-device da-projector"><div class="da-proj-layout"><div class="da-proj-body"><div class="da-lens"></div></div><div class="da-beam"></div><div class="da-proj-screen"><div class="da-screen"></div></div></div><span class="da-label">Projector</span></div>';
$da .= '<div class="da-device da-vwall"><div class="da-vwall-grid"><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div><div class="da-panel"><div class="da-screen"></div></div></div><span class="da-label">Video Wall</span></div>';
$da .= '</div>';
$visual_html = $da;
$visual_cls = 'platform-visual has-anim';
} else {
$visual_html = oribi_render_icon( $a['visual'] ?? '' );
$visual_cls = 'platform-visual';