Skip to main content

Documentation Index

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

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

The contact and about pages of sys.witch share a family of self-contained, stateful components. SummoningForm handles all user-facing contact functionality entirely on the client — no backend required. TimelineEntry threads the work history together with an animated left-rail timeline, and TraitSigilGrid renders a five-up grid of personality sigils that define the portfolio owner’s character.

SummoningForm

SummoningForm is a fully self-contained contact form component with its own local state. It requires no props — all behaviour is internal. The form sits inside an animated GlowFrame wrapper (neon-magenta) and is centred at max-w-2xl on the contact page.

Fields

Field label (display)HTML idTypePlaceholderRequired
Entity IdentifiernametextYour nameYes
Return Frequencyemailemailyour@email.comYes
Transmission PayloadmessagetextareaWhat knowledge do you seek?Yes
Each label is rendered in font-mono text-xs text-neon-magenta uppercase tracking-wider alongside a small rune sigil (rune-1, rune-2, rune-3 respectively). Inputs use a bg-base/50 fill with a border-neon-magenta/30 border that transitions to a full border-neon-magenta + ring-1 ring-neon-magenta on focus.

Submit Behaviour

SummoningForm is a static, front-end-only form. There is no backend endpoint or mailto: link wired up in the current implementation. The onSubmit handler:
  1. Calls event.preventDefault() to block native submission.
  2. Sets an isSubmitting boolean to true, causing the submit button (normally labelled "Send The Signal") to pulse and display "Channeling...".
  3. After 1 500 ms simulates completion: isSubmitting returns to false and isSuccess is set to true.
  4. After a further 3 000 ms isSuccess resets to false, returning the form to its default state.
The form does not actually send any data. To wire it to a real backend, replace the setTimeout simulation inside the onSubmit handler with a fetch or axios call to your preferred form API (e.g. Formspree, Resend, or a custom endpoint).

Success State

When isSuccess is true the form is replaced by an animated success panel displaying:
  • A large rune-4 sigil with a magenta glow.
  • The heading “Signal Transmitted” in font-display.
  • The subtext "The message has entered the void." in font-mono.
The success panel animates in with initial={{ opacity: 0, scale: 0.9 }} via Framer Motion.

Props

SummoningForm accepts no props — all state is managed internally with React.useState.

JSX Usage

import { SummoningForm } from "@/components/contact/SummoningForm";

export default function ContactPage() {
  return (
    <section>
      <SummoningForm />
    </section>
  );
}

TimelineEntry

TimelineEntry renders a single item in the vertical work-history timeline on the About page. Each entry consists of a circular node icon on the left rail and an animated content block to its right. The vertical rail itself is drawn as a gradient line from neon-cyan down to transparent — the isLast prop suppresses this line on the final entry to avoid a dangling tail.

Props

PropTypeRequiredDefaultDescription
yearstringYesThe year or date range to display (e.g. "2023 – 2025")
titlestringYesRole or event name, rendered in font-display
descriptionstringYesBody paragraph for the timeline entry
isLastbooleanNofalseSet to true on the final entry to hide the tail line

Visual Structure

  • The left rail node is a w-10 h-10 circle with border-2 border-neon-cyan and a shadow-glow-cyan that holds a rune-4 sigil.
  • The connecting gradient line is w-[2px] running full height, fading from-neon-cyan via-neon-cyan/20 to-transparent.
  • The content block slides in from the right (x: 20 → 0) using a whileInView Framer Motion animation with once: true and a -100px margin.
  • The year is rendered in font-mono text-neon-cyan text-sm; the title in font-display text-xl; the description in font-sans text-text-secondary.

JSX Usage

import { TimelineEntry } from "@/components/about/TimelineEntry";

const history = [
  {
    year: "2023 – Present",
    title: "Front-End Developer",
    description: "Building cyber-occult interfaces for discerning clients.",
  },
  {
    year: "2021 – 2023",
    title: "Junior Developer",
    description: "Learning the arcane arts of component architecture.",
    isLast: true,
  },
];

export default function AboutPage() {
  return (
    <section>
      {history.map((entry, i) => (
        <TimelineEntry
          key={entry.year}
          year={entry.year}
          title={entry.title}
          description={entry.description}
          isLast={i === history.length - 1}
        />
      ))}
    </section>
  );
}
Always pass isLast={true} to the final TimelineEntry in the list. The component defaults to false, so forgetting it on the last item will render a dangling gradient line with nowhere to go.

TraitSigilGrid

TraitSigilGrid renders a fixed five-item grid of personality trait tiles on the About page. The trait list is hardcoded inside the component — it does not accept any props.

Hardcoded Traits

Trait nameSigil IDNeon colour
Detail-Orientedtrait-detailcyan
Creativetrait-creativemagenta
Sarcastictrait-sarcasticpurple
Practicaltrait-practicallime
Pattern-Spottertrait-patterncyan

Rendered Layout

The grid uses grid-cols-2 md:grid-cols-3 lg:grid-cols-5 so all five tiles sit in a single row on wide viewports. Each tile is a flex flex-col items-center text-center column containing:
  1. A w-20 h-20 rounded-lg icon box with a per-trait border-neon-{color}/30 border.
  2. The trait’s sigil icon at size={32}, centred in the box.
  3. The trait name in font-mono text-sm text-text-secondary below.
Each tile has a Framer Motion entrance animation (whileInView, once: true) with a stagger delay of index * 0.1s. On hover the tile lifts with -translate-y-2, the border brightens to full neon-{color}, a matching shadow-glow-{color} appears, and the sigil icon rotates 180° via CSS group-hover:rotate-180.

Props

TraitSigilGrid accepts no props.

JSX Usage

import { TraitSigilGrid } from "@/components/about/TraitSigilGrid";

export default function AboutPage() {
  return (
    <section>
      <h2>Encoded Traits</h2>
      <TraitSigilGrid />
    </section>
  );
}
To modify the traits, edit the hardcoded traits array directly inside TraitSigilGrid.js. Each entry requires a name, a sigilId registered in data/sigils.js, and a color matching one of the theme tokens (cyan, magenta, purple, lime).

Build docs developers (and LLMs) love