Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion website/src/components/Header.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
import { SITE_TITLE } from '../consts';
import HeaderLink from './HeaderLink.astro';
import MobileMenuButton from './navigation/MobileMenuButton.astro';
import MobileNavigation from './navigation/MobileNavigation.astro';
---

<header class="m-0 px-4 bg-gray-900 shadow-[0_2px_8px_rgba(15,18,25,5%)] border-b border-gray-700">
Expand All @@ -10,7 +12,7 @@ import HeaderLink from './HeaderLink.astro';
>{SITE_TITLE}</a
>
</h2>
<div class="internal-links">
<div class="internal-links hidden md:flex">
<HeaderLink
href="/blog"
class="py-6 px-2 text-gray-100 border-b-4 border-transparent no-underline">Blog</HeaderLink
Expand All @@ -24,5 +26,7 @@ import HeaderLink from './HeaderLink.astro';
class="py-6 px-2 text-gray-100 border-b-4 border-transparent no-underline">About</HeaderLink
>
</div>
<MobileMenuButton />
</nav>
<MobileNavigation />
</header>
84 changes: 84 additions & 0 deletions website/src/components/navigation/DesktopNavigation.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
import { getCollection } from 'astro:content';
import projects from '../../data/projects.json';
import videos from '../../data/videos.json';

// Get recent blog posts
const blogPosts = await getCollection('blog');
const recentBlogPosts = blogPosts
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
.slice(0, 3);

// Get recent projects (already sorted by date in projects.json)
const recentProjects = projects.slice(0, 3);

// Get recent videos (already sorted by date in videos.json)
const recentVideos = videos.slice(0, 3);
---

<div class="hidden md:block w-80 h-screen fixed right-0 top-0 bg-gray-900 border-l border-gray-700 p-6 overflow-y-auto">
<nav class="mb-8">
<ul class="space-y-4">
<li><a href="/blog" class="text-gray-100 hover:text-accent transition-colors">Blog</a></li>
<li><a href="/projects" class="text-gray-100 hover:text-accent transition-colors">Projects</a></li>
<li><a href="/about" class="text-gray-100 hover:text-accent transition-colors">About</a></li>
</ul>
</nav>

<div class="mb-6">
<h3 class="text-sm font-semibold text-gray-400 mb-3">My recent posts or projects</h3>

<!-- Recent blog posts -->
<div class="space-y-4 mb-6">
{recentBlogPosts.map((post) => (
<article class="border-b border-gray-700 pb-3">
<h4 class="font-medium text-gray-100 mb-1">
<a href={`/blog/${post.slug}`} class="hover:text-accent transition-colors">{post.data.title}</a>
</h4>
<p class="text-sm text-gray-300 line-clamp-2 mb-1">
{post.data.description || ''}
</p>
<time class="text-xs text-gray-500">
{post.data.pubDate.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}
</time>
</article>
))}
</div>

<!-- Recent projects -->
<div class="space-y-4 mb-6">
{recentProjects.map((project) => (
<article class="border-b border-gray-700 pb-3">
<h4 class="font-medium text-gray-100 mb-1">
<a href={project.url} target="_blank" rel="noopener noreferrer" class="hover:text-accent transition-colors">{project.name}</a>
</h4>
<p class="text-sm text-gray-300 line-clamp-2 mb-1">
{project.description || ''}
</p>
</article>
))}
</div>

<!-- Recent videos -->
<div class="space-y-4">
{recentVideos.map((video) => (
<article class="border-b border-gray-700 pb-3">
<h4 class="font-medium text-gray-100 mb-1">
<a href={video.link} target="_blank" rel="noopener noreferrer" class="hover:text-accent transition-colors">{video.title}</a>
</h4>
<time class="text-xs text-gray-500">
{new Date(video.publishDate).toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}
</time>
</article>
))}
</div>
</div>
</div>
16 changes: 16 additions & 0 deletions website/src/components/navigation/MobileMenuButton.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
---

<div class="md:hidden">
<button
id="mobile-menu-button"
class="text-gray-100 p-2 rounded-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-accent"
aria-expanded="false"
aria-controls="mobile-menu"
>
<span class="sr-only">Open main menu</span>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
100 changes: 100 additions & 0 deletions website/src/components/navigation/MobileNavigation.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
import { getCollection } from 'astro:content';
import projects from '../../data/projects.json';
import videos from '../../data/videos.json';

// Get recent blog posts
const blogPosts = await getCollection('blog');
const recentBlogPosts = blogPosts
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
.slice(0, 3);

// Get recent projects (already sorted by date in projects.json)
const recentProjects = projects.slice(0, 3);

// Get recent videos (already sorted by date in videos.json)
const recentVideos = videos.slice(0, 3);
---

<script is:global src="../components/navigation/mobile-menu.js"></script>

<div id="mobile-menu" class="hidden md:hidden fixed inset-0 z-50 bg-gray-900 overflow-y-auto">
<div class="flex justify-end p-4">
<button
id="close-mobile-menu"
class="text-gray-100 p-2 rounded-md hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-accent"
aria-label="Close menu"
>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>

<div class="px-4 py-6">
<nav class="mb-8">
<ul class="space-y-4">
<li><a href="/blog" class="block text-2xl text-gray-100 hover:text-accent transition-colors">Blog</a></li>
<li><a href="/projects" class="block text-2xl text-gray-100 hover:text-accent transition-colors">Projects</a></li>
<li><a href="/about" class="block text-2xl text-gray-100 hover:text-accent transition-colors">About</a></li>
</ul>
</nav>

<div class="mb-6">
<h3 class="text-lg font-semibold text-gray-400 mb-4">My recent posts or projects</h3>

<!-- Recent blog posts -->
<div class="space-y-6 mb-8">
{recentBlogPosts.map((post) => (
<article>
<h4 class="font-medium text-gray-100 mb-2">
<a href={`/blog/${post.slug}`} class="hover:text-accent transition-colors text-lg">{post.data.title}</a>
</h4>
<p class="text-sm text-gray-300 mb-2">
{post.data.description || ''}
</p>
<time class="text-xs text-gray-500">
{post.data.pubDate.toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}
</time>
</article>
))}
</div>

<!-- Recent projects -->
<div class="space-y-6 mb-8">
{recentProjects.map((project) => (
<article>
<h4 class="font-medium text-gray-100 mb-2">
<a href={project.url} target="_blank" rel="noopener noreferrer" class="hover:text-accent transition-colors text-lg">{project.name}</a>
</h4>
<p class="text-sm text-gray-300 mb-2">
{project.description || ''}
</p>
</article>
))}
</div>

<!-- Recent videos -->
<div class="space-y-6">
{recentVideos.map((video) => (
<article>
<h4 class="font-medium text-gray-100 mb-2">
<a href={video.link} target="_blank" rel="noopener noreferrer" class="hover:text-accent transition-colors text-lg">{video.title}</a>
</h4>
<time class="text-xs text-gray-500">
{new Date(video.publishDate).toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}
</time>
</article>
))}
</div>
</div>
</div>
</div>
35 changes: 35 additions & 0 deletions website/src/components/navigation/mobile-menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function initMobileMenu() {
const mobileMenuButton = document.getElementById('mobile-menu-button');
const closeMobileMenuButton = document.getElementById('close-mobile-menu');
const mobileMenu = document.getElementById('mobile-menu');

if (mobileMenuButton && closeMobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.remove('hidden');
mobileMenuButton.setAttribute('aria-expanded', 'true');
});

closeMobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.add('hidden');
mobileMenuButton.setAttribute('aria-expanded', 'false');
});

// Close menu when clicking outside
mobileMenu.addEventListener('click', function(e) {
if (e.target === mobileMenu) {
mobileMenu.classList.add('hidden');
mobileMenuButton.setAttribute('aria-expanded', 'false');
}
});
}
}

// Try to init immediately if DOM is already loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMobileMenu);
} else {
initMobileMenu();
}

// Also try to init after Astro view transitions
document.addEventListener('astro:page-load', initMobileMenu);
3 changes: 2 additions & 1 deletion website/src/layouts/BlogPost.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro';
import Header from '../components/Header.astro';
import DesktopNavigation from '../components/navigation/DesktopNavigation.astro';

type Props = CollectionEntry<'blog'>['data'];

Expand Down Expand Up @@ -97,7 +98,7 @@ const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
>
<Header />
<main
class="w-full max-w-full m-0 py-[3em] px-[1em] max-[720px]:p-[1em] w-[calc(100%-2em)]"
class="w-full max-w-full m-0 py-[3em] px-[1em] max-[720px]:p-[1em] w-[calc(100%-2em)] md:pr-[22rem]"
>
<article>
<div class="w-full">
Expand Down
4 changes: 3 additions & 1 deletion website/src/layouts/Page.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
import Header from '../components/Header.astro';
import DesktopNavigation from '../components/navigation/DesktopNavigation.astro';
import backgroundImage from '../assets/Gemini_Generated_Image_swzj74swzj74swzj.png';

export interface Props {
Expand All @@ -23,7 +24,7 @@ const { title, description } = Astro.props;
>
<Header />
<main
class="w-full max-w-full m-0 py-[3em] px-[1em] max-[720px]:py-[1em] max-[720px]:px-[0.25rem] w-[calc(100%-2em)]"
class="w-full max-w-full m-0 py-[3em] px-[1em] max-[720px]:py-[1em] max-[720px]:px-[0.25rem] w-[calc(100%-2em)] md:pr-[22rem]"
>
<article>
<div
Expand All @@ -36,6 +37,7 @@ const { title, description } = Astro.props;
</div>
</article>
</main>
<DesktopNavigation />
<Footer />
</body>
</html>