15 examples Responsive beginner

15 Pure CSS Loading Animations

Every animation is hand-coded from scratch using only CSS — no libraries, no JavaScript, no dependencies. Copy any snippet and drop it straight into your project.

15 unique loaders 0 dependencies 100% pure CSS customisable
01 / 15
House Unlock
Pure CSS
Unlocking your tour…
A closed-front-door loader for property sites. The window stays dark while the key turns; once "loaded" (the .ready class is added), the window glows warm gold and the door opens to reveal a softly-lit room. Ideal as a real-estate page-load cover.
/* ── Stage (drop-in cover for the page) ── */
.hu-stage {
  display: grid; place-items: center;
  padding: 24px;
  background:
    radial-gradient(60% 60% at 50% 100%, rgba(212,175,55,0.10) 0%, transparent 70%),
    linear-gradient(180deg, #14132b 0%, #0b0b1a 100%);
  border-radius: 16px;
  width: 220px; height: 220px;
}

/* ── House silhouette ── */
.hu-house {
  position: relative;
  width: 132px; height: 152px;
  margin: 0; padding: 0;
  font-family: system-ui, sans-serif;
}
.hu-roof {
  position: absolute; top: 0; left: 50%;
  width: 0; height: 0;
  border: 30px solid transparent;
  border-bottom-color: #d4af37;
  transform: translateX(-50%);
  filter: drop-shadow(0 4px 10px rgba(212,175,55,0.25));
}
.hu-body {
  position: absolute; top: 56px; left: 6px;
  width: 120px; height: 86px;
  background: linear-gradient(180deg, #f5f0e6 0%, #e8e0d0 100%);
  border-radius: 4px 4px 6px 6px;
  box-shadow: 0 12px 28px -12px rgba(0,0,0,0.55);
  overflow: hidden;
}
.hu-floor {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 6px;
  background: #2a1f1a;
}

/* ── Window: dark while loading, warm-gold once ready ── */
.hu-window {
  position: absolute; top: 14px; left: 14px;
  width: 36px; height: 28px;
  border: 2px solid #d4af37;
  border-radius: 3px;
  background: #0a0a14;
  overflow: hidden;
  transition: background 0.6s ease, box-shadow 0.6s ease;
}
.hu-window::before,
.hu-window::after {
  content: ""; position: absolute;
  background: #d4af37;
}
.hu-window::before { top: 0; bottom: 0; left: 50%; width: 1.5px; transform: translateX(-50%); }
.hu-window::after  { left: 0; right: 0; top: 50%; height: 1.5px; transform: translateY(-50%); }
.hu-window-glass {
  position: absolute; inset: 0;
  background: radial-gradient(circle at 50% 70%, #ffd479 0%, #d4af37 50%, transparent 75%);
  opacity: 0; transform: scale(0.7);
  transition: opacity 0.7s ease, transform 0.7s ease;
}

/* ── Door: closed and locked, then swings inward ── */
.hu-door {
  position: absolute; bottom: 6px; left: 50%;
  width: 38px; height: 60px;
  margin-left: -19px;
  background: linear-gradient(180deg, #6b3f2a 0%, #4a2d1f 100%);
  border-radius: 5px 5px 0 0;
  border: 1px solid rgba(0,0,0,0.4);
  transform-origin: left center;
  transform: rotateY(0deg);
  transition: transform 0.85s cubic-bezier(.5,0,.3,1.2);
  box-shadow: inset -2px 0 0 rgba(255,255,255,0.04);
}
.hu-door-handle {
  position: absolute; right: 5px; top: 50%;
  width: 5px; height: 5px;
  margin-top: -2.5px;
  background: #d4af37;
  border-radius: 50%;
  box-shadow: 0 0 6px rgba(212,175,55,0.55);
}

/* ── Key: floats in, turns, then disappears ── */
.hu-key {
  position: absolute; right: 10px; top: 50%;
  width: 22px; height: 8px;
  margin-top: -4px;
  transform-origin: 18px center;
  animation: huKeyTurn 2.4s cubic-bezier(.5,0,.3,1.2) infinite;
}
.hu-key-bow {
  position: absolute; left: 0; top: -3px;
  width: 14px; height: 14px;
  border: 2px solid #d4af37;
  border-radius: 50%;
  background: rgba(212,175,55,0.12);
  box-shadow: 0 0 8px rgba(212,175,55,0.35);
}
.hu-key-shaft {
  position: absolute; left: 12px; top: 50%;
  width: 9px; height: 2px;
  margin-top: -1px;
  background: #d4af37;
  border-radius: 1px;
}
.hu-key-teeth {
  position: absolute; right: 0; top: 50%;
  width: 4px; height: 4px;
  margin-top: -2px;
  background: #d4af37;
  border-radius: 0 1px 1px 0;
  box-shadow: -3px 2px 0 0 #d4af37;
}
@keyframes huKeyTurn {
  0%, 18%   { transform: rotate(0deg)   translateX(0)    scale(1);   opacity: 0; }
  10%       { opacity: 1; }
  35%, 60%  { transform: rotate(90deg)  translateX(0)    scale(1);   opacity: 1; }
  78%       { transform: rotate(90deg)  translateX(-6px) scale(0.6); opacity: 0; }
  100%      { transform: rotate(0deg)   translateX(0)    scale(1);   opacity: 0; }
}

/* ── Caption ── */
.hu-caption {
  position: absolute; left: 50%; bottom: -28px;
  transform: translateX(-50%);
  font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; font-weight: 600;
  color: #d4af37;
  white-space: nowrap;
  animation: huCaption 2.4s ease-in-out infinite;
}
@keyframes huCaption {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* ── Ready state (add .ready when the page is loaded) ── */
.hu-house.ready .hu-window {
  background: #ffd479;
  box-shadow:
    0 0 14px rgba(255,212,121,0.55),
    inset 0 0 12px rgba(255,212,121,0.4);
}
.hu-house.ready .hu-window-glass { opacity: 1; transform: scale(1); }
.hu-house.ready .hu-door         { transform: rotateY(-72deg); }
.hu-house.ready .hu-key,
.hu-house.ready .hu-caption      { display: none; }

/* ── Drop-in usage (cover the whole page until loaded) ── */
/*
.hu-cover {
  position: fixed; inset: 0; z-index: 9999;
  display: grid; place-items: center;
  background: #0b0b1a;
  transition: opacity .6s ease, visibility .6s;
}
body.loaded .hu-cover { opacity: 0; visibility: hidden; }
*/
<div class="hu-stage">
  <figure class="hu-house" aria-label="Loading your home tour">
    <span class="hu-roof"></span>
    <span class="hu-body">
      <span class="hu-window">
        <span class="hu-window-glass"></span>
      </span>
      <span class="hu-door">
        <span class="hu-door-handle"></span>
        <span class="hu-key">
          <span class="hu-key-bow"></span>
          <span class="hu-key-shaft"></span>
          <span class="hu-key-teeth"></span>
        </span>
      </span>
      <span class="hu-floor"></span>
    </span>
    <figcaption class="hu-caption">Unlocking your tour…</figcaption>
  </figure>
</div>
02 / 15
Listing Card Skeleton
Pure CSS
A premium listing-card skeleton that mirrors the real layout — photo, price, address, agent — then crossfades into the loaded card when `.ready` is added. Users see the page shape before content arrives, which is the modern, perceived-performance pattern used by Airbnb and Booking. Respects `prefers-reduced-motion`.
/* ── Skeleton card ── */
.lc-card {
  position: relative;
  display: grid; gap: 8px;
  width: 240px;
  padding: 10px;
  background: #15151d;
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 14px;
  font-family: system-ui, sans-serif;
}
.lc-img {
  display: block; position: relative;
  width: 100%; aspect-ratio: 5 / 3;
  border-radius: 10px;
  background: linear-gradient(135deg, #1f2433 0%, #2a3045 100%);
  overflow: hidden;
}
.lc-img-shimmer {
  position: absolute; inset: 0;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(255,255,255,0.08) 50%,
    transparent 100%);
  transform: translateX(-100%);
  animation: lcShimmer 1.6s ease-in-out infinite;
}
@keyframes lcShimmer {
  100% { transform: translateX(100%); }
}
.lc-bar {
  display: block; height: 10px;
  background: linear-gradient(90deg, #1f2433, #2a3045, #1f2433);
  background-size: 200% 100%;
  border-radius: 6px;
  animation: lcPulse 1.6s ease-in-out infinite;
}
@keyframes lcPulse {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.lc-row {
  display: flex; align-items: center; gap: 6px;
}
.lc-row-top { justify-content: space-between; }
.lc-bar-price  { width: 82px; height: 16px; }
.lc-bar-badge  { width: 56px; height: 16px; border-radius: 99px; }
.lc-bar-line   { height: 9px; }
.lc-bar-w-90   { width: 90%; }
.lc-bar-w-60   { width: 60%; }
.lc-row-meta { gap: 5px; }
.lc-bar-pill { width: 48px; height: 16px; border-radius: 99px; }
.lc-row-agent { gap: 8px; padding-top: 4px; border-top: 1px solid rgba(255,255,255,0.05); margin-top: 2px; }
.lc-avatar {
  display: block;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: linear-gradient(135deg, #1f2433, #2a3045);
  background-size: 200% 100%;
  animation: lcPulse 1.6s ease-in-out infinite;
}
.lc-bar-name { flex: 1; height: 10px; }

/* ── Loaded card (revealed when .ready) ── */
.lc-loaded {
  position: absolute; inset: 0;
  padding: 10px;
  background: #15151d;
  border-radius: 14px;
  display: grid; gap: 8px;
  align-content: start;
  font-family: system-ui, sans-serif;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s ease;
}
.lc-loaded-img {
  display: block;
  width: 100%; aspect-ratio: 5 / 3;
  border-radius: 10px;
  background:
    linear-gradient(180deg, rgba(15,15,19,0) 60%, rgba(15,15,19,0.45) 100%),
    linear-gradient(135deg, #5b8cb8 0%, #8aa6c0 35%, #d4b896 100%);
  position: relative;
}
.lc-loaded-img::before {
  content: ''; position: absolute;
  bottom: 8px; left: 8px;
  width: 22px; height: 22px;
  background: rgba(255,255,255,0.18);
  border: 1px solid rgba(255,255,255,0.4);
  border-radius: 50%;
  backdrop-filter: blur(6px);
}
.lc-loaded-img::after {
  content: '♡'; position: absolute;
  bottom: 11px; left: 14px;
  font-size: 11px; color: #fff;
  line-height: 1;
}
.lc-loaded-price {
  font-size: 16px; font-weight: 700; color: #f0eeff;
  letter-spacing: -0.01em;
  display: flex; justify-content: space-between; align-items: center;
}
.lc-loaded-price::after {
  content: 'For sale';
  font-size: 9px; font-weight: 600;
  background: rgba(46,184,138,0.18);
  color: #2eb88a;
  padding: 3px 8px;
  border-radius: 99px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.lc-loaded-badge { display: none; }
.lc-loaded-addr {
  font-size: 11px; color: #9d9bbf;
  line-height: 1.4;
}
.lc-loaded-meta {
  display: flex; gap: 5px; flex-wrap: wrap;
}
.lc-loaded-meta span {
  font-size: 9px; font-weight: 600;
  background: rgba(124,108,255,0.1);
  color: #a78bfa;
  padding: 3px 8px;
  border-radius: 99px;
  letter-spacing: 0.04em;
}
.lc-loaded-agent {
  display: flex; align-items: center; gap: 8px;
  padding-top: 4px;
  border-top: 1px solid rgba(255,255,255,0.05);
  margin-top: 2px;
}
.lc-loaded-avatar {
  display: grid; place-items: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: linear-gradient(135deg, #d4b896, #b89970);
  color: #2a1f1a;
  font-size: 9px; font-weight: 800;
  letter-spacing: 0.04em;
}
.lc-loaded-agent-name {
  font-size: 10px; color: #c8c0ff;
}

/* ── Reveal ── */
.lc-card.ready .lc-loaded { opacity: 1; pointer-events: auto; }
.lc-card.ready > :not(.lc-loaded) { opacity: 0; }
.lc-card > :not(.lc-loaded) { transition: opacity 0.4s ease; }

/* ── Respect motion preferences ── */
@media (prefers-reduced-motion: reduce) {
  .lc-img-shimmer,
  .lc-bar,
  .lc-avatar { animation: none; }
}

/* ── Drop-in usage ──
<article class="lc-card" aria-busy="true">
  ... skeleton markup ...
  <span class="lc-loaded" aria-hidden="true"> ... real content ... </span>
</article>

window.addEventListener('load', () => {
  document.querySelectorAll('.lc-card').forEach(c => {
    c.classList.add('ready');
    c.setAttribute('aria-busy', 'false');
  });
});
*/
<div class="lc-card" aria-busy="true" aria-live="polite">
  <span class="lc-img">
    <span class="lc-img-shimmer" aria-hidden="true"></span>
  </span>
  <span class="lc-row lc-row-top">
    <span class="lc-bar lc-bar-price"></span>
    <span class="lc-bar lc-bar-badge"></span>
  </span>
  <span class="lc-bar lc-bar-line lc-bar-w-90"></span>
  <span class="lc-bar lc-bar-line lc-bar-w-60"></span>
  <span class="lc-row lc-row-meta">
    <span class="lc-bar lc-bar-pill"></span>
    <span class="lc-bar lc-bar-pill"></span>
    <span class="lc-bar lc-bar-pill"></span>
  </span>
  <span class="lc-row lc-row-agent">
    <span class="lc-avatar"></span>
    <span class="lc-bar lc-bar-name"></span>
  </span>
  <span class="lc-loaded" aria-hidden="true">
    <span class="lc-loaded-img"></span>
    <span class="lc-loaded-price">£1,250,000</span>
    <span class="lc-loaded-badge">For sale</span>
    <span class="lc-loaded-addr">42 Oakwood Lane, Notting Hill</span>
    <span class="lc-loaded-meta"><span>4 bed</span><span>3 bath</span><span>2,140 ft²</span></span>
    <span class="lc-loaded-agent">
      <span class="lc-loaded-avatar">SR</span>
      <span class="lc-loaded-agent-name">Sarah Rowan · Boutique Estates</span>
    </span>
  </span>
</div>
03 / 15
Progress Path
Light JS
0%
Preparing your tour 0%
An honest progress bar built on the native `<progress>` element — semantic, screen-reader-announced, and bindable to real load progress (image preloads, fetch chunks, route transitions). A moving plane glides along the path, with the percentage announced via `aria-valuenow`. Degrades gracefully: a static bar appears if JS is disabled.
/* ── Progress Path ── */
.pp-loader {
  width: 240px;
  display: grid; gap: 10px;
  font-family: system-ui, sans-serif;
}
.pp-track {
  position: relative;
  height: 28px;
  display: grid; align-items: center;
  padding: 0 10px;
}
/* Native progress styled as an inset rail */
.pp-progress {
  appearance: none; -webkit-appearance: none;
  width: 100%; height: 4px;
  border: 0; border-radius: 99px;
  background: rgba(255,255,255,0.06);
  overflow: hidden;
  color: #d4af37; /* IE/Edge legacy */
}
.pp-progress::-webkit-progress-bar { background: rgba(255,255,255,0.06); border-radius: 99px; }
.pp-progress::-webkit-progress-value {
  background: linear-gradient(90deg, #5b8cb8, #d4af37);
  border-radius: 99px;
  transition: width 0.2s ease;
}
.pp-progress::-moz-progress-bar {
  background: linear-gradient(90deg, #5b8cb8, #d4af37);
  border-radius: 99px;
}

/* Pins at start and end */
.pp-pin {
  position: absolute; top: 50%;
  width: 6px; height: 6px; margin-top: -3px;
  border-radius: 50%;
  background: rgba(255,255,255,0.2);
}
.pp-pin-start { left: 7px;  background: #5b8cb8; box-shadow: 0 0 8px rgba(91,140,184,0.55); }
.pp-pin-end   { right: 7px; background: rgba(212,175,55,0.45); }

/* Glider — the moving icon along the bar */
.pp-glider {
  position: absolute; top: 50%;
  left: 10px;
  width: 22px; height: 22px;
  margin-top: -11px;
  display: grid; place-items: center;
  background: linear-gradient(135deg, #ffd479, #d4af37);
  color: #1a1a2e;
  border-radius: 50%;
  box-shadow:
    0 0 0 3px rgba(212,175,55,0.18),
    0 4px 14px -4px rgba(212,175,55,0.7);
  transform: translateX(0);
  transition: transform 0.2s ease;
}

.pp-meta {
  display: flex; justify-content: space-between; align-items: center;
}
.pp-label {
  font-size: 11px; letter-spacing: 0.12em;
  text-transform: uppercase; font-weight: 600;
  color: #9d9bbf;
}
.pp-percent {
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px; font-weight: 700;
  color: #d4af37;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .pp-glider { transition: none; }
  .pp-progress::-webkit-progress-value { transition: none; }
}

/* ── Drop-in usage (real progress) ──
<div class="pp-loader" role="status">
  <div class="pp-track">
    <progress class="pp-progress" value="0" max="100" aria-label="Page loading"></progress>
    <span class="pp-glider"><!-- icon --></span>
    <span class="pp-pin pp-pin-start"></span>
    <span class="pp-pin pp-pin-end"></span>
  </div>
  <div class="pp-meta">
    <span class="pp-label">Preparing your tour</span>
    <span class="pp-percent" aria-live="polite">0%</span>
  </div>
</div>

// Map to real loading work — image preloads, fetch chunks, etc.
const loader   = document.querySelector('.pp-loader');
const progress = loader.querySelector('.pp-progress');
const glider   = loader.querySelector('.pp-glider');
const percent  = loader.querySelector('.pp-percent');

function setProgress(value) {
  const v = Math.max(0, Math.min(100, value));
  progress.value = v;
  percent.textContent = Math.round(v) + '%';

  // Move glider along the rail (track is padded 10px on each side)
  const trackWidth = loader.querySelector('.pp-track').offsetWidth - 20 - 22;
  glider.style.transform = 'translateX(' + (trackWidth * v / 100) + 'px)';
}

// Example: image preload progress
const images = [...]; // your URLs
let loaded = 0;
images.forEach(src => {
  const img = new Image();
  img.onload = img.onerror = () => {
    loaded++;
    setProgress((loaded / images.length) * 100);
  };
  img.src = src;
});
*/
<div class="pp-loader" role="status">
  <div class="pp-track">
    <progress class="pp-progress" value="0" max="100" aria-label="Page loading progress">0%</progress>
    <span class="pp-glider" aria-hidden="true">
      <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
        <path d="M2 12l20-9-9 20-2-9-9-2z"/>
      </svg>
    </span>
    <span class="pp-pin pp-pin-start" aria-hidden="true"></span>
    <span class="pp-pin pp-pin-end" aria-hidden="true"></span>
  </div>
  <div class="pp-meta">
    <span class="pp-label">Preparing your tour</span>
    <span class="pp-percent" aria-live="polite">0%</span>
  </div>
</div>
04 / 15
Heat-Map Compass
Pure CSS
51.5074°N · 0.1278°W
A premium navigation loader for property and travel platforms — a brass compass dial with cardinal points lighting one at a time as warm "heat" rings pulse outward. A monospace coordinate readout drifts below, suggesting the system is "scanning your neighbourhood". Honest narrative for indeterminate location-aware loads.
.hc-loader {
  position: relative;
  width: 180px; height: 180px;
  margin: 0; padding: 0;
  display: grid; place-items: center;
  font-family: system-ui, sans-serif;
}

/* Pulsing heat rings — blue (cold) → gold (hot) */
.hc-rings {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  pointer-events: none;
}
.hc-ring {
  position: absolute;
  width: 84px; height: 84px;
  border-radius: 50%;
  border: 1.5px solid #5b8cb8;
  opacity: 0;
  animation: hcRing 2.6s ease-out infinite;
}
.hc-ring:nth-child(1) { animation-delay: 0s;    }
.hc-ring:nth-child(2) { animation-delay: 0.85s; }
.hc-ring:nth-child(3) { animation-delay: 1.7s;  }
@keyframes hcRing {
  0%   { transform: scale(0.6); border-color: #5b8cb8; opacity: 0; }
  15%  { opacity: 0.9; }
  60%  { border-color: #d4af37; }
  100% { transform: scale(2.05); border-color: #d4af37; opacity: 0; }
}

/* Compass dial — brass ring with cardinal marks */
.hc-dial {
  position: relative;
  width: 96px; height: 96px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 30% 25%, rgba(255,212,121,0.12) 0%, transparent 55%),
    radial-gradient(circle at 50% 50%, #1a1d2e 0%, #0f1220 70%);
  border: 2px solid #d4af37;
  box-shadow:
    inset 0 0 18px rgba(212,175,55,0.18),
    0 4px 18px -4px rgba(212,175,55,0.4),
    0 0 0 1px rgba(212,175,55,0.25);
  animation: hcDialSpin 18s linear infinite;
}
@keyframes hcDialSpin { to { transform: rotate(360deg); } }

/* Cardinal marks — staggered glow */
.hc-mark {
  position: absolute;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px; font-weight: 700;
  color: rgba(212,175,55,0.4);
  letter-spacing: 0.06em;
  transition: color 0.3s ease;
  animation: hcMark 2.6s ease-in-out infinite;
}
.hc-mark-n { top: 4px;    left: 50%; transform: translateX(-50%); animation-delay: 0s;    }
.hc-mark-e { right: 6px;  top: 50%;  transform: translateY(-50%); animation-delay: 0.65s; }
.hc-mark-s { bottom: 4px; left: 50%; transform: translateX(-50%); animation-delay: 1.3s;  }
.hc-mark-w { left: 6px;   top: 50%;  transform: translateY(-50%); animation-delay: 1.95s; }
@keyframes hcMark {
  0%, 80%, 100% { color: rgba(212,175,55,0.4); text-shadow: none; }
  10%, 30%      { color: #ffd479; text-shadow: 0 0 8px rgba(255,212,121,0.7); }
}

/* Needle — long gold half + short slate half */
.hc-needle {
  position: absolute; top: 50%; left: 50%;
  width: 2px; height: 70px;
  margin: -35px 0 0 -1px;
  background: linear-gradient(180deg,
    #ffd479 0%,
    #d4af37 48%,
    #2a3045 52%,
    #1a1d2e 100%);
  border-radius: 2px;
  transform-origin: center center;
  animation: hcNeedle 5s cubic-bezier(.5,.05,.3,.95) infinite;
  box-shadow: 0 0 8px rgba(212,175,55,0.4);
}
@keyframes hcNeedle {
  0%   { transform: rotate(-22deg); }
  25%  { transform: rotate(80deg);  }
  50%  { transform: rotate(-40deg); }
  75%  { transform: rotate(140deg); }
  100% { transform: rotate(-22deg); }
}
.hc-pivot {
  position: absolute; top: 50%; left: 50%;
  width: 6px; height: 6px;
  margin: -3px 0 0 -3px;
  background: #ffd479;
  border-radius: 50%;
  box-shadow:
    0 0 0 2px #1a1d2e,
    0 0 10px rgba(255,212,121,0.6);
}

/* Coordinate readout */
.hc-coord {
  position: absolute; bottom: -22px; left: 50%;
  transform: translateX(-50%);
  font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px; font-weight: 600;
  color: #5eead4;
  letter-spacing: 0.04em;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .hc-ring, .hc-dial, .hc-mark, .hc-needle { animation: none; }
  .hc-mark { color: #ffd479; }
}

/* ── Drop-in usage ──
<figure class="hc-loader" role="status" aria-label="Searching nearby properties">
  ... markup ...
</figure>

// Optional: rotate the coord readout to suggest live scanning
const coords = ['51.5074°N · 0.1278°W', '40.7128°N · 74.0060°W', '34.0522°N · 118.2437°W'];
let i = 0;
setInterval(() => {
  document.querySelector('[data-hc-coord]').textContent = coords[++i % coords.length];
}, 1800);
*/
<figure class="hc-loader" role="status" aria-label="Searching nearby properties">
  <span class="hc-rings" aria-hidden="true">
    <span class="hc-ring"></span>
    <span class="hc-ring"></span>
    <span class="hc-ring"></span>
  </span>
  <span class="hc-dial" aria-hidden="true">
    <span class="hc-mark hc-mark-n">N</span>
    <span class="hc-mark hc-mark-e">E</span>
    <span class="hc-mark hc-mark-s">S</span>
    <span class="hc-mark hc-mark-w">W</span>
    <span class="hc-needle"></span>
    <span class="hc-pivot"></span>
  </span>
  <figcaption class="hc-coord" aria-live="polite" data-hc-coord>51.5074°N · 0.1278°W</figcaption>
</figure>
05 / 15
Floor-by-Floor
Pure CSS
Touring residences…
A vertical luxury-building loader. Floors light up bottom-to-top like an elevator passing each level, room silhouettes flicker on inside, and a penthouse glow crowns the building when `.ready` is added. Turns waiting time into an architectural narrative — perfect for residential developments and high-end real estate.
.fb-loader {
  position: relative;
  width: 110px; height: 200px;
  margin: 0; padding: 0;
  display: grid; place-items: end center;
  font-family: system-ui, sans-serif;
}

/* Building silhouette */
.fb-building {
  position: relative;
  width: 84px;
  display: flex; flex-direction: column;
  border-radius: 4px 4px 0 0;
  filter: drop-shadow(0 12px 24px rgba(0,0,0,0.55));
}

/* Crowning roof line */
.fb-roof {
  height: 4px;
  background: linear-gradient(90deg, transparent 8%, #d4af37 8%, #d4af37 92%, transparent 92%);
  border-radius: 2px 2px 0 0;
  margin-bottom: 1px;
}

/* Penthouse — special top floor */
.fb-penthouse {
  position: relative;
  height: 22px;
  background: linear-gradient(180deg, #1f2433 0%, #161b2a 100%);
  border: 1px solid rgba(212,175,55,0.25);
  border-bottom: 0;
  display: grid; place-items: center;
}
.fb-pent-light {
  width: 60%; height: 8px;
  border-radius: 2px;
  background: rgba(255,212,121,0.08);
  transition: background 0.6s ease, box-shadow 0.6s ease;
}

/* Standard floors — six identical bands */
.fb-floor {
  position: relative;
  height: 22px;
  background: linear-gradient(180deg, #1a1f2e 0%, #14182a 100%);
  border: 1px solid rgba(255,255,255,0.04);
  border-bottom: 0;
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 4px; padding: 4px 6px;
  box-sizing: border-box;
}
/* Each "room" — a tiny window */
.fb-room {
  background: rgba(255,255,255,0.03);
  border-radius: 1.5px;
  transition: background 0.4s ease, box-shadow 0.4s ease;
}
/* Foundation */
.fb-base {
  height: 6px;
  background: #2a1f1a;
  border-radius: 0 0 4px 4px;
}

/* ── Lighting cycle: floors illuminate bottom-to-top ── */
.fb-floor[data-floor="1"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 0s; }
.fb-floor[data-floor="2"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 0.7s; }
.fb-floor[data-floor="3"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 1.4s; }
.fb-floor[data-floor="4"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 2.1s; }
.fb-floor[data-floor="5"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 2.8s; }
.fb-floor[data-floor="6"] .fb-room { animation: fbWindow 7s ease-in-out infinite; animation-delay: 3.5s; }

/* Stagger rooms within a floor for life */
.fb-floor .fb-room:nth-child(2) { animation-delay: calc(var(--d, 0s) + 0.15s); }
.fb-floor .fb-room:nth-child(3) { animation-delay: calc(var(--d, 0s) + 0.3s);  }

@keyframes fbWindow {
  0%, 5%   { background: rgba(255,255,255,0.03); box-shadow: none; }
  15%, 60% {
    background: #ffd479;
    box-shadow:
      0 0 4px rgba(255,212,121,0.55),
      inset 0 0 3px rgba(255,212,121,0.45);
  }
  80%, 100% { background: rgba(255,255,255,0.03); box-shadow: none; }
}

/* ── Caption ── */
.fb-caption {
  position: absolute; bottom: -22px; left: 50%;
  transform: translateX(-50%);
  font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; font-weight: 600;
  color: #d4af37;
  white-space: nowrap;
  animation: fbCap 2.6s ease-in-out infinite;
}
@keyframes fbCap {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 1; }
}

/* ── Ready state — every floor lit, penthouse glowing, caption gone ── */
.fb-loader.ready .fb-room {
  animation: none;
  background: #ffd479;
  box-shadow:
    0 0 4px rgba(255,212,121,0.55),
    inset 0 0 3px rgba(255,212,121,0.45);
}
.fb-loader.ready .fb-pent-light {
  background: #ffd479;
  box-shadow:
    0 0 14px rgba(255,212,121,0.7),
    inset 0 0 8px rgba(255,212,121,0.5);
}
.fb-loader.ready .fb-caption { display: none; }

@media (prefers-reduced-motion: reduce) {
  .fb-room, .fb-caption { animation: none; }
  .fb-room { background: rgba(255,212,121,0.45); }
}

/* ── Drop-in usage ──
<figure class="fb-loader" role="status" aria-label="Loading">
  ... markup ...
</figure>

window.addEventListener('load', () => {
  document.querySelector('.fb-loader').classList.add('ready');
});
*/
<figure class="fb-loader" role="status" aria-label="Loading building tour">
  <span class="fb-building" aria-hidden="true">
    <span class="fb-roof"></span>
    <span class="fb-penthouse">
      <span class="fb-pent-light"></span>
    </span>
    <span class="fb-floor" data-floor="6"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
    <span class="fb-floor" data-floor="5"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
    <span class="fb-floor" data-floor="4"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
    <span class="fb-floor" data-floor="3"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
    <span class="fb-floor" data-floor="2"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
    <span class="fb-floor" data-floor="1"><span class="fb-room"></span><span class="fb-room"></span><span class="fb-room"></span></span>
    <span class="fb-base"></span>
  </span>
  <figcaption class="fb-caption">Touring residences…</figcaption>
</figure>
06 / 15
DNA Helix
Pure CSS
Alternating dots oscillate up and down in a staggered wave, mimicking the visual rhythm of a DNA double helix.
.dna { display: flex; gap: 6px; align-items: center; }
.dna-dot {
  width: 12px; height: 12px; border-radius: 50%;
  animation: dna 1.2s ease-in-out infinite;
}
.dna-dot:nth-child(odd)  { background: #7c6cff; }
.dna-dot:nth-child(even) { background: #ff6c8a; }
.dna-dot:nth-child(1) { animation-delay: 0s; }
.dna-dot:nth-child(2) { animation-delay: 0.1s; }
.dna-dot:nth-child(3) { animation-delay: 0.2s; }
.dna-dot:nth-child(4) { animation-delay: 0.3s; }
.dna-dot:nth-child(5) { animation-delay: 0.4s; }
.dna-dot:nth-child(6) { animation-delay: 0.5s; }
@keyframes dna {
  0%, 100% { transform: translateY(0) scale(1); }
  50%       { transform: translateY(-18px) scale(0.7); }
}
<div class="dna">
  <div class="dna-dot"></div>
  <div class="dna-dot"></div>
  <div class="dna-dot"></div>
  <div class="dna-dot"></div>
  <div class="dna-dot"></div>
  <div class="dna-dot"></div>
</div>
07 / 15
Liquid Blob
Pure CSS
A gradient circle morphs its border-radius through organic blob shapes, creating a fluid, living feeling.
.blob {
  width: 60px; height: 60px;
  background: linear-gradient(135deg, #7c6cff, #ff6c8a);
  border-radius: 50%;
  animation: blob 2.5s ease-in-out infinite;
}
@keyframes blob {
  0%,100% { border-radius: 50% 50% 50% 50% / 50% 50% 50% 50%; }
  25%      { border-radius: 60% 40% 70% 30% / 40% 60% 40% 60%; }
  50%      { border-radius: 30% 70% 40% 60% / 60% 30% 70% 40%; }
  75%      { border-radius: 50% 50% 30% 70% / 30% 70% 50% 50%; }
}
<div class="blob"></div>
08 / 15
Orbit System
Pure CSS
Two rings rotate at different speeds and directions around a central core, like a miniature solar system.
.orbit-wrap {
  position: relative; width: 60px; height: 60px;
}
.orbit-core {
  position: absolute; inset: 0; margin: auto;
  width: 14px; height: 14px; border-radius: 50%;
  background: #7c6cff;
}
.orbit-ring {
  position: absolute; inset: 0;
  border: 2px solid transparent;
  border-top-color: #ff6c8a;
  border-radius: 50%;
  animation: orbit 1s linear infinite;
}
.orbit-ring:nth-child(3) {
  inset: 8px;
  border-top-color: #2ecc8a;
  animation-duration: 0.7s;
  animation-direction: reverse;
}
@keyframes orbit { to { transform: rotate(360deg); } }
<div class="orbit-wrap">
  <div class="orbit-core"></div>
  <div class="orbit-ring"></div>
  <div class="orbit-ring"></div>
</div>
09 / 15
Signal Bars
Pure CSS
Five bars of varying heights pulse in and out of opacity with a staggered delay, like an equaliser or signal indicator.
.bars { display: flex; align-items: flex-end; gap: 5px; height: 40px; }
.bar {
  width: 10px; border-radius: 3px 3px 0 0;
  background: #7c6cff;
  animation: bars 1s ease-in-out infinite;
}
.bar:nth-child(1) { height: 20%; animation-delay: 0s; }
.bar:nth-child(2) { height: 50%; animation-delay: 0.15s; }
.bar:nth-child(3) { height: 80%; animation-delay: 0.3s; }
.bar:nth-child(4) { height: 60%; animation-delay: 0.45s; }
.bar:nth-child(5) { height: 35%; animation-delay: 0.6s; }
@keyframes bars {
  0%, 100% { opacity: 0.25; }
  50%       { opacity: 1; }
}
<div class="bars">
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
</div>
10 / 15
Clock Sweep
Pure CSS
Two pseudo-element hands rotate at different speeds inside a circular face — a literal animated clock using only CSS.
.clock {
  width: 52px; height: 52px; border-radius: 50%;
  border: 3px solid rgba(124,108,255,0.25);
  position: relative;
}
.clock::before, .clock::after {
  content: ''; position: absolute; border-radius: 2px;
}
.clock::before {
  width: 2px; height: 22px; background: #7c6cff;
  top: 4px; left: 50%; margin-left: -1px;
  transform-origin: bottom center;
  animation: chour 6s linear infinite;
}
.clock::after {
  width: 2px; height: 16px; background: #ff6c8a;
  top: 10px; left: 50%; margin-left: -1px;
  transform-origin: bottom center;
  animation: cmin 1s linear infinite;
}
@keyframes chour { to { transform: rotate(360deg); } }
@keyframes cmin  { to { transform: rotate(360deg); } }
<div class="clock"></div>
11 / 15
Bouncing Chain
Pure CSS
Four coloured dots bounce independently with staggered delays, creating a chain-like wave of movement.
.chain { display: flex; gap: 8px; align-items: center; }
.chain-dot {
  width: 14px; height: 14px; border-radius: 50%;
  animation: chain 0.9s cubic-bezier(0.36,0.07,0.19,0.97) infinite;
}
.chain-dot:nth-child(1) { background: #7c6cff; animation-delay: 0s; }
.chain-dot:nth-child(2) { background: #ff6c8a; animation-delay: 0.18s; }
.chain-dot:nth-child(3) { background: #2ecc8a; animation-delay: 0.36s; }
.chain-dot:nth-child(4) { background: #f5a623; animation-delay: 0.54s; }
@keyframes chain {
  0%, 100% { transform: translateY(0); }
  40%       { transform: translateY(-20px); }
  60%       { transform: translateY(-8px); }
}
<div class="chain">
  <div class="chain-dot"></div>
  <div class="chain-dot"></div>
  <div class="chain-dot"></div>
  <div class="chain-dot"></div>
</div>
12 / 15
Neon Ring Draw
Pure CSS
A glowing ring spinner with a layered neon box-shadow creates a vivid electric glow as it spins.
.neon-ring {
  width: 56px; height: 56px; border-radius: 50%;
  border: 3px solid transparent;
  border-top-color: #2ecc8a;
  box-shadow: 0 0 14px #2ecc8a, 0 0 28px rgba(46,204,138,0.35);
  animation: nring 0.9s linear infinite;
}
.neon-ring::before {
  content: ''; display: block;
  width: 100%; height: 100%; border-radius: 50%;
  border: 3px solid transparent;
  border-bottom-color: rgba(46,204,138,0.3);
  box-sizing: border-box;
}
@keyframes nring { to { transform: rotate(360deg); } }
<div class="neon-ring"></div>
13 / 15
Pixel Dissolve
Pure CSS
A 4×4 grid of squares fade and shrink in a rolling wave, like pixels dissolving from a screen.
.pixels { display: grid; grid-template-columns: repeat(4,12px); gap: 4px; }
.px {
  width: 12px; height: 12px; border-radius: 2px;
  background: #7c6cff;
  animation: px 1.4s ease-in-out infinite;
}
.px:nth-child(1)  { animation-delay: 0s;    }
.px:nth-child(2)  { animation-delay: 0.1s;  }
.px:nth-child(3)  { animation-delay: 0.2s;  }
.px:nth-child(4)  { animation-delay: 0.3s;  }
.px:nth-child(5)  { animation-delay: 0.4s;  }
.px:nth-child(6)  { animation-delay: 0.5s;  }
.px:nth-child(7)  { animation-delay: 0.6s;  }
.px:nth-child(8)  { animation-delay: 0.7s;  }
.px:nth-child(9)  { animation-delay: 0.8s;  }
.px:nth-child(10) { animation-delay: 0.9s;  }
.px:nth-child(11) { animation-delay: 1.0s;  }
.px:nth-child(12) { animation-delay: 1.1s;  }
.px:nth-child(13) { animation-delay: 1.2s;  }
.px:nth-child(14) { animation-delay: 1.3s;  }
.px:nth-child(15) { animation-delay: 0.65s; }
.px:nth-child(16) { animation-delay: 0.85s; }
@keyframes px {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%       { opacity: 0; transform: scale(0.3); }
}
<div class="pixels">
  <div class="px"></div><div class="px"></div>
  <div class="px"></div><div class="px"></div>
  <div class="px"></div><div class="px"></div>
  <div class="px"></div><div class="px"></div>
  <div class="px"></div><div class="px"></div>
  <div class="px"></div><div class="px"></div>
  <div class="px"></div><div class="px"></div>
  <div class="px"></div><div class="px"></div>
</div>
14 / 15
Hourglass Flip
Pure CSS
A pure CSS triangle-based hourglass that flips 180° and pauses, then repeats — built from two border triangles.
.hourglass {
  width: 40px; height: 40px;
  border-left: 20px solid transparent;
  border-right: 20px solid transparent;
  border-top: 20px solid #7c6cff;
  border-bottom: 20px solid #ff6c8a;
  border-radius: 4px;
  animation: hflip 1.8s ease-in-out infinite;
}
@keyframes hflip {
  0%,45%   { transform: rotate(0deg); }
  55%,100% { transform: rotate(180deg); }
}
<div class="hourglass"></div>
15 / 15
Heartbeat Line
Pure CSS
An SVG heartbeat waveform scrolls horizontally in a continuous loop — an inline data URI with a CSS translateX animation.
.hb-wrap { width: 120px; height: 40px; overflow: hidden; }
.hb-line {
  width: 200%; height: 100%;
  background: url("data:image/svg+xml,...") repeat-x center/50% 100%;
  animation: hbeat 1s linear infinite;
}
@keyframes hbeat { to { transform: translateX(-50%); } }
<div class="hb-wrap">
  <div class="hb-line"></div>
</div>

Related collections