Innovations

Alternating Content

3 alternating content rows — text left/image right, text right/image left. Each row has eyebrow, headline, description, and link. Gradient placeholder images.

Preview

Source

tsx
"use client";

import { motion } from "framer-motion";
import { ArrowRight, BarChart3, Layers, Zap } from "lucide-react";

const rows = [
  {
    eyebrow: "Step 01 — Discover",
    headline: "Understand your audience at a deeper level",
    description:
      "Our analytics engine surfaces the patterns hidden in your data. Know exactly who your customers are, what they want, and when they're ready to buy — before your competitors do.",
    link: { label: "Explore analytics", href: "#" },
    icon: BarChart3,
    gradient: "from-violet-500 via-purple-500 to-indigo-600",
    decoratorGradient: "from-violet-200 to-indigo-200",
    imageRight: true,
  },
  {
    eyebrow: "Step 02 — Build",
    headline: "Ship products your team will actually love using",
    description:
      "Drag-and-drop builders, pre-built templates, and an API-first architecture mean your team moves at the speed of thought. From prototype to production in hours, not weeks.",
    link: { label: "See the builder", href: "#" },
    icon: Layers,
    gradient: "from-emerald-400 via-teal-500 to-cyan-600",
    decoratorGradient: "from-emerald-200 to-cyan-200",
    imageRight: false,
  },
  {
    eyebrow: "Step 03 — Scale",
    headline: "Grow without the growing pains",
    description:
      "Auto-scaling infrastructure, global CDN, and intelligent caching handle any traffic spike. We've engineered for 10x your current load — so you can focus on growth, not ops.",
    link: { label: "View infrastructure", href: "#" },
    icon: Zap,
    gradient: "from-rose-400 via-pink-500 to-fuchsia-600",
    decoratorGradient: "from-rose-200 to-fuchsia-200",
    imageRight: true,
  },
];

export default function AlternatingContent() {
  return (
    <section className="py-20 sm:py-28 bg-background">
      <div className="container mx-auto px-6">
        {/* Section header */}
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.6 }}
          className="text-center max-w-2xl mx-auto mb-20"
        >
          <span className="text-sm font-semibold uppercase tracking-widest text-primary">
            How it works
          </span>
          <h2 className="mt-3 text-3xl sm:text-4xl lg:text-5xl font-extrabold tracking-tight text-foreground">
            Three steps to a better product
          </h2>
          <p className="mt-4 text-lg text-muted-foreground">
            From discovery to scale, we've built the tools and the process that
            takes teams from good to extraordinary.
          </p>
        </motion.div>

        {/* Alternating rows */}
        <div className="flex flex-col gap-20 sm:gap-28">
          {rows.map((row, i) => {
            const Icon = row.icon;
            return (
              <div
                key={row.eyebrow}
                className={`grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center ${
                  !row.imageRight ? "lg:[&>*:first-child]:order-2" : ""
                }`}
              >
                {/* Text side */}
                <motion.div
                  initial={{ opacity: 0, x: row.imageRight ? -30 : 30 }}
                  whileInView={{ opacity: 1, x: 0 }}
                  viewport={{ once: true, margin: "-80px" }}
                  transition={{ duration: 0.6, ease: "easeOut" }}
                  className="flex flex-col gap-5"
                >
                  <span className="text-xs font-bold uppercase tracking-widest text-primary">
                    {row.eyebrow}
                  </span>
                  <h3 className="text-2xl sm:text-3xl lg:text-4xl font-extrabold tracking-tight text-foreground leading-tight">
                    {row.headline}
                  </h3>
                  <p className="text-muted-foreground leading-relaxed text-base sm:text-lg">
                    {row.description}
                  </p>
                  <a
                    href={row.link.href}
                    className="inline-flex items-center gap-2 text-primary font-semibold text-sm hover:gap-3 transition-all duration-200 w-fit"
                  >
                    {row.link.label}
                    <ArrowRight className="w-4 h-4" />
                  </a>
                </motion.div>

                {/* Image/visual side */}
                <motion.div
                  initial={{ opacity: 0, x: row.imageRight ? 30 : -30 }}
                  whileInView={{ opacity: 1, x: 0 }}
                  viewport={{ once: true, margin: "-80px" }}
                  transition={{ duration: 0.6, ease: "easeOut", delay: 0.1 }}
                  className="relative"
                >
                  <div
                    className={`aspect-[4/3] rounded-2xl bg-gradient-to-br ${row.gradient} overflow-hidden shadow-xl`}
                  >
                    {/* Decorative pattern overlay */}
                    <div className="absolute inset-0 opacity-20"
                      style={{
                        backgroundImage: "radial-gradient(circle at 1px 1px, white 1px, transparent 0)",
                        backgroundSize: "24px 24px",
                      }}
                    />
                    {/* Center icon */}
                    <div className="absolute inset-0 flex items-center justify-center">
                      <div className="bg-white/20 backdrop-blur-sm rounded-3xl p-8 border border-white/30">
                        <Icon className="w-16 h-16 text-white" />
                      </div>
                    </div>
                    {/* Corner decoration */}
                    <div className={`absolute bottom-4 right-4 text-xs font-bold uppercase tracking-widest text-white/50`}>
                      Step 0{i + 1}
                    </div>
                  </div>
                  {/* Shadow/glow effect */}
                  <div className={`absolute -inset-4 bg-gradient-to-br ${row.decoratorGradient} opacity-20 blur-2xl -z-10 rounded-3xl`} />
                </motion.div>
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}
Claude Code Instructions

CLI Install

npx innovations add alternating-content

Where to use it

Place in your 'How it works' or 'Features deep dive' section, typically between the features grid and testimonials. In Astro (src/pages/index.astro): import AlternatingContent from '../components/innovations/content/alternating-content'; <AlternatingContent client:visible /> In Next.js (app/page.tsx): import AlternatingContent from '@/components/innovations/content/alternating-content'; Edit the rows array at the top of the component. Each row has: - eyebrow: small label above headline (e.g., "Step 01 — Discover") - headline: main row title - description: body text - link: { label, href } for the CTA link - icon: any lucide-react icon - gradient: Tailwind gradient classes for the image placeholder - imageRight: true/false controls which side the image appears To use real images, replace the gradient div with an <img> or Next.js <Image> component with the same aspect-[4/3] and rounded-2xl classes.