Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/nightshade/llms.txt

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

The Pacts page lives at route /work and is rendered by the WorkPage component (minified export Fe). It presents the developer’s professional history as three parchment-styled cards stacked vertically. Each card enters from the left as the user scrolls and reveals a wax-seal stamp, an animated underline on the job title, and decorative SVG corner brackets that reinforce the aged-document aesthetic. The header is centered at the top of a max-w-4xl mx-auto py-12 wrapper:
  • Title"The Book of Pacts" in Cormorant Unicase (font-heading text-5xl text-glow-teal)
  • Subtitle"A ledger of professional bindings." in JetBrains Mono (font-code text-sm text-witch-turquoise/70)

The Parchment Card

Each work entry is a motion.div with .parchment-bg, border border-witch-plum/30 rounded-sm, p-8, and overflow-hidden. The cards are spaced with space-y-8 (32px gaps).

The .parchment-bg CSS Class

The parchment background is a custom utility defined in main.css. It combines a dark base color with an SVG fractal-noise texture overlaid at 5% opacity:
.parchment-bg {
  background-color: #1a1c23;
  background-image: url("data:image/svg+xml,%3Csvg width='100' height='100'
    viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E
    %3Cfilter id='noise'%3E
      %3CfeTurbulence type='fractalNoise' baseFrequency='0.8'
        numOctaves='4' stitchTiles='stitch'/%3E
    %3C/filter%3E
    %3Crect width='100' height='100' filter='url(%23noise)' opacity='0.05'/%3E
    %3C/svg%3E");
}
The feTurbulence filter with type="fractalNoise" produces an organic, papery grain. Because the SVG is inlined as a data URI, no external file is needed.

Wax Seal

In the top-right corner of each card (absolute top-6 right-8) is a circular wax seal:
<div className="w-16 h-16 bg-red-900/80 rounded-full
  flex items-center justify-center
  shadow-[inset_0_0_10px_rgba(0,0,0,0.8)]
  border border-red-800/50
  transform rotate-12">
  <span className="font-code text-[10px] text-red-200/80
    text-center leading-tight transform -rotate-12">
    SEALED<br />{entry.period.split(" - ")[0]}
  </span>
</div>
The outer div is rotated 12°, and the inner span counter-rotates by -12° so the text remains readable while the seal itself sits at a slight slant. The start year is extracted from the period string by splitting on " - " and taking index [0].

Animated Title Underline

The job title (<h2>) is a relative inline-block. Beneath it, a motion.div acts as a decorative underline line that animates its width from 0 to "100%" on hover:
<h2 className="text-2xl font-heading text-witch-turquoise mb-1 relative inline-block">
  {entry.title}
  <motion.div
    className="absolute -bottom-1 left-0 h-0.5 bg-witch-amber"
    initial={{ width: 0 }}
    animate={{ width: hoveredId === entry.id ? "100%" : 0 }}
    transition={{ duration: 0.4, ease: "easeOut" }}
  />
</h2>
The hover state is tracked by a useState(null) variable in the parent component, updated by onMouseEnter and onMouseLeave handlers on the card’s outer motion.div.

Corner Ornaments

Each card has two decorative SVG bracket marks — one in the top-left corner and one rotated 180° in the bottom-right corner:
// Top-left bracket
<svg className="absolute top-2 left-2 w-6 h-6 text-witch-plum/40"
  viewBox="0 0 24 24" fill="none" stroke="currentColor">
  <path d="M2 2v6h6M2 2l8 8" strokeWidth="1" />
</svg>

// Bottom-right bracket (rotated 180°)
<svg className="absolute bottom-2 right-2 w-6 h-6 text-witch-plum/40 transform rotate-180"
  viewBox="0 0 24 24" fill="none" stroke="currentColor">
  <path d="M2 2v6h6M2 2l8 8" strokeWidth="1" />
</svg>
Both use text-witch-plum/40 (40% opacity plum color) and a thin strokeWidth="1" to remain subtle.

Work History Data

The work entries are defined as the workHistory array (minified as De) at module scope:
const workHistory = [
  {
    id: 1,
    title: "Senior Alchemist (Frontend)",
    company: "Ethereal Systems",
    period: "2022 - Present",
    description:
      "Lead the coven of UI developers. Transmuted legacy jQuery monoliths into pristine React applications. Reduced summoning (load) times by 40%.",
  },
  {
    id: 2,
    title: "Spellweaver (Fullstack)",
    company: "Arcane Analytics",
    period: "2019 - 2022",
    description:
      "Maintained the great ledger (database). Built data visualization portals that allowed mortals to comprehend vast streams of telemetry.",
  },
  {
    id: 3,
    title: "Apprentice Conjurer",
    company: "Startup Void",
    period: "2017 - 2019",
    description:
      "Learned the dark arts of CSS floats and callback hell before discovering the light of Flexbox and Promises.",
  },
];
Senior Alchemist (Frontend) at Ethereal Systems (2022 – Present) — Led a frontend team through a jQuery-to-React migration. The “40% load time reduction” maps to real-world bundle splitting, lazy loading, and elimination of synchronous render-blocking scripts.Spellweaver (Fullstack) at Arcane Analytics (2019 – 2022) — Full-stack role spanning database maintenance and telemetry dashboards. The “great ledger” metaphor refers to a PostgreSQL database; the dashboards consumed streaming data for internal analytics.Apprentice Conjurer at Startup Void (2017 – 2019) — Early-career role at a startup. The reference to “CSS floats and callback hell” is a period-accurate joke — these were genuinely common patterns before Flexbox/Grid and Promises/async-await became standard.

Scroll Animation

Each card uses whileInView to slide in from the left as it enters the viewport:
<motion.div
  initial={{ opacity: 0, x: -20 }}
  whileInView={{ opacity: 1, x: 0 }}
  viewport={{ once: true }}
>
Unlike the About page timeline, these cards do not use a margin on the viewport option or a staggered delay. All three cards animate independently: each triggers the moment its top edge enters the viewport.
viewport={{ once: true }} means each card animates in exactly once. Scrolling back up and down again will not replay the entrance animation.

Customization

1

Add a new work entry

Append a new object to the workHistory array (De in the minified build). Provide id, title, company, period, and description. The id is used as the React list key and as the hover-state comparison value — it must be unique:
{
  id: 4,
  title: "Principal Enchanter",
  company: "The Grand Archive",
  period: "2024 - Present",
  description:
    "Overseeing the digitization of ancient tomes. Leading a cross-functional coven of engineers and designers.",
},
The wax seal will automatically display 2024 (the start year from period.split(" - ")[0]).
2

Change the entry card background

The .parchment-bg class is defined in main.css. To darken the base color, change background-color: #1a1c23 to a darker hex value. To remove the noise texture entirely, delete the background-image line. To increase the grain visibility, raise the SVG rect opacity from 0.05 to 0.1 or higher.
3

Adjust the underline animation speed

The underline motion.div has transition={{ duration: 0.4, ease: "easeOut" }}. Increase duration for a slower draw, or change ease to "linear" for a mechanical feel instead of the default ease-out deceleration.
4

Move the wax seal position

The seal’s parent div uses absolute top-6 right-8. Change right-8 to right-4 to pull it closer to the card edge, or replace top-6 with top-4 to raise it. The card’s main content area uses pr-24 (96px right padding) to ensure text never runs under the seal — adjust this padding if you reposition the seal horizontally.
To add a technology stack tag list to each card (similar to the Projects page), add a stack: ["React", "TypeScript"] field to each workHistory entry and render it below the description paragraph as a row of small font-code badges with border border-witch-plum/50 styling.

Build docs developers (and LLMs) love