Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/cosmic-developer/llms.txt

Use this file to discover all available pages before exploring further.

Cosmic Developer handles navigation entirely on the client using React Router v6. There is no server involved in resolving page URLs — every route change is managed in the browser by React Router’s <Routes> and <Route> components, wrapped inside a single <AnimatePresence> that drives cross-page transition animations.

Why Hash Routing?

The app uses HashRouter rather than BrowserRouter deliberately. When a React SPA is deployed to a static host (GitHub Pages, Netlify, S3, etc.), the server has no knowledge of client-side routes. If a user refreshes on /projects, the server looks for a file at that path, finds nothing, and returns a 404. HashRouter avoids this problem entirely by storing the current route in the URL fragment (#). The browser never sends the fragment to the server — the full URL seen by the user might be https://example.com/#/projects, but the server only sees a request for /. React Router then reads the fragment and renders the correct page component. Static HTML stubs in the pages/ directory (pages/About.html, pages/Projects.html, etc.) exist as pre-rendered fallback shells, but the interactive SPA experience is always delivered by the JavaScript bundle.

Route Table

Every route is registered in AppRoutes inside assets/main.js. The nav labels come from the navItems array defined in Navigation.js, where each item has a path, a space-themed label used in the overlay menu, and a plain desc used as the accessible title:
PathPage ComponentNav Label
/HomeEarthrise
/aboutAboutOrigin Coordinates
/projectsProjectsProbes & Payloads
/skillsSkillsInstruments
/workWorkMission Log
/case-studiesCaseStudiesFlight Recordings
/articlesArticlesTransmissions
/testimonialsTestimonialsGround Control Says
/contactContactOpen Channel

AppRoutes Implementation

AppRoutes reads the current location from useLocation() and passes it as both the <AnimatePresence> key and the <Routes> location prop. This is the pattern required to make exit animations fire correctly — if location were not passed explicitly, React Router would unmount the old page before Framer Motion can animate it out.
function AppRoutes() {
  const location = useLocation();

  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/"            element={<Home />} />
        <Route path="/about"       element={<About />} />
        <Route path="/projects"    element={<Projects />} />
        <Route path="/skills"      element={<Skills />} />
        <Route path="/work"        element={<Work />} />
        <Route path="/case-studies" element={<CaseStudies />} />
        <Route path="/articles"    element={<Articles />} />
        <Route path="/testimonials" element={<Testimonials />} />
        <Route path="/contact"     element={<Contact />} />
      </Routes>
    </AnimatePresence>
  );
}

AnimatePresence and Page Transitions

AnimatePresence with mode="wait" ensures that the outgoing page fully completes its exit animation before the incoming page begins its entrance. Without mode="wait", both animations would run simultaneously and overlap visually. Each page component is wrapped in <PageTransition>, which is a motion.div that defines the three animation states:
// PageTransition.js — the wrapper every page uses
function PageTransition({ children }) {
  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.95, filter: "blur(10px)" }}
      animate={{ opacity: 1, scale: 1,    filter: "blur(0px)"  }}
      exit={{    opacity: 0, scale: 1.05, filter: "blur(10px)" }}
      transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
      className="min-h-screen w-full pt-24 pb-20 px-6 md:px-12 lg:px-24 flex flex-col"
    >
      {children}
    </motion.div>
  );
}
The key={location.pathname} prop on <Routes> is what triggers AnimatePresence to treat each navigation as a mount/unmount cycle. When the pathname changes, React sees a new key, unmounts the current <Routes> subtree (running exit animations), then mounts the new one (running entrance animations).

Adding a New Route

1

Create the page component

Add a new function component in assets/main.js (or import it from a separate file). Wrap the return value in <PageTransition> to get the standard enter/exit animation:
function Blog() {
  return (
    <PageTransition>
      <div className="flex-1 flex flex-col">
        <h1 className="text-4xl font-heading font-bold text-star-white">
          Blog
        </h1>
        {/* Your content here */}
      </div>
    </PageTransition>
  );
}
2

Register the route in AppRoutes

Add a <Route> entry inside the <Routes> block in AppRoutes:
<Route path="/blog" element={<Blog />} />
3

Add the route to navItems in Navigation.js

The navItems array in Navigation.js drives both the full-screen overlay menu and the active-link highlighting. Add an entry with a path, a thematic label, and a plain desc:
const navItems = [
  // ... existing items
  { path: "/blog", label: "Signal Archive", desc: "Blog" },
];
4

Add a static HTML stub (optional)

For maximum compatibility on static hosts, copy one of the existing stubs in the pages/ directory (e.g., pages/About.html) and save it as pages/Blog.html. Update the <title> tag. This stub is only ever shown to crawlers or users with JavaScript disabled.
The navItems array in Navigation.js is the single source of truth for the overlay menu. It has the shape { path: string, label: string, desc: string }. The label is the large cosmic-styled text displayed in the full-screen overlay, while desc is used as the accessible description. Keep labels short enough to render well at large heading sizes — the overlay uses text-5xl to text-8xl depending on viewport width.

Build docs developers (and LLMs) love