jedmund-svelte/src/lib/components/UniverseCard.svelte
2025-06-11 00:54:05 -07:00

197 lines
3.6 KiB
Svelte

<script lang="ts">
import type { Snippet } from 'svelte'
import UniverseIcon from '$icons/universe.svg'
import PhotosIcon from '$icons/photos.svg'
import { formatDate } from '$lib/utils/date'
import { goto } from '$app/navigation'
interface UniverseItem {
slug: string
publishedAt: string
[key: string]: any
}
let {
item,
type = 'post',
children
}: {
item: UniverseItem
type?: 'post' | 'album'
children?: Snippet
} = $props()
const href = $derived(type === 'album' ? `/photos/${item.slug}` : `/universe/${item.slug}`)
const handleCardClick = (event: MouseEvent) => {
// Check if the click is on an interactive element
const target = event.target as HTMLElement
const isInteractive =
target.closest('a') ||
target.closest('button') ||
target.closest('.slideshow') ||
target.closest('.album-slideshow') ||
target.tagName === 'A' ||
target.tagName === 'BUTTON'
if (!isInteractive) {
goto(href)
}
}
</script>
<article class="universe-card universe-card--{type}">
<div
class="card-content"
onclick={handleCardClick}
role="button"
tabindex="0"
onkeydown={(e) => e.key === 'Enter' && handleCardClick(e)}
>
{@render children?.()}
<div class="card-footer">
<a {href} class="card-link" tabindex="-1">
<time class="card-date" datetime={item.publishedAt}>
{formatDate(item.publishedAt)}
</time>
</a>
{#if type === 'album'}
<PhotosIcon class="card-icon" />
{:else}
<UniverseIcon class="card-icon" />
{/if}
</div>
</div>
</article>
<style lang="scss">
@import '../../assets/styles/animations';
.universe-card {
width: 100%;
max-width: 700px;
margin: 0 auto;
}
.card-content {
padding: $unit-3x;
background: $grey-100;
border-radius: $card-corner-radius;
border: 1px solid $grey-95;
transition: all 0.2s ease;
cursor: pointer;
outline: none;
&:hover {
border-color: $grey-85;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
&:focus-visible {
outline: 2px solid $red-60;
outline-offset: 2px;
}
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: $unit-2x;
padding-top: $unit-2x;
}
.card-link {
text-decoration: none;
color: inherit;
}
.card-date {
color: $grey-40;
font-size: 0.875rem;
font-weight: 400;
transition: color 0.2s ease;
}
:global(.card-icon) {
width: 16px;
height: 16px;
fill: $grey-40;
transition: all 0.2s ease;
}
.universe-card--post {
.card-content:hover {
.card-date {
color: $red-60;
}
:global(.card-icon) {
fill: $red-60;
transform: rotate(15deg);
}
:global(.card-title-link) {
color: $red-60;
}
}
:global(.card-title-link) {
color: $grey-10;
text-decoration: none;
transition: all 0.2s ease;
}
}
.universe-card--album {
.card-content:hover {
.card-date {
color: $red-60;
}
:global(.card-icon) {
fill: $red-60;
}
:global(.card-icon rect:nth-child(1)) {
transition: all 0.3s ease;
height: 6px;
y: 2px;
}
:global(.card-icon rect:nth-child(2)) {
transition: all 0.3s ease;
height: 10px;
y: 2px;
}
:global(.card-icon rect:nth-child(3)) {
transition: all 0.3s ease;
height: 8px;
y: 10px;
}
:global(.card-icon rect:nth-child(4)) {
transition: all 0.3s ease;
height: 4px;
y: 14px;
}
:global(.card-title-link) {
color: $red-60;
}
}
// Base state for smooth transition back
:global(.card-icon rect) {
transition: all 0.3s ease;
}
:global(.card-title-link) {
color: $grey-10;
text-decoration: none;
transition: all 0.2s ease;
}
}
</style>