Workspace / component / backgrounds

Aurora Background

Vertical light curtains in teal and violet that sway against a night sky. Blurred layers composite on the GPU for a soft volumetric read.

Workspacecomponentbackgrounds

paxfx add blocks/component-backgrounds-auroralocal only

Tier: medium. Reduced motion: Curtains hold a fixed pose.

"use client";

import type { ReactNode } from "react";
import { cn, useReducedMotion } from "@pax-sh/blocks";

export interface AuroraBackgroundProps {
  /** 0 to 1; brightness of the light curtains. */
  intensity?: number;
  /** Multiplier on sway speed. 1 is the tuned default. */
  speed?: number;
  className?: string;
  children?: ReactNode;
}

/**
 * Vertical light curtains in teal and violet swaying against a night sky.
 * Each curtain is a blurred gradient layer animating transform only on its
 * own duration, so the volumetric read composites entirely on the GPU.
 * Under reduced motion the curtains hold a fixed pose.
 */
export function AuroraBackground({
  intensity = 0.8,
  speed = 1,
  className,
  children,
}: AuroraBackgroundProps) {
  const reduced = useReducedMotion();
  const clamped = Math.min(1, Math.max(0, intensity));
  const dur = (seconds: number) => `${seconds / Math.max(0.1, speed)}s`;

  const curtain = (
    left: string,
    width: string,
    background: string,
    animation: string,
    opacity: number,
  ) => (
    <div
      aria-hidden="true"
      className="pointer-events-none absolute top-[-25%] h-[150%] blur-3xl will-change-transform"
      style={{
        left,
        width,
        background,
        opacity: opacity * clamped,
        mixBlendMode: "screen",
        transform: "skewX(-8deg)",
        animation: reduced ? "none" : animation,
      }}
    />
  );

  return (
    <div
      className={cn(
        "relative isolate overflow-hidden rounded-2xl border",
        "border-[color-mix(in_srgb,var(--pax-paper,#f6f2ea)_14%,transparent)]",
        "bg-[var(--pax-night,#0b0d10)] text-[var(--pax-paper,#f6f2ea)]",
        className,
      )}
      data-pax-asset="component-backgrounds-aurora"
    >
      <style>{`
        @keyframes pax-aurora-sway-a {
          from { transform: skewX(-8deg) translate3d(-6%, -2%, 0); }
          to { transform: skewX(-4deg) translate3d(7%, 2%, 0); }
        }
        @keyframes pax-aurora-sway-b {
          from { transform: skewX(-10deg) translate3d(6%, 2%, 0); }
          to { transform: skewX(-5deg) translate3d(-7%, -2%, 0); }
        }
        @keyframes pax-aurora-sway-c {
          from { transform: skewX(-6deg) translate3d(-4%, 1%, 0); }
          to { transform: skewX(-11deg) translate3d(5%, -2%, 0); }
        }
      `}</style>
      {curtain(
        "8%",
        "26%",
        "linear-gradient(to bottom, transparent 4%, rgba(45,212,191,0.5) 34%, rgba(45,212,191,0.14) 68%, transparent 96%)",
        `pax-aurora-sway-a ${dur(22)} ease-in-out infinite alternate`,
        0.9,
      )}
      {curtain(
        "38%",
        "30%",
        "linear-gradient(to bottom, transparent 6%, rgba(139,92,246,0.48) 40%, rgba(139,92,246,0.12) 72%, transparent 98%)",
        `pax-aurora-sway-b ${dur(31)} ease-in-out infinite alternate`,
        0.85,
      )}
      {curtain(
        "66%",
        "24%",
        "linear-gradient(to bottom, transparent 5%, rgba(45,212,191,0.34) 30%, rgba(139,92,246,0.24) 62%, transparent 95%)",
        `pax-aurora-sway-c ${dur(27)} ease-in-out infinite alternate`,
        0.8,
      )}
      <div
        aria-hidden="true"
        className="pointer-events-none absolute inset-0"
        style={{
          background: "linear-gradient(to top, var(--pax-night,#0b0d10) 0%, transparent 34%)",
        }}
      />
      <div className="relative z-[1]">{children}</div>
    </div>
  );
}