Innovations
heroes /

Animated Blob Hero

Vibrant hero with multiple morphing CSS blob shapes animating in the background. Centered text with framer-motion entrance.

Preview

Source

tsx
"use client";

import { motion } from "framer-motion";
import { ArrowRight, Star } from "lucide-react";
import { Button } from "@/components/ui/button";

const blobs = [
  {
    className: "top-[-10%] left-[-5%] w-[500px] h-[500px]",
    gradient: "from-violet-500 via-purple-400 to-fuchsia-500",
    duration: 10,
    delay: 0,
  },
  {
    className: "bottom-[-15%] right-[-10%] w-[600px] h-[600px]",
    gradient: "from-sky-400 via-cyan-400 to-teal-400",
    duration: 12,
    delay: 2,
  },
  {
    className: "top-[30%] right-[10%] w-[350px] h-[350px]",
    gradient: "from-rose-400 via-pink-400 to-fuchsia-400",
    duration: 9,
    delay: 1,
  },
];

const morphKeyframes = [
  "60% 40% 30% 70% / 60% 30% 70% 40%",
  "30% 60% 70% 40% / 50% 60% 30% 60%",
  "50% 50% 40% 60% / 40% 70% 60% 50%",
  "70% 30% 50% 50% / 30% 60% 40% 70%",
  "60% 40% 30% 70% / 60% 30% 70% 40%",
];

export default function AnimatedBlobHero() {
  return (
    <section className="relative min-h-screen flex items-center justify-center overflow-hidden bg-background">
      {/* Blobs */}
      {blobs.map((blob, i) => (
        <motion.div
          key={i}
          className={`absolute ${blob.className} bg-gradient-to-br ${blob.gradient} opacity-20 blur-3xl`}
          animate={{ borderRadius: morphKeyframes }}
          transition={{
            duration: blob.duration,
            repeat: Infinity,
            ease: "easeInOut",
            delay: blob.delay,
          }}
        />
      ))}

      {/* Sharp inner blobs (less blur, more vivid) */}
      <motion.div
        className="absolute top-[15%] left-[20%] w-48 h-48 bg-gradient-to-br from-primary to-violet-600 opacity-30 blur-xl"
        animate={{ borderRadius: morphKeyframes, scale: [1, 1.1, 0.95, 1.05, 1] }}
        transition={{ duration: 7, repeat: Infinity, ease: "easeInOut" }}
      />
      <motion.div
        className="absolute bottom-[20%] right-[25%] w-32 h-32 bg-gradient-to-br from-cyan-400 to-sky-600 opacity-40 blur-lg"
        animate={{ borderRadius: morphKeyframes, scale: [1, 0.9, 1.1, 0.95, 1] }}
        transition={{ duration: 5, repeat: Infinity, ease: "easeInOut", delay: 1.5 }}
      />

      {/* Content */}
      <div className="relative z-10 container mx-auto px-6 text-center max-w-3xl">
        <motion.div
          initial={{ opacity: 0, y: 40 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.7, ease: "easeOut" }}
          className="flex flex-col items-center gap-6"
        >
          {/* Rating badge */}
          <motion.div
            initial={{ opacity: 0, scale: 0.9 }}
            animate={{ opacity: 1, scale: 1 }}
            transition={{ duration: 0.4, delay: 0.15 }}
            className="inline-flex items-center gap-1.5 bg-card border border-border rounded-full px-4 py-2 shadow-sm"
          >
            {[1, 2, 3, 4, 5].map((s) => (
              <Star key={s} className="w-3.5 h-3.5 fill-amber-400 text-amber-400" />
            ))}
            <span className="text-xs font-semibold text-foreground ml-1">
              Rated 4.9 by 3,000+ users
            </span>
          </motion.div>

          <motion.h1
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.65, delay: 0.2 }}
            className="text-5xl sm:text-6xl lg:text-7xl font-extrabold tracking-tight text-foreground leading-[1.05]"
          >
            Design without{" "}
            <span className="relative inline-block">
              <span className="relative z-10 bg-gradient-to-r from-violet-500 via-fuchsia-500 to-rose-500 bg-clip-text text-transparent">
                limits
              </span>
            </span>
          </motion.h1>

          <motion.p
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.65, delay: 0.3 }}
            className="text-lg sm:text-xl text-muted-foreground max-w-xl leading-relaxed"
          >
            A creative workspace that adapts to you — expressive, intuitive, and
            powerful enough for professionals who refuse to compromise.
          </motion.p>

          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.65, delay: 0.4 }}
            className="flex flex-col sm:flex-row items-center gap-3 pt-2"
          >
            <Button size="lg" className="gap-2 text-base">
              Try it free
              <ArrowRight className="w-4 h-4" />
            </Button>
            <Button size="lg" variant="ghost" className="text-base">
              Explore gallery
            </Button>
          </motion.div>
        </motion.div>
      </div>
    </section>
  );
}
Claude Code Instructions

CLI Install

npx innovations add animated-blob

Where to use it

Place as the first section after the navbar on your landing page. In Astro (src/pages/index.astro): import AnimatedBlobHero from '../components/innovations/heroes/animated-blob'; <AnimatedBlobHero client:load /> In Next.js (app/page.tsx): import AnimatedBlobHero from '@/components/innovations/heroes/animated-blob'; // Add at the top of the page return Blob colors are controlled via Tailwind gradient classes in the blobs array at the top of the component. Edit 'from-', 'via-', and 'to-' classes to match your brand palette. The blur-3xl class controls softness — reduce to blur-2xl for sharper blobs.