Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/neon-retro-web/llms.txt

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

Neon Retro Web runs two distinct animation layers that never interfere with each other. Framer Motion owns the page-level experience — orchestrating entrance, presence, and exit transitions as React Router v6 navigates between routes. Below that, three custom CSS keyframe animations compiled into assets/main.css handle repeating, component-level effects: a blinking cursor badge, a horizontally scrolling marquee ticker, and a slow gear rotation. Understanding which layer controls which effect makes it straightforward to tune the feel of the site or add new motion without breaking existing transitions.
tailwind.config.js is not included in the public repository. Neon Retro Web ships as a pre-built static site — the three custom keyframe animations (blink, marquee, spin) and their utility classes are compiled into assets/main.css. To add or change keyframe definitions in the Tailwind pipeline you need the original source project. For one-off timing tweaks you can edit the animation: values directly in assets/main.css.

Page Transitions with Framer Motion

AnimatePresence and Route Setup

AnimatePresence is mounted once in main.jsx, wrapping the React Router outlet so that exiting route components can complete their exit animation before being unmounted from the DOM:
// main.jsx
import { AnimatePresence } from "framer-motion";
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";

function AnimatedRoutes() {
  const location = useLocation();
  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/"        element={<Home />} />
        <Route path="/about"   element={<About />} />
        <Route path="/work"    element={<Work />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </AnimatePresence>
  );
}
The mode="wait" prop tells AnimatePresence to finish the outgoing page’s exit animation completely before the incoming page begins its entrance — preventing two pages from being visible simultaneously.

Standard Page Motion Pattern

Every page component wraps its root element in a motion.div with a consistent fade:
// Any page component, e.g. About.jsx
import { motion } from "framer-motion";

export default function About() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.3 }}
    >
      {/* page content */}
    </motion.div>
  );
}

Changing to a Slide Transition

Replace the opacity-only variants with x (horizontal) or y (vertical) offsets to get a slide effect instead of a fade:
<motion.div
  initial={{ opacity: 0, x: 40 }}
  animate={{ opacity: 1, x: 0 }}
  exit={{ opacity: 0, x: -40 }}
  transition={{ duration: 0.35, ease: "easeInOut" }}
>

Window Component Spring Animation

The retro OS window chrome component uses a spring physics transition for its mount animation, giving it a satisfying bounce that echoes the era’s exaggerated UI feedback:
// Window.jsx
<motion.div
  initial={{ opacity: 0, scale: 0.92, y: 20 }}
  animate={{ opacity: 1, scale: 1, y: 0 }}
  transition={{
    type: "spring",
    stiffness: 300,
    damping: 25,
  }}
>
Reduce stiffness (e.g., 150) for a slower, bouncier spring. Increase damping (e.g., 40) to remove the bounce and make it settle quickly.

CSS Keyframe Animations

Three custom keyframe animations are compiled into assets/main.css. They complement Framer Motion for effects that run continuously rather than triggering once on mount.

Animation Quick Reference

AnimationClassDurationUsage
Blinkanimate-blink1.5s step-end infiniteBadgeNew — “NEW” badge cursor blink
Marqueeanimate-marquee20s linear infiniteMarquee — horizontal news-ticker scroll
Spin Slowanimate-spin-slow4s linear infiniteGear / cog icon decorations

Keyframe Definitions

Component Usage

BadgeNew

The BadgeNew component renders a small “NEW” pill badge on project cards. animate-blink is applied to the badge text or a trailing cursor character to draw attention to recently added items without using motion that conflicts with AnimatePresence.
<span className="animate-blink text-y2k-lime font-code text-xs">

</span>

Marquee

The Marquee component renders a horizontally scrolling ticker strip at the top or bottom of the layout. animate-marquee is applied to the inner text container, which must be whitespace-nowrap to prevent line wrapping mid-scroll.
<div className="overflow-hidden">
  <p className="animate-marquee whitespace-nowrap font-code text-y2k-teal">
    {tickerText}
  </p>
</div>

Gear Icons

Decorative SVG gear or cog icons throughout the UI use animate-spin-slow to maintain a sense of mechanical activity in the background without being distracting.
<GearIcon className="animate-spin-slow text-y2k-violet opacity-30" />

Neon Retro Web does not currently implement prefers-reduced-motion support. Users who have enabled the “reduce motion” setting in their OS accessibility preferences will still see all animations play. Adding a prefers-reduced-motion media query override is strongly recommended before deploying to a public audience:
@media (prefers-reduced-motion: reduce) {
  /* Disable all CSS keyframe animations */
  .animate-blink,
  .animate-marquee,
  .animate-spin-slow {
    animation: none;
  }

  /* Restore opacity for blink elements so text remains visible */
  .animate-blink {
    opacity: 1;
  }
}
For Framer Motion, pass the useReducedMotion hook value as a flag to conditionally skip transition variants:
import { useReducedMotion } from "framer-motion";

export default function PageWrapper({ children }) {
  const shouldReduce = useReducedMotion();

  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={shouldReduce ? { duration: 0 } : { duration: 0.3 }}
    >
      {children}
    </motion.div>
  );
}

Build docs developers (and LLMs) love