Some icon stuff and some labs stuff
This commit is contained in:
parent
77a0e7cdd5
commit
b1efb730a1
7 changed files with 333 additions and 19 deletions
|
|
@ -1,5 +1,7 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2" y="4" width="16" height="12" rx="2" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
||||
<circle cx="6.5" cy="8.5" r="1.5" fill="currentColor"/>
|
||||
<path d="m12 12 2-2 4 4v2a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-2l4-4 2 2" stroke="currentColor" stroke-width="1.5" fill="none"/>
|
||||
<!-- Masonry grid of photos -->
|
||||
<rect x="2" y="2" width="7" height="10" rx="1" stroke="currentColor" stroke-width="1.5"/>
|
||||
<rect x="11" y="2" width="7" height="6" rx="1" stroke="currentColor" stroke-width="1.5"/>
|
||||
<rect x="2" y="14" width="7" height="4" rx="1" stroke="currentColor" stroke-width="1.5"/>
|
||||
<rect x="11" y="10" width="7" height="8" rx="1" stroke="currentColor" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 505 B |
135
src/lib/components/LabCard.svelte
Normal file
135
src/lib/components/LabCard.svelte
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<script lang="ts">
|
||||
import type { LabProject } from '$lib/types/labs'
|
||||
|
||||
const { project }: { project: LabProject } = $props()
|
||||
</script>
|
||||
|
||||
<article class="lab-card">
|
||||
<div class="card-header">
|
||||
<h3 class="project-title">{project.title}</h3>
|
||||
<span class="project-year">{project.year}</span>
|
||||
</div>
|
||||
|
||||
<p class="project-description">{project.description}</p>
|
||||
|
||||
{#if project.url || project.github}
|
||||
<div class="project-links">
|
||||
{#if project.url}
|
||||
<a href={project.url} 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>
|
||||
{/if}
|
||||
{#if project.github}
|
||||
<a href={project.github} target="_blank" rel="noopener noreferrer" class="project-link secondary">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
|
||||
<style lang="scss">
|
||||
.lab-card {
|
||||
background: $grey-100;
|
||||
border-radius: $card-corner-radius;
|
||||
padding: $unit-3x;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@include breakpoint('phone') {
|
||||
padding: $unit-2x;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: $unit-2x;
|
||||
gap: $unit-2x;
|
||||
}
|
||||
|
||||
.project-title {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: $grey-00;
|
||||
line-height: 1.3;
|
||||
|
||||
@include breakpoint('phone') {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.project-year {
|
||||
font-size: 0.875rem;
|
||||
color: $grey-40;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.project-description {
|
||||
margin: 0 0 $unit-3x 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
color: $grey-20;
|
||||
|
||||
@include breakpoint('phone') {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.project-links {
|
||||
display: flex;
|
||||
gap: $unit-2x;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.project-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $unit;
|
||||
padding: $unit $unit-2x;
|
||||
border-radius: $unit-2x;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&.primary {
|
||||
background: $labs-color;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background: darken($labs-color, 10%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: transparent;
|
||||
color: $grey-20;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: $grey-00;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -31,13 +31,13 @@
|
|||
|
||||
function navigateLightbox(direction: 'prev' | 'next') {
|
||||
if (lightboxAlbumPhotos.length === 0) return
|
||||
|
||||
|
||||
if (direction === 'prev') {
|
||||
lightboxIndex = lightboxIndex > 0 ? lightboxIndex - 1 : lightboxAlbumPhotos.length - 1
|
||||
} else {
|
||||
lightboxIndex = lightboxIndex < lightboxAlbumPhotos.length - 1 ? lightboxIndex + 1 : 0
|
||||
}
|
||||
|
||||
|
||||
lightboxPhoto = lightboxAlbumPhotos[lightboxIndex]
|
||||
}
|
||||
</script>
|
||||
|
|
@ -51,8 +51,8 @@
|
|||
</div>
|
||||
|
||||
{#if lightboxPhoto}
|
||||
<PhotoLightbox
|
||||
photo={lightboxPhoto}
|
||||
<PhotoLightbox
|
||||
photo={lightboxPhoto}
|
||||
albumPhotos={lightboxAlbumPhotos}
|
||||
currentIndex={lightboxIndex}
|
||||
onClose={closeLightbox}
|
||||
|
|
@ -63,8 +63,8 @@
|
|||
<style lang="scss">
|
||||
.photo-grid-container {
|
||||
width: 100%;
|
||||
padding: $unit-6x $unit-2x;
|
||||
|
||||
padding: 0 $unit-2x;
|
||||
|
||||
@include breakpoint('phone') {
|
||||
padding: $unit-3x $unit;
|
||||
}
|
||||
|
|
@ -85,4 +85,4 @@
|
|||
columns: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
const navItems: NavItem[] = [
|
||||
{ icon: WorkIcon, text: 'Work', href: '/', variant: 'work' },
|
||||
{ icon: PhotosIcon, text: 'Photos', href: '/photos', variant: 'photos' },
|
||||
{ icon: LabsIcon, text: 'Labs', href: '#', variant: 'labs' },
|
||||
{ icon: LabsIcon, text: 'Labs', href: '/labs', variant: 'labs' },
|
||||
{ icon: UniverseIcon, text: 'Universe', href: '/universe', variant: 'universe' }
|
||||
]
|
||||
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
const activeIndex = $derived(
|
||||
currentPath === '/' ? 0 :
|
||||
currentPath.startsWith('/photos') ? 1 :
|
||||
currentPath.startsWith('/labs') ? 2 :
|
||||
currentPath.startsWith('/universe') ? 3 :
|
||||
-1
|
||||
)
|
||||
|
|
@ -145,7 +146,11 @@
|
|||
font-family: 'cstd', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
transition: color 0.2s ease;
|
||||
transition: color 0.2s ease, background-color 0.2s ease;
|
||||
|
||||
&:hover:not(.active) {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:global(svg.nav-icon) {
|
||||
width: 20px;
|
||||
|
|
@ -167,10 +172,7 @@
|
|||
animation: cursorWiggle 0.6s ease;
|
||||
}
|
||||
|
||||
// Second item is Photos (index 2)
|
||||
.nav-item:nth-of-type(2) :global(svg.animate) {
|
||||
animation: photoFlash 0.6s ease;
|
||||
}
|
||||
// Second item is Photos (index 2) - animation handled by individual rect animations
|
||||
|
||||
// Third item is Labs (index 3)
|
||||
.nav-item:nth-of-type(3) :global(svg.animate) {
|
||||
|
|
@ -188,9 +190,80 @@
|
|||
75% { transform: rotate(8deg) scale(1.05); }
|
||||
}
|
||||
|
||||
@keyframes photoFlash {
|
||||
0%, 100% { transform: scale(1); filter: brightness(1); }
|
||||
50% { transform: scale(1.1); filter: brightness(1.3); }
|
||||
@keyframes photoMasonry {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
25% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1);
|
||||
}
|
||||
75% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Specific animation for photo masonry rectangles
|
||||
.nav-item:nth-of-type(2) :global(svg.animate rect:nth-child(1)) {
|
||||
animation: masonryRect1 0.6s ease;
|
||||
}
|
||||
|
||||
.nav-item:nth-of-type(2) :global(svg.animate rect:nth-child(2)) {
|
||||
animation: masonryRect2 0.6s ease;
|
||||
}
|
||||
|
||||
.nav-item:nth-of-type(2) :global(svg.animate rect:nth-child(3)) {
|
||||
animation: masonryRect3 0.6s ease;
|
||||
}
|
||||
|
||||
.nav-item:nth-of-type(2) :global(svg.animate rect:nth-child(4)) {
|
||||
animation: masonryRect4 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes masonryRect1 {
|
||||
0%, 100% {
|
||||
height: 10px;
|
||||
y: 2px;
|
||||
}
|
||||
50% {
|
||||
height: 6px;
|
||||
y: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes masonryRect2 {
|
||||
0%, 100% {
|
||||
height: 6px;
|
||||
y: 2px;
|
||||
}
|
||||
50% {
|
||||
height: 10px;
|
||||
y: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes masonryRect3 {
|
||||
0%, 100% {
|
||||
height: 4px;
|
||||
y: 14px;
|
||||
}
|
||||
50% {
|
||||
height: 8px;
|
||||
y: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes masonryRect4 {
|
||||
0%, 100% {
|
||||
height: 8px;
|
||||
y: 10px;
|
||||
}
|
||||
50% {
|
||||
height: 4px;
|
||||
y: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tubeBubble {
|
||||
|
|
|
|||
14
src/lib/types/labs.ts
Normal file
14
src/lib/types/labs.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export interface LabProject {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
status: 'active' | 'maintenance' | 'archived'
|
||||
technologies: string[]
|
||||
url?: string
|
||||
github?: string
|
||||
image?: string
|
||||
featured?: boolean
|
||||
year: number
|
||||
}
|
||||
|
||||
export type ProjectStatus = 'active' | 'maintenance' | 'archived'
|
||||
38
src/routes/labs/+page.svelte
Normal file
38
src/routes/labs/+page.svelte
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import LabCard from '$components/LabCard.svelte'
|
||||
import type { PageData } from './$types'
|
||||
|
||||
const { data }: { data: PageData } = $props()
|
||||
|
||||
const projects = $derived(data.projects)
|
||||
</script>
|
||||
|
||||
<div class="labs-container">
|
||||
<div class="projects-grid">
|
||||
{#each projects as project}
|
||||
<LabCard {project} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.labs-container {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
padding: 0 $unit-2x;
|
||||
|
||||
@include breakpoint('phone') {
|
||||
padding: $unit-3x $unit;
|
||||
}
|
||||
}
|
||||
|
||||
.projects-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-3x;
|
||||
|
||||
@include breakpoint('phone') {
|
||||
gap: $unit-2x;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
src/routes/labs/+page.ts
Normal file
52
src/routes/labs/+page.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import type { PageLoad } from './$types'
|
||||
import type { LabProject } from '$lib/types/labs'
|
||||
|
||||
export const load: PageLoad = async () => {
|
||||
const projects: LabProject[] = [
|
||||
{
|
||||
id: 'granblue-team',
|
||||
title: 'granblue.team',
|
||||
description: 'A comprehensive web application for Granblue Fantasy players to track raids, manage crews, and optimize team compositions. Features real-time raid tracking, character databases, and community tools.',
|
||||
status: 'active',
|
||||
technologies: [],
|
||||
url: 'https://granblue.team',
|
||||
github: 'https://github.com/jedmund/granblue-team',
|
||||
year: 2022,
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
id: 'subway-board',
|
||||
title: 'Subway Board',
|
||||
description: 'A beautiful, minimalist dashboard displaying real-time NYC subway arrival times. Clean interface inspired by the classic subway map design with live MTA data integration.',
|
||||
status: 'maintenance',
|
||||
technologies: [],
|
||||
github: 'https://github.com/jedmund/subway-board',
|
||||
year: 2023,
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
id: 'siero-discord',
|
||||
title: 'Siero for Discord',
|
||||
description: 'A Discord bot for Granblue Fantasy communities providing character lookups, raid notifications, and server management tools. Serves thousands of users across multiple servers.',
|
||||
status: 'active',
|
||||
technologies: [],
|
||||
github: 'https://github.com/jedmund/siero-bot',
|
||||
year: 2021,
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
id: 'homelab',
|
||||
title: 'Homelab',
|
||||
description: 'Self-hosted infrastructure running on Kubernetes with monitoring, media servers, and development environments. Includes automated deployments and backup strategies.',
|
||||
status: 'active',
|
||||
technologies: [],
|
||||
github: 'https://github.com/jedmund/homelab',
|
||||
year: 2023,
|
||||
featured: false
|
||||
}
|
||||
]
|
||||
|
||||
return {
|
||||
projects
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue