Keyframe animations for cards, icons, loaders and headings - drop a class on an element and it comes alive.
1Pulse Wave Effect
Sends expanding rings out of a button, avatar or status dot to draw the eye. Class: fx-pulse-wave-effect
Ruby Rossi
Live now<div class="d-flex flex-wrap align-items-center justify-content-center gap-3 p-3 h-100" style="min-height:200px">
<button type="button" class="btn btn-primary rounded-circle d-inline-flex align-items-center justify-content-center fs-4 fx-pulse-wave-effect" style="width:4rem;height:4rem" aria-label="Start live session"><i class="bi bi-broadcast" aria-hidden="true"></i></button>
<div class="d-flex align-items-center gap-2">
<img src="../assets/img/avatar-2.svg" alt="Portrait of the host" class="rounded-circle fx-pulse-wave-effect" width="48" height="48">
<div>
<div class="fw-semibold small">
Ruby Rossi
</div>
<span class="badge text-bg-success">Live now</span>
</div>
</div>
</div>
/* CSS (already in assets/css/effects.css) */
.fx-pulse-wave-effect { animation: fx-pulse-wave-effect-pulse 2s ease-out infinite; }
@keyframes fx-pulse-wave-effect-pulse {
0% { box-shadow: 0 0 0 0 rgba(var(--wt-primary-rgb), .55), 0 0 0 0 rgba(var(--wt-primary-rgb), .35); }
70% { box-shadow: 0 0 0 16px rgba(var(--wt-primary-rgb), 0), 0 0 0 32px rgba(var(--wt-primary-rgb), 0); }
100% { box-shadow: 0 0 0 0 rgba(var(--wt-primary-rgb), 0), 0 0 0 0 rgba(var(--wt-primary-rgb), 0); }
}
@media (prefers-reduced-motion: reduce) {
.fx-pulse-wave-effect, .fx-pulse-wave-effect::before, .fx-pulse-wave-effect::after, .fx-pulse-wave-effect *, .fx-pulse-wave-effect *::before, .fx-pulse-wave-effect *::after { animation: none !important; transition: none !important; }
}2Glitch Text
Splits a heading into offset colour slices that jitter like a broken screen; repeat the text in data-text. Class: fx-glitch-text
SYSTEM ERROR
<div class="d-flex flex-wrap align-items-center justify-content-center gap-3 p-3 h-100" style="min-height:200px">
<div class="bg-dark rounded-4 px-4 py-5 text-center w-100" style="max-width:19rem">
<h2 class="display-6 fw-bold text-white mb-0 fx-glitch-text" data-text="SYSTEM ERROR">SYSTEM ERROR</h2>
</div>
</div>
/* CSS (already in assets/css/effects.css) */
.fx-glitch-text { position: relative; display: inline-block; }
.fx-glitch-text::before, .fx-glitch-text::after {
content: attr(data-text); position: absolute; inset: 0; pointer-events: none;
-webkit-clip-path: inset(50% 0 50% 0); clip-path: inset(50% 0 50% 0);
}
.fx-glitch-text::before { color: var(--wt-accent); left: 2px; text-shadow: -1px 0 var(--wt-primary); animation: fx-glitch-text-a 2.6s steps(1) infinite; }
.fx-glitch-text::after { color: var(--wt-secondary); left: -2px; text-shadow: 1px 0 var(--wt-accent); animation: fx-glitch-text-b 3.1s steps(1) infinite; }
@keyframes fx-glitch-text-a {
0% { -webkit-clip-path: inset(0 0 82% 0); clip-path: inset(0 0 82% 0); transform: translate(-3px, -1px); }
15% { -webkit-clip-path: inset(30% 0 52% 0); clip-path: inset(30% 0 52% 0); transform: translate(3px, 1px); }
30% { -webkit-clip-path: inset(62% 0 20% 0); clip-path: inset(62% 0 20% 0); transform: translate(-2px, 0); }
45% { -webkit-clip-path: inset(50% 0 50% 0); clip-path: inset(50% 0 50% 0); }
80% { -webkit-clip-path: inset(12% 0 70% 0); clip-path: inset(12% 0 70% 0); transform: translate(2px, -1px); }
90% { -webkit-clip-path: inset(50% 0 50% 0); clip-path: inset(50% 0 50% 0); }
}
@keyframes fx-glitch-text-b {
0% { -webkit-clip-path: inset(70% 0 8% 0); clip-path: inset(70% 0 8% 0); transform: translate(2px, 1px); }
20% { -webkit-clip-path: inset(8% 0 76% 0); clip-path: inset(8% 0 76% 0); transform: translate(-3px, 0); }
35% { -webkit-clip-path: inset(50% 0 50% 0); clip-path: inset(50% 0 50% 0); }
70% { -webkit-clip-path: inset(40% 0 38% 0); clip-path: inset(40% 0 38% 0); transform: translate(3px, -1px); }
85% { -webkit-clip-path: inset(50% 0 50% 0); clip-path: inset(50% 0 50% 0); }
}
@media (prefers-reduced-motion: reduce) {
.fx-glitch-text, .fx-glitch-text::before, .fx-glitch-text::after, .fx-glitch-text *, .fx-glitch-text *::before, .fx-glitch-text *::after { animation: none !important; transition: none !important; }
}