Cnippet

Lunar Blog-1

Cards 3

About

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Lunar Blog-1 to your project and fits it to your theme.

Code

How Lunar Blog-1 is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import { ChevronLeft, ChevronRight } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { Badge } from "@/demos/ui/badge";
import { Button } from "@/components/ui/button";
import {
  Carousel,
  type CarouselApi,
  CarouselContent,
  CarouselItem,
} from "@/demos/ui/carousel";

const posts = [
  {
    category: "Robotics",
    image: "https://cdn.21st.dev/assets/mirror/c2/c2f0ac926d78a63b48f7e75d8fa908d32246521390061e6b9152cef1b9e4471a.jpg",
    publishedDate: "March 20, 2025",
    tailwindClass: " basis-[60%]",
    title: "The Rise of Collaborative Robots: A New Era in Automation",
    type: "full",
  },
  {
    author: {
      avatar: "https://cdn.21st.dev/assets/mirror/e0/e058437411e954b747056a494f26349751828f12c2137a883e5aebbd1fcf5eef.jpg",
      name: "Sarah Chen",
    },
    category: "Arduino",
    image: "https://cdn.21st.dev/assets/mirror/5c/5c3abc2be979f3a41a10d7bed7bcb5af6b124867c1ecd4e9e1587145ea102e12.jpg",
    publishedDate: "March 19, 2025",
    readTime: "6 min read",
    title: "Getting Started with Arduino: 5 Beginner Projects",
    type: "image",
  },
  {
    category: "Microcontrollers",
    image: "https://cdn.21st.dev/assets/mirror/30/3006467e705045b5efeb46b5367fd00c64f0ebe3363a06437ad9e1fcf098b1b3.jpg",
    publishedDate: "March 18, 2025",
    title: "Top 5 Microcontroller Boards for 2025",
    type: "dark",
  },
  {
    author: {
      avatar: "https://cdn.21st.dev/assets/mirror/d5/d549c11c16ad2335895c39339d1a4307b648b24a6baae68662246cf9bd37ac13.jpg",
      name: "Mark Davis",
    },
    category: "Microcontrollers",
    image: "https://cdn.21st.dev/assets/mirror/59/592a79b4e44db67dc15265441bcdbdbb8fddb106a333934dc429761d378d7e64.jpg",
    publishedDate: "March 17, 2025",
    readTime: "7 min read",
    title: "ESP32 vs Raspberry Pi Pico: Choosing the Right MCU",
    type: "image",
  },
  {
    category: "AI & Machine Learning",
    image: "https://cdn.21st.dev/assets/mirror/45/4542c1adfc02531b5564b78b4a2da5d145e9be3fa70d3f3056bfd3b5eba672cf.jpg",
    publishedDate: "March 16, 2025",
    tailwindClass: " basis-[60%]",
    title: "TinyML: Running Neural Networks on Microcontrollers",
    type: "full",
  },
];

function CurvedCorner({ position }: { position: "left" | "bottom" }) {
  if (position === "left") {
    return (
      <div className="absolute top-0 right-auto bottom-auto -left-2 flex h-2 w-2 items-center justify-center">
        <svg
          fill="none"
          height="8"
          viewBox="0 0 8 8"
          width="8"
          xmlns="http://www.w3.org/2000/svg"
        >
          <path
            clipRule="evenodd"
            d="M8 0H0C4.41828 0 8 3.58172 8 8V0Z"
            fill="white"
            fillRule="evenodd"
          />
        </svg>
      </div>
    );
  }
  return (
    <div className="absolute top-auto right-0 -bottom-2 left-auto flex h-2 w-2 items-center justify-center">
      <svg
        fill="none"
        height="8"
        viewBox="0 0 8 8"
        width="8"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          clipRule="evenodd"
          d="M8 0H0C4.41828 0 8 3.58172 8 8V0Z"
          fill="white"
          fillRule="evenodd"
        />
      </svg>
    </div>
  );
}

export default function BlogVariant3() {
  const [api, setApi] = useState<CarouselApi>();
  const [current, setCurrent] = useState(0);
  const [count, setCount] = useState(0);

  useEffect(() => {
    if (!api) return;

    setCount(api.scrollSnapList().length);
    setCurrent(api.selectedScrollSnap());

    api.on("select", () => {
      setCurrent(api.selectedScrollSnap());
    });
  }, [api]);

  const scrollPrev = useCallback(() => {
    api?.scrollPrev();
  }, [api]);

  const scrollNext = useCallback(() => {
    api?.scrollNext();
  }, [api]);

  const progress = count > 0 ? ((current + 1) / count) * 100 : 0;

  return (
    <section className="w-full bg-white py-12 sm:py-16 lg:py-20">
      <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
        <div className="flex flex-col gap-6 sm:flex-row sm:items-end sm:justify-between">
          <div>
            <Badge
              className="mb-4 rounded-full border-gray-300 px-4 py-0 font-medium font-onest text-gray-700 text-sm"
              variant="outline"
            >
              Future Tech
            </Badge>

            <h2 className="font-medium max-w-lg font-onest text-4xl text-gray-900 tracking-tight sm:text-5xl">
            Explore the future of
              <br />
              robotics 
            </h2>
          </div>

          <Button
            className="w-fit rounded-full border-blue-600 bg-blue-600 px-8 py-2.5 font-medium font-onest text-sm text-white hover:bg-blue-700"
            render={<Link href="#" />}
          >
            View all
          </Button>
        </div>
      </div>
      <div className="mt-10">
        <Carousel
          className="w-full"
          opts={{
            align: "start",
            loop: false,
          }}
          setApi={setApi}
        >
          <CarouselContent className="-ml-5 translate-x-4 space-x-6 sm:translate-x-40">
            {posts.map((post) => (
              <CarouselItem
                className={`pl-5 ${post.tailwindClass ? post.tailwindClass : "basis-[30%]"}`}
                key={post.title}
              >
                {post.type === "full" && <FullCard post={post} />}
                {post.type === "image" && <ImageCard post={post} />}
                {post.type === "dark" && <DarkCard post={post} />}
              </CarouselItem>
            ))}
          </CarouselContent>
        </Carousel>

        <div className="mx-auto mt-8 flex max-w-7xl items-center gap-6 px-4 md:px-8">
          <div className="flex-1">
            <div className="h-0.5 w-full bg-gray-100">
              <div
                className="h-full bg-blue-600 transition-all duration-300"
                style={{ width: `${progress}%` }}
              />
            </div>
          </div>

          <div className="flex items-center gap-2">
            <button
              className="flex size-10 items-center justify-center rounded-full border border-gray-300 bg-white text-gray-600 transition-colors hover:border-gray-400 hover:text-gray-900 disabled:opacity-50"
              disabled={current === 0}
              onClick={scrollPrev}
            >
              <ChevronLeft className="size-5" />
            </button>
            <button
              className="flex size-10 items-center justify-center rounded-full border border-gray-300 bg-white text-gray-600 transition-colors hover:border-gray-400 hover:text-gray-900 disabled:opacity-50"
              disabled={current === count - 1}
              onClick={scrollNext}
            >
              <ChevronRight className="size-5" />
            </button>
          </div>
        </div>
      </div>
    </section>
  );
}

function FullCard({
  post,
}: {
  post: {
    category: string;
    title: string;
    publishedDate: string;
    image: string;
  };
}) {
  return (
    <article className="group relative grid h-105 cursor-pointer grid-cols-1 gap-4 overflow-hidden sm:grid-cols-12">
      <div className="relative z-10 flex w-full flex-col sm:col-span-5">
        <Link
          className="font-medium font-onest text-blue-600 text-sm hover:text-blue-700"
          href="#"
        >
          {post.category}
        </Link>

        <h3 className="mt-3 font-medium font-onest text-2xl! text-gray-900 tracking-tight sm:text-4xl!">
          {post.title}
        </h3>

        <p className="mt-3 text-gray-500 text-sm">
          Published on {post.publishedDate}
        </p>
        <Button
          className="mt-auto w-fit rounded-full border-blue-600 bg-blue-600 px-6 py-2 font-medium font-onest text-sm text-white hover:bg-blue-700"
          render={<Link href="#" />}
        >
          Read more
        </Button>
      </div>
      <div className="sm:col-span-7">
        <Image
          alt=""
          className="h-full overflow-hidden rounded-3xl object-cover"
          height={1080}
          src={post.image}
          width={1920}
        />
      </div>
    </article>
  );
}

function ImageCard({
  post,
}: {
  post: {
    category: string;
    title: string;
    publishedDate: string;
    readTime?: string;
    image: string;
    author?: {
      name: string;
      avatar: string;
    };
  };
}) {
  return (
    <article className="group flex h-full cursor-pointer flex-col">
      <div className="relative aspect-4/3 w-full overflow-hidden rounded-b-3xl rounded-tl-3xl">
        <Image
          alt={post.title}
          className="object-cover transition-transform duration-300 group-hover:scale-105"
          fill
          src={post.image}
        />

        {post.readTime && (
          <span className="absolute top-0 right-0 rounded-bl-lg bg-white px-3 py-1 font-medium text-gray-600 text-xs">
            {post.readTime}
            <CurvedCorner position="left" />
            <CurvedCorner position="bottom" />
          </span>
        )}
      </div>

      <div>
        <h3 className="mt-3 font-medium font-onest text-gray-900 text-md! tracking-tight sm:text-xl!">
          {post.title}
        </h3>
        <p className="mt-3 text-gray-500 text-sm">
          Published on {post.publishedDate}
        </p>
      </div>

      {post.author && (
        <div className="mt-auto flex items-center justify-between">
          <div>
            <p className="text-gray-500 text-xs">Article by</p>
            <p className="font-medium font-onest text-gray-900">
              {post.author.name}
            </p>
          </div>
          <div className="relative size-12 overflow-hidden rounded-md">
            <Image
              alt={post.author.name}
              className="object-cover"
              fill
              src={post.author.avatar}
            />
          </div>
        </div>
      )}
    </article>
  );
}

function DarkCard({
  post,
}: {
  post: {
    category: string;
    title: string;
    publishedDate: string;
    image: string;
  };
}) {
  return (
    <article className="group relative flex h-105 cursor-pointer flex-col justify-between overflow-hidden rounded-3xl bg-neutral-800 p-6">
      <div className="absolute inset-0 opacity-40">
        <Image alt="" className="object-cover" fill src={post.image} />
      </div>

      <div className="absolute inset-0 bg-linear-to-t from-neutral-900/80 via-neutral-900/40 to-transparent" />

      <div className="relative z-10">
        <Link
          className="font-medium font-onest text-amber-500 text-sm hover:text-amber-400"
          href="#"
        >
          {post.category}
        </Link>

        <h3 className="mt-3 font-medium font-onest text-2xl! text-white leading-snug sm:text-3xl!">
          {post.title}
        </h3>

        <p className="mt-3 text-gray-300 text-sm">
          Published on {post.publishedDate}
        </p>
      </div>

      <div className="relative z-10">
        <Button
          className="rounded-full bg-blue-600 px-6 py-2 font-medium font-onest text-sm text-white hover:bg-blue-700"
          render={<Link href="#" />}
        >
          Read more
        </Button>
      </div>
    </article>
  );
}

More by Cnippet

View all
  • Team

    Team

    1.3K
  • Davincho Hero-1

    Davincho Hero-1

    683
  • Aero Hero-3

    Aero Hero-3

    558
  • Features

    Features

    510
  • Random Letter Swap Navigation

    Random Letter Swap Navigation

    487
  • Services with Animated Hover Modal

    Services with Animated Hover Modal

    465
  • Blogs

    Blogs

    396
  • Aero Hero-1

    Aero Hero-1

    356

More in Cards

View all
  • Stats cards with links

    Stats cards with links

    33
  • Course Design Cards

    Course Design Cards

    1.1K
  • Card

    Card

    70
  • Statistics Card 2

    Statistics Card 2

    264
  • Statistics Card 10

    Statistics Card 10

    263
  • Card

    Card

    17
  • Card Studio

    Card Studio

    9
  • The Item one

    The Item one

    3