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.

The Layout component is the outermost shell of every page in Nightshade. It composes all ambient UI layers — the custom cursor, animated smoke background, and fixed navigation bar — around an animated content area, ensuring every route transition plays the same brightness-fade animation and every page inherits the dark witch aesthetic.

Component signature

Layout accepts a single children prop and renders it inside a motion.main wrapper. Route changes are detected automatically through React Router’s useLocation hook.
components/Layout.js
const Layout = ({ children }) => {
  const location = useLocation();
  return (
    <div className="min-h-screen bg-witch-dark relative selection:bg-witch-plum/40 selection:text-witch-moonlight">
      <FamiliarCursor />
      <SmokeLayer />
      <Navigation />
      <motion.main
        key={location.pathname}
        initial={{ opacity: 0, filter: "brightness(0.5)" }}
        animate={{ opacity: 1, filter: "brightness(1)" }}
        exit={{ opacity: 0, filter: "brightness(0)" }}
        transition={{ duration: 0.8, ease: "easeInOut" }}
        className="pt-32 pb-16 px-6 max-w-6xl mx-auto relative z-10 min-h-screen flex flex-col"
      >
        {children}
      </motion.main>
      {/* Vignette overlay */}
      <div className="fixed inset-0 pointer-events-none z-40 shadow-[inset_0_0_150px_rgba(0,0,0,0.9)]" />
    </div>
  );
};

Internal structure

Layout orchestrates four distinct layers, rendered in stacking order:

FamiliarCursor

Replaces the browser cursor with a spring-physics witch SVG. Renders at z-[99]z-[100], above everything else.

SmokeLayer

Fixed full-screen animated background blobs at z-0. Provides the shifting atmospheric color behind all page content.

Navigation

Fixed top bar with sigil icons for each route. Sits above the smoke layer and below the cursor.

motion.main

The animated content wrapper at z-10. Receives children and plays the brightness-fade transition on every route change.
A fifth element — a fixed vignette div at z-40 — sits above the content area but below the cursor, darkening screen edges on every page.

Page transition

When the active route changes, Framer Motion’s AnimatePresence unmounts the outgoing page and mounts the incoming one. The key={location.pathname} prop on motion.main is the mechanism that makes this work: it signals to Framer Motion that the element should be treated as a completely new component mount whenever the path changes.
Phaseopacityfilter
Enter (initial)0brightness(0.5)
Active (animate)1brightness(1)
Exit0brightness(0)
The transition runs for 0.8s with an easeInOut curve. Pages appear to warm up from half-brightness rather than simply fading in from black, reinforcing the candlelit atmosphere.
key={location.pathname} on motion.main is critical. Without it, Framer Motion would not register a route change as a component remount, and the enter/exit animations would never fire.

Vignette overlay

The vignette is a fixed inset-0 div with pointer-events-none and a box-shadow using the CSS inset keyword:
box-shadow: inset 0 0 150px rgba(0,0,0,0.9)
This paints a heavy dark gradient inward from all four screen edges, framing the content as if it were lit by a single central candle. It sits at z-40 — above page content and the smoke layer, but below the cursor at z-[99].

Layout classes

ClassPurpose
pt-32Offsets content below the fixed navigation bar
pb-16 px-6Vertical and horizontal breathing room
max-w-6xl mx-autoCenters the content column and caps its width
min-h-screen flex flex-colEnsures short pages still fill the viewport

Usage

Every page component in Nightshade is automatically wrapped by Layout through the route configuration. There is no need to import or apply Layout manually inside a page file.
// Route configuration (e.g. App.jsx)
<AnimatePresence mode="wait">
  <Routes location={location} key={location.pathname}>
    <Route path="/" element={<Layout><Sanctum /></Layout>} />
    <Route path="/about" element={<Layout><TheWitch /></Layout>} />
    {/* …other routes */}
  </Routes>
</AnimatePresence>
Because AnimatePresence must see both the entering and exiting elements simultaneously, wrap Routes with AnimatePresence mode="wait" at the router level — not inside Layout itself.

Build docs developers (and LLMs) love