Book Card.

A 3D interactive book component with realistic physics and styling.

Interactive Preview

PortfolioReimagined

Divyansh Mishra

Portfolio Reimagined

Divyansh Mishra

Installation Guide

** 01. Dependencies **
pnpm add motion motion
** 02. Source Code **

Create components/book-card.tsx and paste the code below:

"use client";
import { cn } from "@/lib/utils";
import { motion } from "motion/react";
interface BookCardProps {
  title?: string;
  author?: string;
  width?: string;
  themeColor?: string;
}
const BookCard = ({
  title = "My Book",
  author = "Author",
  width = "200px",
  themeColor = "#e74c3c",
}: BookCardProps) => {
  return (
    <div
      className={cn("@container flex flex-col items-center justify-center p-4")}
      style={{ width }}
    >
      <motion.div
        className="relative aspect-[3/4.5] w-full cursor-pointer"
        initial="rest"
        whileHover="hover"
        animate="rest"
        style={{ perspective: "1200px", transformStyle: "preserve-3d" }}
      >
        <div
          className="absolute inset-0 rounded-l-[4cqw] rounded-r-[12cqw] shadow-2xl"
          style={{ backgroundColor: themeColor }}
        >
          <div className="absolute inset-y-0 left-0 w-[15%] rounded-l-[4cqw] bg-black/20" />
        </div>
        <div className="absolute inset-y-[2%] right-[2%] left-[12%] flex flex-col rounded-r-[8cqw] bg-white shadow-inner">
          <div className="h-[5%] w-full border-b border-black/5" />
          <div className="relative w-full flex-1 overflow-hidden bg-white">
            <div className="absolute inset-0 bg-[repeating-linear-gradient(transparent,transparent_4%,#000_5%)] opacity-10" />
          </div>
          <div className="flex h-[10%] w-full items-center border-t border-black/5 px-2">
            <div className="h-1 w-full rounded-full bg-black/5" />
          </div>
          <div className="absolute right-[20%] -bottom-[8%] z-10 h-[30%] w-[18%]">
            <div
              className="h-full w-full bg-[#f1c40f] shadow-md"
              style={{
                clipPath: "polygon(0 0, 100% 0, 100% 100%, 50% 85%, 0 100%)",
              }}
            />
          </div>
        </div>
        <motion.div
          variants={{
            rest: { rotateY: 0 },
            hover: { rotateY: -30 },
          }}
          transition={{ type: "spring", stiffness: 60, damping: 20 }}
          style={{
            transformOrigin: "left center",
            zIndex: 40,
            transformStyle: "preserve-3d",
            backgroundColor: themeColor,
          }}
          className="absolute inset-0 rounded-l-[4cqw] rounded-r-[12cqw] border-l border-black/10 shadow-lg"
        >
          <div className="absolute inset-y-0 left-[12%] w-0.5 bg-black/10 shadow-[1px_0_2px_rgba(0,0,0,0.1)]" />
          <div className="pointer-events-none flex h-full w-full flex-col items-center justify-center px-[15%] text-center text-white uppercase">
            <h2 className="my-[10%] flex flex-col text-[11cqw] leading-[0.9] font-black tracking-tighter">
              {title.split(" ").map((word, i) => (
                <span key={i}>{word}</span>
              ))}
            </h2>
            <div className="h-[0.5cqw] w-[25%] bg-white/30" />
            <span className="mt-[15%] text-[5cqw] font-bold tracking-[0.2em] opacity-80">
              {author}
            </span>
          </div>
        </motion.div>
      </motion.div>
      <div className="mt-8 flex flex-col items-center gap-1">
        <h3 className="text-sm font-bold tracking-tight text-neutral-800 dark:text-neutral-200">
          {title}
        </h3>
        <p className="text-[10px] font-semibold tracking-[0.2em] text-neutral-400 uppercase">
          {author}
        </p>
      </div>
    </div>
  );
};
export default BookCard;
** 03. Usage Example **
import BookCard from "@/components/book-card";

export default function MyComponent() {
  return (
    <BookCard
      title="The Design"
      author="D. Mishra"
      width="160px"
      themeColor="#471396"
    />
  );
}