diff --git a/src/assets/icons/photos.svg b/src/assets/icons/photos.svg index 8c3b4fd..a218f99 100644 --- a/src/assets/icons/photos.svg +++ b/src/assets/icons/photos.svg @@ -1,5 +1,7 @@ - - - + + + + + \ No newline at end of file diff --git a/src/lib/components/LabCard.svelte b/src/lib/components/LabCard.svelte new file mode 100644 index 0000000..99d81a1 --- /dev/null +++ b/src/lib/components/LabCard.svelte @@ -0,0 +1,135 @@ + + +
+
+

{project.title}

+ {project.year} +
+ +

{project.description}

+ + {#if project.url || project.github} + + {/if} +
+ + \ No newline at end of file diff --git a/src/lib/components/PhotoGrid.svelte b/src/lib/components/PhotoGrid.svelte index 172f341..9069060 100644 --- a/src/lib/components/PhotoGrid.svelte +++ b/src/lib/components/PhotoGrid.svelte @@ -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] } @@ -51,8 +51,8 @@ {#if lightboxPhoto} - .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; } } - \ No newline at end of file + diff --git a/src/lib/components/SegmentedController.svelte b/src/lib/components/SegmentedController.svelte index 38c1c06..cd8754d 100644 --- a/src/lib/components/SegmentedController.svelte +++ b/src/lib/components/SegmentedController.svelte @@ -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 { diff --git a/src/lib/types/labs.ts b/src/lib/types/labs.ts new file mode 100644 index 0000000..57216cc --- /dev/null +++ b/src/lib/types/labs.ts @@ -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' \ No newline at end of file diff --git a/src/routes/labs/+page.svelte b/src/routes/labs/+page.svelte new file mode 100644 index 0000000..ef45071 --- /dev/null +++ b/src/routes/labs/+page.svelte @@ -0,0 +1,38 @@ + + +
+
+ {#each projects as project} + + {/each} +
+
+ + diff --git a/src/routes/labs/+page.ts b/src/routes/labs/+page.ts new file mode 100644 index 0000000..e609f99 --- /dev/null +++ b/src/routes/labs/+page.ts @@ -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 + } +} \ No newline at end of file