Skip to main content

Documentation Index

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

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

The Layout component is the persistent site shell that wraps every page in Digital Alchemy. It renders a fixed top-header with navigation, an animated <main> content area that orchestrates page transitions, a fixed footer, and the ambient CursorWisp overlay. Every view component is mounted inside Layout via main.js.

Structure

Header

Fixed to the top (fixed top-0 z-50). Contains the “M.A.” logo, desktop nav links, and a hamburger toggle for mobile. Uses mix-blend-difference so the text remains readable over any background.

Main Content

Wrapped in Framer Motion’s AnimatePresence with mode="wait". Each page enters and exits with a blur+opacity fade. The area has pt-32 to clear the fixed header and pb-24 to clear the fixed footer.

Footer

Fixed to the bottom at opacity-40. Displays the current year and the tagline “Crafted with magic” in small-caps uppercase.

Background layers

The page background is composed of two stacked layers:
  1. bg-ink — a near-black #050505 base applied to the <body>.
  2. Mystic-glow radial gradient — a full-screen position: fixed overlay with a radial gradient centred on the page, rendered at 40% opacity, giving the impression of a glowing magical light source behind the content.
The CursorWisp component is rendered directly inside Layout so it floats above all content on every route.

Page transition animation

AnimatePresence wraps the routed {children} with mode="wait", which ensures the current page’s exit animation completes before the next page starts entering. This prevents two pages from being visible simultaneously. Each child page is wrapped in a motion.main that applies the following transition:
<AnimatePresence mode="wait">
  <motion.main
    key={location.pathname}
    initial={{ opacity: 0, filter: 'blur(10px)' }}
    animate={{ opacity: 1, filter: 'blur(0px)' }}
    exit={{ opacity: 0, filter: 'blur(10px)' }}
    transition={{
      duration: 0.6,
      ease: [0.22, 1, 0.36, 1],
    }}
  >
    {children}
  </motion.main>
</AnimatePresence>
The cubic bezier [0.22, 1, 0.36, 1] is an expo-out curve — fast at the start and decelerating smoothly, giving page entrances a snappy, purposeful feel rather than a lazy linear fade.

The header contains a full set of nav items, each with an icon and a label styled in small-caps uppercase. On desktop the active route shows an animated dot indicator that uses Framer Motion’s layoutId="nav-indicator" to slide smoothly between items as the route changes.
const navItems = [
  { path: '/',             label: 'The Sanctum'        },
  { path: '/about',        label: 'The Witch'          },
  { path: '/projects',     label: 'Summonings'         },
  { path: '/skills',       label: 'Familiar Languages' },
  { path: '/work',         label: 'Lineage'            },
  { path: '/case-studies', label: 'Grimoire'           },
  { path: '/articles',     label: 'Forbidden Knowledge'},
  { path: '/testimonials', label: 'The Coven'          },
  { path: '/contact',      label: 'Cast a Message'     },
];
The logo “M.A.” is rendered in turquoise using a serif typeface and links to "/". On desktop the nav items render inline. On mobile they are hidden behind the hamburger toggle.

Mobile navigation

The mobile menu uses an AnimatePresence-controlled slide-down overlay. When the hamburger icon is tapped the overlay animates in; when dismissed it animates out. Each nav item inside the overlay enters with a staggered entrance:
// Each item staggers with an incremental delay based on its index
<motion.div
  initial={{ opacity: 0, y: -8 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ delay: index * 0.05 }}
>
  {navItem}
</motion.div>
The overlay is dismissed automatically when a nav link is tapped (via the onClick handler on each link) or when the hamburger is toggled a second time.

Usage

Layout wraps the entire <Routes> tree in main.js:
import { Layout } from './components/Layout';

root.render(
  <HashRouter>
    <Layout>
      <Routes>
        {/* all route definitions */}
      </Routes>
    </Layout>
  </HashRouter>
);
Layout itself does not manage routing state — it receives children from the router. The useLocation() hook inside Layout provides the current key passed to motion.main so Framer Motion can detect route changes and trigger the exit/enter sequence.

Build docs developers (and LLMs) love