Skip to main content

Documentation Index

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

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

The Raven page is the portfolio’s contact entry point. It presents a message form that matches the overall dark, arcane aesthetic of witch-dev — frosted glass panels, glowing text, and deep-purple accents — while giving visitors a clear path to get in touch with Alysha directly. The Send icon (imported as se from Lucide React) serves as the submit button icon, reinforcing the “dispatch a raven” metaphor.
The contact route is registered as:
PropertyValue
Route/#/contact
Label"Raven"
IconMail (Lucide React)
The "Raven" label and Mail icon appear in the sidebar navigation alongside the other portfolio sections (Home, Skills / Affinities, Writing / Scrolls).

Design

The Contact page uses the same visual language as every other page in the portfolio:

Glass Panel

The form sits inside a glass-panel container — a frosted-glass card with a subtle backdrop blur, dark semi-transparent background, and a thin border that catches the ambient purple glow.

Dark Typography

Input labels, placeholder text, and the page heading follow the portfolio-wide type scale. Headings use text-glow-purple; body text and placeholders use muted slate tones.
The submit button uses the Lucide Send icon (aliased as se in the bundle) to echo the raven-dispatch theme rather than a plain text label.

Customization: Wiring Up a Real Form Backend

Because witch-dev is a fully static site deployed without a server, the contact form cannot send email on its own. The browser’s security model blocks direct SMTP connections from client-side code, so a third-party form service or serverless function is required to actually deliver messages.
Popular options for static-site contact forms include Formspree, EmailJS, and Netlify Forms. Each requires only a small amount of configuration and a few lines of client-side JavaScript.
EmailJS lets you send email directly from the browser by calling their API with your service credentials — no backend required.
1

Install the EmailJS browser SDK

npm install @emailjs/browser
2

Create a service and template in the EmailJS dashboard

Log in to emailjs.com, connect your email provider, and create a message template. Note your Service ID, Template ID, and Public Key.
3

Wire the form to EmailJS

Replace or extend the existing handleSubmit stub in the Contact component:
// Example: wiring up EmailJS or Formspree
import emailjs from '@emailjs/browser';

const handleSubmit = async (e) => {
  e.preventDefault();
  await emailjs.sendForm(
    'YOUR_SERVICE_ID',
    'YOUR_TEMPLATE_ID',
    e.target,
    'YOUR_PUBLIC_KEY'
  );
};
The e.target argument passes the entire form element — EmailJS reads each input’s name attribute to map it to your template variables, so make sure your <input> and <textarea> elements carry matching name props.
4

Add loading and success/error feedback

Wrap the sendForm call in a try/catch and use a state variable to show a confirmation message or an error notice inside the glass-panel card after submission.

Controlled Form Pattern

Below is a minimal controlled-form implementation that fits the portfolio’s component style and can be dropped into the existing Contact page:
import { useState } from 'react';
import { Send } from 'lucide-react';   // aliased as `se` in the bundle
import emailjs from '@emailjs/browser';

export default function ContactForm() {
  const [fields, setFields] = useState({ name: '', email: '', message: '' });
  const [status, setStatus] = useState('idle'); // 'idle' | 'sending' | 'sent' | 'error'

  const handleChange = (e) =>
    setFields((prev) => ({ ...prev, [e.target.name]: e.target.value }));

  const handleSubmit = async (e) => {
    e.preventDefault();
    setStatus('sending');
    try {
      await emailjs.sendForm(
        'YOUR_SERVICE_ID',
        'YOUR_TEMPLATE_ID',
        e.target,
        'YOUR_PUBLIC_KEY'
      );
      setStatus('sent');
    } catch {
      setStatus('error');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="glass-panel flex flex-col gap-4 p-8">
      <input
        name="name"
        value={fields.name}
        onChange={handleChange}
        placeholder="Your name"
        className="bg-transparent border border-purple-800 rounded px-4 py-2 text-slate-200"
        required
      />
      <input
        name="email"
        type="email"
        value={fields.email}
        onChange={handleChange}
        placeholder="your@email.com"
        className="bg-transparent border border-purple-800 rounded px-4 py-2 text-slate-200"
        required
      />
      <textarea
        name="message"
        value={fields.message}
        onChange={handleChange}
        placeholder="Your message..."
        rows={5}
        className="bg-transparent border border-purple-800 rounded px-4 py-2 text-slate-200 resize-none"
        required
      />
      <button
        type="submit"
        disabled={status === 'sending'}
        className="flex items-center gap-2 self-end px-6 py-2 bg-purple-700 hover:bg-purple-600 rounded text-white transition-colors"
      >
        <Send size={16} />
        {status === 'sending' ? 'Sending…' : 'Send'}
      </button>

      {status === 'sent'  && <p className="text-lime-400">Your raven is on its way!</p>}
      {status === 'error' && <p className="text-red-400">Something went wrong. Try again.</p>}
    </form>
  );
}
Keep your EmailJS Public Key in a .env file (e.g. VITE_EMAILJS_PUBLIC_KEY) and reference it as import.meta.env.VITE_EMAILJS_PUBLIC_KEY. Public keys are safe to expose in client-side bundles, but Service IDs and Template IDs should still be treated as semi-private and kept out of public repositories where possible.

Alternative: Formspree

If you prefer a zero-SDK approach, Formspree accepts a standard <form> POST:
<form
  action="https://formspree.io/f/YOUR_FORM_ID"
  method="POST"
  className="glass-panel flex flex-col gap-4 p-8"
>
  <input name="name"    type="text"  placeholder="Your name"       required />
  <input name="email"   type="email" placeholder="your@email.com"  required />
  <textarea name="message" placeholder="Your message..." required />
  <button type="submit" className="...">
    <Send size={16} /> Send
  </button>
</form>
Formspree’s free tier has a monthly submission limit. For a high-traffic portfolio, consider upgrading or switching to a self-hosted serverless function (e.g. a Vercel Edge Function or a Netlify Function) that calls the SendGrid or Resend API.

Static Deployment Context

witch-dev is a Vite + React SPA with no accompanying API server. When deployed to a static host (GitHub Pages, Vercel static output, Netlify, Cloudflare Pages, etc.) there is no Node.js process running server-side to handle form submissions. This means:
  • You cannot use nodemailer or any server-only email library directly inside the React component.
  • Any service that requires a secret API key (SendGrid, Mailgun) must be called from a serverless function — not from the browser — to avoid exposing credentials in the client bundle.
  • EmailJS and Formspree are designed for exactly this constraint: they handle the server-side email delivery on your behalf, requiring only a public-facing identifier on the client side.

Build docs developers (and LLMs) love