Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
168 lines
3.6 KiB
Plaintext
168 lines
3.6 KiB
Plaintext
---
|
|
import type { MediaValue } from "emdash";
|
|
import { Image } from "emdash/ui";
|
|
|
|
interface Props {
|
|
title: string;
|
|
summary?: string;
|
|
featuredImage: MediaValue | string;
|
|
href: string;
|
|
client?: string;
|
|
year?: string;
|
|
categories?: string[];
|
|
tags?: string[];
|
|
}
|
|
|
|
const { title, summary, featuredImage, href, client, year, categories, tags } =
|
|
Astro.props;
|
|
// Combine categories and tags for display
|
|
const allTags = [...(categories || []), ...(tags || [])];
|
|
---
|
|
|
|
<article class="project-card">
|
|
<a href={href} class="card-link">
|
|
<div class="card-image">
|
|
<Image image={featuredImage} />
|
|
<div class="card-overlay">
|
|
<span class="card-view">View Project</span>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<h2 class="card-title">{title}</h2>
|
|
<div class="card-meta">
|
|
{client && <span class="card-client">{client}</span>}
|
|
{client && year && <span class="card-separator">·</span>}
|
|
{year && <span class="card-year">{year}</span>}
|
|
</div>
|
|
{summary && <p class="card-summary">{summary}</p>}
|
|
{
|
|
allTags.length > 0 && (
|
|
<div class="card-categories">
|
|
{allTags.map((tag) => (
|
|
<span class="card-category">{tag}</span>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
</a>
|
|
</article>
|
|
|
|
<style>
|
|
.project-card {
|
|
position: relative;
|
|
}
|
|
|
|
.card-link {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-lg, 1.5rem);
|
|
text-decoration: none;
|
|
color: inherit;
|
|
}
|
|
|
|
.card-image {
|
|
position: relative;
|
|
overflow: hidden;
|
|
border-radius: var(--radius, 4px);
|
|
}
|
|
|
|
.card-image img {
|
|
width: 100%;
|
|
height: auto;
|
|
aspect-ratio: 4 / 3;
|
|
object-fit: cover;
|
|
transition: transform var(--transition-slow, 300ms ease);
|
|
}
|
|
|
|
.card-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0);
|
|
transition: background var(--transition-base, 200ms ease);
|
|
}
|
|
|
|
.card-view {
|
|
font-family: var(--font-serif, Georgia, serif);
|
|
font-size: var(--font-size-sm, 0.875rem);
|
|
color: white;
|
|
padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem);
|
|
border: 1px solid white;
|
|
border-radius: var(--radius, 4px);
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
transition:
|
|
opacity var(--transition-base, 200ms ease),
|
|
transform var(--transition-base, 200ms ease);
|
|
}
|
|
|
|
.project-card:hover .card-image img {
|
|
transform: scale(1.03);
|
|
}
|
|
|
|
.project-card:hover .card-overlay {
|
|
background: rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.project-card:hover .card-view {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.card-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-xs, 0.25rem);
|
|
}
|
|
|
|
.card-title {
|
|
font-family: var(--font-serif, Georgia, serif);
|
|
font-size: var(--font-size-xl, 1.5rem);
|
|
font-weight: 500;
|
|
transition: color var(--transition-fast, 150ms ease);
|
|
}
|
|
|
|
.project-card:hover .card-title {
|
|
color: var(--color-accent, #7c3aed);
|
|
}
|
|
|
|
.card-meta {
|
|
font-size: var(--font-size-sm, 0.875rem);
|
|
color: var(--color-muted, #6b7280);
|
|
}
|
|
|
|
.card-separator {
|
|
margin: 0 var(--spacing-xs, 0.25rem);
|
|
}
|
|
|
|
.card-summary {
|
|
font-size: var(--font-size-sm, 0.875rem);
|
|
color: var(--color-muted, #6b7280);
|
|
line-height: 1.6;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card-categories {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--spacing-sm, 0.5rem);
|
|
margin-top: var(--spacing-sm, 0.5rem);
|
|
}
|
|
|
|
.card-category {
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--color-muted, #6b7280);
|
|
padding: var(--spacing-xs, 0.25rem) var(--spacing-sm, 0.5rem);
|
|
border: 1px solid var(--color-border, #e5e7eb);
|
|
border-radius: var(--radius, 4px);
|
|
}
|
|
</style>
|