SPB Git

spb/spboucher.ai Public

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

TypeScript 93.4% HTML 5.5% CSS 1%
3.2 KB · 96 lines tsx
Raw Blame History
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