Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/blairxu13/persona3-website/llms.txt

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

The main navigation menu is driven by a single ITEMS array defined at the top of src/P3Menu.jsx. Each object in the array describes one menu entry — its display label, the route it navigates to, and a set of typographic offsets that produce the signature staggered, skewed Persona-style layout. Adding, removing, or reordering entries requires only editing this array (and, for new pages, registering a matching route in App.jsx).

The ITEMS Array

Below is the default configuration shipped with the portfolio. Each entry maps directly to one visible label in the menu.
// src/P3Menu.jsx
const ITEMS = [
  { id:"about",    label:"ABOUT ME",      page:"about",    fontSize:80, offsetX:0,  offsetY:0,  skew:-6,  skewY:10  },
  { id:"resume",   label:"RESUME",        page:"resume",   fontSize:66, offsetX:20, offsetY:8,  skew:-11, skewY:-10 },
  { id:"github",   label:"GITHUB LINK",   page:"github",   fontSize:68, offsetX:8,  offsetY:6,  skew:0,   skewY:-4  },
  { id:"socials",  label:"SOCIALS",       page:"socials",  fontSize:74, offsetX:16, offsetY:8,  skew:-3,  skewY:5   },
  { id:"sideproj", label:"SIDE PROJECTS", page:"sideproj", fontSize:56, offsetX:10, offsetY:6,  skew:-4,  skewY:7   },
];
The github and sideproj entries in the default ITEMS array do not have matching routes in App.jsx. Clicking them will call onNavigate("github") or onNavigate("sideproj"), which triggers navigate("/github") — a path with no registered <Route>, resulting in a blank or 404 view. See External Links below for how to handle the GitHub case, and the Adding a Menu Item section for wiring up a real route.

Item Object Fields

Each object in ITEMS accepts the following fields:
id
string
required
A unique React key for the list item. Used internally by React’s reconciler — not displayed to the user.
label
string
required
The text displayed in the menu. Rendered in Anton italic, uppercase. Keep labels short (1–3 words) so they fit within the staggered layout.
page
string
required
The route segment passed to onNavigate. In App.jsx, the handler calls navigate(/${page}), so ”about”routes to/about. This value must match a registered Route path in App.jsx` (or be handled specially for external links).
fontSize
number
required
Font size of the label in pixels. Larger values create more visual prominence. The default range is 56–80 px. Adjust this to communicate hierarchy — primary items like “ABOUT ME” use larger sizes.
offsetX
number
required
Applied as marginRight in pixels. Positive values push the item inward from the right edge, creating the horizontal stagger across entries. Use values in the 0–20 px range to maintain visual cohesion.
offsetY
number
required
Applied as marginTop in pixels. Adds vertical breathing room between items beyond the default flex gap. Varying this across entries breaks the rigid grid feel.
skew
number
required
CSS skewX in degrees applied to the label wrapper. Negative values lean the label to the left (the default Persona slant). Keep values between -12 and 0 for legibility.
skewY
number
required
CSS skewY in degrees applied to the label wrapper. Combined with skew, this produces the characteristic parallelogram warp on each label. Positive and negative values alternate across items to give each entry a distinct tilt.

Adding a Menu Item

1

Add an entry to the ITEMS array in P3Menu.jsx

Open src/P3Menu.jsx and append a new object to ITEMS. Choose a unique id, a short uppercase label, and a page slug that will become the URL path.
// src/P3Menu.jsx
const ITEMS = [
  { id:"about",    label:"ABOUT ME",      page:"about",    fontSize:80, offsetX:0,  offsetY:0,  skew:-6,  skewY:10  },
  { id:"resume",   label:"RESUME",        page:"resume",   fontSize:66, offsetX:20, offsetY:8,  skew:-11, skewY:-10 },
  { id:"github",   label:"GITHUB LINK",   page:"github",   fontSize:68, offsetX:8,  offsetY:6,  skew:0,   skewY:-4  },
  { id:"socials",  label:"SOCIALS",       page:"socials",  fontSize:74, offsetX:16, offsetY:8,  skew:-3,  skewY:5   },
  { id:"sideproj", label:"SIDE PROJECTS", page:"sideproj", fontSize:56, offsetX:10, offsetY:6,  skew:-4,  skewY:7   },
  // New entry:
  { id:"contact",  label:"CONTACT",       page:"contact",  fontSize:70, offsetX:12, offsetY:6,  skew:-5,  skewY:8   },
];
2

Register a Route in App.jsx

Open src/App.jsx and add a <Route> for the new path inside the existing router tree.
// src/App.jsx
import ContactPage from './ContactPage';

// Inside your <Routes>:
<Route path="/contact" element={<ContactPage />} />
3

Create the page component

Create src/ContactPage.jsx (or any filename matching your import above). At minimum, render a full-viewport container with a back-navigation trigger.
// src/ContactPage.jsx
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';

export default function ContactPage() {
  const navigate = useNavigate();

  useEffect(() => {
    const handleKey = (e) => {
      if (e.key === 'ArrowLeft') navigate(-1);
    };
    window.addEventListener('keydown', handleKey);
    return () => window.removeEventListener('keydown', handleKey);
  }, [navigate]);

  return (
    <div style={{ width: '100vw', height: '100vh', background: '#04060f', color: '#fff' }}>
      <h1>Contact</h1>
    </div>
  );
}
4

Optionally add a PageTransition variant

PageTransition.jsx does not use a color-map lookup. Each transition style is its own named component function, and TransitionOverlay selects between them with explicit if checks. To add a custom wipe for your new page:1. Define a new transition component in src/PageTransition.jsx, following the same pattern as the existing ones (e.g. AboutTransition, SocialsTransition):
// src/PageTransition.jsx

function ContactTransition() {
  const panels = [
    { color: '#00184c', delay: 0 },
    { color: '#c4001a', delay: 0.05 },
    { color: '#ffffff', delay: 0.1 },
  ];
  return panels.map((panel, i) => (
    <motion.div
      key={i}
      style={{ position: 'fixed', inset: 0, background: panel.color, zIndex: 999 - i }}
      initial={{ scaleX: 0 }}
      animate={{ scaleX: [0, 1, 1, 0] }}
      transition={{ duration: 0.45, delay: panel.delay, times: [0, 0.4, 0.6, 1] }}
    />
  ));
}
2. Add an if branch for the new variant inside TransitionOverlay:
function TransitionOverlay({ variant }) {
  if (variant === 'about')   return <AboutTransition />;
  if (variant === 'resume')  return <ResumeTransition />;
  if (variant === 'socials') return <SocialsTransition />;
  if (variant === 'contact') return <ContactTransition />; // ← add this
  return <DefaultTransition />;
}
3. Pass the variant name when wrapping your page in App.jsx:
<Route path="/contact" element={
  <PageTransition variant="contact"><ContactPage /></PageTransition>
} />

The default github item calls navigate("/github"), which goes nowhere useful. For items that should open an external URL rather than an internal route, intercept navigation inside the onNavigate handler in App.jsx:
// src/App.jsx
const handleNavigate = (page) => {
  if (page === 'github') {
    window.open('https://github.com/your-username', '_blank', 'noopener,noreferrer');
    return;
  }
  navigate(`/${page}`);
};

// Pass to P3Menu:
<P3Menu onNavigate={handleNavigate} />
This keeps the ITEMS array clean — the page field still acts as a key — while routing external destinations through window.open instead of React Router.

Styling Tips

The combination of fontSize, skew, skewY, offsetX, and offsetY is what produces the layered, hand-placed feel of the Persona menu. A few guidelines:
  • fontSize communicates hierarchy. Primary items (ABOUT ME at 80 px) should be noticeably larger than secondary items (SIDE PROJECTS at 56 px).
  • Alternate skewY signs (positive / negative) across adjacent items. This zigzag rhythm prevents the list from feeling like a uniform slant.
  • offsetX creates depth. Items pushed further inward (larger offsetX) appear more “recessed,” while zero-offset items feel closest to the viewer.
  • Keep skew (skewX) moderate. Values beyond ±15° make the Anton italic letterforms difficult to read at large sizes.
  • Vary offsetY slightly rather than relying solely on the flex gap. A few pixels of difference between items removes the mechanical regularity of a traditional list.
// Example: a 3-item menu with clear visual hierarchy
const ITEMS = [
  { id:"about",   label:"ABOUT ME", page:"about",   fontSize:82, offsetX:0,  offsetY:0,  skew:-6, skewY:10 },
  { id:"resume",  label:"RESUME",   page:"resume",  fontSize:68, offsetX:18, offsetY:6,  skew:-9, skewY:-8 },
  { id:"contact", label:"CONTACT",  page:"contact", fontSize:60, offsetX:12, offsetY:10, skew:-4, skewY:6  },
];

Build docs developers (and LLMs) love