import { Downasaur, Github, Home as HomeIcon, Image, ImageDelete, Mail, Notes, } from "@nsmr/pixelart-react"; import { useEffect, useState } from "react"; import { Link, Route, Routes, useNavigate, useParams } from "react-router-dom"; import { AskAI } from "@/components/ask-ai"; import { ProjectListItem } from "@/components/ProjectListItem"; import { TimeSince } from "@/components/time-since"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { type ProjectEntry, projectList, projects } from "@/projects"; import { locationPhotos, locations } from "@/travel"; import { ThemeToggle } from "./components/theme-toggle"; import { Button } from "./components/ui/button"; import { cn } from "./lib/utils"; const asciiFiles = [ "cat-sleep.txt", "polar-bear.txt", "penguin-surfboard.txt", "cat-shock.txt", "exclamation.txt", "fat-cat-head.txt", "grumpy-dog.txt", "cat-peek.txt", "cat-loaf.txt", ]; const homeTabs = ["work", "travel"] as const; type HomeTab = (typeof homeTabs)[number]; function App() { return ( } /> } /> } /> ); } export default App; function Home() { const isDevMode = import.meta.env.VITE_PUBLIC_DEV === "1"; const isTabsEnabled = import.meta.env.VITE_TABS === "1"; const navigate = useNavigate(); const [asciiArt, setAsciiArt] = useState(""); const [activeHomeTab, setActiveHomeTab] = useState("work"); const [activeProjectIndex, setActiveProjectIndex] = useState( null, ); const [activeLocationIndex, setActiveLocationIndex] = useState( null, ); const [expandedLocationIndex, setExpandedLocationIndex] = useState< number | null >(null); const [previewPhotoPath, setPreviewPhotoPath] = useState(null); const [travelFocusLevel, setTravelFocusLevel] = useState< "location" | "photo" >("location"); const [activePhotoIndexByLocation, setActivePhotoIndexByLocation] = useState< Record >({}); const [asciiFile] = useState( () => asciiFiles[Math.floor(Math.random() * asciiFiles.length)], ); const sortedProjects: ProjectEntry[] = [...projectList].sort( (a, b) => parseDate(b.metadata.date).getTime() - parseDate(a.metadata.date).getTime(), ); const visibleProjects = sortedProjects.filter( (project) => isDevMode || !project.metadata.hidden, ); useEffect(() => { let isActive = true; fetch(`/ascii/${asciiFile}`) .then((response) => response.text()) .then((text) => { if (isActive) setAsciiArt(text); }); return () => { isActive = false; }; }, [asciiFile]); useEffect(() => { setActiveProjectIndex((prev) => { if (visibleProjects.length === 0) return prev; if (prev === null) return null; return Math.min(prev, visibleProjects.length - 1); }); }, [visibleProjects.length]); useEffect(() => { if (activeHomeTab !== "travel") { setTravelFocusLevel("location"); return; } setActiveLocationIndex((prev) => { if (locations.length === 0) return null; if (prev === null) return 0; return clampIndex(prev, locations.length); }); setExpandedLocationIndex((prev) => { if (prev === null || locations.length === 0) return null; return clampIndex(prev, locations.length); }); }, [activeHomeTab]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.defaultPrevented || event.isComposing) return; if (event.metaKey || event.ctrlKey || event.altKey) return; if (isTabsEnabled && event.key === "Tab") { event.preventDefault(); setActiveHomeTab((prev) => { const currentIndex = homeTabs.indexOf(prev); const safeIndex = currentIndex === -1 ? 0 : currentIndex; const nextIndex = (safeIndex + 1) % homeTabs.length; return homeTabs[nextIndex]; }); return; } if (isInteractiveTarget(event.target)) return; const key = event.key.length === 1 ? event.key.toLowerCase() : event.key; if (activeHomeTab === "work") { if (visibleProjects.length === 0) return; const isDesktop = window.matchMedia("(min-width: 768px)").matches; const columns = isDesktop ? 2 : 1; let delta = 0; if (key === "ArrowLeft" || key === "h") delta = -1; if (key === "ArrowRight" || key === "l") delta = 1; if (key === "ArrowUp" || key === "k") delta = -columns; if (key === "ArrowDown" || key === "j") delta = columns; if (delta !== 0) { event.preventDefault(); setActiveProjectIndex((prev) => { if (prev === null) return 0; return clampIndex(prev + delta, visibleProjects.length); }); return; } if (key === "Enter") { if (activeProjectIndex === null) return; event.preventDefault(); const target = visibleProjects[activeProjectIndex]; if (!target) return; navigate(`/projects/${target.metadata.slug}`); } return; } if (locations.length === 0) return; const expandedLocation = expandedLocationIndex === null ? null : locations[expandedLocationIndex]; const expandedLocationPhotoList = expandedLocation ? (locationPhotos[expandedLocation.id] ?? []) : []; const isAtPhotoLevel = travelFocusLevel === "photo" && expandedLocationIndex !== null && expandedLocationPhotoList.length > 0; const locationDelta = getLinearDelta(key); if (isAtPhotoLevel) { if (expandedLocationIndex === null || !expandedLocation) return; const locationId = expandedLocation.id; const currentPhotoIndex = activePhotoIndexByLocation[locationId] ?? 0; if ((key === "ArrowUp" || key === "k") && currentPhotoIndex === 0) { event.preventDefault(); setTravelFocusLevel("location"); return; } if ( (key === "ArrowDown" || key === "j") && currentPhotoIndex === expandedLocationPhotoList.length - 1 ) { event.preventDefault(); setTravelFocusLevel("location"); setActiveLocationIndex( clampIndex(expandedLocationIndex + 1, locations.length), ); return; } if (locationDelta !== 0) { event.preventDefault(); const nextPhotoIndex = clampIndex( currentPhotoIndex + locationDelta, expandedLocationPhotoList.length, ); setActivePhotoIndexByLocation((prev) => ({ ...prev, [locationId]: nextPhotoIndex, })); const nextPhotoName = expandedLocationPhotoList[nextPhotoIndex]; if (nextPhotoName) { setPreviewPhotoPath( getTravelPhotoPath(expandedLocation, nextPhotoName), ); } return; } return; } if (locationDelta !== 0) { if ( (key === "ArrowUp" || key === "k") && activeLocationIndex !== null ) { const nextLocationIndex = clampIndex( activeLocationIndex - 1, locations.length, ); const nextLocation = locations[nextLocationIndex]; const nextLocationPhotos = nextLocation ? (locationPhotos[nextLocation.id] ?? []) : []; const lastPhotoIndex = nextLocationPhotos.length - 1; const lastPhotoName = nextLocationPhotos[lastPhotoIndex]; if ( expandedLocationIndex === nextLocationIndex && nextLocation && lastPhotoName ) { event.preventDefault(); setActiveLocationIndex(nextLocationIndex); setActivePhotoIndexByLocation((prev) => ({ ...prev, [nextLocation.id]: lastPhotoIndex, })); setPreviewPhotoPath( getTravelPhotoPath(nextLocation, lastPhotoName), ); setTravelFocusLevel("photo"); return; } } if ( activeLocationIndex !== null && expandedLocationIndex === activeLocationIndex && (key === "ArrowDown" || key === "j") ) { const location = locations[activeLocationIndex]; const photoList = location ? (locationPhotos[location.id] ?? []) : []; const firstPhotoName = photoList[0]; if (location && firstPhotoName) { event.preventDefault(); setActivePhotoIndexByLocation((prev) => ({ ...prev, [location.id]: 0, })); setPreviewPhotoPath(getTravelPhotoPath(location, firstPhotoName)); setTravelFocusLevel("photo"); return; } } event.preventDefault(); setActiveLocationIndex((prev) => { if (prev === null) return 0; return clampIndex(prev + locationDelta, locations.length); }); return; } if (key === "Enter") { if (activeLocationIndex === null) return; event.preventDefault(); if (expandedLocationIndex === activeLocationIndex) { setExpandedLocationIndex(null); setPreviewPhotoPath(null); setTravelFocusLevel("location"); return; } const location = locations[activeLocationIndex]; if (!location) return; const photoList = locationPhotos[location.id] ?? []; setExpandedLocationIndex(activeLocationIndex); if (photoList.length === 0) { setPreviewPhotoPath(null); setTravelFocusLevel("location"); return; } const nextPhotoIndex = clampIndex( activePhotoIndexByLocation[location.id] ?? 0, photoList.length, ); const nextPhotoName = photoList[nextPhotoIndex]; if (!nextPhotoName) return; setActivePhotoIndexByLocation((prev) => ({ ...prev, [location.id]: nextPhotoIndex, })); setPreviewPhotoPath(getTravelPhotoPath(location, nextPhotoName)); setTravelFocusLevel("photo"); } }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; }, [ activeHomeTab, activeLocationIndex, activePhotoIndexByLocation, activeProjectIndex, expandedLocationIndex, navigate, travelFocusLevel, visibleProjects, ]); return (
{asciiArt ? (
            {asciiArt}
          
) : null}

Oliver Bryan

Age:
{isTabsEnabled ? ( setActiveHomeTab(value as HomeTab)} className="w-full max-w-5xl gap-0" > Work Travel
{visibleProjects.map((project, index) => ( ))}
{locations.map((location, index) => (
{expandedLocationIndex === index && (locationPhotos[location.id].length === 0 ? (
) : (
{locationPhotos[location.id].map( (photo, photoIndex) => ( ), )}
{previewPhotoPath ? ( {"active-photo"} ) : (
No photo selected
)}
))}
))}
) : (
{visibleProjects.map((project, index) => ( ))}
)}

arrows or hjkl, then enter

); } function clampIndex(index: number, itemCount: number): number { return Math.max(0, Math.min(itemCount - 1, index)); } function getLinearDelta(key: string): number { if (key === "ArrowLeft" || key === "h" || key === "ArrowUp" || key === "k") { return -1; } if ( key === "ArrowRight" || key === "l" || key === "ArrowDown" || key === "j" ) { return 1; } return 0; } function getTravelPhotoPath( location: { city: string; country: string; date: string }, photo: string, ): string { return `/travel/${location.city} ${location.country} ${location.date}/${photo}`; } function isInteractiveTarget(target: EventTarget | null): boolean { if (!(target instanceof HTMLElement)) return false; if (target.isContentEditable) return true; const tagName = target.tagName; return ( tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT" || tagName === "BUTTON" || tagName === "A" ); } function ProjectRoute() { const { slug } = useParams(); if (!slug || !projects[slug]) return ; const { Component } = projects[slug]; return ; } function NotFound() { return (
? 404 Not Found
); } // function NotFound() { // return ( //
// // // // //
// ); // } function parseDate(dateStr: string): Date { const lower = dateStr.toLowerCase(); if (lower.includes("q1")) return new Date("2023-01-01"); if (lower.includes("q2")) return new Date("2023-04-01"); if (lower.includes("q3")) return new Date("2023-07-01"); if (lower.includes("q4")) return new Date("2023-10-01"); const months: Record = { january: 0, february: 1, march: 2, april: 3, may: 4, june: 5, july: 6, august: 7, september: 8, october: 9, november: 10, december: 11, }; for (const [monthName, monthIndex] of Object.entries(months)) { if (lower.includes(monthName)) { const yearMatch = dateStr.match(/\b(20\d{2})\b/); if (yearMatch) { return new Date(Number.parseInt(yearMatch[1], 10), monthIndex, 1); } } } const yearMatch = dateStr.match(/\b(20\d{2})\b/); if (yearMatch) { return new Date(Number.parseInt(yearMatch[1], 10), 0, 1); } return new Date(0); }