Skip to main content

Documentation Index

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

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

Navigation is the primary wayfinding component for Aurora Cosmos. On large screens it appears as a slim glassmorphism sidebar on the left edge of the viewport that expands to reveal route labels when hovered. On small screens it collapses into a top bar with a hamburger button that opens a full-screen animated drawer. Both surfaces share the same route array and active-route logic, so they always stay in sync with the current URL.

Desktop Sidebar

The desktop sidebar is hidden below the lg breakpoint (hidden lg:flex) and mounted as position: fixed at z-index: 50 so it always overlays page content.
1

Collapsed state (w-20)

When the cursor is elsewhere on the page, the sidebar occupies only 80 px (w-20). Each nav item displays only its icon — the text label exists in the DOM but is invisible (opacity-0).
2

Expanded state (hover:w-64)

When the user hovers over the sidebar, Tailwind’s group + group-hover: classes animate the sidebar to w-64 (256 px) over a 300 ms transition-all. Icon alignment shifts from centred to left-aligned (group-hover:items-start), and all label spans fade from opacity-0 to opacity-100 via group-hover:opacity-100.
3

Footer label

At the very bottom of the sidebar (below the nav items) the string SYS.ONLINE is rendered in font-mono text-slate-500. It is also hidden when collapsed and becomes visible with group-hover:opacity-100.
The sidebar wrapper carries the .glass-panel utility (glassmorphism) with left, top, and bottom borders removed so only the right border creates a subtle dividing line:
<nav className="hidden lg:flex flex-col fixed left-0 top-0 h-screen
                w-20 hover:w-64 glass-panel border-l-0 border-t-0 border-b-0
                z-50 transition-all duration-300 group">

Mobile Menu

On screens narrower than lg, a fixed top bar takes over. It displays the logo dot on the left and a hamburger/close button (Menu / X from lucide-react) on the right. Tapping the button toggles a boolean state value.
const [isOpen, setIsOpen] = useState(false);

// Top bar
<div className="lg:hidden fixed top-0 left-0 w-full z-50 glass-panel
                border-x-0 border-t-0 p-4 flex justify-between items-center">
  {/* Logo dot */}
  <div className="w-6 h-6 rounded-full bg-gradient-to-tr
                  from-aurora-turquoise to-cosmic-violet box-glow" />
  <button onClick={() => setIsOpen(!isOpen)} className="text-white p-2">
    {isOpen ? <X size={24} /> : <Menu size={24} />}
  </button>
</div>
When isOpen is true, AnimatePresence mounts a full-screen motion.div overlay over bg-space-900/95 backdrop-blur-xl:
<AnimatePresence>
  {isOpen && (
    <motion.div
      initial={{ opacity: 0, y: -20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
      className="lg:hidden fixed inset-0 z-40 bg-space-900/95
                 backdrop-blur-xl pt-24 px-6"
    >
      {/* Route list */}
    </motion.div>
  )}
</AnimatePresence>
AnimatePresence ensures the exit animation (opacity: 0, y: -20) plays before the overlay is removed from the DOM. Tapping any route link sets isOpen to false, triggering that exit.

Routes

The route configuration lives in a module-level constant ua. Each entry is an object with a path, a label, and a lucide-react icon component reference.
import { Rocket, User, Briefcase, CodeXml, PenTool, FileText, Mail } from "lucide-react";

const ua = [
  { path: "/",            label: "Home",         icon: Rocket   },
  { path: "/about",       label: "About",        icon: User     },
  { path: "/projects",    label: "Projects",     icon: Briefcase },
  { path: "/skills",      label: "Skills",       icon: CodeXml  },
  { path: "/writing",     label: "Writing",      icon: PenTool  },
  { path: "/case-studies",label: "Case Studies", icon: FileText },
  { path: "/contact",     label: "Contact",      icon: Mail     },
];
The full route table:
PathLabelIcon
/Homerocket
/aboutAboutuser
/projectsProjectsbriefcase
/skillsSkillscode-xml
/writingWritingpen-tool
/case-studiesCase Studiesfile-text
/contactContactmail
Routes are iterated with ua.map() in both the desktop sidebar and the mobile drawer, so both surfaces always reflect the same list.

Active Route Indicator

Navigation calls useLocation() from react-router-dom to get the current pathname and compares it to each route’s path. A matched route receives text-aurora-turquoise; unmatched links are text-slate-400 and gain hover:text-white hover:bg-white/5. The active indicator is a motion.div with a shared layoutId:
{isActive && (
  <motion.div
    layoutId="activeNav"
    className="absolute left-0 w-1 h-full bg-aurora-turquoise rounded-r-full"
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    transition={{ duration: 0.3 }}
  />
)}
Because all nav items share layoutId="activeNav", Framer Motion’s layout animation automatically slides the 4 px aurora-turquoise bar from the previous active item to the new one whenever the route changes — no manual animation code is required.

Logo Dot

The desktop logo is an 8 × 8 (32 px) circle rendered at the top of the sidebar:
<div className="w-8 h-8 rounded-full bg-gradient-to-tr
                from-aurora-turquoise to-cosmic-violet
                animate-pulse box-glow" />
It pulses continuously with animate-pulse and carries the .box-glow utility for the aurora glow effect. The mobile version is a slightly smaller 6 × 6 (24 px) variant without the animate-pulse. At the bottom of the desktop sidebar (below the nav links), the system status string renders in monospace:
<div className="mt-auto w-full text-xs text-slate-500 font-mono
                opacity-0 group-hover:opacity-100 transition-opacity duration-300">
  SYS.ONLINE
</div>
This element is invisible in the collapsed state (opacity-0) and fades in as part of the same group-hover transition that reveals the nav labels. It is not rendered in the mobile menu.

Adding a Route

1

Add an entry to the ua array

Open components/Navigation.js and append your route to the ua array. Choose an icon from lucide-react.
import { Star } from "lucide-react";

// Inside Navigation.js
const ua = [
  // ...existing routes
  { path: "/gallery", label: "Gallery", icon: Star },
];
2

Create the page component

Add a new file for the page component and export a default React component.
// pages/Gallery.jsx
export default function Gallery() {
  return (
    <main className="relative z-10 min-h-screen pt-24 px-8">
      <h1 className="text-white text-4xl font-display">Gallery</h1>
    </main>
  );
}
3

Register the route in the router

Import the new page and add a <Route> element to your router configuration.
import Gallery from "./pages/Gallery";

// Inside your router definition
<Route path="/gallery" element={<Gallery />} />
4

Add a static HTML pre-render file (if applicable)

If the project uses static pre-rendering, create a corresponding gallery.html file in the output directory so the route resolves on direct load or refresh.

No Props

Navigation accepts no props. It reads its own location via useLocation() internally and renders both the desktop sidebar and the mobile top bar / drawer within a single <>…</> fragment. Mount it once in the app shell — it handles all breakpoints by itself.
// App.jsx
import Navigation from "./components/Navigation";

export default function App() {
  return (
    <>
      <AuroraBackground />
      <Starfield />
      <Navigation />           {/* renders sidebar on lg, top bar below lg */}
      <main className="relative z-10">
        <Outlet />
      </main>
    </>
  );
}
Navigation uses NavLink from react-router-dom for all route links. It must be rendered inside a React Router context (i.e., inside <BrowserRouter> or <RouterProvider>). Rendering it outside a router context will throw a runtime error.

Build docs developers (and LLMs) love