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/og-image.jpg b/public/og-image.jpg new file mode 100644 index 0000000..a78252f Binary files /dev/null and b/public/og-image.jpg differ 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 + diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index e47b2f0..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,102 +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 */} -