Skip to main content

Documentation Index

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

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

The Contact page turns the act of sending a message into a ritual. The form sits at the centre of a slowly rotating summoning circle — one outer dashed ring, one inner solid ring, and two overlapping triangles forming a hexagram — all rendered in SVG and spinning continuously in the background. Submitting the form triggers a 2-second “brewing” state before the form is replaced by a success confirmation panel.

The Rotating SVG Background

A motion.svg with viewBox="0 0 500 500" forms the summoning circle backdrop. It is wrapped in an absolute inset-0 pointer-events-none opacity-20 div so it sits behind the form and doesn’t intercept clicks. The SVG itself is animated with:
animate={{ rotate: 360 }}
transition={{ duration: 100, repeat: Infinity, ease: "linear" }}
One full rotation every 100 seconds — slow enough to be ambient rather than dizzying. The SVG contains four shapes:
ShapeElementKey attributes
Outer dashed ring<circle cx="250" cy="250" r="240">strokeDasharray="10 20"
Inner solid ring<circle cx="250" cy="250" r="220">No dash — continuous stroke
Upward triangle<polygon points="250,30 440,380 60,380">Points form an equilateral upward triangle
Inverted triangle<polygon points="250,470 60,120 440,120">Overlapping with the upward triangle to form a hexagram

The Three Form Fields

The form is a glass-panel p-8 rounded-xl border border-spell/20 panel centred within the summoning circle container. It uses a space-y-6 wrapper for field spacing. Each field consists of a <label> in Pinyon Script and an <input> or <textarea> with a bottom-border-only focus style:
<label className="block font-cursive text-2xl text-parchment/80 mb-2">
  True Name
</label>
<input
  type="text"
  required
  className="w-full bg-midnight-darker/50 border-b border-spell/30 px-4 py-2 text-parchment font-garamond focus:outline-none focus:border-spell transition-colors disabled:opacity-50"
/>

Form Submission Flow

The handleSubmit function manages three pieces of state: formData (name, email, message), isSubmitting (boolean), and isSuccess (boolean).
1

User submits the form

onSubmit calls e.preventDefault() then sets isSubmitting: true. All inputs and the submit button receive disabled={isSubmitting} and the button label switches from "Cast the Message" to "Brewing...".
2

2-second simulated delay

A setTimeout fires after 2000ms, sets isSubmitting: false and isSuccess: true. This is the placeholder for a real API call.
3

Success state replaces the form

When isSuccess is true the form JSX is swapped for a motion.div confirmation panel containing a <Sigil> icon, "Message Cast" in Cinzel, and a prose line reading “The spirits have received your intent. Await my reply.”
4

Reset

A "Cast Another" button calls setIsSuccess(false), restoring the empty form so the user can submit again.

Success State UI

<motion.div
  initial={{ opacity: 0, scale: 0.8 }}
  animate={{ opacity: 1, scale: 1 }}
  className="text-center z-10 glass-panel p-12 rounded-full aspect-square flex flex-col items-center justify-center border-spell/50"
>
  <Sigil size={80} className="mb-6 text-spell" />
  <h2 className="font-cinzel text-2xl text-spell text-glow mb-4">
    Message Cast
  </h2>
  <p className="font-garamond text-parchment/80 italic">
    The spirits have received your intent.<br />Await my reply.
  </p>
  <button
    onClick={() => setIsSuccess(false)}
    className="mt-8 font-cinzel text-sm tracking-widest text-parchment/50 hover:text-spell transition-colors"
  >
    Cast Another
  </button>
</motion.div>
The form is purely front-end — the setTimeout simulates a network call but no message is actually sent. You must wire it to a real backend before deploying.

Wiring Up a Real Backend

Replace the setTimeout in handleSubmit with a fetch POST to your chosen service. Here is an example using Formspree:
const handleSubmit = async (e) => {
  e.preventDefault();
  setIsSubmitting(true);

  try {
    const response = await fetch("https://formspree.io/f/YOUR_FORM_ID", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        name: formData.name,
        email: formData.email,
        message: formData.message,
      }),
    });

    if (response.ok) {
      setIsSuccess(true);
    } else {
      // Handle error state here
      console.error("Submission failed");
    }
  } finally {
    setIsSubmitting(false);
  }
};
Alternatives to Formspree include EmailJS (client-side only, no server needed), Resend (developer-friendly transactional email API), or a custom serverless function on Vercel/Netlify. All three can replace the setTimeout block in the same way.

Build docs developers (and LLMs) love