/* === About + Contact + Nav + Footer === */

const Nav = ({ onTweaksOpen }) => {
  const [scrolled, setScrolled] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 80);
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <nav style={{
      position: "fixed",
      top: 0, left: 0, right: 0,
      padding: "20px var(--pad)",
      zIndex: 100,
      display: "flex",
      justifyContent: "space-between",
      alignItems: "center",
      background: scrolled ? "color-mix(in oklab, var(--bg) 84%, transparent)" : "transparent",
      backdropFilter: scrolled ? "blur(12px)" : "none",
      transition: "background 320ms var(--ease-out-quart)",
      borderBottom: scrolled ? "1px solid var(--rule)" : "1px solid transparent"
    }}>
      <a href="#top" data-cursor="Top" aria-label="Omnia Core — zurück nach oben" style={{ display: "flex", alignItems: "center", gap: 10 }}>
        <Mark />
        <span className="mono" style={{ fontWeight: 600 }}>OMNIA / CORE</span>
      </a>
      <div style={{ display: "flex", gap: 36 }}>
        <a href="#about" data-cursor="↘" className="mono nav-link" style={{ color: "var(--fg-muted)" }}>(01) ÜBER MICH</a>
        <a href="#work" data-cursor="↘" className="mono nav-link" style={{ color: "var(--fg-muted)" }}>(02) PROJEKTE</a>
        <a href="#contact" data-cursor="↘" className="mono nav-link" style={{ color: "var(--fg-muted)" }}>(03) KONTAKT</a>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
        <span className="mono" style={{ color: "var(--fg-muted)" }}>
          <span aria-hidden="true" style={{
            display: "inline-block", width: 6, height: 6, borderRadius: "50%",
            background: "var(--accent)", marginRight: 6,
            animation: "pulse 1.8s ease-in-out infinite"
          }} />
          Verfügbar
        </span>
      </div>
    </nav>);

};

const Mark = () =>
<svg viewBox="0 0 24 24" width="38" height="38" focusable="false" aria-hidden="true" style={{ display: "block" }}>
    <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="1.1" fill="none" />
    <circle cx="12" cy="12" r="5" fill="var(--accent)" />
    <line x1="12" y1="2" x2="12" y2="22" stroke="currentColor" strokeWidth="0.5" />
  </svg>;


const About = () => {
  return (
    <section id="about" style={{
      background: "var(--bg-inverse)",
      color: "var(--fg-on-dark)",
      padding: "var(--s-11) 0 var(--s-10)",
      position: "relative",
      overflow: "hidden"
    }}>
      <div className="grid">
        <div className="mono" style={{ gridColumn: "1 / span 4", color: "var(--fg-muted-on-dark)", marginBottom: 48 }}>(01) — ÜBER MICH · SVEN REICHELT

        </div>
        <div style={{ gridColumn: "1 / span 12" }}>
          <p style={{
            fontFamily: "var(--font-display)",
            fontSize: "clamp(38px, 5.2vw, 88px)",
            lineHeight: 1.04,
            letterSpacing: "-0.025em",
            fontWeight: 300,
            maxWidth: "22ch",
            color: "var(--fg-on-dark)"
          }}>
            <em style={{ color: "var(--accent)", fontStyle: "italic" }}>Software</em>,{" "}
            die dein Team wirklich{" "}
            <em style={{ fontStyle: "italic" }}>voranbringt</em> – statt auszubremsen.
          </p>
          <p style={{
            fontFamily: "var(--font-text)",
            fontSize: "clamp(16px, 1.4vw, 20px)",
            lineHeight: 1.55,
            letterSpacing: "-0.005em",
            fontWeight: 400,
            maxWidth: "52ch",
            color: "var(--fg-muted-on-dark)",
            marginTop: "var(--s-6)"
          }}>
            Als Fachinformatiker mit Hands-on-Mentalität entwickle ich Webseiten,
            SaaS-Lösungen und KI-Automationen, die funktionieren: praxisnah, effizient
            und mit echtem Mehrwert für deinen Arbeitsalltag.
          </p>
        </div>
      </div>

      <div className="grid" style={{ marginTop: "var(--s-10)" }}>
        <div style={{ gridColumn: "1 / span 4" }}>
          <Pillar eyebrow="(A) — ANALYSE" title="Schnell und Solide" body="Ganzheitliche Bedarfsanalyse des Projekts, fundierte Themen- und Marktrecherche sowie die Konzeption und Umsetzung eines maßgeschneiderten Tools oder einer modernen Website." />
        </div>
        <div style={{ gridColumn: "5 / span 4" }}>
          <Pillar eyebrow="(B) — OPTIMIERUNG" title="Smarte Prozessoptimierung" body="KI dort einsetzen, wo sie wirklich Zeit spart — nicht als Selbstzweck. Mit optimierten, nachvollziehbaren Workflows und prüfbaren Ergebnissen. Praxisnah, hands-on und ohne Hype." />
        </div>
        <div style={{ gridColumn: "9 / span 4" }}>
          <Pillar eyebrow="(C) — UMSETZUNG" title="Tech-Umsetzung" body="Ich packe Dinge an und entwickle echte Lösungen. Technisches Know-how aus Netzwerk, KI und Programmierung verbinde ich mit unternehmerischem Denken — damit dein Tool im Alltag funktioniert, nicht nur auf der Präsentationsfolie." />
        </div>
      </div>

      {/* Faux-3D rotating disc as marginalia */}
      <div style={{
        position: "absolute",
        top: "50%",
        right: -120,
        transform: "translateY(-50%)",
        width: 360, height: 360,
        opacity: 0.65,
        pointerEvents: "none"
      }}>
        <Disc />
      </div>
    </section>);

};

const Pillar = ({ n, eyebrow, title, body }) =>
<div>
    <div className="mono" style={{ color: "var(--accent)", marginBottom: 18 }}>
      {eyebrow || `(${n}) — Wie ich arbeite`}
    </div>
    <h3 style={{
    fontFamily: "var(--font-display)",
    fontStyle: "italic",
    fontSize: 42,
    letterSpacing: "-0.02em",
    lineHeight: 1,
    marginBottom: 18,
    fontWeight: 400
  }}>
      {title}
    </h3>
    <p style={{
    fontFamily: "var(--font-text)",
    fontSize: 16, lineHeight: 1.55,
    color: "var(--fg-muted-on-dark)",
    maxWidth: "28ch"
  }}>{body}</p>
  </div>;


const Disc = () =>
<svg viewBox="0 0 360 360" focusable="false" aria-hidden="true" style={{ width: "100%", height: "100%", animation: "spin 80s linear infinite" }}>
    <defs>
      <radialGradient id="discGrad">
        <stop offset="0%" stopColor="rgba(242,240,235,0.08)" />
        <stop offset="60%" stopColor="rgba(242,240,235,0.04)" />
        <stop offset="100%" stopColor="rgba(242,240,235,0)" />
      </radialGradient>
    </defs>
    <circle cx="180" cy="180" r="178" fill="url(#discGrad)" />
    {[...Array(8)].map((_, i) =>
  <circle key={i} cx="180" cy="180" r={20 + i * 20}
  stroke="rgba(242,240,235,0.08)" strokeWidth="0.5" fill="none" />
  )}
    <circle cx="180" cy="180" r="6" fill="var(--accent)" />
    <text x="180" y="80" textAnchor="middle" fontFamily="var(--font-mono)" fontSize="9"
  fill="rgba(242,240,235,0.4)" letterSpacing="2">
      ★ WERKSTATT · MÜNCHEN · MMXXVI ★
    </text>
  </svg>;


const WEB3FORMS_KEY = "03fbdb04-5464-47b0-92ba-f14e97c27850";

const validateContact = (name, email, message) => {
  const errors = {};
  if (!name.trim()) errors.name = "Bitte gib deinen Namen an.";
  if (!email.trim()) {
    errors.email = "Bitte gib deine E-Mail-Adresse an.";
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
    errors.email = "Diese E-Mail-Adresse sieht ungültig aus.";
  }
  if (!message.trim()) errors.message = "Bitte schreib eine kurze Nachricht.";
  return errors;
};

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const Contact = () => {
  const [name, setName] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [message, setMessage] = React.useState("");
  const [status, setStatus] = React.useState("idle"); // idle | loading | success | error
  const [errors, setErrors] = React.useState({});
  const [statusMessage, setStatusMessage] = React.useState("");

  // Field-level validity used for live "errors clear when fixed" + green tick.
  // Per-field validators reused by both submit and live updates.
  const isNameValid = (v) => v.trim().length >= 2;
  const isEmailValid = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim());
  const isMessageValid = (v) => v.trim().length > 0;

  // Wrapped setters: clear the corresponding error as soon as the field becomes valid.
  const updateName = React.useCallback((v) => {
    setName(v);
    setErrors((prev) => {
      if (!prev.name) return prev;
      if (isNameValid(v)) {
        const { name: _, ...rest } = prev;
        return rest;
      }
      return prev;
    });
  }, []);
  const updateEmail = React.useCallback((v) => {
    setEmail(v);
    setErrors((prev) => {
      if (!prev.email) return prev;
      if (isEmailValid(v)) {
        const { email: _, ...rest } = prev;
        return rest;
      }
      return prev;
    });
  }, []);
  const updateMessage = React.useCallback((v) => {
    setMessage(v);
    setErrors((prev) => {
      if (!prev.message) return prev;
      if (isMessageValid(v)) {
        const { message: _, ...rest } = prev;
        return rest;
      }
      return prev;
    });
  }, []);

  // When all errors are cleared via live editing, clear the global error
  // status banner too so the page no longer announces "X Felder müssen korrigiert werden".
  React.useEffect(() => {
    if (status === "error" && Object.keys(errors).length === 0) {
      setStatus("idle");
      setStatusMessage("");
    }
  }, [status, errors]);

  const nameRef = React.useRef(null);
  const emailRef = React.useRef(null);
  const messageRef = React.useRef(null);
  const statusRef = React.useRef(null);
  const submitRef = React.useRef(null);
  const formWrapRef = React.useRef(null);

  // === Black-Hole Particle Guide State ===
  const [particlesActive, setParticlesActive] = React.useState(false);
  const [particleTarget, setParticleTarget] = React.useState(null);
  const [particleMode, setParticleMode] = React.useState("attract"); // attract | repel | suck
  const [particleTone, setParticleTone] = React.useState("default"); // default | error | success

  // === Hyperspace Jump State ===
  // Phases: idle | charge (0.4s) | jump (0.6s) | tunnel (0.8s) | snap (0.3s) | confirm (0.5s+)
  const [jumpPhase, setJumpPhase] = React.useState("idle");
  const [jumpCenter, setJumpCenter] = React.useState(null);
  const [showConfirmation, setShowConfirmation] = React.useState(false);
  const audioCtxRef = React.useRef(null);

  // Compute jump center (button center within canvas-local coords).
  // Canvas is inset by `-60px -80px` from the form wrapper, so we add the
  // same offset used by updateTarget to keep the explosion centered on the button.
  const computeJumpCenter = React.useCallback(() => {
    const wrap = formWrapRef.current;
    const btn = submitRef.current;
    if (!wrap || !btn) return null;
    const wr = wrap.getBoundingClientRect();
    const br = btn.getBoundingClientRect();
    return {
      x: br.left + br.width / 2 - wr.left + 80,
      y: br.top + br.height / 2 - wr.top + 60,
    };
  }, []);

  // Web Audio: charge hum + whoosh sweep (best-effort, silent if blocked)
  const playHyperspaceSound = React.useCallback(() => {
    try {
      if (!audioCtxRef.current) {
        const Ctx = window.AudioContext || window.webkitAudioContext;
        if (!Ctx) return;
        audioCtxRef.current = new Ctx();
      }
      const ac = audioCtxRef.current;
      if (ac.state === "suspended") ac.resume();
      const now = ac.currentTime;
      // Charge hum (low oscillator rising)
      const o1 = ac.createOscillator();
      const g1 = ac.createGain();
      o1.type = "sawtooth";
      o1.frequency.setValueAtTime(60, now);
      o1.frequency.exponentialRampToValueAtTime(220, now + 0.4);
      g1.gain.setValueAtTime(0.0001, now);
      g1.gain.exponentialRampToValueAtTime(0.08, now + 0.3);
      g1.gain.exponentialRampToValueAtTime(0.0001, now + 0.5);
      o1.connect(g1).connect(ac.destination);
      o1.start(now); o1.stop(now + 0.55);
      // Whoosh: noise burst + downward sweep
      const bufLen = Math.floor(ac.sampleRate * 1.4);
      const buf = ac.createBuffer(1, bufLen, ac.sampleRate);
      const data = buf.getChannelData(0);
      for (let i = 0; i < bufLen; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / bufLen);
      const noise = ac.createBufferSource();
      noise.buffer = buf;
      const bp = ac.createBiquadFilter();
      bp.type = "bandpass";
      bp.frequency.setValueAtTime(2000, now + 0.4);
      bp.frequency.exponentialRampToValueAtTime(200, now + 1.6);
      bp.Q.value = 8;
      const g2 = ac.createGain();
      g2.gain.setValueAtTime(0.0001, now + 0.4);
      g2.gain.exponentialRampToValueAtTime(0.18, now + 0.7);
      g2.gain.exponentialRampToValueAtTime(0.0001, now + 1.6);
      noise.connect(bp).connect(g2).connect(ac.destination);
      noise.start(now + 0.4); noise.stop(now + 1.7);
    } catch (e) { /* swallow — sound is decorative */ }
  }, []);

  // Determine which step we're on based on validity
  const currentStep = React.useMemo(() => {
    if (name.trim().length < 2) return "name";
    if (!EMAIL_RE.test(email.trim())) return "email";
    if (!message.trim()) return "message";
    return "submit";
  }, [name, email, message]);

  // Recompute target rect (in canvas-local coords) on resize/scroll/step.
  // The canvas is inset by -80px (left) / -60px (top) from the form wrapper
  // to allow a soft edge fade, so we must shift target coords by that
  // offset — otherwise the swirl sits to the upper-left of the actual field.
  const CANVAS_OFFSET_X = 80; // matches `inset: -60px -80px` on the canvas
  const CANVAS_OFFSET_Y = 60;
  const updateTarget = React.useCallback(() => {
    const wrap = formWrapRef.current;
    if (!wrap) return;
    const wrapRect = wrap.getBoundingClientRect();
    let el = null;
    if (currentStep === "name") el = nameRef.current;
    else if (currentStep === "email") el = emailRef.current;
    else if (currentStep === "message") el = messageRef.current;
    else if (currentStep === "submit") el = submitRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    setParticleTarget({
      x: r.left - wrapRect.left + CANVAS_OFFSET_X,
      y: r.top - wrapRect.top + CANVAS_OFFSET_Y,
      w: r.width,
      h: r.height,
    });
  }, [currentStep]);

  React.useEffect(() => {
    updateTarget();
    const onResize = () => updateTarget();
    window.addEventListener("resize", onResize);
    window.addEventListener("scroll", onResize, { passive: true });
    return () => {
      window.removeEventListener("resize", onResize);
      window.removeEventListener("scroll", onResize);
    };
  }, [updateTarget]);

  // Sync mode/tone with form state
  React.useEffect(() => {
    if (status === "loading") {
      setParticleMode("suck");
      setParticleTone("default");
    } else if (status === "success") {
      setParticleMode("attract");
      setParticleTone("success");
    } else if (errors.email) {
      setParticleMode("repel");
      setParticleTone("error");
      const t = setTimeout(() => {
        setParticleMode("attract");
        setParticleTone("default");
      }, 900);
      return () => clearTimeout(t);
    } else {
      setParticleMode("attract");
      setParticleTone("default");
    }
  }, [status, errors.email]);

  const triggerHyperspace = React.useCallback(() => {
    const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const center = computeJumpCenter();
    setJumpCenter(center);

    if (reduce) {
      // Reduced motion: skip the spectacle — direct confirmation
      setShowConfirmation(true);
      return Promise.resolve();
    }

    playHyperspaceSound();

    return new Promise((resolve) => {
      setJumpPhase("charge");
      setTimeout(() => setJumpPhase("jump"), 400);
      setTimeout(() => setJumpPhase("tunnel"), 1000);
      setTimeout(() => setJumpPhase("snap"), 1800);
      setTimeout(() => {
        setJumpPhase("confirm");
        setShowConfirmation(true);
        resolve();
      }, 2100);
      // Confirmation phase fades out after 1.4s of ambient afterglow
      setTimeout(() => setJumpPhase("idle"), 3500);
    });
  }, [computeJumpCenter, playHyperspaceSound]);

  const resetIfDone = React.useCallback(() => {
    if (status === "success" || status === "error") {
      setStatus("idle");
      setStatusMessage("");
    }
  }, [status]);

  const onSubmit = async (e) => {
    e.preventDefault();
    if (status === "loading") return;

    const honeypot = e.target.elements.botcheck && e.target.elements.botcheck.value;

    const validationErrors = validateContact(name, email, message);
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
      const count = Object.keys(validationErrors).length;
      setStatus("error");
      setStatusMessage(count === 1 ? "1 Feld muss korrigiert werden." : `${count} Felder müssen korrigiert werden.`);
      // Focus first invalid field
      if (validationErrors.name) nameRef.current && nameRef.current.focus();else
      if (validationErrors.email) emailRef.current && emailRef.current.focus();else
      if (validationErrors.message) messageRef.current && messageRef.current.focus();
      return;
    }

    setErrors({});
    setStatus("loading");
    setStatusMessage("Nachricht wird gesendet …");

    try {
      const res = await fetch("https://api.web3forms.com/submit", {
        method: "POST",
        headers: { "Content-Type": "application/json", Accept: "application/json" },
        body: JSON.stringify({
          access_key: WEB3FORMS_KEY,
          name: name.trim(),
          email: email.trim(),
          message: message.trim(),
          botcheck: honeypot || "",
          subject: `Neue Anfrage über omniacore.net — von ${name.trim()}`,
          from_name: "Omnia Core Website"
        })
      });
      const data = await res.json();
      if (data.success) {
        setStatus("success");
        setStatusMessage("Nachricht raus — ich melde mich zeitnah bei dir.");
        // Trigger the hyperspace jump animation (or instant confirm if reduced motion)
        triggerHyperspace();
        // Clear fields after the jump finishes (cleared mid-tunnel so they reset
        // before the confirmation appears)
        setTimeout(() => {
          setName(""); setEmail(""); setMessage("");
        }, 1800);
        // Move focus to status region for AT users
        setTimeout(() => {
          if (!statusRef.current) return;
          statusRef.current.focus();
          const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
          statusRef.current.scrollIntoView({ block: "center", behavior: reduce ? "auto" : "smooth" });
        }, 150);
      } else {
        throw new Error(data.message || "Web3Forms hat die Nachricht abgelehnt.");
      }
    } catch (err) {
      setStatus("error");
      setStatusMessage(
        <>Senden fehlgeschlagen. Bitte versuche es erneut oder schreib direkt an <a href="mailto:info@omniacore.net" style={{ textDecoration: "underline" }}>info@omniacore.net</a>.</>
      );
      setTimeout(() => statusRef.current && statusRef.current.focus(), 50);
    }
  };

  const buttonLabel =
  status === "loading" ? "Wird gesendet …" :
  status === "success" ? <><span aria-hidden="true">✓ </span>Nachricht raus — wir sprechen uns</> :
  status === "error" ? <>Erneut senden <span aria-hidden="true">→</span></> :
  <>Nachricht senden <span aria-hidden="true">→</span></>;

  const isError = status === "error";
  const isSuccess = status === "success";
  const isLoading = status === "loading";

  return (
    <section id="contact" aria-labelledby="contact-heading" style={{ padding: "var(--s-11) 0 var(--s-10)" }}>
      <div className="mono" style={{ paddingLeft: "var(--pad)", paddingRight: "var(--pad)", color: "var(--fg-muted)", marginBottom: "var(--s-8)" }}>
        (03) — KONTAKT
      </div>

      <div className="grid">
        <div style={{ gridColumn: "1 / span 5" }}>
          <h3 className="mono" style={{ color: "var(--fg-muted)", marginBottom: 12, fontWeight: 500 }}>
            Womit ich dir helfen kann
          </h3>
          <p style={{ fontSize: 15, lineHeight: 1.6, maxWidth: "32ch" }}>
            SaaS-Produkte, Webseiten (gerne auch im Immobilienbereich)
            und IT-Dienstleistungen rund um Netzwerk und Systemintegration.
            Am liebsten mit Teams, die smarter arbeiten wollen — und Lust
            auf saubere Lösungen haben.
          </p>
          <img
            src="sven.jpg"
            alt="Sven Reichelt, Gründer von Omnia Core"
            width="432"
            height="432"
            loading="lazy"
            decoding="async"
            style={{
              display: "block",
              width: "100%",
              maxWidth: 320,
              height: "auto",
              aspectRatio: "var(--ratio-portrait)",
              objectFit: "cover",
              borderRadius: 16,
              marginTop: "var(--s-7)"
            }} />

        </div>

        <div
          ref={formWrapRef}
          onMouseEnter={() => setParticlesActive(true)}
          onFocus={() => setParticlesActive(true)}
          style={{ gridColumn: "7 / span 6", position: "relative", minHeight: 520 }}>
          <BlackHoleParticles
            active={particlesActive}
            target={particleTarget}
            mode={particleMode}
            tone={particleTone}
            jumpPhase={jumpPhase}
            jumpCenter={jumpCenter} />
        <form
          onSubmit={onSubmit}
          aria-busy={isLoading || undefined}
          aria-describedby={statusMessage ? "form-status" : undefined}
          noValidate
          style={{
            position: "relative",
            zIndex: 2,
            display: showConfirmation ? "none" : "block",
            transformOrigin: "center center",
            transition: jumpPhase === "idle" ? "none" : "transform 600ms cubic-bezier(0.7,0,0.84,0), opacity 400ms",
            transform:
              jumpPhase === "charge" ? "scale(0.97)" :
              jumpPhase === "jump" ? "scaleY(1.4) scaleX(0.6) perspective(800px) rotateX(2deg)" :
              jumpPhase === "tunnel" || jumpPhase === "snap" ? "scaleY(2.4) scaleX(0.2)" :
              "none",
            opacity:
              jumpPhase === "tunnel" || jumpPhase === "snap" ? 0 :
              jumpPhase === "jump" ? 0.5 :
              jumpPhase === "confirm" ? 0 :
              1,
            filter:
              jumpPhase === "charge" ? "brightness(1.1)" :
              jumpPhase === "jump" ? "blur(2px) brightness(1.4)" :
              "none",
            pointerEvents: jumpPhase !== "idle" ? "none" : "auto"
          }}>

          {/* Honeypot — invisible to humans and AT */}
          <div aria-hidden="true" style={{ position: "absolute", left: "-9999px", width: 1, height: 1, overflow: "hidden" }}>
            <label>Don't fill this out: <input type="text" name="botcheck" tabIndex={-1} autoComplete="off" /></label>
          </div>

          <Field label="Name" name="name" value={name} onChange={updateName} placeholder="Dein Name" autoComplete="name" autoCapitalize="words" enterKeyHint="next" required error={errors.name} valid={name.length > 0 && isNameValid(name)} ref={nameRef} onFocusReset={resetIfDone} />
          <Field label="E-Mail" name="email" value={email} onChange={updateEmail} placeholder="du@firma.com" type="email" autoComplete="email" inputMode="email" enterKeyHint="next" autoCapitalize="none" autoCorrect="off" spellCheck={false} required error={errors.email} valid={email.length > 0 && isEmailValid(email)} ref={emailRef} onFocusReset={resetIfDone} />
          <Field label="Nachricht" name="message" value={message} onChange={updateMessage} placeholder="Erzähl mir vom Projekt — was brauchst du, was funktioniert nicht, wann muss es stehen?" multiline enterKeyHint="send" required error={errors.message} valid={message.length > 0 && isMessageValid(message)} ref={messageRef} onFocusReset={resetIfDone} />

          <p className="mono" style={{ color: "var(--fg-muted)", marginTop: 8, marginBottom: 16 }}>
            <span aria-hidden="true">* </span>Pflichtfeld
          </p>

          <div
            id="form-status"
            ref={statusRef}
            role="status"
            aria-live="polite"
            aria-atomic="true"
            tabIndex={-1}
            style={{
              minHeight: statusMessage ? "auto" : 0,
              marginTop: 8,
              marginBottom: 16,
              padding: statusMessage ? "12px 16px" : 0,
              fontSize: 14,
              lineHeight: 1.5,
              color: "var(--fg)",
              borderLeft: statusMessage ? `2px solid ${isSuccess ? "var(--success)" : isError ? "var(--accent)" : "var(--fg-muted)"}` : "0",
              background: statusMessage ?
              isSuccess ? "color-mix(in srgb, var(--success) 8%, transparent)" : "rgba(255, 75, 31, 0.06)" :
              "transparent",
              outline: "none"
            }}>

            {statusMessage}
          </div>

          <button
            ref={submitRef}
            type="submit"
            data-cursor={isLoading ? "" : "Senden"}
            aria-disabled={isLoading || undefined}
            onMouseEnter={(e) => {
              if (isSuccess || isLoading) return;
              e.currentTarget.style.background = "var(--fg)";
              e.currentTarget.style.color = "var(--bg)";
            }}
            onMouseLeave={(e) => {
              if (isSuccess || isLoading) return;
              e.currentTarget.style.background = "transparent";
              e.currentTarget.style.color = "var(--fg)";
            }}
            style={{
              marginTop: 16,
              padding: "22px 28px",
              minHeight: 44,
              background: isSuccess ? "var(--success)" : "transparent",
              color: isSuccess ? "var(--bg-inverse)" : "var(--fg)",
              border: isSuccess ? "1px solid var(--success)" : "1px solid var(--fg)",
              display: "inline-flex",
              alignItems: "center", gap: 16,
              fontFamily: "var(--font-mono)",
              fontSize: 11,
              letterSpacing: "0.12em",
              textTransform: "uppercase",
              fontWeight: 600,
              opacity: isLoading ? 0.7 : 1,
              transition: "background 240ms var(--ease-out-quart), color 240ms var(--ease-out-quart), transform 320ms var(--ease-out-expo), opacity 200ms",
              transform: isSuccess ? "scale(1.02)" : "scale(1)",
              animation: jumpPhase === "charge" ? "hyperspaceCharge 0.4s cubic-bezier(0.7,0,0.84,0) both" : "none"
            }}>

            {buttonLabel}
          </button>
        </form>

        {/* Hyperspace confirmation overlay */}
        {showConfirmation &&
        <div
          role="status"
          aria-live="polite"
          style={{
            position: "relative",
            zIndex: 3,
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            justifyContent: "center",
            textAlign: "center",
            pointerEvents: "auto",
            padding: "80px 24px",
            minHeight: 480,
            animation: "hyperspaceConfirmIn 700ms cubic-bezier(0.16,1,0.3,1) both"
          }}>
            <div style={{
              fontSize: 72,
              lineHeight: 1,
              color: "#3ee07a",
              textShadow: "0 0 20px rgba(62,224,122,0.7), 0 0 40px rgba(62,224,122,0.4), 0 0 80px rgba(62,224,122,0.25)",
              marginBottom: 24,
              fontWeight: 600
            }}>
              ✓
            </div>
            <div style={{
              fontFamily: "var(--font-display)",
              fontStyle: "italic",
              fontSize: "clamp(28px, 3.4vw, 44px)",
              letterSpacing: "-0.02em",
              color: "var(--fg)",
              textShadow: "0 0 12px rgba(150,200,255,0.4)",
              marginBottom: 12,
              maxWidth: "20ch"
            }}>
              Nachricht gesendet
            </div>
            <div className="mono" style={{
              color: "var(--fg-muted)",
              maxWidth: "32ch"
            }}>
              Ich melde mich zeitnah — meistens innerhalb von 24 h.
            </div>
            <button
              type="button"
              onClick={() => {
                setShowConfirmation(false);
                setStatus("idle");
                setStatusMessage("");
                setJumpPhase("idle");
              }}
              data-cursor="OK"
              style={{
                marginTop: 32,
                padding: "14px 22px",
                background: "transparent",
                color: "var(--fg)",
                border: "1px solid var(--rule)",
                fontFamily: "var(--font-mono)",
                fontSize: 11,
                letterSpacing: "0.12em",
                textTransform: "uppercase",
                fontWeight: 600,
                borderRadius: 8
              }}>
              Neue Nachricht
            </button>
          </div>
        }
        </div>
      </div>

      <div style={{ paddingLeft: "var(--pad)", paddingRight: "var(--pad)", marginTop: "var(--s-11)" }}>
        <h2 id="contact-heading" style={{
          fontFamily: "var(--font-display)",
          fontStyle: "italic",
          fontSize: "clamp(64px, 11vw, 200px)",
          lineHeight: 0.92,
          letterSpacing: "-0.04em",
          fontWeight: 400,
          maxWidth: "16ch"
        }}>
          Bauen wir gemeinsam etwas Gutes<span style={{ color: "var(--accent)" }}>.</span>
        </h2>
      </div>
    </section>);

};

const ContactLine = ({ label, value, href }) => {
  const Tag = href ? "a" : "div";
  return (
    <Tag href={href} data-cursor={href ? "Öffnen" : null} style={{
      display: "grid",
      gridTemplateColumns: "120px 1fr",
      alignItems: "baseline",
      padding: "20px 0",
      borderTop: "1px solid var(--rule)"
    }}>
      <span className="mono" style={{ color: "var(--fg-muted)" }}>{label}</span>
      <span style={{
        fontFamily: "var(--font-display)",
        fontStyle: "italic",
        fontSize: 32,
        letterSpacing: "-0.02em"
      }}>
        {value}
        {href && <span aria-hidden="true" style={{ color: "var(--accent)", marginLeft: 12, fontSize: 18 }}>↗</span>}
      </span>
    </Tag>);

};

const Field = React.forwardRef(({
  label, value, onChange, placeholder, type = "text", multiline, autoComplete,
  name, required, error, valid, onFocusReset,
  inputMode, enterKeyHint, autoCapitalize, autoCorrect, spellCheck
}, ref) => {
  const [focused, setFocused] = React.useState(false);
  const id = React.useId();
  const errId = `${id}-err`;
  const invalid = !!error;
  // "valid" only highlights green when there's a value AND no error
  const isValid = !!valid && !invalid;
  return (
    <div style={{ display: "block", marginBottom: 28, position: "relative" }}>
      <label htmlFor={id} className="mono" style={{
        color: invalid ? "var(--accent)" : isValid ? "var(--success)" : focused ? "var(--accent)" : "var(--fg-muted)",
        display: "block",
        marginBottom: 12,
        transition: "color 240ms"
      }}>
        <span aria-hidden="true">↳ </span>{label}{required && <span aria-hidden="true"> *</span>}
        {isValid && <span aria-hidden="true" style={{ marginLeft: 8, color: "var(--success)" }}>✓</span>}
      </label>
      {multiline ?
      <textarea
        id={id}
        name={name}
        ref={ref}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        onFocus={() => { setFocused(true); onFocusReset && onFocusReset(); }}
        onBlur={() => setFocused(false)}
        rows={4}
        enterKeyHint={enterKeyHint}
        required={required}
        aria-required={required || undefined}
        aria-invalid={invalid || undefined}
        aria-describedby={invalid ? errId : undefined}
        style={fieldStyle(focused, invalid, isValid)} /> :


      <input
        id={id}
        name={name}
        ref={ref}
        type={type}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        onFocus={() => { setFocused(true); onFocusReset && onFocusReset(); }}
        onBlur={() => setFocused(false)}
        autoComplete={autoComplete}
        inputMode={inputMode}
        enterKeyHint={enterKeyHint}
        autoCapitalize={autoCapitalize}
        autoCorrect={autoCorrect}
        spellCheck={spellCheck}
        required={required}
        aria-required={required || undefined}
        aria-invalid={invalid || undefined}
        aria-describedby={invalid ? errId : undefined}
        style={fieldStyle(focused, invalid, isValid)} />

      }
      {invalid &&
      <span id={errId} className="mono" style={{
        display: "block",
        marginTop: 8,
        color: "var(--fg)",
        borderLeft: "2px solid var(--accent)",
        paddingLeft: 8
      }}>
          <span aria-hidden="true">⚠ </span>Fehler: {error}
        </span>
      }
    </div>);

});

const fieldStyle = (focused, invalid, isValid) => ({
  width: "100%",
  padding: "16px 0",
  fontFamily: "var(--font-display)",
  fontStyle: "italic",
  fontSize: 28,
  letterSpacing: "-0.01em",
  color: "var(--fg)",
  background: "transparent",
  border: 0,
  borderBottom: `${invalid || focused || isValid ? 2 : 1}px solid ${
    invalid ? "var(--accent)" :
    isValid ? "var(--success)" :
    focused ? "var(--accent)" :
    "var(--rule)"
  }`,
  resize: "none",
  transition: "border-color 240ms, border-width 160ms"
});

const Footer = () => {
  const [time, setTime] = React.useState(new Date());
  React.useEffect(() => {
    const t = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(t);
  }, []);

  return (
    <footer style={{
      padding: "var(--s-7) var(--pad) var(--s-5)",
      borderTop: "1px solid var(--rule)"
    }}>
      <div className="grid" style={{ padding: 0, alignItems: "end", marginBottom: 56 }}>
        <div style={{ gridColumn: "1 / span 6" }}>
          <div style={{
            fontFamily: "var(--font-display)",
            fontStyle: "italic",
            fontSize: "clamp(40px, 5vw, 80px)",
            lineHeight: 0.96,
            letterSpacing: "-0.03em"
          }}>
            Bauen wir<br />gemeinsam etwas Gutes<span style={{ color: "var(--accent)" }}>.</span>
          </div>
        </div>
        <div style={{ gridColumn: "10 / span 3", textAlign: "right" }}>
          <div className="mono" style={{ fontSize: 14, color: "var(--fg-muted)" }}>
            {(() => {
              const fmt = new Intl.DateTimeFormat("de-DE", {
                hour: "2-digit", minute: "2-digit", second: "2-digit",
                timeZone: "Europe/Berlin", timeZoneName: "short",
                hour12: false
              });
              const parts = fmt.formatToParts(time);
              const get = (t) => parts.find((p) => p.type === t)?.value || "00";
              return `${get("hour")}:${get("minute")}:${get("second")} ${get("timeZoneName")}`;
            })()}
          </div>
          <div className="mono" style={{ fontSize: 14, color: "var(--fg-muted)", marginTop: 4 }}>48.368728° N · 10.898371° E

          </div>
        </div>
      </div>

      <div className="grid" style={{ padding: 0, paddingTop: 24, borderTop: "1px solid var(--rule)" }}>
        <div className="mono" style={{ gridColumn: "1 / span 4", color: "var(--fg-muted)" }}>
          © MMXXVI — Omnia Core · <a href="mailto:info@omniacore.net" style={{ color: "inherit", textDecoration: "underline" }}>info@omniacore.net</a>
        </div>
        <div className="mono" style={{ gridColumn: "5 / span 4", color: "var(--fg-muted)", textAlign: "center" }}>OMNIA CORE GESETZT IN AUGSBURG/MÜNCHEN

        </div>
        <div className="mono" style={{ gridColumn: "9 / span 4", color: "var(--fg-muted)", textAlign: "right" }}>
          <a href="#top" data-cursor="Top" style={{ color: "inherit" }}><span aria-hidden="true">↑ </span>Zurück nach oben</a>
        </div>
      </div>
    </footer>);

};

window.Nav = Nav;
window.About = About;
window.Contact = Contact;
window.Footer = Footer;