Skip to main content

Documentation Index

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

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

The Contact page (/contact) is titled The Summoning and subtitled “Establish a Connection.” It combines a full-page animated SVG summoning circle in the background with a focused contact form in the foreground. The form moves through two states — idle and submittingsuccess — with distinct UI for each.

Visual & UX Overview

Animated Summoning Circle

An SVG element is absolutely positioned and centered in the page background at opacity: 0.30. The entire SVG rotates continuously at animate: {rotate: 360} with duration: 100s, repeat: Infinity, ease: "linear". Individual elements (two circles, two triangles) animate their pathLength from 0 to 1 on mount.

Two-State Form Flow

The form cycles through idle (ready to fill) → submitting (2-second simulated delay) → success (confirmation panel replaces the form). State is managed by a single formState string variable via useState("idle"). There is no real backend — a setTimeout simulates the send.

Three Form Fields

The form has three required fields: True Name (text input), Scrying Address (email input), and The Inscription (textarea). All use HTML required for validation. There is no client-side JavaScript validation function — the browser’s native constraint validation is used.

Success Confirmation

On success, the form is replaced by a panel with a <Sigil type="star" /> icon, the heading “Message Sent into the Aether”, and the message “The raven has taken flight. Expect a response before the next full moon.” A “Send Another” button resets state to idle.

Summoning Circle SVG Structure

The background SVG (viewBox="0 0 600 600", width="600" height="600") contains four animated elements all wrapped in a single rotating motion.svg:
<motion.svg
  width="600" height="600" viewBox="0 0 600 600"
  animate={{ rotate: 360 }}
  transition={{ duration: 100, repeat: Infinity, ease: "linear" }}
>
  {/* Outer circle */}
  <motion.circle
    cx="300" cy="300" r="280"
    fill="none" stroke="#a855f7" strokeWidth="1" strokeDasharray="4 12"
    initial={{ pathLength: 0 }}
    animate={{ pathLength: 1 }}
    transition={{ duration: 4, ease: "easeInOut" }}
  />

  {/* Inner circle */}
  <motion.circle
    cx="300" cy="300" r="260"
    fill="none" stroke="#2dd4bf" strokeWidth="2"
    initial={{ pathLength: 0 }}
    animate={{ pathLength: 1 }}
    transition={{ duration: 3, delay: 1, ease: "easeInOut" }}
  />

  {/* Triangle (upward pointing) */}
  <motion.polygon
    points="300,40 525,450 75,450"
    fill="none" stroke="#a855f7" strokeWidth="1"
    initial={{ pathLength: 0, opacity: 0 }}
    animate={{ pathLength: 1, opacity: 0.5 }}
    transition={{ duration: 3, delay: 2, ease: "easeInOut" }}
  />

  {/* Triangle (downward pointing / inverted) */}
  <motion.polygon
    points="300,560 75,150 525,150"
    fill="none" stroke="#a855f7" strokeWidth="1"
    initial={{ pathLength: 0, opacity: 0 }}
    animate={{ pathLength: 1, opacity: 0.5 }}
    transition={{ duration: 3, delay: 2.5, ease: "easeInOut" }}
  />
</motion.svg>
All rotation is applied to the outer motion.svg element so all four shapes rotate together as a single unit around the SVG’s own center.

Form Fields

FieldInput TypeLabelRequired
nametext”True Name”Yes
emailemail”Scrying Address (Email)“Yes
messagetextarea”The Inscription”Yes

Form State Machine

const [formState, setFormState] = useState("idle");

const handleSubmit = (e) => {
  e.preventDefault();
  setFormState("submitting");
  setTimeout(() => {
    setFormState("success");
  }, 2000);
};

State Descriptions

StateUI ShownTrigger
idleForm with three fields and a “Send the Raven” submit buttonInitial render / reset
submittingSubmit button shows spinner and “Casting…” text; fields remainValid form submission
successForm replaced by confirmation panel with Sigil and success message2s after submitting begins
The 2-second simulated delay is intentional — it gives the user feedback that something is happening and makes the mystical theme feel more deliberate. Replace the setTimeout with a real API call (e.g., fetch('/api/contact', {...}) or a service like EmailJS/Formspree) when deploying to production.

Success State

{formState === "success" && (
  <motion.div
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }}
    className="text-center py-12"
  >
    <Sigil type="star" size={80} color="#2dd4bf" className="mx-auto mb-6" />
    <h3 className="font-serif text-2xl text-mystic-mint mb-2">
      Message Sent into the Aether
    </h3>
    <p className="text-slate-400 font-sans text-sm">
      The raven has taken flight. Expect a response before the next full moon.
    </p>
    <button onClick={() => setFormState("idle")}>
      Send Another
    </button>
  </motion.div>
)}

Customization

Replace the setTimeout mock with a real HTTP request. Recommended services for static portfolio sites:
  • Formspree (https://formspree.io) — POST to their endpoint, no backend needed
  • EmailJS — send directly from the browser using an email service API key
  • Netlify Forms — add netlify attribute to the form element if hosted on Netlify
Set the action endpoint in an environment variable (VITE_CONTACT_ENDPOINT) so it can differ between development and production.
Adjust the duration on the motion.svg’s transition (currently 100). Increase the value (e.g., 200) to slow the rotation; decrease it (e.g., 60) to speed it up. The individual element pathLength animations are separate and not affected by this change.
Add a new <input> or <select> element inside the <form> with a matching id and required attribute. The submit button’s disabled state is already tied to formState === "submitting", so new fields are automatically disabled during submission.

Component Dependencies

ComponentSourceRole
Sigilcomponents/Sigil.jsStar sigil displayed in the success confirmation panel
BackgroundEffectscomponents/BackgroundEffects.jsAmbient particle and blur layer beneath the summoning circle
Navigationcomponents/Navigation.jsFixed top navigation bar
motion / AnimatePresenceFramer MotionSummoning circle rotation, pathLength draws, success panel entrance
The summoning circle SVG renders at opacity: 0.30. If you want to make it more prominent while the form is submitting, animate its opacity up to 0.5 while formState === "submitting" to suggest the ritual is actively in progress.

Build docs developers (and LLMs) love