// components.jsx — shared reusables for the HOTMESS brand site
// Wordmark / Reveal / SpotsCounter / MemberForm / PulseGlobe / SafetyDemo / Placeholder / TierDrawer

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

// ─────────────────────────────────────────────────────────────────────────────
// useReveal — IntersectionObserver hook; adds .in class when an element enters
function useReveal(opts = {}) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => {
        entries.forEach((e) => {
          if (e.isIntersecting) {
            el.classList.add("in");
            io.unobserve(el);
          }
        });
      },
      { threshold: opts.threshold ?? 0.15, rootMargin: opts.rootMargin ?? "0px 0px -10% 0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return ref;
}

// Reveal — drop-in wrapper that adds .rv class + observes
function Reveal({ as: Tag = "div", delay = 0, className = "", children, ...rest }) {
  const ref = useReveal();
  const dCls = delay > 0 ? ` d${delay}` : "";
  return (
    <Tag ref={ref} className={`rv${dCls} ${className}`} {...rest}>
      {children}
    </Tag>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Wordmark — HOT (white) MESS (gold). Optional animated stagger.
function Wordmark({ size = "inherit", animated = false, splitGold = true, as: Tag = "span" }) {
  const letters = "HOTMESS".split("");
  if (!animated) {
    return (
      <Tag className="wm" style={{ fontSize: size }}>
        <span>HOT</span>
        <span className={splitGold ? "gold" : ""}>MESS</span>
      </Tag>
    );
  }
  return (
    <Tag className="hero-mark" aria-label="HOTMESS">
      {letters.map((ch, i) => (
        <span className="l" key={i}>
          <span style={{ color: splitGold && i >= 3 ? "var(--gold)" : "var(--ink)" }}>{ch}</span>
        </span>
      ))}
    </Tag>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// Placeholder — striped block with monospace caption (for missing imagery)
function Placeholder({ label = "image", ratio = "4/3", style }) {
  return (
    <div className="ph" style={{ aspectRatio: ratio, ...style }}>
      <span className="ph-label">{label}</span>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// SpotsCounter — ticks down to a target. Used in the founding tier cards.
function SpotsCounter({ total, taken = 0, label = "spots" }) {
  const ref = useReveal();
  const [n, setN] = useState(total);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (es) => {
        es.forEach((e) => {
          if (!e.isIntersecting) return;
          const target = Math.max(0, total - taken);
          let i = total;
          const tick = () => {
            i -= 1;
            setN(i);
            if (i > target) setTimeout(tick, 60 + Math.random() * 40);
          };
          setTimeout(tick, 120);
          io.unobserve(el);
        });
      },
      { threshold: 0.4 }
    );
    io.observe(el);
    return () => io.disconnect();
  }, [total, taken]);
  return (
    <div ref={ref} style={{ display: "inline-flex", alignItems: "baseline", gap: 8 }}>
      <span className="display" style={{ fontSize: "44px", color: "var(--gold)", lineHeight: 1 }}>
        {String(n).padStart(2, "0")}
      </span>
      <span className="mono" style={{ color: "var(--ink-3)" }}>
        / {total} {label}
      </span>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// MemberForm — wired to POST /api/members/signup.
// Collects the minimum required by the API: full_name, email,
// claimed_username, city, age_confirmed. Username auto-derived from
// the email local-part with the API's allowed character set; user can
// edit before submitting. Source-of-truth tier assignment happens
// server-side in `app/api/members/signup/route.ts`.
function MemberForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [username, setUsername] = useState("");
  const [city, setCity] = useState("London");
  const [age, setAge] = useState(false);
  const [state, setState] = useState("idle"); // idle | sending | err | ok
  const [msg, setMsg] = useState("");

  // Auto-derive a sensible username from the email local-part when the
  // user hasn't typed one. Keeps it lowercase, only [a-z0-9_], capped at 20.
  const deriveUsername = (em) => {
    const local = (em || "").split("@")[0] || "";
    return local.toLowerCase().replace(/[^a-z0-9_]/g, "_").slice(0, 20);
  };

  const onEmailChange = (e) => {
    const v = e.target.value;
    setEmail(v);
    if (state !== "idle" && state !== "sending") setState("idle");
    if (!username) setUsername(deriveUsername(v));
  };

  const onSubmit = async (e) => {
    e.preventDefault();
    // Client-side guardrails (match server contract)
    if (!name.trim()) { setState("err"); setMsg("Name required."); return; }
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
      setState("err"); setMsg("That doesn't read like an email."); return;
    }
    if (!/^[a-z0-9_]{3,20}$/.test(username)) {
      setState("err"); setMsg("Username: 3–20 lowercase letters, numbers, or underscore."); return;
    }
    if (!age) {
      setState("err"); setMsg("Confirm you're 18+ and accept the terms.");
      return;
    }

    setState("sending"); setMsg("");
    try {
      const res = await fetch("/api/members/signup", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          full_name: name.trim(),
          email: email.trim().toLowerCase(),
          claimed_username: username,
          instagram_handle: null,
          city,
          referrer: "homepage",
          age_confirmed: true,
        }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok) {
        setState("err");
        setMsg(data.error || "Something went wrong. Try again.");
        return;
      }
      setState("ok");
      setMsg("In. Welcome email from welcome@hotmessldn.com is on the way — check spam.");
      setName(""); setEmail(""); setUsername(""); setAge(false);
    } catch (err) {
      setState("err");
      setMsg("Network error. Try again.");
    }
  };

  const sending = state === "sending";

  return (
    <form onSubmit={onSubmit} noValidate>
      <div className="form-row" style={{ marginBottom: 10 }}>
        <input
          type="text"
          placeholder="Your name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          disabled={sending}
          aria-label="Full name"
        />
      </div>
      <div className="form-row" style={{ marginBottom: 10 }}>
        <input
          type="email"
          placeholder="you@somewhere.com"
          value={email}
          onChange={onEmailChange}
          className={state === "err" ? "err" : ""}
          disabled={sending}
          aria-label="Email address"
        />
      </div>
      <div className="form-row" style={{ marginBottom: 10 }}>
        <input
          type="text"
          placeholder="username"
          value={username}
          onChange={(e) => setUsername(e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "").slice(0, 20))}
          disabled={sending}
          aria-label="Claimed username"
          style={{ flex: 0.6 }}
        />
        <select
          value={city}
          onChange={(e) => setCity(e.target.value)}
          disabled={sending}
          aria-label="City"
          style={{
            flex: 0.4, background: "transparent", border: "1px solid var(--line-2)",
            color: "var(--ink)", padding: "14px 16px", fontFamily: "var(--mono)",
            letterSpacing: "0.06em", fontSize: 12, outline: "none",
          }}
        >
          <option value="London">London</option>
          <option value="Manchester">Manchester</option>
          <option value="Brighton">Brighton</option>
          <option value="Other">Other</option>
        </select>
      </div>
      <label style={{
        display: "flex", gap: 10, alignItems: "flex-start",
        fontFamily: "var(--mono)", fontSize: 11, letterSpacing: "0.06em",
        color: "var(--ink-3)", marginBottom: 12, maxWidth: 520,
      }}>
        <input
          type="checkbox"
          checked={age}
          onChange={(e) => setAge(e.target.checked)}
          disabled={sending}
          style={{ marginTop: 2 }}
          aria-label="Confirm 18+ and accept terms"
        />
        <span>I'm 18 or older and accept the founding-member terms.</span>
      </label>
      <div className="form-row">
        <button type="submit" className="btn btn-solid" disabled={sending}>
          {sending ? "Sending…" : "Claim"}
        </button>
      </div>
      {msg ? (
        <div className={`form-msg ${state === "ok" ? "ok" : state === "err" ? "err" : ""}`}>{msg}</div>
      ) : (
        <div className="form-msg" style={{ color: "var(--ink-4)" }}>
          50 Original · 100 Founding · 100 Early — 250 total · free for life
        </div>
      )}
    </form>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// PulseGlobe — SVG sphere with a slow rotation, gridlines, pulsing beacons.
// Lightweight: no canvas, no WebGL. Beacons are positioned at lat/lng then
// projected onto a circle (orthographic).
const BEACONS = [
  { lng: -0.13, lat: 51.51, label: "LONDON" },     // home
  { lng: 2.35,  lat: 48.86, label: "PARIS" },
  { lng: 13.40, lat: 52.52, label: "BERLIN" },
  { lng: 2.17,  lat: 41.39, label: "BARCELONA" },
  { lng: 1.43,  lat: 38.91, label: "IBIZA" },
  { lng: -73.98, lat: 40.76, label: "NYC" },
  { lng: -118.24, lat: 34.05, label: "LA" },
  { lng: 151.21, lat: -33.86, label: "SYDNEY" },
  { lng: 139.69, lat: 35.69, label: "TOKYO" },
  { lng: -43.20, lat: -22.91, label: "RIO" },
  { lng: 28.05, lat: -26.20, label: "JOBURG" },
];

function project(lng, lat, rotation, R = 100) {
  const lambda = ((lng + rotation) * Math.PI) / 180;
  const phi = (lat * Math.PI) / 180;
  const x = R * Math.cos(phi) * Math.sin(lambda);
  const y = -R * Math.sin(phi);
  const z = Math.cos(phi) * Math.cos(lambda); // visibility
  return { x, y, z };
}

function PulseGlobe({ size = 520 }) {
  const [rot, setRot] = useState(20);
  const raf = useRef(0);
  const last = useRef(performance.now());
  useEffect(() => {
    let alive = true;
    const tick = (t) => {
      const dt = (t - last.current) / 1000;
      last.current = t;
      setRot((r) => (r + dt * 4) % 360); // 4 deg/sec
      if (alive) raf.current = requestAnimationFrame(tick);
    };
    raf.current = requestAnimationFrame(tick);
    return () => { alive = false; cancelAnimationFrame(raf.current); };
  }, []);
  const R = 100;
  const grid = useMemo(() => {
    const parallels = [-60, -30, 0, 30, 60].map((lat) => {
      const phi = (lat * Math.PI) / 180;
      const r = R * Math.cos(phi);
      const y = -R * Math.sin(phi);
      // Squish into ellipse for orthographic projection of sphere
      return { rx: r, ry: r * 0.30, cy: y };
    });
    const meridians = [];
    for (let lngBase = 0; lngBase < 180; lngBase += 30) meridians.push(lngBase);
    return { parallels, meridians };
  }, []);
  return (
    <div style={{ width: "100%", display: "flex", justifyContent: "center" }}>
      <svg viewBox="-130 -130 260 260" width={size} height={size} style={{ maxWidth: "100%", filter: "drop-shadow(0 30px 60px rgba(200,150,44,0.18))" }}>
        <defs>
          <radialGradient id="globe-shade" cx="35%" cy="35%" r="75%">
            <stop offset="0%" stopColor="#0e1419" />
            <stop offset="55%" stopColor="#070a0d" />
            <stop offset="100%" stopColor="#020203" />
          </radialGradient>
          <radialGradient id="halo" cx="50%" cy="50%" r="50%">
            <stop offset="60%" stopColor="rgba(200,150,44,0)" />
            <stop offset="100%" stopColor="rgba(200,150,44,0.22)" />
          </radialGradient>
          <filter id="beacon-glow" x="-200%" y="-200%" width="500%" height="500%">
            <feGaussianBlur stdDeviation="2" />
          </filter>
        </defs>

        {/* halo */}
        <circle cx="0" cy="0" r="120" fill="url(#halo)" />
        {/* sphere */}
        <circle cx="0" cy="0" r={R} fill="url(#globe-shade)" stroke="rgba(255,255,255,0.10)" strokeWidth="0.5" />

        {/* parallels */}
        {grid.parallels.map((p, i) => (
          <ellipse key={`par-${i}`} cx="0" cy={p.cy} rx={p.rx} ry={p.ry} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth="0.4" />
        ))}

        {/* meridians — drawn as rotated ellipses */}
        {grid.meridians.map((m, i) => {
          const a = ((m + rot) * Math.PI) / 180;
          const rx = Math.abs(R * Math.cos(a));
          return (
            <ellipse key={`mer-${i}`} cx="0" cy="0" rx={rx} ry={R} fill="none" stroke="rgba(255,255,255,0.07)" strokeWidth="0.4" />
          );
        })}

        {/* clip beacons to globe */}
        <clipPath id="globe-clip"><circle cx="0" cy="0" r={R - 1} /></clipPath>
        <g clipPath="url(#globe-clip)">
          {BEACONS.map((b, i) => {
            const p = project(b.lng, b.lat, rot, R);
            if (p.z < 0) return null;
            const opacity = 0.55 + 0.45 * p.z;
            return (
              <g key={b.label} transform={`translate(${p.x}, ${p.y})`} opacity={opacity}>
                <circle r="6" fill="var(--gold)" opacity="0.18">
                  <animate attributeName="r" values="2;9;2" dur={`${2 + (i % 3) * 0.6}s`} repeatCount="indefinite" />
                  <animate attributeName="opacity" values="0.35;0;0.35" dur={`${2 + (i % 3) * 0.6}s`} repeatCount="indefinite" />
                </circle>
                <circle r="1.6" fill="var(--gold)" filter="url(#beacon-glow)" />
                <circle r="0.9" fill="#fff" />
              </g>
            );
          })}
        </g>

        {/* terminator (day/night softening) */}
        <circle cx="0" cy="0" r={R} fill="rgba(0,0,0,0.18)" pointerEvents="none" />
      </svg>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// SafetyDemo — faithful recreation of the HOTMESS app's Safety Hub screen.
// Matches the visual language of the real Pulse screenshot: HOTMESS wordmark
// at top, big bold uppercase title, dark rounded menu panel with cyan icons,
// gold square action button, bottom tab bar.

function PhoneIcon()    { return <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#4dc0ff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/></svg>; }
function ClockIcon()    { return <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#4dc0ff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>; }
function ShieldIcon(props){ return <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={props.color || "rgba(255,255,255,0.45)"} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>; }
function BellIcon(p)    { const c = p?.color || "rgba(255,255,255,0.85)"; return <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>; }
function BellMuted()    { return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/><line x1="2" y1="2" x2="22" y2="22" stroke="#fff"/></svg>; }
function PinIcon()      { return <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#050507" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>; }

// iOS-style status bar icons
function SignalDots() {
  return (
    <svg width="17" height="11" viewBox="0 0 17 11" fill="#fff">
      <rect x="0"  y="7" width="2.5" height="4" rx="0.5"/>
      <rect x="4"  y="5" width="2.5" height="6" rx="0.5"/>
      <rect x="8"  y="3" width="2.5" height="8" rx="0.5"/>
      <rect x="12" y="0" width="2.5" height="11" rx="0.5"/>
    </svg>
  );
}
function WifiIcon() {
  return (
    <svg width="15" height="11" viewBox="0 0 15 11" fill="none" stroke="#fff" strokeWidth="1.4" strokeLinecap="round">
      <path d="M1 4 A 8 8 0 0 1 14 4"/>
      <path d="M3.5 6.2 A 5 5 0 0 1 11.5 6.2"/>
      <path d="M6 8.4 A 2 2 0 0 1 9 8.4"/>
      <circle cx="7.5" cy="9.6" r="0.9" fill="#fff" stroke="none"/>
    </svg>
  );
}
function BatteryIcon({ pct = 30, lowColor = "#f2c24a" }) {
  const fill = pct <= 30 ? lowColor : "#fff";
  return (
    <svg width="25" height="12" viewBox="0 0 25 12" fill="none">
      <rect x="0.5" y="0.5" width="22" height="11" rx="3" stroke="#fff" strokeOpacity="0.7"/>
      <rect x="23" y="3.5" width="1.5" height="5" rx="0.5" fill="#fff" fillOpacity="0.7"/>
      <rect x="2" y="2" width={(pct / 100) * 19} height="8" rx="1.5" fill={fill}/>
    </svg>
  );
}

// Tab-bar icons — match the actual HOTMESS app
function TabHome(p)    { return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={p.color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 11l9-8 9 8"/><path d="M5 10v10h14V10"/></svg>; }
function TabPulse(p)   { return <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={p.color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 12 6 12 9 4 12 20 15 9 18 15 21 12"/></svg>; }
function TabGhost(p)   { return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={p.color} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M5 11a7 7 0 0 1 14 0v9l-2.5-2-2 2-2-2-2 2-2.5-2L5 20z"/><circle cx="9.5" cy="11" r="0.8" fill={p.color}/><circle cx="14.5" cy="11" r="0.8" fill={p.color}/></svg>; }
function TabMusic(p)   { return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={p.color} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M9 18V5l11-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="17" cy="16" r="3"/></svg>; }
function TabShop(p)    { return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={p.color} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round"><path d="M6 7h12l-1 13H7L6 7z"/><path d="M9 7V5a3 3 0 0 1 6 0v2"/></svg>; }
function TabMore(p)    { return <svg width="20" height="20" viewBox="0 0 24 24" fill={p.color}><circle cx="6" cy="6" r="1.6"/><circle cx="12" cy="6" r="1.6"/><circle cx="18" cy="6" r="1.6"/><circle cx="6" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="18" cy="12" r="1.6"/><circle cx="6" cy="18" r="1.6"/><circle cx="12" cy="18" r="1.6"/><circle cx="18" cy="18" r="1.6"/></svg>; }

function SafetyDemo() {
  const [mode, setMode] = useState("idle"); // idle | hold | sent | ringing
  const holdT = useRef(null);
  const onDown = () => {
    setMode("hold");
    holdT.current = setTimeout(() => setMode("sent"), 1400);
  };
  const onUp = () => {
    if (mode === "hold") { clearTimeout(holdT.current); setMode("idle"); }
  };
  const onFake = () => {
    setMode("ringing");
    setTimeout(() => setMode("idle"), 4600);
  };

  // App-style phone shell
  const phoneStyle = {
    position: "relative", width: "100%", maxWidth: 360,
    aspectRatio: "360/740", borderRadius: 38,
    background: "#000",
    border: "1px solid rgba(255,255,255,0.10)",
    overflow: "hidden",
    boxShadow: "0 40px 100px rgba(0,0,0,0.65), 0 0 0 6px #07070a, 0 0 0 7px #1a1a1f",
    fontFamily: "var(--display)",
  };

  return (
    <div style={phoneStyle} aria-label="HOTMESS Safety Hub demo">
      {/* Dynamic island / notch */}
      <div style={{ position: "absolute", top: 12, left: "50%", transform: "translateX(-50%)", width: 108, height: 26, background: "#000", borderRadius: 16, zIndex: 5 }}></div>

      {/* Status bar */}
      <div style={{
        position: "absolute", top: 0, left: 0, right: 0, padding: "14px 24px 0",
        display: "flex", justifyContent: "space-between", alignItems: "center",
        color: "#fff", fontFamily: "var(--display)", fontSize: 15, letterSpacing: "0",
        zIndex: 3,
      }}>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
          20:34 <BellMuted />
        </span>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
          <SignalDots /><WifiIcon /><BatteryIcon pct={30} />
        </span>
      </div>

      {/* HOTMESS app nav */}
      <div style={{
        position: "absolute", top: 44, left: 0, right: 0, padding: "14px 22px",
        display: "flex", justifyContent: "space-between", alignItems: "center", zIndex: 3,
      }}>
        <span style={{ fontFamily: "var(--display)", fontSize: 22, letterSpacing: "-0.01em", lineHeight: 1 }}>
          <span style={{ color: "#fff" }}>HOT</span><span style={{ color: "var(--gold)" }}>MESS</span>
        </span>
        <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <BellIcon />
          <div style={{ width: 26, height: 26, borderRadius: "50%", overflow: "hidden", border: "1px solid rgba(255,255,255,0.2)", backgroundImage: "url('assets/founder-1.jpg')", backgroundSize: "cover", backgroundPosition: "center" }}></div>
        </div>
      </div>

      {/* Title block */}
      {mode !== "ringing" && (
        <div style={{ position: "absolute", top: 102, left: 0, right: 0, textAlign: "center", padding: "0 24px", zIndex: 2 }}>
          <div style={{ fontFamily: "var(--display)", fontSize: 40, lineHeight: 1, color: "#fff", letterSpacing: "-0.01em" }}>SAFETY</div>
          <div style={{ fontFamily: "var(--display)", fontSize: 11, marginTop: 8, letterSpacing: "0.22em", color: "rgba(255,255,255,0.55)" }}>HOLD&nbsp;TO&nbsp;ARM&nbsp;SOS.</div>
        </div>
      )}

      {/* Armed pill */}
      {mode !== "ringing" && (
        <div style={{ position: "absolute", top: 186, left: 22, zIndex: 2, padding: "6px 12px", background: "rgba(20,20,24,0.75)", border: "1px solid rgba(255,255,255,0.10)", borderRadius: 999, display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ width: 8, height: 8, borderRadius: "50%", background: mode === "hold" ? "var(--gold)" : "#3ee07a", boxShadow: mode === "hold" ? "0 0 8px var(--gold)" : "0 0 8px #3ee07a", animation: "blink 1.6s ease-in-out infinite" }}></span>
          <span className="mono" style={{ color: "#fff", fontSize: 10 }}>
            {mode === "idle"  && "READY"}
            {mode === "hold"  && "ARMING…"}
            {mode === "sent"  && "SOS SENT"}
          </span>
        </div>
      )}

      {/* Big SOS hold button */}
      {mode !== "ringing" && (
        <div style={{ position: "absolute", top: 240, left: 0, right: 0, display: "flex", justifyContent: "center", zIndex: 2 }}>
          <button
            onMouseDown={onDown} onMouseUp={onUp} onMouseLeave={onUp}
            onTouchStart={(e) => { e.preventDefault(); onDown(); }} onTouchEnd={onUp}
            style={{
              width: 168, height: 168, borderRadius: "50%", border: "none",
              background: mode === "sent" ? "#3ee07a" : "var(--gold)",
              color: "var(--bg)", fontFamily: "var(--display)", fontSize: 30, letterSpacing: "0.02em",
              position: "relative", cursor: "pointer",
              boxShadow: mode === "hold"
                ? "0 0 0 14px rgba(200,150,44,0.20), 0 0 0 30px rgba(200,150,44,0.10), 0 0 36px rgba(200,150,44,0.4)"
                : "0 0 30px rgba(200,150,44,0.25)",
              transition: "box-shadow .25s, background .25s",
              lineHeight: 0.95,
            }}
            aria-label="Hold to arm SOS"
          >
            {mode === "sent" ? "SENT" : <>HOLD<br/>FOR<br/>SOS</>}
            {mode === "hold" && (
              <span style={{
                position: "absolute", inset: -8, borderRadius: "50%",
                border: "2px solid var(--gold)",
                animation: "sos-ring 1.4s linear",
              }}></span>
            )}
          </button>
          <style>{`@keyframes sos-ring { from{ transform: scale(0.92); opacity:1 } to{ transform: scale(1.55); opacity:0 } }`}</style>
        </div>
      )}

      {/* Safety menu panel (mirrors the in-app overlay) */}
      {mode !== "ringing" && (
        <div style={{
          position: "absolute", bottom: 96, left: 16, right: 84, zIndex: 2,
          background: "rgba(20,20,24,0.78)", backdropFilter: "blur(12px)",
          border: "1px solid rgba(255,255,255,0.10)", borderRadius: 14,
          padding: 4,
        }}>
          {[
            { icon: <PhoneIcon />,  label: "Fake Call",       onClick: onFake, active: true },
            { icon: <ClockIcon />,  label: "Check-in Timer",  onClick: () => {}, active: true },
            { icon: <ShieldIcon color="rgba(255,255,255,0.55)" />, label: "Trusted Contacts", onClick: () => {}, active: false },
          ].map((row, i) => (
            <button key={i} onClick={row.onClick} style={{
              display: "flex", alignItems: "center", gap: 12,
              width: "100%", padding: "10px 12px", background: "transparent", border: "none",
              cursor: row.active ? "pointer" : "default",
              borderTop: i > 0 ? "1px solid rgba(255,255,255,0.06)" : "none",
            }}>
              <span style={{ width: 22, display: "flex", alignItems: "center", justifyContent: "center" }}>{row.icon}</span>
              <span style={{ color: row.active ? "#fff" : "rgba(255,255,255,0.5)", fontFamily: "var(--sans)", fontSize: 14, letterSpacing: 0 }}>{row.label}</span>
            </button>
          ))}
        </div>
      )}

      {/* Gold pin button bottom-right (drops a beacon) */}
      {mode !== "ringing" && (
        <button aria-label="Drop beacon" style={{
          position: "absolute", bottom: 102, right: 16, zIndex: 2,
          width: 52, height: 52, borderRadius: 12, border: "none",
          background: "var(--gold)",
          boxShadow: "0 0 24px rgba(200,150,44,0.35)",
          display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer",
        }}>
          <PinIcon />
        </button>
      )}

      {/* Bottom tab bar */}
      {mode !== "ringing" && (
        <div style={{
          position: "absolute", bottom: 0, left: 0, right: 0,
          padding: "8px 8px 20px", display: "flex",
          justifyContent: "space-between", alignItems: "flex-start",
          borderTop: "1px solid rgba(255,255,255,0.08)",
          background: "rgba(0,0,0,0.88)",
          zIndex: 4,
        }}>
          {[
            { label: "Home",    Icon: TabHome,  active: false },
            { label: "Pulse",   Icon: TabPulse, active: true },
            { label: "Ghosted", Icon: TabGhost, active: false },
            { label: "Music",   Icon: TabMusic, active: false },
            { label: "Shop",    Icon: TabShop,  active: false },
            { label: "More",    Icon: TabMore,  active: false },
          ].map(({ label, Icon, active }) => {
            const color = active ? "var(--gold)" : "rgba(255,255,255,0.55)";
            return (
              <div key={label} style={{
                display: "flex", flexDirection: "column", alignItems: "center", gap: 3,
                color: color, flex: 1, minWidth: 0,
              }}>
                {active ? (
                  <div style={{
                    width: 30, height: 30, borderRadius: "50%",
                    border: "1.5px solid var(--gold)",
                    display: "flex", alignItems: "center", justifyContent: "center",
                    boxShadow: "0 0 10px rgba(200,150,44,0.30)",
                  }}>
                    <Icon color={color} />
                  </div>
                ) : (
                  <div style={{ width: 30, height: 30, display: "flex", alignItems: "center", justifyContent: "center" }}>
                    <Icon color={color} />
                  </div>
                )}
                <span style={{
                  fontFamily: "var(--sans)",
                  fontSize: 10,
                  letterSpacing: "0.01em",
                  textTransform: "none",
                  fontWeight: 400,
                }}>{label}</span>
              </div>
            );
          })}
        </div>
      )}

      {/* Ringing screen (fake call) — matches iOS style */}
      {mode === "ringing" && (
        <div style={{ position: "absolute", inset: 0, background: "linear-gradient(to bottom, #0a0a0d 0%, #000 60%)", color: "#fff", padding: "92px 22px 36px", display: "flex", flexDirection: "column", alignItems: "center", textAlign: "center", zIndex: 6 }}>
          <div className="mono" style={{ color: "rgba(255,255,255,0.55)" }}>INCOMING CALL</div>
          <div style={{ fontFamily: "var(--display)", fontSize: 30, marginTop: 14, letterSpacing: "-0.01em" }}>MUM</div>
          <div className="mono" style={{ color: "rgba(255,255,255,0.55)", marginTop: 4 }}>MOBILE · UK</div>
          <div style={{ width: 96, height: 96, borderRadius: "50%", background: "linear-gradient(135deg, #2a2a30, #0a0a0d)", marginTop: 30, position: "relative", border: "1px solid rgba(255,255,255,0.1)" }}>
            <span style={{ position: "absolute", inset: -8, borderRadius: "50%", border: "2px solid rgba(62,224,122,0.6)", animation: "ring 1.2s ease-out infinite" }}></span>
          </div>
          <div style={{ marginTop: "auto", display: "flex", gap: 36, alignItems: "center" }}>
            <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
              <div style={{ width: 58, height: 58, borderRadius: "50%", background: "#ff3b30", display: "flex", alignItems: "center", justifyContent: "center" }}>
                <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z" transform="rotate(135 12 12)"/></svg>
              </div>
              <span className="mono" style={{ color: "rgba(255,255,255,0.55)" }}>DECLINE</span>
            </div>
            <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
              <div style={{ width: 58, height: 58, borderRadius: "50%", background: "#34c759", display: "flex", alignItems: "center", justifyContent: "center" }}>
                <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.37 1.9.72 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z"/></svg>
              </div>
              <span className="mono" style={{ color: "rgba(255,255,255,0.55)" }}>ACCEPT</span>
            </div>
          </div>
          <style>{`@keyframes ring { from{ transform:scale(1); opacity:1 } to{ transform:scale(1.8); opacity:0 } }`}</style>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// TierDrawer — fixed bottom drawer. Toggles open; shows tier comparison.
function TierDrawer() {
  const [open, setOpen] = useState(false);
  const tiers = [
    { name: "FOUNDING VENUE", price: "£250", cap: 50, blurb: "Standard globe pin · 2 beacons/quarter · 1 NFC + 2 QR Tap-Pack." },
    { name: "FOUNDING SIGNAL", price: "£500", cap: 25, blurb: "Pulsing globe pin · 4 beacons/qtr · 2 NFC + 4 QR · Licensing Letter." },
    { name: "FOUNDING ANCHOR", price: "£1,000", cap: 10, blurb: "Persistent named label · 8+1 beacons/qtr · co-branded Tap-Pack · monthly call · postcode exclusivity." },
    { name: "FOUNDING PROMOTER", price: "£350", cap: 15, blurb: "Movable globe pin · unlimited events · portable NFC fob + QR." },
    { name: "FOUNDING CHAIN", price: "£750", cap: 5, blurb: "Multi-site operator. Base = 3 locations; +£200/extra." },
    { name: "FOUNDING WELLNESS", price: "£400", cap: 10, blurb: "Calm globe pin (recovery-aware) · 1 NFC + QR · Licensing Letter." },
  ];
  return (
    <aside className={`drawer ${open ? "open" : ""}`} aria-label="Tier comparison">
      <div className="drawer-handle" onClick={() => setOpen((v) => !v)}>
        <span className="h">▲ TIER COMPARISON · ALL 6 FOUNDING PARTNER LEVELS</span>
        <span className="h" style={{ color: "var(--ink-3)" }}>{open ? "CLOSE ▼" : "OPEN ▲"}</span>
      </div>
      <div className="drawer-body">
        <div className="grid-3">
          {tiers.map((t) => (
            <div key={t.name} className="panel" style={{ padding: 18 }}>
              <div className="mono" style={{ color: "var(--gold)", marginBottom: 6 }}>{t.name}</div>
              <div className="display" style={{ fontSize: 38, color: "var(--ink)", lineHeight: 1 }}>{t.price}</div>
              <div className="mono" style={{ color: "var(--ink-3)", marginTop: 4 }}>CAP · {t.cap} SPOTS</div>
              <p style={{ color: "var(--ink-2)", fontSize: 13, lineHeight: 1.5, marginTop: 12 }}>{t.blurb}</p>
            </div>
          ))}
        </div>
      </div>
    </aside>
  );
}


// ─────────────────────────────────────────────────────────────────────────────
// PartnerCheckoutModal — inline commitment-letter + Stripe handoff for the
// six paid partner tiers. Lives on the new homepage; opening it does NOT
// navigate away. On accept + continue, POSTs to /api/partners/checkout
// with the tier slug + commitment_letter_v1_accepted: true, then redirects
// to Stripe Checkout (the one navigation that's unavoidable). The Next.js
// /partners/apply route remains for free recovery-partner intake; paid
// tiers never reach it.
const COMMITMENT_LETTER_TEXT = `Mutual commitment letter (v1)

By accepting this letter, the partner organisation and HOTMESS LONDON
(Smash Daddys Ltd) agree to the following ten clauses for the duration
of the founding-partner relationship:

  1. The partner is named on HOTMESS surfaces only with their explicit consent.
  2. HOTMESS does not aggregate, sell, or licence partner data to third parties.
  3. The partner may withdraw at any time with 7 days written notice.
  4. The partner's premises (where applicable) are added to the Pulse globe
     only after the partner has reviewed the geographic representation.
  5. The Tap-Pack hardware remains the partner's to use; HOTMESS does not
     remote-disable it short of a serious safety breach.
  6. The Founding Anchor postcode-exclusivity commitment is honoured for
     the founding year.
  7. The Licensing Letter on HOTMESS letterhead is for the partner's
     licensing-review use; HOTMESS does not register it as a regulatory filing.
  8. The partner agrees not to misrepresent the HOTMESS relationship
     (no "official partner of X" claims beyond what's been signed).
  9. Disputes are resolved first by direct conversation; escalation to a
     mutually-named third party only after good-faith conversation fails.
 10. Either party may renegotiate the relationship at the end of the
     founding year; the founding badge + permanent status survive
     renegotiation.`;

function PartnerCheckoutModal({ tier, onClose }) {
  const [accepted, setAccepted] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState("");

  // Lock body scroll while open
  useEffect(() => {
    if (!tier) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = prev;
      window.removeEventListener("keydown", onKey);
    };
  }, [tier, onClose]);

  if (!tier) return null;

  const onContinue = async () => {
    if (!accepted) {
      setError("Tick the commitment-letter box before continuing.");
      return;
    }
    setError("");
    setSubmitting(true);
    try {
      const res = await fetch("/api/partners/checkout", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          tier: tier.slug,
          commitment_letter_v1_accepted: true,
        }),
      });
      const data = await res.json().catch(() => ({}));
      if (!res.ok || !data.url) {
        setError(data.error || "Could not start Stripe checkout. Try again or email vendors@hotmessldn.com.");
        setSubmitting(false);
        return;
      }
      window.location.href = data.url;
    } catch (e) {
      setError("Network error reaching Stripe. Try again or email vendors@hotmessldn.com.");
      setSubmitting(false);
    }
  };

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label={`Claim ${tier.name} — commitment letter and Stripe handoff`}
      onClick={onClose}
      style={{
        position: "fixed", inset: 0, zIndex: 90,
        background: "rgba(5,5,7,0.78)",
        backdropFilter: "blur(8px)",
        WebkitBackdropFilter: "blur(8px)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: "24px 16px",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          background: "var(--bg)",
          border: "1px solid var(--gold)",
          maxWidth: 640, width: "100%",
          maxHeight: "calc(100vh - 48px)",
          overflowY: "auto",
          padding: 28,
          boxShadow: "0 30px 80px rgba(0,0,0,0.6)",
        }}
      >
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 16 }}>
          <div>
            <div className="mono" style={{ color: "var(--gold)" }}>APPLYING FOR</div>
            <div className="display" style={{ fontSize: 32, lineHeight: 1, marginTop: 6, textTransform: "uppercase" }}>
              {tier.name}
            </div>
            <div className="mono" style={{ marginTop: 6, color: "var(--ink-3)" }}>
              {tier.price} · CAP {tier.cap} · {tier.note}
            </div>
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="Close"
            className="mono"
            style={{
              background: "transparent", border: "1px solid var(--line-2)",
              color: "var(--ink-2)", padding: "6px 10px",
              cursor: "pointer", fontSize: 11, letterSpacing: "0.18em",
            }}
          >
            CLOSE ✕
          </button>
        </div>

        <p className="serif" style={{
          marginTop: 16, fontSize: 18, lineHeight: 1.35,
          color: "var(--ink-2)", maxWidth: "48ch",
        }}>
          Read the mutual commitment letter, tick the box, continue to Stripe. The
          acceptance is recorded as a durable Stripe metadata field next to the
          receipt.
        </p>

        <div className="mono" style={{ marginTop: 20, color: "var(--gold)" }}>
          MUTUAL COMMITMENT LETTER · V1
        </div>
        <pre style={{
          marginTop: 8, padding: 16,
          maxHeight: 240, overflowY: "auto",
          fontFamily: "var(--mono)", fontSize: 12, lineHeight: 1.55,
          whiteSpace: "pre-wrap",
          border: "1px solid var(--line)",
          background: "rgba(255,255,255,0.02)",
          color: "var(--ink-2)",
        }}>{COMMITMENT_LETTER_TEXT}</pre>

        <label style={{
          display: "flex", alignItems: "flex-start", gap: 12,
          marginTop: 20, fontSize: 14, lineHeight: 1.5,
          color: "var(--ink)",
        }}>
          <input
            type="checkbox"
            checked={accepted}
            onChange={(e) => setAccepted(e.target.checked)}
            aria-label="Accept the mutual commitment letter"
            style={{ marginTop: 4, accentColor: "var(--gold)" }}
          />
          <span>
            I have read and accept the mutual commitment letter on behalf of the{" "}
            <span style={{ color: "var(--gold)" }}>{tier.name}</span> applicant.
          </span>
        </label>

        {error ? (
          <p className="form-msg err" style={{ marginTop: 14 }}>{error}</p>
        ) : null}

        <div style={{ marginTop: 24, display: "flex", gap: 10, flexWrap: "wrap" }}>
          <button
            type="button"
            onClick={onContinue}
            disabled={submitting}
            className="btn btn-solid"
            style={{ padding: "14px 22px", fontSize: 11 }}
          >
            {submitting ? "REDIRECTING TO STRIPE…" : `CONTINUE TO STRIPE — ${tier.price} ↗`}
          </button>
          <a
            href="/HOTMESS_FOUNDING_OFFER.pdf"
            target="_blank"
            rel="noreferrer"
            className="btn btn-ghost"
            style={{ padding: "14px 22px", fontSize: 11 }}
          >
            BRIEF PDF ↓
          </a>
        </div>

        <p className="mono" style={{ marginTop: 20, color: "var(--ink-4)", fontSize: 10, letterSpacing: "0.14em" }}>
          STRIPE COLLECTS BILLING ADDRESS · UK-VAT BELOW THRESHOLD · DOCTRINE: docs/founder/vendors/commitment-letter.md
        </p>
      </div>
    </div>
  );
}


// Export to window so other Babel scripts can use these
Object.assign(window, {
  Reveal,
  useReveal,
  Wordmark,
  Placeholder,
  SpotsCounter,
  MemberForm,
  PulseGlobe,
  SafetyDemo,
  TierDrawer,
  PartnerCheckoutModal,
});
