Skip to content

Commit 7019788

Browse files
committed
feat: refactor ProjectCard and add ProjectCardFeatured for improved project display
1 parent 7ad59a4 commit 7019788

4 files changed

Lines changed: 236 additions & 123 deletions

File tree

src/components/ProjectCard.css

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -143,52 +143,111 @@
143143
gap: var(--space-sm);
144144
}
145145

146-
/* Icon badge + title row */
147-
.pc-header {
146+
/* Small uppercase meta line above the title: category • date */
147+
.pc-meta-line {
148148
display: flex;
149-
align-items: flex-start;
150-
gap: 0.75rem;
149+
align-items: center;
150+
gap: 0.5rem;
151+
font-size: 0.7rem;
152+
font-weight: 600;
153+
text-transform: uppercase;
154+
letter-spacing: 0.06em;
155+
color: var(--color-muted);
156+
min-height: 1.2em;
157+
}
158+
159+
.pc-meta-category {
160+
color: var(--project-accent, var(--color-accent));
161+
}
162+
163+
.pc-meta-category + .pc-meta-date::before {
164+
content: "•";
165+
margin-right: 0.5rem;
166+
color: var(--color-divider);
151167
}
152168

153-
.pc-icon-badge {
154-
width: 2.5rem;
155-
height: 2.5rem;
169+
.pc-meta-date {
170+
color: var(--color-muted);
171+
}
172+
173+
/* Icon overlay on the thumbnail — bottom-left pill */
174+
.pc-icon-overlay {
175+
position: absolute;
176+
bottom: 0.625rem;
177+
left: 0.625rem;
178+
z-index: 3;
179+
display: flex;
180+
align-items: center;
181+
justify-content: center;
182+
width: 2.25rem;
183+
height: 2.25rem;
156184
border-radius: var(--radius-md);
157185
background: var(--project-accent, var(--color-accent));
158186
color: white;
159-
flex-shrink: 0;
187+
font-size: 1rem;
188+
box-shadow: 0 4px 12px
189+
color-mix(in srgb, var(--project-accent, var(--color-accent)) 40%, #000 40%);
190+
transition:
191+
transform 0.25s ease,
192+
box-shadow 0.25s ease;
193+
}
194+
195+
.project-card-regular:hover .pc-icon-overlay {
196+
transform: scale(1.06);
197+
}
198+
199+
/* Inline strip (icon + featured/archived badges) — shown when the thumbnail is hidden */
200+
.pc-inline-strip {
160201
display: flex;
161202
align-items: center;
203+
gap: 0.5rem;
204+
margin-bottom: 0.25rem;
205+
}
206+
207+
.pc-icon-inline {
208+
display: inline-flex;
209+
align-items: center;
162210
justify-content: center;
163-
font-size: 1.1rem;
164-
transition:
165-
transform 0.2s ease,
166-
box-shadow 0.2s ease;
211+
width: 2rem;
212+
height: 2rem;
213+
border-radius: var(--radius-md);
214+
background: var(--project-accent, var(--color-accent));
215+
color: white;
216+
font-size: 0.9rem;
217+
flex-shrink: 0;
167218
}
168219

169-
.project-card-regular:hover .pc-icon-badge {
170-
transform: scale(1.08);
171-
box-shadow: 0 4px 10px
172-
color-mix(
173-
in srgb,
174-
var(--project-accent, var(--color-accent)) 35%,
175-
transparent
176-
);
220+
.pc-inline-badge {
221+
display: inline-flex;
222+
align-items: center;
223+
justify-content: center;
224+
width: 1.5rem;
225+
height: 1.5rem;
226+
border-radius: 50%;
227+
font-size: 0.7rem;
228+
flex-shrink: 0;
177229
}
178230

179-
.pc-header-meta {
180-
flex: 1;
181-
min-width: 0;
182-
display: flex;
183-
flex-direction: column;
184-
gap: 0.2rem;
231+
.pc-inline-badge--featured {
232+
background: var(--color-accent);
233+
color: var(--color-text-on-primary, white);
185234
}
186235

187-
.pc-date {
188-
font-size: 0.75rem;
236+
.pc-inline-badge--archived {
237+
background: var(--color-raised);
238+
border: 1px solid var(--color-divider);
189239
color: var(--color-muted);
190240
}
191241

242+
/* Hide the inline strip when the thumbnail is visible (≥540px, non-minimal cards).
243+
The `--always` modifier opts minimal cards (which never show a thumbnail) into
244+
the mobile strip at every viewport size. */
245+
@media (min-width: 540px) {
246+
.pc-inline-strip:not(.pc-inline-strip--always) {
247+
display: none;
248+
}
249+
}
250+
192251
/* Archive corner badge (top-right of thumbnail, mirrors featured-star) */
193252
.pc-archived-badge {
194253
position: absolute;

src/components/ProjectCard.tsx

Lines changed: 41 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -10,97 +10,15 @@ import { placeholderImage } from "../utils/placeholder";
1010
type ProjectCardProps = {
1111
project: Project;
1212
minimal?: boolean;
13-
featured?: boolean;
1413
};
1514

16-
const ProjectCard = ({
17-
project,
18-
minimal = false,
19-
featured = false,
20-
}: ProjectCardProps) => {
15+
const ProjectCard = ({ project, minimal = false }: ProjectCardProps) => {
2116
const styleVars = {
2217
"--project-accent": project.accentColor || undefined,
2318
} as CSSProperties;
2419

2520
const technologies = project.technologies ?? [];
26-
27-
if (featured) {
28-
return (
29-
<article className="project-card project-card-featured" style={styleVars}>
30-
<Link
31-
to={entryPath(project)}
32-
className="project-image-container"
33-
tabIndex={-1}
34-
aria-hidden
35-
>
36-
{project.featured && (
37-
<span
38-
className="featured-star"
39-
aria-label="Featured project"
40-
title="Featured project"
41-
>
42-
<Icon name="star" />
43-
</span>
44-
)}
45-
{project.icon && (
46-
<span className="project-icon-badge" aria-hidden="true">
47-
<Icon name={project.icon} />
48-
</span>
49-
)}
50-
<img
51-
src={project.thumbnail || placeholderImage(project.title)}
52-
alt={project.title}
53-
className="project-image"
54-
loading="lazy"
55-
/>
56-
</Link>
57-
<div className="project-info">
58-
<div className="flex flex-wrap items-center gap-2 mb-2">
59-
{project.categories.map((cat) => (
60-
<span
61-
key={cat}
62-
className="px-2 py-0.5 rounded-full bg-accent/10 text-accent text-xs font-semibold"
63-
>
64-
{cat}
65-
</span>
66-
))}
67-
{project.date && (
68-
<span className="text-xs text-muted">{project.date}</span>
69-
)}
70-
</div>
71-
<h3 className="project-title">
72-
<Link to={entryPath(project)} className="project-card-link">
73-
{project.title}
74-
</Link>
75-
</h3>
76-
<p className="project-subtitle">{project.tagline}</p>
77-
<p className="project-description">{project.description}</p>
78-
{technologies.length > 0 && (
79-
<div className="project-tech tech-row tech-row-tech">
80-
{technologies.slice(0, 4).map((t) => (
81-
<span key={`tech-${t}`}>
82-
<Link to={`/tech/${toSlug(t)}`} className="tech-tag">
83-
{t}
84-
</Link>
85-
</span>
86-
))}
87-
{technologies.length > 4 && (
88-
<Link
89-
to={entryPath(project)}
90-
className="tech-tag tech-tag-more"
91-
>
92-
+{technologies.length - 4}
93-
</Link>
94-
)}
95-
</div>
96-
)}
97-
<Link to={entryPath(project)} className="project-view-link">
98-
View Project <Icon name="arrow-right" size="0.7em" />
99-
</Link>
100-
</div>
101-
</article>
102-
);
103-
}
21+
const primaryCategory = project.categories?.[0];
10422

10523
return (
10624
<article
@@ -138,23 +56,52 @@ const ProjectCard = ({
13856
className="pc-thumbnail"
13957
loading="lazy"
14058
/>
59+
<span className="pc-icon-overlay" aria-hidden="true">
60+
<Icon name={project.icon || "cube"} />
61+
</span>
14162
</Link>
14263
)}
14364
<div className="pc-accent-bar" />
14465
<div className="pc-body">
145-
<div className="pc-header">
146-
<span className="pc-icon-badge" aria-hidden="true">
66+
<div
67+
className={`pc-inline-strip${minimal ? " pc-inline-strip--always" : ""}`}
68+
aria-hidden="true"
69+
>
70+
<span className="pc-icon-inline">
14771
<Icon name={project.icon || "cube"} />
14872
</span>
149-
<div className="pc-header-meta">
150-
<h3 className="project-title">
151-
<Link to={entryPath(project)} className="project-card-link">
152-
{project.title}
153-
</Link>
154-
</h3>
155-
{project.date && <span className="pc-date">{project.date}</span>}
156-
</div>
73+
{project.featured && (
74+
<span
75+
className="pc-inline-badge pc-inline-badge--featured"
76+
title="Featured project"
77+
>
78+
<Icon name="star" />
79+
</span>
80+
)}
81+
{project.archived && (
82+
<span
83+
className="pc-inline-badge pc-inline-badge--archived"
84+
title="This project is archived"
85+
>
86+
<Icon name="archive" />
87+
</span>
88+
)}
15789
</div>
90+
{(primaryCategory || project.date) && (
91+
<div className="pc-meta-line">
92+
{primaryCategory && (
93+
<span className="pc-meta-category">{primaryCategory}</span>
94+
)}
95+
{project.date && (
96+
<span className="pc-meta-date">{project.date}</span>
97+
)}
98+
</div>
99+
)}
100+
<h3 className="project-title">
101+
<Link to={entryPath(project)} className="project-card-link">
102+
{project.title}
103+
</Link>
104+
</h3>
158105
<p className="project-subtitle">{project.tagline}</p>
159106
{!minimal && technologies.length > 0 && (
160107
<div className="project-tech tech-row tech-row-tech">

0 commit comments

Comments
 (0)