From 33af66d0a3a687dd7cea538fd569e4acd6c2c780 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 01/23] 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 | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 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..a0652ef 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -30,4 +30,40 @@ monthly 0.7 + + https://donbr.github.io/assets/projects + 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 d7fd5d0d8f7db6eadb6f9c72c3d4a52e0f947abd 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 02/23] 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 | 207 +++++++++++++++++-------------- 1 file changed, 117 insertions(+), 90 deletions(-) diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 2a3c251..5787042 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -1,14 +1,42 @@ -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: '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,93 +47,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 */} -