Skip to main content

Documentation Index

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

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

The Navigation component is the primary wayfinding system for the Player One portfolio. It renders as an unobtrusive fixed button in the bottom-right corner of every page, labeled INVENTORY with a menu icon. Pressing it summons a full-screen modal overlay styled as an arcade stage-select screen, complete with a CRT scanline layer, glitching heading, and a grid of illuminated route tiles.

Route Configuration

Navigation is driven by a static route config array containing seven entries. Each entry carries a path, a display label, and a Lucide React icon component.
PathLabelIcon
/TITLE SCREENGamepad2
/aboutLOREUser
/projectsLEVEL SELECTCodeXml
/skillsHIGH SCORESTrophy
/writingCODEXBookOpen
/case-studiesBOSS BATTLESBriefcase
/contactSAVE GAMEPhone

Usage

import Navigation from "./components/Navigation";

// Drop it once at the app root — no props required.
export default function App() {
  return (
    <>
      <Navigation />
      {/* rest of the app */}
    </>
  );
}
Navigation uses React Router’s useLocation hook internally, so it must be rendered inside a <BrowserRouter> (or equivalent) context.

Visual & Behavioral Description

The INVENTORY Button

A small fixed button anchored to the bottom-right of the viewport (fixed bottom-6 right-6 z-50). It carries the font-pixel class (Press Start 2P) and is styled with a magenta border and magenta box-glow (border-arcade-magenta, box-glow-magenta). Clicking it sets internal isOpen state to true, triggering the modal. When open, AnimatePresence from Framer Motion mounts the overlay with a fade-in animation. The modal occupies the full viewport and layers the following:
  1. CRTOverlay — the standard scanline + vignette component is rendered inside the modal for an immersive display effect.
  2. SELECT STAGE heading — rendered with a CSS glitch keyframe animation in lime (font-pixel), visually flickering to reinforce the retro aesthetic. The modal itself carries a lime border and lime box-glow (border-arcade-lime, box-glow-lime).
  3. Route grid — a responsive CSS grid of route tiles, each showing the route’s icon and label.

Active Route Highlighting

useLocation() compares the current pathname against each route’s path. The active tile receives:
  • A magenta border (border-arcade-magenta) with a magenta background tint
  • An animate-pulse arrow indicator displayed alongside the label
  • A text indicator appended after the label
All non-active tiles use the standard lime border treatment.

Closing the Modal

The overlay can be dismissed by clicking the close button (top-right ) or by clicking directly on a route link, which also triggers React Router navigation.

Implementation Notes

import { useState } from "react";
import { useLocation, Link } from "react-router-dom";
import { AnimatePresence, motion } from "framer-motion";
import { Menu, Gamepad2, User, CodeXml, Trophy, BookOpen, Briefcase, Phone } from "lucide-react";
import CRTOverlay from "./CRTOverlay";

const routes = [
  { path: "/",            label: "TITLE SCREEN",  icon: Gamepad2  },
  { path: "/about",       label: "LORE",          icon: User      },
  { path: "/projects",    label: "LEVEL SELECT",  icon: CodeXml   },
  { path: "/skills",      label: "HIGH SCORES",   icon: Trophy    },
  { path: "/writing",     label: "CODEX",         icon: BookOpen  },
  { path: "/case-studies",label: "BOSS BATTLES",  icon: Briefcase },
  { path: "/contact",     label: "SAVE GAME",     icon: Phone     },
];

export default function Navigation() {
  const [isOpen, setIsOpen] = useState(false);
  const { pathname } = useLocation();

  return (
    <>
      {/* Trigger button */}
      <button
        className="fixed bottom-6 right-6 z-50 font-pixel ..."
        onClick={() => setIsOpen(true)}
      >
        <Menu size={16} /> INVENTORY
      </button>

      {/* Modal */}
      <AnimatePresence>
        {isOpen && (
          <motion.div
            className="fixed inset-0 z-[100] bg-black/95 ..."
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
          >
            <CRTOverlay />
            <h2 className="font-pixel text-lime glitch">SELECT STAGE</h2>

            <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
              {routes.map(({ path, label, icon: Icon }) => {
                const isActive = pathname === path;
                return (
                  <Link
                    key={path}
                    to={path}
                    onClick={() => setIsOpen(false)}
                    className={isActive ? "border-magenta animate-pulse ..." : "border-lime ..."}
                  >
                    <Icon size={24} />
                    {label} {isActive && "◀"}
                  </Link>
                );
              })}
            </div>
          </motion.div>
        )}
      </AnimatePresence>
    </>
  );
}
Because the modal mounts its own CRTOverlay, the scanline effect inside the nav is independent of any CRTOverlay rendered in the main app layout — stacking them intentionally deepens the visual effect.

Where It’s Used

Navigation is mounted once at the application root (typically inside App.jsx or the top-level router component) so it persists across all route changes without re-mounting. Its z-[100] stacking context ensures it floats above page content, the HUD, and any page-level CRT overlays.

Build docs developers (and LLMs) love