Better labs cards

This commit is contained in:
Justin Edmund 2025-06-02 14:34:23 -07:00
parent 2203e050bf
commit 69193db45a
3 changed files with 95 additions and 65 deletions

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Project } from '$lib/types/project' import type { Project } from '$lib/types/project'
import Button from './admin/Button.svelte'
const { project }: { project: Project } = $props() const { project }: { project: Project } = $props()
@ -11,28 +12,40 @@
{#if isClickable} {#if isClickable}
<a href={projectUrl} class="lab-card clickable"> <a href={projectUrl} class="lab-card clickable">
<div class="card-header"> <div class="card-header">
<div class="project-title-container">
<h3 class="project-title">{project.title}</h3> <h3 class="project-title">{project.title}</h3>
<span class="project-year">{project.year}</span> <span class="project-year">{project.year}</span>
</div> </div>
<p class="project-description">{project.description}</p>
{#if project.externalUrl} {#if project.externalUrl}
<div class="project-links"> <Button
<span class="project-link primary external"> variant="primary"
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"> buttonSize="medium"
<path href={project.externalUrl}
d="M10 6H6a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-4M14 4h6m0 0v6m0-6L10 14" target="_blank"
rel="noopener noreferrer"
iconPosition="right"
onclick={(e) => e.stopPropagation()}
>
Visit site
<svg
slot="icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
/> >
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg> </svg>
Visit Project </Button>
</span>
</div>
{/if} {/if}
</div>
<p class="project-description">{project.description}</p>
<!-- Add status indicators for different project states --> <!-- Add status indicators for different project states -->
{#if project.status === 'password-protected'} {#if project.status === 'password-protected'}
@ -58,12 +71,11 @@
{:else} {:else}
<article class="lab-card"> <article class="lab-card">
<div class="card-header"> <div class="card-header">
<div class="project-title-container">
<h3 class="project-title">{project.title}</h3> <h3 class="project-title">{project.title}</h3>
<span class="project-year">{project.year}</span> <span class="project-year">{project.year}</span>
</div> </div>
<p class="project-description">{project.description}</p>
{#if project.externalUrl} {#if project.externalUrl}
<div class="project-links"> <div class="project-links">
<a <a
@ -85,6 +97,9 @@
</a> </a>
</div> </div>
{/if} {/if}
</div>
<p class="project-description">{project.description}</p>
<!-- Add status indicators for different project states --> <!-- Add status indicators for different project states -->
{#if project.status === 'list-only'} {#if project.status === 'list-only'}
@ -116,12 +131,14 @@
background: $grey-100; background: $grey-100;
border-radius: $card-corner-radius; border-radius: $card-corner-radius;
padding: $unit-3x; padding: $unit-3x;
display: flex;
flex-direction: column;
gap: $unit-3x;
transition: transition:
transform 0.2s ease, transform 0.2s ease,
box-shadow 0.2s ease; box-shadow 0.2s ease;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
display: block;
&:hover { &:hover {
transform: translateY(-2px); transform: translateY(-2px);
@ -135,20 +152,35 @@
@include breakpoint('phone') { @include breakpoint('phone') {
padding: $unit-2x; padding: $unit-2x;
} }
p {
margin-bottom: 0;
}
} }
.card-header { .card-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: baseline; align-items: flex-start;
margin-bottom: $unit-2x;
gap: $unit-2x; gap: $unit-2x;
// Style the Button component when used in card header
:global(.btn) {
flex-shrink: 0;
margin-top: 2px; // Align with title baseline
}
}
.project-title-container {
display: flex;
flex-direction: column;
gap: $unit-half;
} }
.project-title { .project-title {
margin: 0; margin: 0;
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 400;
color: $grey-00; color: $grey-00;
line-height: 1.3; line-height: 1.3;
@ -158,9 +190,9 @@
} }
.project-year { .project-year {
font-size: 0.875rem; font-size: 1rem;
color: $grey-40; color: $grey-40;
font-weight: 500; font-weight: 400;
white-space: nowrap; white-space: nowrap;
} }

View file

@ -16,9 +16,9 @@
const navItems: NavItem[] = [ const navItems: NavItem[] = [
{ icon: WorkIcon, text: 'Work', href: '/', variant: 'work' }, { icon: WorkIcon, text: 'Work', href: '/', variant: 'work' },
{ icon: LabsIcon, text: 'Labs', href: '/labs', variant: 'labs' }, { icon: UniverseIcon, text: 'Universe', href: '/universe', variant: 'universe' },
{ icon: PhotosIcon, text: 'Photos', href: '/photos', variant: 'photos' }, { icon: PhotosIcon, text: 'Photos', href: '/photos', variant: 'photos' },
{ icon: UniverseIcon, text: 'Universe', href: '/universe', variant: 'universe' } { icon: LabsIcon, text: 'Labs', href: '/labs', variant: 'labs' }
] ]
// Track hover state for each item // Track hover state for each item
@ -28,11 +28,11 @@
const activeIndex = $derived( const activeIndex = $derived(
currentPath === '/' currentPath === '/'
? 0 ? 0
: currentPath.startsWith('/labs') : currentPath.startsWith('/universe')
? 1 ? 1
: currentPath.startsWith('/photos') : currentPath.startsWith('/photos')
? 2 ? 2
: currentPath.startsWith('/universe') : currentPath.startsWith('/labs')
? 3 ? 3
: -1 : -1
) )
@ -186,25 +186,17 @@
} }
// Different animations for each nav item // Different animations for each nav item
// First item is Work (index 1) // First item is Work
.nav-item:nth-of-type(1) :global(svg.animate) { .nav-item:nth-of-type(1) :global(svg.animate) {
animation: cursorWiggle 0.6s ease; animation: cursorWiggle 0.6s ease;
} }
// Second item is Photos (index 2) - animation handled by individual rect animations // Second item is Universe
// Third item is Labs (index 3)
.nav-item:nth-of-type(2) :global(svg.animate) { .nav-item:nth-of-type(2) :global(svg.animate) {
animation: tubeRotate 0.6s ease;
transform-origin: center bottom;
}
// Fourth item is Universe (index 4)
.nav-item:nth-of-type(4) :global(svg.animate) {
animation: starSpin 0.6s ease; animation: starSpin 0.6s ease;
} }
// Specific animation for photo masonry rectangles // Third item is Photos - animation handled by individual rect animations
.nav-item:nth-of-type(3) :global(svg.animate rect:nth-child(1)) { .nav-item:nth-of-type(3) :global(svg.animate rect:nth-child(1)) {
animation: masonryRect1 0.6s ease; animation: masonryRect1 0.6s ease;
} }
@ -220,4 +212,10 @@
.nav-item:nth-of-type(3) :global(svg.animate rect:nth-child(4)) { .nav-item:nth-of-type(3) :global(svg.animate rect:nth-child(4)) {
animation: masonryRect4 0.6s ease; animation: masonryRect4 0.6s ease;
} }
// Fourth item is Labs
.nav-item:nth-of-type(4) :global(svg.animate) {
animation: tubeRotate 0.6s ease;
transform-origin: center bottom;
}
</style> </style>

View file

@ -33,7 +33,7 @@
width: 100%; width: 100%;
max-width: 900px; max-width: 900px;
margin: 0 auto; margin: 0 auto;
padding: $unit-4x $unit-3x; padding: 0 $unit-3x;
@include breakpoint('phone') { @include breakpoint('phone') {
padding: $unit-3x $unit-2x; padding: $unit-3x $unit-2x;