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’s UI is assembled from six focused React components, each responsible for a distinct layer of the interface. They are defined in the components/ directory as individual ES modules and loaded into the browser ahead of the main bundle via <link rel="modulepreload"> in index.html. This section describes each component, how they depend on one another, and how the module preload strategy improves cold-start performance.

The Six Core Components

1. Layout

Layout is the application shell. It renders the full-screen dark-background container and composes <FamiliarCursor>, <SmokeLayer>, and <Navigation> as persistent layers. The page content passed as children is rendered inside a motion.main element that animates opacity and brightness on each route change:
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
        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" }}
        key={location.pathname}
      >
        {children}
      </motion.main>
      <div className="fixed inset-0 pointer-events-none z-40 shadow-[inset_0_0_150px_rgba(0,0,0,0.9)]" />
    </div>
  );
};

2. Navigation

Navigation renders the fixed top bar. It maps over a navItems array to produce icon + label links and uses AnimatePresence to animate a flame indicator (activeFlame) that follows the active route using Framer Motion’s shared layoutId:
components/Navigation.js
const navItems = [
  { path: "/",             label: "The Sanctum",  icon: SanctumIcon,      incantation: "Initium Novum" },
  { path: "/about",        label: "The Witch",    icon: WitchIcon,        incantation: "Nosce Te Ipsum" },
  { path: "/projects",     label: "Summonings",   icon: SummoningsIcon,   incantation: "Fiat Lux" },
  { path: "/skills",       label: "Arcana",       icon: ArcanaIcon,       incantation: "Potentia Abscondita" },
  { path: "/work",         label: "Pacts",        icon: PactsIcon,        incantation: "Foedus Aeternum" },
  { path: "/case-studies", label: "Workings",     icon: WorkingsIcon,     incantation: "Opus Magnum" },
  { path: "/blog",         label: "Journal",      icon: JournalIcon,      incantation: "Verba Volant" },
  { path: "/testimonials", label: "Whispers",     icon: WhispersIcon,     incantation: "Vox Populi" },
  { path: "/contact",      label: "Commune",      icon: CommuneIcon,      incantation: "Nuntius Transmissus" },
];
Each item’s incantation is a Latin phrase that appears below the icon as a tooltip on hover. The active route is detected with useLocation() — a nav item is considered active when location.pathname matches its path exactly, or starts with it for non-root paths.

3. Candle

Candle is an SVG-based decorative component used throughout the interface wherever a flame visual is needed — in the home page candle-selection UI, alongside blog post cards, in the testimonials section, and on case study entries. It accepts height, width, delayIndex, isLit, and className props:
components/Candle.js
const Candle = ({
  height = 60,
  width = 20,
  delayIndex = 1,
  isLit = true,
  className = "",
}) => { /* ... */ };
When isLit is true, a motion.div flame element with a radial gradient and the CSS animate-flicker class appears above the wax body. The delayIndex prop selects one of four flicker-delay CSS utility classes (flicker-delay-1 through flicker-delay-4) to stagger the animation between multiple candles on screen.

4. FamiliarCursor

FamiliarCursor replaces the browser’s default cursor with a two-part custom cursor: a small dot that snaps to the pointer with a quick tween transition, and an SVG witch-familiar icon whose position follows with spring physics (stiffness 150, damping 25, mass 0.5):
components/FamiliarCursor.js
const springConfig = { damping: 25, stiffness: 150, mass: 0.5 };
const x = useSpring(0, springConfig);
const y = useSpring(0, springConfig);
The component listens to mousemove to update both elements and to mouseover to detect when the cursor hovers an interactive element (<a>, <button>, or [role="button"]), scaling up the dot on hover. On touch-only devices (pointer: coarse) the component returns null — no custom cursor is rendered.

5. SmokeLayer

SmokeLayer is a fixed, full-screen atmospheric background layer set behind all page content. It renders three motion.div blobs with blurred, semi-transparent fills in the app’s teal, plum, and turquoise palette colours. Each blob animates continuously on a long loop (25–35 seconds), drifting and scaling in a looping keyframe sequence:
components/SmokeLayer.js
<motion.div
  animate={{
    x: ["0%", "20%", "0%"],
    y: ["0%", "-10%", "0%"],
    scale: [1, 1.2, 1],
  }}
  transition={{ duration: 25, repeat: Infinity, ease: "easeInOut" }}
  style={{ filter: "url(#smoke-filter)" }}
/>
An inline SVG <filter> element applies fractal noise displacement via <feTurbulence> and <feDisplacementMap>, with the turbulence base frequency itself animated to create a slow, organic drift effect. The layer uses mix-blend-screen and 30% opacity to integrate naturally with the dark background.

6. Sigils

Sigils exports nine SVG icon components, one per navigation route. They are used exclusively by Navigation as the icon value in each navItems entry. Each sigil is a stateless functional component that accepts size and className props and renders a thematic SVG path design:
ExportUsed for
SanctumIcon (S)/ — The Sanctum
WitchIcon (a)/about — The Witch
SummoningsIcon (b)/projects — Summonings
ArcanaIcon (c)/skills — Arcana
PactsIcon (d)/work — Pacts
WorkingsIcon (e)/case-studies — Workings
JournalIcon (f)/blog — Journal
WhispersIcon (h)/testimonials — Whispers
CommuneIcon (g)/contact — Commune

Module Preload Strategy

Each component module is declared as a <link rel="modulepreload"> in index.html, listed in dependency order before assets/main.js executes:
index.html
<script type="module" crossorigin src="./assets/main.js"></script>
<link rel="modulepreload" crossorigin href="./assets/jsx-runtime.js">
<link rel="modulepreload" crossorigin href="./components/Sigils.js">
<link rel="modulepreload" crossorigin href="./assets/proxy.js">
<link rel="modulepreload" crossorigin href="./components/Navigation.js">
<link rel="modulepreload" crossorigin href="./components/FamiliarCursor.js">
<link rel="modulepreload" crossorigin href="./components/SmokeLayer.js">
<link rel="modulepreload" crossorigin href="./components/Layout.js">
<link rel="modulepreload" crossorigin href="./components/Candle.js">
The browser fetches and parses all listed modules in parallel before the first render. This eliminates the sequential waterfall of dynamic import() requests that would otherwise occur as each component is evaluated for the first time, reducing perceived cold-start latency on the initial page load.
All components import JSX utilities from ../assets/proxy.js for Framer Motion bindings — motion, AnimatePresence, useSpring, and related APIs are re-exported from there. React JSX itself (jsx, jsxs, and Fragment) comes from ../assets/jsx-runtime.js. Neither file contains application logic; they exist solely to bundle shared dependencies once and share them across every component module.

Build docs developers (and LLMs) love