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 Commune page (/contact) renders the ContactPage component (Je) — a minimalist dark-parchment form where visitors can send a message. When the form is submitted, it dissolves away and a wax-seal confirmation panel rises in its place. A “Cast another?” link resets the view back to the blank form.

Route

/contactContactPage (Je)

Page Structure

The page is wrapped in max-w-2xl mx-auto py-12. The header block renders “Commune” in font-heading text-5xl and the subtitle “Cast a message into the dark.” in font-code text-witch-turquoise/70. Below the header, the entire interactive area sits inside a single parchment-bg p-8 md:p-12 border border-witch-plum/30 rounded-sm container. AnimatePresence mode="wait" governs the crossfade between the form view and the success view.

Form Fields

The form contains three fields and a footer action bar.

Name

label: "Your Name (True or Chosen)"
type: "text"
required: true

Email

label: "Return Address (Email)"
type: "email"
required: true

Message

label: "The Missive"
element: <textarea rows={4} />
required: true
All three inputs share the same base styling: transparent background (bg-transparent), no outline (outline-none), font-body text-witch-moonlight, and a bottom-border that transitions on focus:
border-b-2 border-witch-plum/50 focus:border-witch-amber
The textarea differs slightly — it adds bg-witch-dark/30, all-sides border border-witch-plum/50, p-4 rounded-sm, and resize-none. Below the fields, a flex justify-between items-center row holds two groups: Left — quick links:
  • [ Send a Raven ] — a mailto: anchor in font-code text-xs text-witch-turquoise, hovering to text-witch-amber.
  • [ Astral Plane (GitHub) ] — an external link in font-code text-xs text-witch-plum, hovering to text-witch-amber.
Right — submit button: A relative overflow-hidden border border-witch-plum/50 rounded-sm button labelled “Seal & Send”. A div inside it acts as a sliding plum overlay (bg-witch-plum/20), scaling from scaleX: 0 to scaleX: 1 on group-hover using transform origin-left. While the submission delay is running, the label changes to "Sealing..." and the button is disabled with opacity-50.

Submission Flow

The form’s onSubmit handler manages a two-stage state transition without sending data to any server:
const handleSubmit = (event) => {
  event.preventDefault();
  setIsSending(true);
  setTimeout(() => {
    setIsSending(false);
    setIsSuccess(true);
  }, 2000);
};
1

User clicks Seal & Send

isSending becomes true. The button label switches to "Sealing..." and disables.
2

2-second delay elapses

isSending resets to false; isSuccess becomes true.
3

Form exits

AnimatePresence unmounts the form with exit={{ opacity: 0, y: -20, filter: "blur(10px)" }}.
4

Confirmation panel enters

The success panel mounts with initial={{ opacity: 0, scale: 0.8 }} animating to { opacity: 1, scale: 1 }.

Confirmation Panel

The success state is a centered column rendered under AnimatePresence key "success":
  1. Wax seal — a w-24 h-24 bg-red-900 rounded-full circle that enters with a spring bounce (initial: scale: 3 → scale: 1). Inside it, "Sealed" is rendered in font-heading text-red-200/80 text-2xl.
  2. Heading"Message Cast" in font-heading text-3xl text-witch-turquoise.
  3. Message"Your missive has entered the ether. I shall respond before the next new moon." in font-body text-witch-moonlight/70.
  4. Reset link — a "Cast another?" button that calls setIsSuccess(false), returning the view to the blank form.

Wiring Up a Real Backend

The form currently calls event.preventDefault() and does not POST to any backend. All submission logic is client-side only. To receive real messages, wire the handler to a transactional email service such as Formspree or Resend.

Formspree Example

Replace the setTimeout block with a fetch call to your Formspree endpoint:
const handleSubmit = async (event) => {
  event.preventDefault();
  setIsSending(true);

  const formData = new FormData(event.target);
  const response = await fetch("https://formspree.io/f/<your-id>", {
    method: "POST",
    body: formData,
    headers: { Accept: "application/json" },
  });

  setIsSending(false);
  if (response.ok) {
    setIsSuccess(true);
  }
};
Add name attributes to each input (name="name", name="email", name="message") so FormData picks them up correctly.

Resend Example

Route the submission through a serverless function (e.g. a Vercel API route) that calls the Resend SDK:
// api/contact.js
import { Resend } from "resend";

const resend = new Resend(process.env.RESEND_API_KEY);

export default async function handler(req, res) {
  const { name, email, message } = req.body;
  await resend.emails.send({
    from: "noreply@yourdomain.com",
    to: "hello@yourdomain.com",
    subject: `New message from ${name}`,
    text: message,
    replyTo: email,
  });
  res.status(200).json({ ok: true });
}
Then fetch("/api/contact", { method: "POST", body: JSON.stringify(formData) }) from the form handler.

Build docs developers (and LLMs) love