content /
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-contentWhere 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.