11'use client'
22
33import clsx from 'clsx'
4- import { Github , Download } from 'lucide-react'
4+ import { Github , Download , ExternalLink } from 'lucide-react'
55import useSWR from 'swr'
6- import type { BrandsMap } from '~/components/ui/brand'
7- import { Brand } from '~/components/ui/brand'
8- import { GradientBorder } from '~/components/effects/gradient-border'
9- import { GrowingUnderline } from '~/components/effects/growing-underline'
6+ import { Brand , type BrandsMap } from '~/components/ui/brand'
107import { Image } from '~/components/ui/image'
118import { Link } from '~/components/ui/link'
12- import { TiltedGridBackground } from '~/components/effects/tilted-grid-background'
139import type { PROJECTS } from '~/data/projects'
1410import type { GithubRepository , NpmPackage } from '~/types/data'
1511import { fetcher } from '~/utils/misc'
1612
1713function NpmStats ( { npmPackageName, downloads } : { npmPackageName : string ; downloads : number } ) {
1814 return (
19- < div className = "space-y-1.5" >
20- < div className = "text-xs text-gray-600 dark:text-gray-400" > Downloads</ div >
21- < div className = "flex items-center gap-2" >
22- < Link
23- href = { `https://www.npmjs.com/package/${ npmPackageName } ` }
24- className = "flex items-center gap-1.5"
25- >
26- < GrowingUnderline data-umami-event = "project-npm-link" >
27- < div className = "flex items-center space-x-1.5" >
28- < Download size = { 16 } strokeWidth = { 1.5 } />
29- < span className = "font-medium" > { downloads } </ span >
30- </ div >
31- </ GrowingUnderline >
32- </ Link >
33- </ div >
34- </ div >
15+ < Link
16+ href = { `https://www.npmjs.com/package/${ npmPackageName } ` }
17+ className = "flex items-center gap-1.5 text-xs text-gray-500 transition-colors hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400"
18+ >
19+ < Download size = { 14 } strokeWidth = { 1.5 } />
20+ < span > { downloads . toLocaleString ( ) } downloads</ span >
21+ </ Link >
3522 )
3623}
3724
3825function GithubStats ( { repository } : { repository : GithubRepository } ) {
3926 return (
40- < div className = "space-y-1.5" >
41- < div className = "text-xs text-gray-600 dark:text-gray-400" >
42- < span className = "hidden sm:inline" > Github stars</ span >
43- < span className = "sm:hidden" > Stars</ span >
44- </ div >
45- < div className = "flex items-center gap-2" >
46- < Link href = { repository . url } className = "flex items-center gap-1.5" >
47- < GrowingUnderline data-umami-event = "project-github-link" >
48- < div className = "flex items-center space-x-1.5" >
49- < Github size = { 16 } strokeWidth = { 1.5 } />
50- < span className = "font-medium" > { repository . stargazerCount } </ span >
51- </ div >
52- </ GrowingUnderline >
53- </ Link >
54- </ div >
55- </ div >
27+ < Link
28+ href = { repository . url }
29+ className = "flex items-center gap-1.5 text-xs text-gray-500 transition-colors hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400"
30+ >
31+ < Github size = { 14 } strokeWidth = { 1.5 } />
32+ < span > { repository . stargazerCount . toLocaleString ( ) } stars</ span >
33+ </ Link >
5634 )
5735}
5836
5937function Stack ( { builtWith } : { builtWith : string [ ] } ) {
6038 return (
61- < div className = "space-y -1.5" >
62- < div className = "text-xs text-gray-600 dark:text-gray-400" > Stack </ div >
63- < div className = "flex h-6 flex-wrap items-center gap-1.5" >
64- { builtWith ?. map ( ( tool ) => {
65- return (
66- < Brand
67- key = { tool }
68- name = { tool as keyof typeof BrandsMap }
69- iconClassName = { clsx ( tool === 'Pygame' ? 'h-4 ' : 'h-4 w-4 ' ) }
70- />
71- )
72- } ) }
73- </ div >
39+ < div className = "flex flex-wrap items-center gap -1.5" >
40+ { builtWith ?. map ( ( tool ) => (
41+ < div
42+ key = { tool }
43+ className = "flex items-center gap-1 rounded-md bg-gray-100 px-2 py-1 text-xs text-gray-700 dark:bg-gray-800 dark:text-gray-300"
44+ >
45+ < Brand
46+ name = { tool as keyof typeof BrandsMap }
47+ iconClassName = { clsx ( tool === 'Pygame' ? 'h-3 w-3 ' : 'h-3 w-3 ' ) }
48+ />
49+ < span > { tool } </ span >
50+ </ div >
51+ ) ) }
7452 </ div >
7553 )
7654}
7755
7856export function ProjectCard ( { project } : { project : ( typeof PROJECTS ) [ 0 ] } ) {
79- const { title, description, imgSrc, url, repo, npmPackageName, builtWith, links } = project
57+ const { title, description, imgSrc, url, repo, npmPackageName, builtWith } = project
8058
8159 const { data : repository } = useSWR < GithubRepository > (
8260 repo ? `/api/github?repo=${ repo } ` : null ,
@@ -87,46 +65,53 @@ export function ProjectCard({ project }: { project: (typeof PROJECTS)[0] }) {
8765 npmPackageName ? `/api/npm?package=${ npmPackageName } ` : null ,
8866 fetcher
8967 )
90- const href = url || repository ?. url
9168
92- const propertyCount = ( npmPackage ? 1 : 0 ) + ( repository ? 1 : 0 ) + ( builtWith . length > 0 ? 1 : 0 )
69+ const href = url || repository ?. url
9370
9471 return (
95- < GradientBorder
96- offset = { 28 }
97- className = "flex flex-col rounded-[40px] p-6 [box-shadow:0_8px_32px_rgba(194,194,218,.3)] dark:bg-white/5 dark:shadow-none md:p-8"
98- >
99- < TiltedGridBackground className = "inset-0 z-[-1] rounded-[40px]" />
100- < div className = "mb-6 flex items-center gap-4" >
101- < Image src = { imgSrc } alt = { title } width = { 100 } height = { 100 } className = "h-15 w-15 shrink-0" />
102- < div className = "flex flex-col items-start gap-1 pt-1" >
103- < h2 className = "text-[22px] font-bold leading-[30px]" >
72+ < div className = "group relative flex h-full flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white transition-all hover:border-primary-500 hover:shadow-lg dark:border-gray-800 dark:bg-white/5 dark:hover:border-primary-500" >
73+ < div className = "relative aspect-[16/9] w-full overflow-hidden bg-gray-100 dark:bg-gray-800" >
74+ < Image
75+ src = { imgSrc }
76+ alt = { title }
77+ fill
78+ className = "object-cover transition-transform duration-500 group-hover:scale-105"
79+ />
80+ < div className = "absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
81+ </ div >
82+
83+ < div className = "flex flex-1 flex-col p-5" >
84+ < div className = "mb-3 flex items-start justify-between gap-4" >
85+ < h2 className = "text-xl font-bold leading-tight text-gray-900 transition-colors group-hover:text-primary-600 dark:text-gray-100 dark:group-hover:text-primary-400" >
10486 { href ? (
105- < Link href = { href } aria-label = { `Link to ${ title } ` } >
106- < GrowingUnderline data-umami-event = "project-title-link" > { title } </ GrowingUnderline >
87+ < Link href = { href } className = "flex items-center gap-2" >
88+ { title }
89+ < ExternalLink
90+ size = { 16 }
91+ className = "opacity-0 transition-opacity group-hover:opacity-100"
92+ />
10793 </ Link >
10894 ) : (
10995 title
11096 ) }
11197 </ h2 >
11298 </ div >
99+
100+ < p className = "mb-4 line-clamp-3 text-sm text-gray-600 dark:text-gray-400" > { description } </ p >
101+
102+ < div className = "mt-auto flex flex-col gap-4" >
103+ < Stack builtWith = { builtWith } />
104+
105+ { ( npmPackageName || repository ) && (
106+ < div className = "flex items-center gap-4 border-t border-gray-100 pt-3 dark:border-gray-700/50" >
107+ { npmPackageName && (
108+ < NpmStats npmPackageName = { npmPackageName } downloads = { npmPackage ?. downloads || 0 } />
109+ ) }
110+ { repository && < GithubStats repository = { repository } /> }
111+ </ div >
112+ ) }
113+ </ div >
113114 </ div >
114- < p className = "mb-16 line-clamp-3 grow text-lg" > { description } </ p >
115- < div
116- className = { clsx ( 'mt-auto flex gap-6 sm:gap-9 md:grid md:gap-0' , {
117- 'md:grid-cols-1' : propertyCount === 1 ,
118- 'md:grid-cols-2' : propertyCount === 2 ,
119- 'md:grid-cols-3' : propertyCount === 3 ,
120- 'md:grid-cols-4' : propertyCount === 4 ,
121- } ) }
122- >
123- { /* {projectProperty} */ }
124- { npmPackageName && (
125- < NpmStats npmPackageName = { npmPackageName } downloads = { npmPackage ?. downloads || 0 } />
126- ) }
127- { repository && < GithubStats repository = { repository } /> }
128- { builtWith . length > 0 && < Stack builtWith = { builtWith } /> }
129- </ div >
130- </ GradientBorder >
115+ </ div >
131116 )
132117}
0 commit comments