feat: Implement card scene animations and enhance feature card attributes for improved visual storytelling

This commit is contained in:
Matt Batchelder
2026-03-22 01:59:55 -04:00
parent d120bec8ce
commit 7918d727eb
3 changed files with 482 additions and 7 deletions

View File

@@ -1256,6 +1256,389 @@ p:last-child { margin-bottom: 0; }
color: #fff;
}
/* ═══════════════════════════════════════════════════════════════
8a-i. Card Scene Mini Animations (.card-scene)
Replaces .feature-icon on feature-cards with scene="" set.
Dark #0d1117 screen aesthetic, pure CSS animations,
matching the design page platform-row visual language.
═══════════════════════════════════════════════════════════════ */
/* ── Shared scaffold ─────────────────────────────────────────── */
.card-scene {
width: 100%;
height: 140px;
margin-bottom: 1.25rem;
border-radius: var(--radius-md);
overflow: hidden;
flex-shrink: 0;
text-align: left;
}
.cs-screen {
width: 100%;
height: 100%;
background: #0d1117;
border: 1px solid rgba(255,255,255,0.08);
border-radius: var(--radius-md);
overflow: hidden;
display: flex;
flex-direction: column;
padding: 10px;
gap: 6px;
box-sizing: border-box;
}
.cs-topbar {
display: flex;
align-items: center;
gap: 6px;
padding-bottom: 6px;
border-bottom: 1px solid rgba(255,255,255,0.06);
flex-shrink: 0;
}
.cs-dots { display: flex; gap: 3px; }
.cs-dots span { width: 6px; height: 6px; border-radius: 50%; }
.cs-dots span:nth-child(1) { background: #ff5f56; }
.cs-dots span:nth-child(2) { background: #ffbd2e; }
.cs-dots span:nth-child(3) { background: #27c93f; }
.cs-title {
font-size: 9px;
font-weight: 600;
letter-spacing: 0.5px;
color: rgba(255,255,255,0.35);
text-transform: uppercase;
flex: 1;
text-align: center;
}
.cs-live-dot {
width: 6px; height: 6px;
border-radius: 50%;
background: #ef4444;
flex-shrink: 0;
animation: cs-live-pulse 1.4s ease-in-out infinite;
}
@keyframes cs-live-pulse {
0%,100% { opacity: 1; box-shadow: 0 0 0 0 rgba(239,68,68,0.5); }
50% { opacity: 0.7; box-shadow: 0 0 0 4px rgba(239,68,68,0); }
}
/* ── Scene 1: Content Management ─────────────────────────────── */
.cs-cms .cs-files {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
justify-content: center;
}
.cs-file {
display: flex;
align-items: center;
gap: 6px;
opacity: 0;
transform: translateY(6px);
animation: cs-file-in 4.5s ease-in-out infinite;
}
.cs-file--1 { animation-delay: 0.1s; }
.cs-file--2 { animation-delay: 0.5s; }
.cs-file--3 { animation-delay: 0.9s; }
@keyframes cs-file-in {
0%,8% { opacity: 0; transform: translateY(6px); }
18%,72% { opacity: 1; transform: translateY(0); }
82%,100% { opacity: 0; transform: translateY(-4px); }
}
.cs-ftype {
font-size: 7px; font-weight: 700;
padding: 2px 5px;
border-radius: 3px;
min-width: 28px;
text-align: center;
letter-spacing: 0.3px;
flex-shrink: 0;
}
.cs-ftype-img { background: rgba(59,130,246,0.2); color: #60a5fa; }
.cs-ftype-vid { background: rgba(168,85,247,0.2); color: #c084fc; }
.cs-ftype-html { background: rgba(var(--color-primary-rgb),0.2); color: var(--color-primary); }
.cs-fbar {
flex: 1;
height: 4px;
background: rgba(255,255,255,0.06);
border-radius: 2px;
overflow: hidden;
position: relative;
}
.cs-fbar::after {
content: '';
position: absolute; top:0; left:0; bottom:0;
border-radius: 2px;
background: rgba(255,255,255,0.2);
}
.cs-file--1 .cs-fbar::after { width: 78%; }
.cs-file--2 .cs-fbar::after { width: 52%; }
.cs-file--3 .cs-fbar::after { width: 34%; }
.cs-fsize {
font-size: 7px;
color: rgba(255,255,255,0.2);
min-width: 26px;
text-align: right;
flex-shrink: 0;
}
.cs-upload {
font-size: 8px; font-weight: 600;
text-align: center;
color: var(--color-primary);
border: 1px solid rgba(var(--color-primary-rgb),0.35);
border-radius: 4px;
padding: 3px 0;
flex-shrink: 0;
animation: cs-upload-glow 2.5s ease-in-out infinite;
}
@keyframes cs-upload-glow {
0%,100% { border-color: rgba(var(--color-primary-rgb),0.3); box-shadow: none; }
50% { border-color: rgba(var(--color-primary-rgb),0.8); box-shadow: 0 0 8px rgba(var(--color-primary-rgb),0.25); }
}
/* ── Scene 2: Smart Scheduling ────────────────────────────────── */
.cs-week {
display: flex;
gap: 4px;
flex: 1;
align-items: flex-end;
}
.cs-cal-day {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.cs-cal-day > span {
font-size: 7px; font-weight: 600;
color: rgba(255,255,255,0.3);
}
.cs-cal-bar {
width: 100%;
border-radius: 3px;
background: rgba(var(--color-primary-rgb),0.65);
animation: cs-bar-glow 3.5s ease-in-out infinite;
}
.cs-cal-day:nth-child(1) .cs-cal-bar { height: 36px; animation-delay: 0.0s; }
.cs-cal-day:nth-child(2) .cs-cal-bar { height: 52px; animation-delay: 0.2s; }
.cs-cal-day:nth-child(3) .cs-cal-bar { height: 28px; animation-delay: 0.4s; }
.cs-cal-day:nth-child(4) .cs-cal-bar { height: 44px; animation-delay: 0.6s; }
.cs-cal-day:nth-child(5) .cs-cal-bar { height: 60px; animation-delay: 0.8s; }
.cs-cal-day:nth-child(6) .cs-cal-bar { height: 18px; animation-delay: 1.0s; opacity: 0.35; }
.cs-cal-day:nth-child(7) .cs-cal-bar { height: 12px; animation-delay: 1.2s; opacity: 0.35; }
@keyframes cs-bar-glow {
0%,100% { opacity: 0.45; }
50% { opacity: 1; box-shadow: 0 0 8px rgba(var(--color-primary-rgb),0.55); }
}
/* ── Scene 3: Live Data Feeds ──────────────────────────────────── */
.cs-feed-rows {
display: flex;
flex-direction: column;
gap: 9px;
flex: 1;
justify-content: center;
}
.cs-feed-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.cs-feed-label {
font-size: 8px; font-weight: 700;
color: rgba(255,255,255,0.3);
letter-spacing: 0.8px;
text-transform: uppercase;
}
.cs-feed-val {
position: relative;
height: 13px;
min-width: 52px;
}
.cs-feed-val span {
position: absolute;
right: 0; top: 0;
font-size: 9px; font-weight: 600;
font-variant-numeric: tabular-nums;
opacity: 0;
animation: cs-val-show 6s linear infinite;
}
.cs-fv1-a,.cs-fv1-b,.cs-fv1-c { color: #4ade80; }
.cs-fv2-a,.cs-fv2-b,.cs-fv2-c { color: rgba(255,255,255,0.8); }
.cs-fv3-a,.cs-fv3-b,.cs-fv3-c { color: var(--color-primary); }
.cs-fv1-a,.cs-fv2-a,.cs-fv3-a { animation-delay: 0s; }
.cs-fv1-b,.cs-fv2-b,.cs-fv3-b { animation-delay: 2s; }
.cs-fv1-c,.cs-fv2-c,.cs-fv3-c { animation-delay: 4s; }
@keyframes cs-val-show {
0%,29% { opacity: 1; }
33%,100%{ opacity: 0; }
}
/* ── Scene 4: Template Library ─────────────────────────────────── */
.cs-tmpl-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 5px;
flex: 1;
}
.cs-tmpl {
background: rgba(255,255,255,0.04);
border-radius: 4px;
border: 1px solid rgba(255,255,255,0.08);
padding: 5px;
display: flex;
flex-direction: column;
gap: 3px;
animation: cs-tmpl-sel 8s ease-in-out infinite;
}
.cs-tmpl--1 { animation-delay: 0s; }
.cs-tmpl--2 { animation-delay: 2s; }
.cs-tmpl--3 { animation-delay: 4s; }
.cs-tmpl--4 { animation-delay: 6s; }
@keyframes cs-tmpl-sel {
0%,1% { border-color: rgba(var(--color-primary-rgb),0.8); box-shadow: 0 0 0 1px rgba(var(--color-primary-rgb),0.25); }
22%,23% { border-color: rgba(var(--color-primary-rgb),0.8); box-shadow: 0 0 0 1px rgba(var(--color-primary-rgb),0.25); }
25%,100% { border-color: rgba(255,255,255,0.08); box-shadow: none; }
}
.cs-tmpl-thumb {
flex: 1;
min-height: 12px;
border-radius: 3px;
background: rgba(255,255,255,0.07);
}
.cs-tmpl-thumb--full { min-height: 20px; }
.cs-tmpl-line {
height: 3px;
border-radius: 2px;
background: rgba(255,255,255,0.14);
}
.cs-tmpl-line--short { width: 60%; }
.cs-tmpl-line--med { width: 85%; }
/* ── Scene 5: Team & Permissions ───────────────────────────────── */
.cs-team-avatars {
display: flex;
justify-content: space-around;
align-items: center;
flex: 1;
padding: 2px 0;
}
.cs-avatar {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.cs-av-circle {
width: 28px; height: 28px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 700; color: #fff;
animation: cs-av-pulse 4s ease-in-out infinite;
}
.cs-avatar--1 .cs-av-circle { background: rgba(var(--color-primary-rgb),0.8); animation-delay: 0s; }
.cs-avatar--2 .cs-av-circle { background: rgba(99,102,241,0.8); animation-delay: 0.8s; }
.cs-avatar--3 .cs-av-circle { background: rgba(107,114,128,0.7); animation-delay: 1.6s; }
@keyframes cs-av-pulse {
0%,100% { transform: scale(1); opacity: 0.8; }
50% { transform: scale(1.1); opacity: 1; box-shadow: 0 0 10px rgba(255,255,255,0.15); }
}
.cs-av-role {
font-size: 7px; font-weight: 700;
padding: 2px 5px;
border-radius: 3px;
letter-spacing: 0.3px;
text-transform: uppercase;
}
.cs-role-admin { background: rgba(var(--color-primary-rgb),0.2); color: var(--color-primary); }
.cs-role-editor { background: rgba(99,102,241,0.2); color: #818cf8; }
.cs-role-viewer { background: rgba(107,114,128,0.15); color: rgba(255,255,255,0.35); }
.cs-perm-strip {
display: flex;
gap: 4px;
justify-content: center;
flex-shrink: 0;
}
.cs-perm-dot {
height: 4px;
border-radius: 2px;
background: rgba(255,255,255,0.1);
}
.cs-perm-dot--1 { width: 32px; background: rgba(var(--color-primary-rgb),0.55); }
.cs-perm-dot--2 { width: 24px; background: rgba(99,102,241,0.55); }
.cs-perm-dot--3 { width: 16px; }
/* ── Scene 6: Playback Analytics ───────────────────────────────── */
.cs-analytics-body {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
padding: 2px 4px;
}
.cs-ring-wrap {
position: relative;
width: 52px; height: 52px;
flex-shrink: 0;
}
.cs-ring {
width: 52px; height: 52px;
border-radius: 50%;
border: 5px solid var(--color-primary);
border-left-color: rgba(255,255,255,0.07);
animation: cs-ring-spin 3.5s ease-in-out infinite;
}
@keyframes cs-ring-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.cs-ring-val {
position: absolute;
inset: 0;
display: flex; align-items: center; justify-content: center;
font-size: 9px; font-weight: 700;
color: rgba(255,255,255,0.7);
}
.cs-bars {
display: flex;
align-items: flex-end;
gap: 4px;
flex: 1;
height: 100%;
padding-bottom: 2px;
}
.cs-sbar {
flex: 1;
border-radius: 2px 2px 0 0;
background: rgba(var(--color-primary-rgb),0.55);
animation: cs-sbar-pulse 2.4s ease-in-out infinite;
}
.cs-sbar--1 { height: 40%; animation-delay: 0.0s; }
.cs-sbar--2 { height: 65%; animation-delay: 0.2s; }
.cs-sbar--3 { height: 80%; animation-delay: 0.4s; }
.cs-sbar--4 { height: 55%; animation-delay: 0.6s; }
.cs-sbar--5 { height: 90%; animation-delay: 0.8s; }
.cs-sbar--6 { height: 70%; animation-delay: 1.0s; }
@keyframes cs-sbar-pulse {
0%,100% { opacity: 0.5; }
50% { opacity: 1; box-shadow: 0 0 6px rgba(var(--color-primary-rgb),0.5); }
}
/* ── Reduced motion ─────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.cs-file { animation: none; opacity: 1; transform: none; }
.cs-cal-bar { animation: none; opacity: 0.7; }
.cs-feed-val span { animation: none; }
.cs-fv1-a,.cs-fv2-a,.cs-fv3-a { opacity: 1; }
.cs-upload { animation: none; }
.cs-live-dot { animation: none; }
.cs-tmpl { animation: none; }
.cs-av-circle { animation: none; opacity: 1; transform: none; }
.cs-ring { animation: none; }
.cs-sbar { animation: none; opacity: 0.7; }
}
/* ── 8b. Value Card ────────────────────────────────────────── */
.value-card {
text-align: center;

View File

@@ -383,6 +383,7 @@ add_action('init', function () {
'description' => ['type' => 'string', 'default' => ''],
'url' => ['type' => 'string', 'default' => ''],
'centered' => ['type' => 'boolean', 'default' => false],
'scene' => ['type' => 'string', 'default' => ''],
],
oribi_card_image_attributes()
),
@@ -1084,6 +1085,7 @@ function oribi_render_feature_card($a)
$href = !empty($a['url']) ? ' href="' . esc_url($a['url']) . '"' : '';
$link_cls = !empty($a['url']) ? ' feature-card-link' : '';
$center = !empty($a['centered']);
$scene = !empty($a['scene']) ? trim($a['scene']) : '';
$img = oribi_card_image_html($a);
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
@@ -1114,7 +1116,9 @@ function oribi_render_feature_card($a)
<?php echo $img['html']; ?>
<?php
endif; ?>
<?php if ((!$img['html'] || $img['position'] !== 'replace-icon') && oribi_has_icon($a)): ?>
<?php if ($scene): ?>
<?php echo oribi_render_card_scene($scene); ?>
<?php elseif ((!$img['html'] || $img['position'] !== 'replace-icon') && oribi_has_icon($a)): ?>
<div class="feature-icon"<?php echo $center ? ' style="margin-inline:auto;"' : ''; ?>><?php echo oribi_render_icon($a); ?></div>
<?php
endif; ?>
@@ -1126,6 +1130,94 @@ function oribi_render_feature_card($a)
return ob_get_clean();
}
/* ── Card Scene Renderer ────────────────────────────────────────────────────── */
function oribi_render_card_scene($scene)
{
switch ($scene) {
case 'cms':
return
'<div class="card-scene cs-cms" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Media Library</div></div>'
. '<div class="cs-files">'
. '<div class="cs-file cs-file--1"><span class="cs-ftype cs-ftype-img">IMG</span><div class="cs-fbar"></div><span class="cs-fsize">2.4 MB</span></div>'
. '<div class="cs-file cs-file--2"><span class="cs-ftype cs-ftype-vid">VID</span><div class="cs-fbar"></div><span class="cs-fsize">18 MB</span></div>'
. '<div class="cs-file cs-file--3"><span class="cs-ftype cs-ftype-html">HTML</span><div class="cs-fbar"></div><span class="cs-fsize">64 KB</span></div>'
. '</div>'
. '<div class="cs-upload">+ Upload</div>'
. '</div></div>';
case 'scheduler':
return
'<div class="card-scene cs-scheduler" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Scheduler</div></div>'
. '<div class="cs-week">'
. '<div class="cs-cal-day"><span>M</span><div class="cs-cal-bar"></div></div>'
. '<div class="cs-cal-day"><span>T</span><div class="cs-cal-bar"></div></div>'
. '<div class="cs-cal-day"><span>W</span><div class="cs-cal-bar"></div></div>'
. '<div class="cs-cal-day"><span>T</span><div class="cs-cal-bar"></div></div>'
. '<div class="cs-cal-day"><span>F</span><div class="cs-cal-bar"></div></div>'
. '<div class="cs-cal-day"><span>S</span><div class="cs-cal-bar"></div></div>'
. '<div class="cs-cal-day"><span>S</span><div class="cs-cal-bar"></div></div>'
. '</div>'
. '</div></div>';
case 'data-feed':
return
'<div class="card-scene cs-data-feed" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-live-dot"></div><div class="cs-title">Live Feed</div></div>'
. '<div class="cs-feed-rows">'
. '<div class="cs-feed-row"><span class="cs-feed-label">TEMP</span><div class="cs-feed-val"><span class="cs-fv1-a">22.4&deg;C</span><span class="cs-fv1-b">23.1&deg;C</span><span class="cs-fv1-c">21.8&deg;C</span></div></div>'
. '<div class="cs-feed-row"><span class="cs-feed-label">QUEUE</span><div class="cs-feed-val"><span class="cs-fv2-a">14</span><span class="cs-fv2-b">17</span><span class="cs-fv2-c">11</span></div></div>'
. '<div class="cs-feed-row"><span class="cs-feed-label">SALES</span><div class="cs-feed-val"><span class="cs-fv3-a">\$1,204</span><span class="cs-fv3-b">\$1,318</span><span class="cs-fv3-c">\$1,271</span></div></div>'
. '</div>'
. '</div></div>';
case 'templates':
return
'<div class="card-scene cs-templates" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Templates</div></div>'
. '<div class="cs-tmpl-grid">'
. '<div class="cs-tmpl cs-tmpl--1"><div class="cs-tmpl-thumb"></div><div class="cs-tmpl-line cs-tmpl-line--med"></div></div>'
. '<div class="cs-tmpl cs-tmpl--2"><div class="cs-tmpl-line cs-tmpl-line--short"></div><div class="cs-tmpl-thumb"></div></div>'
. '<div class="cs-tmpl cs-tmpl--3"><div class="cs-tmpl-thumb cs-tmpl-thumb--full"></div></div>'
. '<div class="cs-tmpl cs-tmpl--4"><div class="cs-tmpl-line cs-tmpl-line--med"></div><div class="cs-tmpl-line cs-tmpl-line--short"></div><div class="cs-tmpl-line cs-tmpl-line--short"></div></div>'
. '</div>'
. '</div></div>';
case 'team':
return
'<div class="card-scene cs-team" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-dots"><span></span><span></span><span></span></div><div class="cs-title">Team</div></div>'
. '<div class="cs-team-avatars">'
. '<div class="cs-avatar cs-avatar--1"><div class="cs-av-circle">A</div><div class="cs-av-role cs-role-admin">Admin</div></div>'
. '<div class="cs-avatar cs-avatar--2"><div class="cs-av-circle">E</div><div class="cs-av-role cs-role-editor">Editor</div></div>'
. '<div class="cs-avatar cs-avatar--3"><div class="cs-av-circle">V</div><div class="cs-av-role cs-role-viewer">Viewer</div></div>'
. '</div>'
. '<div class="cs-perm-strip"><div class="cs-perm-dot cs-perm-dot--1"></div><div class="cs-perm-dot cs-perm-dot--2"></div><div class="cs-perm-dot cs-perm-dot--3"></div></div>'
. '</div></div>';
case 'analytics':
return
'<div class="card-scene cs-analytics" aria-hidden="true">'
. '<div class="cs-screen">'
. '<div class="cs-topbar"><div class="cs-live-dot"></div><div class="cs-title">Analytics</div></div>'
. '<div class="cs-analytics-body">'
. '<div class="cs-ring-wrap"><div class="cs-ring"></div><div class="cs-ring-val">94%</div></div>'
. '<div class="cs-bars"><div class="cs-sbar cs-sbar--1"></div><div class="cs-sbar cs-sbar--2"></div><div class="cs-sbar cs-sbar--3"></div><div class="cs-sbar cs-sbar--4"></div><div class="cs-sbar cs-sbar--5"></div><div class="cs-sbar cs-sbar--6"></div></div>'
. '</div>'
. '</div></div>';
default:
return '';
}
}
/* ── Value Section ─────────────────────────────────────────────────────────── */
function oribi_render_value_section($a, $content)
{