spb/spboucher.ai Public
spboucher.ai — personal website of Simon-Pierre Boucher.
TypeScript 93.4%
HTML 5.5%
CSS 1%
1/*2 project-card.tsx3 spboucher.ai Web4 Author: Simon-Pierre Boucher5 Mail: contact@spboucher.ai6*/78import Image from "next/image";9import Link from "next/link";10import { ArrowUpRight, Download, ExternalLink, Github } from "lucide-react";1112import type { OpenSourceProject } from "@/lib/apps";13import { buttonVariants } from "@/components/ui/button";14import {15 Card,16 CardContent,17 CardDescription,18 CardFooter,19 CardHeader,20 CardTitle,21} from "@/components/ui/card";22import { cn } from "@/lib/utils";2324export function ProjectCard({ project }: { project: OpenSourceProject }) {25 return (26 <Card className="group flex h-full flex-col hover:-translate-y-1 hover:border-primary/25 hover:shadow-[var(--shadow-lift)]">27 <CardHeader className="flex-row items-center gap-4 space-y-0">28 {project.icon ? (29 <Image30 src={project.icon}31 alt={`${project.name} logo`}32 width={64}33 height={64}34 className="h-16 w-16 shrink-0 rounded-[0.7rem] border border-border shadow-[var(--shadow-soft)] transition-transform duration-300 group-hover:scale-105"35 />36 ) : (37 <div38 aria-hidden="true"39 className="flex h-16 w-16 shrink-0 items-center justify-center rounded-[0.7rem] border border-foreground/15 bg-card text-3xl shadow-[var(--shadow-soft)] transition-transform duration-300 group-hover:scale-105"40 >41 {project.emoji}42 </div>43 )}44 <div className="min-w-0">45 <CardTitle className="text-lg">{project.name}</CardTitle>46 <CardDescription className="mt-1">{project.tagline}</CardDescription>47 </div>48 </CardHeader>49 <CardContent className="flex-1">50 <p className="text-sm text-muted-foreground">{project.description}</p>51 </CardContent>52 <CardFooter className="flex flex-wrap items-center gap-2">53 <Link54 href={`/apps/${project.slug}`}55 className={cn(buttonVariants({ variant: "secondary", size: "sm" }))}56 >57 <ArrowUpRight aria-hidden="true" />58 Details59 </Link>60 <a61 href={project.repo}62 target="_blank"63 rel="noopener noreferrer"64 className={cn(buttonVariants({ variant: "outline", size: "sm" }))}65 >66 <Github aria-hidden="true" />67 GitHub Repo68 </a>69 {project.demo ? (70 <a71 href={project.demo}72 target="_blank"73 rel="noopener noreferrer"74 className={cn(buttonVariants({ variant: "default", size: "sm" }))}75 >76 <ExternalLink aria-hidden="true" />77 Live Demo78 </a>79 ) : null}80 {project.dmg ? (81 <a82 href={project.dmg}83 className={cn(buttonVariants({ variant: "default", size: "sm" }))}84 >85 <Download aria-hidden="true" />86 Download DMG87 </a>88 ) : null}89 <span className="ml-auto rounded-[0.4rem] border border-border bg-muted/60 px-2 py-0.5 font-mono text-[0.62rem] uppercase tracking-[0.1em] text-muted-foreground">90 {project.language}91 </span>92 </CardFooter>93 </Card>94 );95}96