Modern CSS Animations
Keyframe animations for cards, icons, loaders and headings - drop a class on an element and it comes alive.
1Multi-Layer Spinner
A loading indicator made of three counter-rotating rings in the theme colours. Class: fx-multi-layer-spinner
Loading...
Preparing your community
<div class="d-flex flex-wrap align-items-center justify-content-center gap-3 p-3 h-100" style="min-height:200px">
<div class="text-center">
<div class="fx-multi-layer-spinner" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="small text-body-secondary mt-3 mb-0">Preparing your community</p>
</div>
</div>
/* CSS (already in assets/css/effects.css) */
.fx-multi-layer-spinner {
position: relative; display: inline-block; width: 4rem; height: 4rem; border-radius: 50%;
border: 4px solid transparent; border-top-color: var(--wt-primary);
animation: fx-multi-layer-spinner-spin 1.1s linear infinite;
}
.fx-multi-layer-spinner::before, .fx-multi-layer-spinner::after { content: ""; position: absolute; border-radius: 50%; border: 4px solid transparent; }
.fx-multi-layer-spinner::before { inset: 4px; border-top-color: var(--wt-secondary); animation: fx-multi-layer-spinner-spin 1.7s linear infinite reverse; }
.fx-multi-layer-spinner::after { inset: 14px; border-top-color: var(--wt-accent); animation: fx-multi-layer-spinner-spin .8s linear infinite; }
@keyframes fx-multi-layer-spinner-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
.fx-multi-layer-spinner, .fx-multi-layer-spinner::before, .fx-multi-layer-spinner::after, .fx-multi-layer-spinner *, .fx-multi-layer-spinner *::before, .fx-multi-layer-spinner *::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; }
}3Typewriter
Types a single line of monospace text letter by letter with a blinking caret; set --fx-ch to the character count. Class: fx-typewriter
terminal
Hello, world.<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 text-white rounded-3 p-3 w-100" style="max-width:18rem">
<div class="small text-white-50 mb-1">
terminal
</div>
<span class="fx-typewriter" style="--fx-ch:13">Hello, world.</span>
</div>
</div>
/* CSS (already in assets/css/effects.css) */
.fx-typewriter {
display: inline-block; max-width: 100%; overflow: hidden; white-space: nowrap; vertical-align: bottom;
font-family: var(--ui-font-monospace, monospace);
width: calc(var(--fx-ch, 20) * 1ch + .15em);
border-right: .12em solid var(--wt-primary);
animation: fx-typewriter-type 4.5s steps(var(--fx-ch, 20)) infinite, fx-typewriter-caret .8s step-end infinite;
}
@keyframes fx-typewriter-type { 0% { width: 0; } 65%, 100% { width: calc(var(--fx-ch, 20) * 1ch + .15em); } }
@keyframes fx-typewriter-caret { 50% { border-color: transparent; } }
@media (prefers-reduced-motion: reduce) {
.fx-typewriter, .fx-typewriter::before, .fx-typewriter::after, .fx-typewriter *, .fx-typewriter *::before, .fx-typewriter *::after { animation: none !important; transition: none !important; }
}