From 262ee64dddefcd49f2c97451b34c6dd81bb0e375 Mon Sep 17 00:00:00 2001 From: donbr <7340008+donbr@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:27:22 -0700 Subject: [PATCH 1/5] fix(routing): serve SPA fallback so deep links and refreshes resolve on GitHub Pages BrowserRouter routes like /assets/projects and /concepts returned GitHub's "Page not found" when requested directly. A postbuild step now copies dist/index.html to dist/404.html, and the sitemap lists the project routes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ppa5DyJCZ1CowKtz58QARN --- package.json | 3 ++- public/sitemap.xml | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1865246..ff17dc3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "predeploy": "npm run build", "deploy": "gh-pages -d dist", "dev": "vite", - "build": "vite build" + "build": "vite build", + "postbuild": "node -e \"require('fs').copyFileSync('dist/index.html', 'dist/404.html')\"" }, "dependencies": { "@types/cytoscape": "^3.21.9", diff --git a/public/sitemap.xml b/public/sitemap.xml index b754606..b3a7e3a 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -30,4 +30,46 @@ monthly 0.7 + + https://donbr.github.io/assets/projects + 2026-10-02 + monthly + 0.9 + + + https://donbr.github.io/concepts + 2026-10-02 + monthly + 0.9 + + + https://donbr.github.io/assets/projects/gdelt-knowledge-base + 2026-10-02 + monthly + 0.6 + + + https://donbr.github.io/assets/projects/advanced-retrieval + 2026-10-02 + monthly + 0.6 + + + https://donbr.github.io/assets/projects/cytoscape + 2026-10-02 + monthly + 0.6 + + + https://donbr.github.io/assets/projects/event-analyzer + 2026-10-02 + monthly + 0.6 + + + https://donbr.github.io/assets/projects/situational-awareness + 2026-10-02 + monthly + 0.6 + From 6bce9d99fabe754d6ac67f87ccd9749abee57bc5 Mon Sep 17 00:00:00 2001 From: donbr <7340008+donbr@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:27:22 -0700 Subject: [PATCH 2/5] fix(nav): collapse navigation into a menu below lg Nine inline links overflowed phone widths. Below 1024px a menu button (aria-expanded, Escape to close) reveals a stacked link list that closes on navigation. Nav items are defined once and shared by both layouts; desktop links no longer wrap between lg and xl. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ppa5DyJCZ1CowKtz58QARN --- src/components/layout/Layout.tsx | 217 +++++++++++++++++-------------- 1 file changed, 118 insertions(+), 99 deletions(-) diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index e47b2f0..1c02193 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -1,14 +1,43 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Link, useLocation } from 'react-router-dom'; +import { Menu, X } from 'lucide-react'; import { cn } from '@/lib/utils.ts'; interface LayoutProps { children: React.ReactNode; } +type NavItem = + | { label: string; kind: 'hash'; hash: string } + | { label: string; kind: 'route'; to: string } + | { label: string; kind: 'external'; href: string }; + +const navItems: NavItem[] = [ + { label: 'About', kind: 'hash', hash: '#about' }, + { label: 'Expertise', kind: 'hash', hash: '#expertise' }, + { label: 'Publications', kind: 'hash', hash: '#publications' }, + { label: 'Projects', kind: 'route', to: '/assets/projects' }, + { label: 'Concepts', kind: 'route', to: '/concepts' }, + { label: 'Teaching', kind: 'hash', hash: '#teaching' }, + { label: 'Certifications', kind: 'hash', hash: '#certifications' }, + { label: 'Contact', kind: 'hash', hash: '#contact' }, + { label: 'Graph Demos', kind: 'external', href: 'https://graph-viz-next.vercel.app/' }, +]; + const Layout: React.FC = ({ children }) => { const location = useLocation(); - + const [menuOpen, setMenuOpen] = useState(false); + + // Close the mobile menu on Escape + useEffect(() => { + if (!menuOpen) return; + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') setMenuOpen(false); + }; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [menuOpen]); + const isActive = (path: string) => { if (path === '/' && location.pathname === '/') { return true; @@ -19,102 +48,92 @@ const Layout: React.FC = ({ children }) => { return false; }; + const isItemActive = (item: NavItem) => { + if (item.kind === 'hash') return location.hash === item.hash; + if (item.kind === 'route') return isActive(item.to); + return false; + }; + + const renderNavLink = (item: NavItem, className: string, activeClassName: string, inactiveClassName: string) => { + const classes = cn(className, isItemActive(item) ? activeClassName : inactiveClassName); + const closeMenu = () => setMenuOpen(false); + + if (item.kind === 'route') { + return ( + + {item.label} + + ); + } + if (item.kind === 'external') { + return ( + + {item.label} + + ); + } + return ( + + {item.label} + + ); + }; + return (
{/* Navigation */} -