Skip to content

Cinematic Effects

When to Use

Use cinematic effects on hero sections, primary CTAs, and feature highlights — one or two per page maximum. Do not use on navigation, body copy, form fields, dashboards, or secondary CTAs.

Decision

When to Apply

Context Use? Why
Hero section headline or CTA Yes The page's single focal moment
Primary action button Yes, one effect only Draws the eye to the conversion point
Feature highlight card Yes, subtle only Differentiates without distracting
Navigation, headers No Users need nav to be calm and scannable
Body copy, long-form content No Animated backgrounds ruin readability
Form fields and inputs No Focus states need clarity, not drama
Secondary or tertiary CTAs No Reserve for the single primary action
Data tables, dashboards No Analytical contexts need calm interfaces

Which Effect for Which Design Style

Effect Kinematic / Dark UI Retro / Y2K Memphis Minimal / Swiss Wabi-Sabi
Border Beam Excellent fit Good fit Poor fit Never Never
Glow (box/text) Excellent fit Good fit Poor fit Never Never
Aurora background Excellent fit Poor fit Poor fit Never Never
Neon text/borders Poor fit Excellent fit Good fit Never Never
Shimmer sweep Excellent fit Good fit Poor fit Sparing Never
Particles / meteors Good fit Good fit Poor fit Never Never
Perspective grid Poor fit Excellent fit Poor fit Never Never

Pattern

@property --beam-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.border-beam {
  background:
    hsl(220 15% 10%) padding-box,
    conic-gradient(from var(--beam-angle), transparent 80%, hsl(250 80% 70%) 90%, transparent 100%) border-box;
  border: 2px solid transparent;
  border-radius: 12px;

  @media (prefers-reduced-motion: no-preference) {
    animation: beam-rotate 3s linear infinite;
  }
}

@keyframes beam-rotate {
  to { --beam-angle: 360deg; }
}

Border Beam (pseudo-element fallback — no @property)

.border-beam-fallback {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
}

.border-beam-fallback::before {
  content: '';
  position: absolute;
  inset: -2px;
  background: conic-gradient(from 0deg, transparent 80%, hsl(250 80% 70%) 90%, transparent);
  border-radius: inherit;
  z-index: -1;

  @media (prefers-reduced-motion: no-preference) {
    animation: beam-spin 3s linear infinite;
  }
}

.border-beam-fallback::after {
  content: '';
  position: absolute;
  inset: 2px;
  background: hsl(220 15% 10%);
  border-radius: calc(12px - 2px);
  z-index: -1;
}

@keyframes beam-spin {
  to { transform: rotate(1turn); }
}

Glow

.glow-card {
  box-shadow:
    0 0 20px hsl(250 80% 65% / 0.3),
    0 0 40px hsl(250 80% 65% / 0.2),
    0 0 80px hsl(250 80% 65% / 0.1);
}

/* Pulsing glow — animate opacity, NOT shadow values */
.glow-pulse {
  @media (prefers-reduced-motion: no-preference) {
    animation: glow-breathe 3s ease-in-out infinite;
  }
}

@keyframes glow-breathe {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.6; }
}

Glow on hover (performance-safe: pseudo-element opacity)

.glow-hover {
  position: relative;
}

.glow-hover::after {
  content: '';
  position: absolute;
  inset: -8px;
  background: radial-gradient(ellipse, hsl(250 80% 65% / 0.4), transparent 70%);
  border-radius: inherit;
  opacity: 0;
  transition: opacity var(--duration-normal) var(--ease-standard);
  pointer-events: none;
  z-index: -1;
}

.glow-hover:hover::after { opacity: 1; }

Text glow

.text-glow {
  --glow: hsl(180 80% 60%);
  color: hsl(180 80% 90%);
  text-shadow:
    0 0 4px  var(--glow),
    0 0 10px var(--glow),
    0 0 20px var(--glow),
    0 0 40px hsl(180 80% 60% / 0.5);
}

Aurora background (filter blur blobs)

.aurora-wrapper {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: hsl(230 30% 8%);
}

.aurora-blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  opacity: 0.7;
  will-change: transform;
}

.aurora-blob--1 {
  width: 600px; height: 600px;
  background: hsl(270 70% 50%);
  top: -20%; left: -10%;

  @media (prefers-reduced-motion: no-preference) {
    animation: aurora-drift-1 18s ease-in-out infinite alternate;
  }
}

@keyframes aurora-drift-1 {
  from { transform: translate(0, 0) rotate(0deg); }
  to   { transform: translate(80px, 40px) rotate(30deg); }
}

Aurora (@property hue cycle — lighter-weight alternative)

@property --aurora-hue {
  syntax: "<number>";
  initial-value: 250;
  inherits: false;
}

.aurora-gradient {
  background:
    radial-gradient(ellipse at 20% 30%, hsl(var(--aurora-hue) 60% 50% / 0.4), transparent 60%),
    radial-gradient(ellipse at 80% 60%, hsl(calc(var(--aurora-hue) + 60) 70% 45% / 0.3), transparent 55%),
    hsl(230 30% 8%);

  @media (prefers-reduced-motion: no-preference) {
    animation: aurora-hue-cycle 12s linear infinite;
  }
}

@keyframes aurora-hue-cycle {
  to { --aurora-hue: 610; }
}

Neon text

.neon-text {
  --neon-color: hsl(150 90% 55%);

  color: hsl(0 0% 100%);
  text-shadow:
    0 0  4px #fff,
    0 0 10px #fff,
    0 0 21px var(--neon-color),
    0 0 42px var(--neon-color),
    0 0 82px hsl(150 90% 55% / 0.7),
    0 0 92px hsl(150 90% 55% / 0.5);
}

Neon border

.neon-border {
  --neon-color: hsl(300 90% 60%);
  border: 2px solid var(--neon-color);
  border-radius: 8px;
  box-shadow:
    0 0  5px var(--neon-color),
    0 0 10px var(--neon-color),
    0 0 20px hsl(300 90% 60% / 0.6),
    inset 0 0 5px  hsl(300 90% 60% / 0.3),
    inset 0 0 20px hsl(300 90% 60% / 0.1);
}

Neon flicker animation

.neon-flicker {
  @media (prefers-reduced-motion: no-preference) {
    animation: neon-flicker 2.5s linear infinite;
  }
}

/* Snaps on/off — no interpolation, that's intentional */
@keyframes neon-flicker {
  0%, 18%, 22%, 25%, 53%, 57%, 100% {
    text-shadow: 0 0 4px #fff, 0 0 10px #fff, 0 0 21px hsl(150 90% 55%), 0 0 42px hsl(150 90% 55%);
    opacity: 1;
  }
  20%, 24%, 55% { text-shadow: none; opacity: 0.8; }
}
.shimmer-btn {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.shimmer-btn::after {
  content: '';
  position: absolute;
  top: 0; left: -100%;
  width: 60%; height: 100%;
  background: linear-gradient(105deg, transparent 20%, hsl(0 0% 100% / 0.25) 50%, transparent 80%);
  pointer-events: none;

  @media (prefers-reduced-motion: no-preference) {
    animation: shimmer-sweep 2.5s ease-in-out infinite;
    animation-delay: 1s;
  }
}

@keyframes shimmer-sweep {
  0%   { transform: translateX(0%); }
  100% { transform: translateX(350%); }
}

Particles / meteor trail

.meteor-container {
  position: absolute; inset: 0;
  overflow: hidden; pointer-events: none;
}

.meteor {
  position: absolute;
  width: 2px; height: 2px;
  background: white; border-radius: 50%;
  box-shadow: 0 0 0 1px hsl(0 0% 100% / 0.1), -80px 0 8px 2px hsl(0 0% 100% / 0.4);
  opacity: 0;

  @media (prefers-reduced-motion: no-preference) {
    animation: meteor-fall var(--duration, 3s) var(--delay, 0s) linear infinite;
  }
}

.meteor:nth-child(1) { --delay: 0s;   --duration: 3s;   top: 10%; right: 20%; }
.meteor:nth-child(2) { --delay: 1.5s; --duration: 4s;   top: 25%; right: 40%; }
.meteor:nth-child(3) { --delay: 0.8s; --duration: 3.5s; top: 5%;  right: 60%; }

@keyframes meteor-fall {
  0%   { transform: translate(0, 0) rotate(-45deg); opacity: 0; }
  5%   { opacity: 1; }
  70%  { opacity: 1; }
  100% { transform: translate(-400px, 400px) rotate(-45deg); opacity: 0; }
}

Perspective grid / retro floor

.retro-grid {
  --grid-color: hsl(280 70% 60% / 0.35);
  --grid-size: 60px;
  position: relative; height: 50vh;
  overflow: hidden; background: hsl(230 40% 8%);
}

.retro-grid__floor {
  position: absolute; inset: 0;
  transform: perspective(400px) rotateX(55deg);
  transform-origin: center bottom;
  background-image:
    linear-gradient(to right, var(--grid-color) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-color) 1px, transparent 1px);
  background-size: var(--grid-size) var(--grid-size);

  @media (prefers-reduced-motion: no-preference) {
    animation: grid-scroll 4s linear infinite;
  }
}

/* Distance fog */
.retro-grid__floor::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, hsl(230 40% 8%) 0%, transparent 30%, transparent 100%);
}

@keyframes grid-scroll {
  from { background-position: center 0; }
  to   { background-position: center var(--grid-size); }
}

Reduced motion: cinematic effects summary

@media (prefers-reduced-motion: reduce) {
  /* Border beam: static gradient border */
  .border-beam {
    animation: none;
    background:
      hsl(220 15% 10%) padding-box,
      linear-gradient(135deg, hsl(250 80% 70%), hsl(320 70% 65%)) border-box;
  }
  /* Glow: keep static, remove pulse */
  .glow-pulse { animation: none; }
  /* Aurora: blobs stay in place */
  .aurora-blob, .aurora-gradient { animation: none; }
  /* Neon: static glow, no flicker */
  .neon-flicker { animation: none; }
  /* Shimmer: disable entirely */
  .shimmer-btn::after, .shimmer-hover::after { display: none; }
  /* Particles/meteors: remove entirely */
  .meteor, .particle { display: none; }
  /* Retro grid: freeze */
  .retro-grid__floor { animation: none; }
}

Common Mistakes

  • Wrong: Using cinematic effects on body content, navigation, or form fields → Right: Reserve for hero sections and primary CTAs only — one or two focal moments per page
  • Wrong: Stacking 3+ effects on one element (glow + shimmer + neon border simultaneously) → Right: One effect per element — competing effects cancel each other out
  • Wrong: Glow, aurora, or neon on light backgrounds → Right: These effects require dark surfaces to read as luminous; on white or light grey they look grey or invisible
  • Wrong: Animating box-shadow blur radius directly → Right: Animate opacity on a pseudo-element holding the shadow; direct shadow animation triggers repaint per frame
  • Wrong: Aurora filter: blur() above 80px on mobile → Right: 40-60px is the practical ceiling; higher values cause exponential GPU cost on mobile
  • Wrong: More than 20 particle/meteor elements animating simultaneously → Right: Keep under 20; above that, performance degrades on low-end mobile
  • Wrong: Neon flicker with animation-duration below 1s → Right: Keep at 2s+ to stay under the WCAG 2.3.1 three-flash-per-second limit
  • Wrong: will-change: transform on all particles preemptively → Right: Apply only where needed — each will-change reserves GPU memory per layer
  • Wrong: Cinematic effects in admin UIs, dashboards, or tool interfaces → Right: These contexts require calm; save effects for the marketing site

See Also