Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/nightshade/llms.txt

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

Nightshade handles all navigation client-side using React Router v6. Because the app is deployed as a static site on GitHub Pages, it uses HashRouter rather than BrowserRouter. Every URL encodes the current route in the hash fragment — for example /#/about — so the browser never sends route paths to the server and the single index.html entry point always loads correctly.

Router Type

HashRouter is imported from react-router-dom and wraps the entire application. All internal URLs follow the pattern /#<path>, keeping routing fully decoupled from the static file server.
import { HashRouter } from "react-router-dom";

Route Table

The following routes are defined in the Routes block inside assets/main.js. Each path maps to a dedicated page component:
PathComponentDescription
/SanctumPageHero home page with candle selection
/aboutAboutPageNarrative timeline about the developer
/projectsProjectsPageOrbital project showcase
/skillsSkillsPageHexagonal skills wheel
/workWorkPageWork history as parchment scrolls
/case-studiesCaseStudiesPageList of major case studies
/case-studies/:slugCaseStudyDetailPageIndividual case study detail
/blogBlogPageBlog post listing
/blog/:slugBlogPostPageBlog post reader
/testimonialsTestimonialsPageClient testimonials
/contactContactPageContact form

Programmatic Navigation

Several page components navigate programmatically rather than using <Link>. The useNavigate() hook is used in back buttons and in the candle-selection UI on the home page:
const navigate = useNavigate();

// Back button in CaseStudyDetailPage
<button onClick={() => navigate("/case-studies")}>← Return</button>

// Candle click on SanctumPage
onClick={() => navigate(item.path)}

Dynamic Route Parameters

The :slug segment appears in two routes. Both CaseStudyDetailPage and BlogPostPage extract it with useParams():
const { slug } = useParams();

// Derive a display title from the slug
const title = slug
  ?.split("-")
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
  .join(" ");

Page Transitions

<AnimatePresence mode="wait"> wraps the entire <Routes> block and is keyed by location.pathname (obtained from useLocation()). This ensures React unmounts the outgoing page component and plays its exit animation fully before mounting the incoming page:
const location = useLocation();

<AnimatePresence mode="wait">
  <Routes location={location} key={location.pathname}>
    {/* route definitions */}
  </Routes>
</AnimatePresence>
Each page component is itself wrapped in a motion.div or motion.main with initial, animate, and exit props, so every route change produces a coordinated fade/brightness transition driven by Framer Motion.

Per-Page HTML Hash-Redirect Pattern

Each route has a corresponding file in the pages/ directory. These static HTML files allow direct linking to specific pages on GitHub Pages. On load, a synchronous inline script checks the current hash and redirects if needed:
pages/About.html
<script>
  window.__STATIC_PAGE_ROUTE__ = "/about";
  if (!window.location.hash || window.location.hash === "#/" || window.location.hash === "#") {
    window.location.hash = "/about";
  }
</script>
The script runs before assets/main.js initialises. By the time React Router reads window.location.hash, it already contains the correct route, so the app renders the right page immediately without a redirect flicker.
Adding a new route to Nightshade requires three coordinated changes:
  1. Add a <Route> element in the Routes block inside assets/main.js, pointing to a new page component.
  2. Add a nav item in the navItems array inside components/Navigation.js with a path, label, icon, and incantation.
  3. Optionally add a new HTML file in the pages/ directory using the hash-redirect pattern shown above, so the route is directly linkable.

Build docs developers (and LLMs) love