Better labs cards
This commit is contained in:
parent
2203e050bf
commit
69193db45a
3 changed files with 95 additions and 65 deletions
|
|
@ -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,29 +12,41 @@
|
||||||
{#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">
|
||||||
<h3 class="project-title">{project.title}</h3>
|
<div class="project-title-container">
|
||||||
<span class="project-year">{project.year}</span>
|
<h3 class="project-title">{project.title}</h3>
|
||||||
|
<span class="project-year">{project.year}</span>
|
||||||
|
</div>
|
||||||
|
{#if project.externalUrl}
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
buttonSize="medium"
|
||||||
|
href={project.externalUrl}
|
||||||
|
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-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M5 12h14" />
|
||||||
|
<path d="m12 5 7 7-7 7" />
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="project-description">{project.description}</p>
|
<p class="project-description">{project.description}</p>
|
||||||
|
|
||||||
{#if project.externalUrl}
|
|
||||||
<div class="project-links">
|
|
||||||
<span class="project-link primary external">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
||||||
<path
|
|
||||||
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"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Visit Project
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Add status indicators for different project states -->
|
<!-- Add status indicators for different project states -->
|
||||||
{#if project.status === 'password-protected'}
|
{#if project.status === 'password-protected'}
|
||||||
<div class="status-indicator password-protected">
|
<div class="status-indicator password-protected">
|
||||||
|
|
@ -58,34 +71,36 @@
|
||||||
{:else}
|
{:else}
|
||||||
<article class="lab-card">
|
<article class="lab-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="project-title">{project.title}</h3>
|
<div class="project-title-container">
|
||||||
<span class="project-year">{project.year}</span>
|
<h3 class="project-title">{project.title}</h3>
|
||||||
|
<span class="project-year">{project.year}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if project.externalUrl}
|
||||||
|
<div class="project-links">
|
||||||
|
<a
|
||||||
|
href={project.externalUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="project-link primary"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path
|
||||||
|
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"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Visit Project
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="project-description">{project.description}</p>
|
<p class="project-description">{project.description}</p>
|
||||||
|
|
||||||
{#if project.externalUrl}
|
|
||||||
<div class="project-links">
|
|
||||||
<a
|
|
||||||
href={project.externalUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="project-link primary"
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
||||||
<path
|
|
||||||
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"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Visit Project
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- Add status indicators for different project states -->
|
<!-- Add status indicators for different project states -->
|
||||||
{#if project.status === 'list-only'}
|
{#if project.status === 'list-only'}
|
||||||
<div class="status-indicator list-only">
|
<div class="status-indicator 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue