SPB Git

spb/spboucher.ai

Public

spboucher.ai — personal website of Simon-Pierre Boucher.

4.3 KB · 136 lines tsx
1/*2  book-card.tsx3  spboucher.ai Web4  Author: Simon-Pierre Boucher5  Mail: contact@spboucher.ai6*/78import Image from "next/image";9import { FileText, Github } from "lucide-react";1011import { Badge } from "@/components/ui/badge";12import { buttonVariants } from "@/components/ui/button";13import type { Book } from "@/lib/books";14import { cn } from "@/lib/utils";1516interface BookCardProps {17  book: Book;18  /** "full" = cover + stats + notice (research page); "compact" = cover + title + buttons. */19  variant?: "full" | "compact";20  className?: string;21}2223/** A book presented as a cover with its key figures and download links. */24export function BookCard({ book, variant = "full", className }: BookCardProps) {25  const stats = [26    { value: book.pages.toLocaleString("en-US"), label: "pages" },27    { value: String(book.chapters), label: "chapters" },28    { value: String(book.figures), label: "figures" },29    { value: String(book.tables), label: "tables" },30    { value: String(book.exercises), label: "solved exercises" },31  ];32  const compact = variant === "compact";3334  return (35    <article36      className={cn(37        "grid gap-6 rounded-[0.9rem] border border-border bg-card p-5 shadow-[var(--shadow-soft)] transition-all duration-300 hover:shadow-[var(--shadow-lift)] sm:p-6",38        compact ? "sm:grid-cols-[7rem_1fr]" : "md:grid-cols-[11rem_1fr]",39        className,40      )}41    >42      <a43        href={book.pdf}44        target="_blank"45        rel="noopener noreferrer"46        className="group block w-28 shrink-0 sm:w-auto"47        aria-label={`${book.title} — PDF`}48      >49        <Image50          src={book.cover}51          alt={`Cover of ${book.title}`}52          width={1120}53          height={1600}54          sizes={compact ? "112px" : "(min-width: 768px) 176px, 112px"}55          className="w-full border border-border/70 shadow-[var(--shadow-lift)] transition-transform duration-300 group-hover:-translate-y-1"56        />57      </a>5859      <div className="min-w-0">60        <div className="flex flex-wrap items-center gap-1.5">61          <Badge>Book · {book.year}</Badge>62          <Badge variant="outline">{book.edition}</Badge>63          <Badge variant="outline">{book.language}</Badge>64        </div>65        <h366          className={cn(67            "mt-3 font-display font-semibold leading-snug tracking-tight",68            compact ? "text-lg" : "text-xl sm:text-2xl",69          )}70        >71          {book.title}72        </h3>73        <p className="mt-1 font-display text-base leading-snug text-foreground/75 [font-style:italic]">74          {book.subtitle}75        </p>76        {!compact && (77          <p className="mt-2 text-xs text-muted-foreground">78            {book.titleEn} — {book.authors.join(", ")}, Université du Québec en79            Outaouais, {book.date}.80          </p>81        )}82        <p83          className={cn(84            "mt-3 text-sm leading-relaxed text-muted-foreground",85            compact && "line-clamp-3",86          )}87        >88          {book.description}89        </p>9091        {!compact && (92          <div className="mt-5 grid grid-cols-3 gap-x-4 gap-y-4 border-t border-border/70 pt-4 sm:grid-cols-5">93            {stats.map((s) => (94              <div key={s.label}>95                <p className="font-display text-xl font-semibold tracking-tight sm:text-2xl">96                  {s.value}97                </p>98                <p className="mt-0.5 font-mono text-[0.62rem] uppercase tracking-[0.12em] text-muted-foreground">99                  {s.label}100                </p>101              </div>102            ))}103          </div>104        )}105106        <div className="mt-5 flex flex-wrap gap-2">107          <a108            href={book.pdf}109            target="_blank"110            rel="noopener noreferrer"111            className={buttonVariants({ size: "sm" })}112          >113            <FileText aria-hidden="true" />114            Read the book (PDF, {book.pdfMb} MB)115          </a>116          <a117            href={book.repo}118            target="_blank"119            rel="noopener noreferrer"120            className={buttonVariants({ variant: "outline", size: "sm" })}121          >122            <Github aria-hidden="true" />123            Sources, data &amp; scripts124          </a>125        </div>126127        {!compact && (128          <p className="mt-4 text-xs leading-relaxed text-muted-foreground/80">129            {book.notice}130          </p>131        )}132      </div>133    </article>134  );135}136
Files · main
Menu

Theme