// Coastal Drift — studio site (hero · about · work · contact · footer)

const { useState, useRef, useEffect, useCallback } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "headline": "We build apps that make waves",
  "subhead": "An independent studio shaping ambient, considered software.",
  "cta": "See our work",
  "secondaryCta": "See our work",
  "accent": ["#ff3d8b", "#c47bff"]
}/*EDITMODE-END*/;

// ── Contact form configuration ──────────────────────────────────────────────
// Posts to our own Pages Function at /api/contact, which verifies a Turnstile
// token and forwards to Web3Forms server-side. The Web3Forms access key lives
// only in Pages secrets, never in client JS.
const FORM_ENDPOINT = "/api/contact";
const TURNSTILE_SITE_KEY = "0x4AAAAAADUjr8pbFbTnpDrD";
const CONTACT_EMAIL = "hello@coastaldrift.dev";
const LEGAL_NAME = "Coastal Drift Developers LLC";

// ── useReveal: IntersectionObserver entrance, reduced-motion safe ───────────
function useReveal(threshold = 0.15) {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (reduced || typeof IntersectionObserver === 'undefined') { setVisible(true); return; }
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) { setVisible(true); io.disconnect(); }
      });
    }, { threshold });
    io.observe(el);
    return () => io.disconnect();
  }, [threshold]);
  return [ref, visible];
}

// ── Nav ─────────────────────────────────────────────────────────────────────
function MobileMenu({ open, onClose }) {
  // Body scroll lock + ESC handler while the menu is open.
  useEffect(() => {
    if (!open) return;
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', onKey);
    return () => {
      document.body.style.overflow = prevOverflow;
      document.removeEventListener('keydown', onKey);
    };
  }, [open, onClose]);

  return (
    <div id="cd-mobile-menu"
         className={`cd-mobile-menu${open ? ' is-open' : ''}`}
         role="dialog" aria-modal="true" aria-label="Site menu"
         aria-hidden={!open}>
      <button className="cd-menu-close" type="button" aria-label="Close menu"
              onClick={onClose} tabIndex={open ? 0 : -1}>
        <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
          <path d="M5 5l10 10M15 5L5 15" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
        </svg>
      </button>
      <ul className="cd-mobile-links" role="list">
        <li><a href="#home" onClick={onClose} tabIndex={open ? 0 : -1}>Home</a></li>
        <li><a href="#about" onClick={onClose} tabIndex={open ? 0 : -1}>About</a></li>
        <li><a href="#work" onClick={onClose} tabIndex={open ? 0 : -1}>Work</a></li>
        <li><a href="#contact" onClick={onClose} tabIndex={open ? 0 : -1}>Contact</a></li>
      </ul>
      <a className="cd-mobile-email" href={`mailto:${CONTACT_EMAIL}`}
         onClick={onClose} tabIndex={open ? 0 : -1}>
        {CONTACT_EMAIL}
      </a>
    </div>
  );
}

function Nav() {
  const [menuOpen, setMenuOpen] = useState(false);
  return (
    <>
      <nav className="cd-nav" aria-label="Primary">
        <a className="cd-brand" href="#home">
          <span>Coastal Drift</span>
        </a>
        <ul className="cd-links" role="list">
          <li><a href="#home">Home</a></li>
          <li><a href="#about">About</a></li>
          <li><a href="#work">Work</a></li>
          <li><a href="#contact">Contact</a></li>
        </ul>
        <button className="cd-menu-toggle" type="button"
                aria-expanded={menuOpen}
                aria-controls="cd-mobile-menu"
                aria-label={menuOpen ? "Close menu" : "Open menu"}
                onClick={() => setMenuOpen((v) => !v)}>
          <span aria-hidden="true"></span>
          <span aria-hidden="true"></span>
        </button>
      </nav>
      <MobileMenu open={menuOpen} onClose={() => setMenuOpen(false)} />
    </>
  );
}

// ── Hero ────────────────────────────────────────────────────────────────────
function Hero({ t }) {
  return (
    <section id="home" className="cd-hero-section">
      {/* Stage anchors to the right edge of the viewport (capped at 1800px).
          Overlays inside it (vignette / fades / bottom-fade) stay sized to the
          stage, so their composition is preserved. */}
      <div className="cd-stage">
        <picture>
          <source media="(max-width: 820px)" srcSet="assets/hero-room-mobile.jpeg"
                  width="1000" height="1792" />
          <img className="cd-bg" src="assets/hero-room.jpeg"
               width="1800" height="1005"
               fetchpriority="high" decoding="async"
               alt="A small studio workspace with a neon Coastal Drift sign over a window facing the sea, an arcade cabinet, desk and couch."
               draggable="false" />
        </picture>
        <div className="cd-vignette" aria-hidden="true"></div>
        <div className="cd-left-fade" aria-hidden="true"></div>
        <div className="cd-top-fade" aria-hidden="true"></div>
        <div className="cd-bottom-fade" aria-hidden="true"></div>
      </div>

      <div className="cd-hero-frame">
        <header className="cd-hero">
          <h1 className="cd-h1">{t.headline}</h1>
          <p className="cd-sub">{t.subhead}</p>
          <div className="cd-cta-row">
            <a className="cd-cta-primary" href="#work">
              <span>{t.cta}</span>
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
                <path d="M2 7h10M8 3l4 4-4 4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
            </a>
          </div>
        </header>
      </div>
    </section>
  );
}

// ── About ───────────────────────────────────────────────────────────────────
function About() {
  const [ref, visible] = useReveal();
  return (
    <section id="about" ref={ref}
             className={`cd-section cd-about${visible ? ' is-revealed' : ''}`}
             aria-labelledby="about-heading">
      <div className="cd-section-inner cd-about-inner">
        <span className="cd-eyebrow">About the studio</span>
        <h2 id="about-heading" className="cd-h2">
          Considered software, built for everyone.
        </h2>
        <div className="cd-prose">
          <p>
            Coastal Drift is a small independent studio shaping calm, ambient
            products from a house by the sea. We work slowly on purpose: fewer
            projects, deeper craft, software that holds up.
          </p>
          <p>
            We build for the moments when life is loud, a first international
            trip, an unfamiliar form, a panic at the gate. Our flagship app,
            Calm Travels, exists for exactly those moments. Accessibility isn&rsquo;t
            a checklist for us. It&rsquo;s the whole point. Every product we ship is
            held to WCAG&nbsp;AA as a baseline, often higher, because clarity and
            access are how software earns trust.
          </p>
        </div>
      </div>
    </section>
  );
}

// ── Work ────────────────────────────────────────────────────────────────────
function WorkItem({ flagship, index, status, name, descriptor, blurb }) {
  return (
    <li className={`cd-work-item${flagship ? ' is-flagship' : ''}`}>
      <span className="cd-work-index" aria-hidden="true">{index}</span>
      <div className="cd-work-body">
        <header className="cd-work-head">
          <h3 className="cd-work-name">{name}</h3>
          {status && <span className="cd-tag">{status}</span>}
        </header>
        <p className="cd-work-desc">{descriptor}</p>
        <p className="cd-work-blurb">{blurb}</p>
      </div>
    </li>
  );
}

function Work() {
  const [ref, visible] = useReveal();
  return (
    <section id="work" ref={ref}
             className={`cd-section cd-work${visible ? ' is-revealed' : ''}`}
             aria-labelledby="work-heading">
      <div className="cd-section-inner">
        <span className="cd-eyebrow">Work</span>
        <h2 id="work-heading" className="cd-h2">Things we&rsquo;ve been shaping.</h2>
        <ol className="cd-work-list" role="list">
          <WorkItem
            flagship
            index="01"
            status="Coming soon"
            name="Calm Travels"
            descriptor="A calm, procedural copilot for anxious travelers."
            blurb="Step-by-step guidance for first-time international trips, passport control, lost-luggage scripts, transit how-tos. Designed with people who actually need it, and held to a strict accessibility bar."
          />
          <WorkItem
            index="02"
            status="In progress"
            name="Project Tideline"
            descriptor="Social software for the connected age."
            blurb="Designed for personal interactions and deep connections without the noise and distraction of traditional social media."
          />
          <WorkItem
            index="03"
            status="Sketching"
            name="Project Driftwood"
            descriptor="Ambient software for the desktop."
            blurb={<>Top secret for the moment, but we&rsquo;re excited to share more when the time is right. If you&rsquo;re curious, <a className="cd-link" href="#contact">say hello</a> and we can drop hints in your inbox as we&rsquo;re ready to share them.</>}
          />
        </ol>
      </div>
    </section>
  );
}

// ── Contact ─────────────────────────────────────────────────────────────────
function Contact() {
  const [ref, visible] = useReveal();
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");
  const [errors, setErrors] = useState({});
  const [status, setStatus] = useState("idle"); // idle | sending | success | error
  const [statusMsg, setStatusMsg] = useState("");
  const [turnstileToken, setTurnstileToken] = useState("");
  const turnstileEl = useRef(null);
  const turnstileWidget = useRef(null);

  useEffect(() => {
    if (!turnstileEl.current) return;
    let cancelled = false;
    const render = () => {
      if (cancelled) return;
      if (window.turnstile) {
        turnstileWidget.current = window.turnstile.render(turnstileEl.current, {
          sitekey: TURNSTILE_SITE_KEY,
          callback: (token) => setTurnstileToken(token),
          "expired-callback": () => setTurnstileToken(""),
          "error-callback": () => setTurnstileToken(""),
          theme: "dark",
        });
      } else {
        setTimeout(render, 150);
      }
    };
    render();
    return () => {
      cancelled = true;
      if (turnstileWidget.current && window.turnstile) {
        try { window.turnstile.remove(turnstileWidget.current); } catch {}
      }
    };
  }, []);

  const resetTurnstile = () => {
    setTurnstileToken("");
    if (turnstileWidget.current && window.turnstile) {
      try { window.turnstile.reset(turnstileWidget.current); } catch {}
    }
  };

  const validate = useCallback(() => {
    const e = {};
    if (!name.trim()) e.name = "Please add your name.";
    if (!email.trim()) e.email = "Please add your email.";
    else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) e.email = "That email doesn't look right.";
    if (!message.trim()) e.message = "Tell us a little about what you're working on.";
    else if (message.trim().length < 10) e.message = "A bit more detail helps (at least 10 characters).";
    setErrors(e);
    return Object.keys(e).length === 0;
  }, [name, email, message]);

  const onSubmit = async (ev) => {
    ev.preventDefault();
    if (status === "sending") return;
    if (!validate()) {
      setStatus("error");
      setStatusMsg("Please fix the highlighted fields and try again.");
      return;
    }
    if (!turnstileToken) {
      setStatus("error");
      setStatusMsg("Please complete the captcha and try again.");
      return;
    }
    setStatus("sending"); setStatusMsg("");
    try {
      const res = await fetch(FORM_ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/json", "Accept": "application/json" },
        body: JSON.stringify({ name, email, message, turnstileToken })
      });
      const data = await res.json().catch(() => ({}));
      const ok = res.ok && data.success !== false;
      if (!ok) throw new Error(data.message || `Send failed (${res.status})`);
      setStatus("success");
      setStatusMsg("Thanks — we'll get back to you shortly.");
      setName(""); setEmail(""); setMessage(""); setErrors({});
      resetTurnstile();
    } catch (err) {
      setStatus("error");
      setStatusMsg(`Couldn't send — please email ${CONTACT_EMAIL} instead.`);
      resetTurnstile();
    }
  };

  // Auto-dismiss the success toast so it doesn't hang around.
  useEffect(() => {
    if (status !== "success") return;
    const id = setTimeout(() => { setStatus("idle"); setStatusMsg(""); }, 6000);
    return () => clearTimeout(id);
  }, [status]);

  const sending = status === "sending";

  return (
    <section id="contact" ref={ref}
             className={`cd-section cd-contact${visible ? ' is-revealed' : ''}`}
             aria-labelledby="contact-heading">
      <div className="cd-section-inner cd-contact-inner">
        <div className="cd-contact-copy">
          <span className="cd-eyebrow">Contact</span>
          <h2 id="contact-heading" className="cd-h2">Say hello.</h2>
          <p className="cd-prose-single">
            Working on something that calls for calm, considered software?
            Curious about Calm Travels? Send a note, we read everything.
            Prefer email? Write to{' '}
            <a className="cd-link" href={`mailto:${CONTACT_EMAIL}`}>{CONTACT_EMAIL}</a>.
          </p>
        </div>

        <form className="cd-form" onSubmit={onSubmit} noValidate>
          <div className="cd-field">
            <label htmlFor="cd-name">Name</label>
            <input id="cd-name" name="name" type="text" autoComplete="name"
                   value={name} onChange={(e) => setName(e.target.value)}
                   aria-invalid={!!errors.name}
                   aria-describedby={errors.name ? "cd-name-err" : undefined}
                   required />
            {errors.name && <span id="cd-name-err" className="cd-err">{errors.name}</span>}
          </div>

          <div className="cd-field">
            <label htmlFor="cd-email">Email</label>
            <input id="cd-email" name="email" type="email" autoComplete="email" inputMode="email"
                   value={email} onChange={(e) => setEmail(e.target.value)}
                   aria-invalid={!!errors.email}
                   aria-describedby={errors.email ? "cd-email-err" : undefined}
                   required />
            {errors.email && <span id="cd-email-err" className="cd-err">{errors.email}</span>}
          </div>

          <div className="cd-field">
            <label htmlFor="cd-message">Message</label>
            <textarea id="cd-message" name="message" rows="5"
                      value={message} onChange={(e) => setMessage(e.target.value)}
                      aria-invalid={!!errors.message}
                      aria-describedby={errors.message ? "cd-message-err" : undefined}
                      required />
            {errors.message && <span id="cd-message-err" className="cd-err">{errors.message}</span>}
          </div>

          <div className="cd-field cd-field-turnstile" ref={turnstileEl} />

          <div className="cd-form-row">
            <button type="submit" className="cd-cta-primary cd-form-submit" disabled={sending}
                    onClick={onSubmit}>
              <span>{sending ? "Sending…" : "Send message"}</span>
              {!sending && (
                <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
                  <path d="M2 7h10M8 3l4 4-4 4" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
                </svg>
              )}
            </button>
            <span className="cd-form-fallback">
              or email <a className="cd-link" href={`mailto:${CONTACT_EMAIL}`}>{CONTACT_EMAIL}</a>
            </span>
          </div>

          <div className="cd-form-status" role="status" aria-live="polite">
            {status === "error" && <span className="cd-err">{statusMsg}</span>}
          </div>
        </form>

        {status === "success" && (
          <div className="cd-toast" role="status" aria-live="polite">
            <span className="cd-toast-pulse" aria-hidden="true"></span>
            <b>Sent.</b>&nbsp;{statusMsg}
          </div>
        )}
      </div>
    </section>
  );
}

// ── Footer ──────────────────────────────────────────────────────────────────
function Footer() {
  const year = new Date().getFullYear();
  return (
    <footer className="cd-footer" aria-labelledby="footer-heading">
      <h2 id="footer-heading" className="cd-sr-only">Footer</h2>
      <div className="cd-footer-inner">
        <div className="cd-footer-brand">
          <a className="cd-brand cd-footer-wordmark" href="#home">Coastal Drift</a>
          <p className="cd-footer-tag">Ambient, considered software, built for everyone.</p>
        </div>
        <div className="cd-footer-nav">
          <div className="cd-footer-col">
            <span className="cd-footer-col-h">Site</span>
            <ul role="list">
              <li><a href="#home">Home</a></li>
              <li><a href="#about">About</a></li>
              <li><a href="#work">Work</a></li>
              <li><a href="#contact">Contact</a></li>
            </ul>
          </div>
          <div className="cd-footer-col">
            <span className="cd-footer-col-h">Legal</span>
            <ul role="list">
              <li><a href="privacy.html">Privacy</a></li>
              <li><a href="terms.html">Terms</a></li>
              <li><a href="eula.html">EULA</a></li>
            </ul>
          </div>
          <div className="cd-footer-col">
            <span className="cd-footer-col-h">Elsewhere</span>
            <ul role="list">
              {/* TODO: replace placeholder hrefs with real social URLs */}
              <li><a href="#" rel="me">Mastodon</a></li>
              <li><a href="#">GitHub</a></li>
              <li><a href={`mailto:${CONTACT_EMAIL}`}>Email</a></li>
            </ul>
          </div>
        </div>
      </div>
      <div className="cd-footer-base">
        <span>&copy; {year} {LEGAL_NAME}. All rights reserved.</span>
        <span className="cd-footer-base-meta">Made by the sea.</span>
      </div>
    </footer>
  );
}

// ── App ─────────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  return (
    <div className="cd-root" style={{
      "--accent-1": t.accent[0],
      "--accent-2": t.accent[1]
    }}>
      <a className="cd-skip" href="#main">Skip to main content</a>
      <Nav />

      <main id="main">
        <Hero t={t} />
        <About />
        <Work />
        <Contact />
      </main>

      <Footer />

      <TweaksPanel>
        <TweakSection label="Copy" />
        <TweakText label="Headline" value={t.headline} onChange={(v) => setTweak("headline", v)} />
        <TweakText label="Subhead" value={t.subhead} onChange={(v) => setTweak("subhead", v)} />
        <TweakText label="Primary CTA" value={t.cta} onChange={(v) => setTweak("cta", v)} />
        <TweakText label="Secondary CTA" value={t.secondaryCta} onChange={(v) => setTweak("secondaryCta", v)} />

        <TweakSection label="Theme" />
        <TweakColor label="Accent" value={t.accent}
          options={[
            ["#ff3d8b", "#c47bff"],
            ["#7df9ff", "#5aa6ff"],
            ["#ffb86b", "#ff6b9d"],
            ["#58e6c1", "#7ab8ff"]
          ]}
          onChange={(v) => setTweak("accent", v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
