/* App shell — router and state. */
/* global React, ReactDOM, Brand, ScreenNav,
   About, Landing, Workspace, Pyramid, Heatmap, Workshop,
   emptySession, sessionFromSample, priorityCounts,
   encodeSession, decodeSession, readSessionFromHash, writeSessionToHash,
   clearSessionHash, currentShareUrl, summarizeEncoded,
   useTweaks, TweaksPanel, TweakSection, TweakRadio, TweakColor, TweakToggle */

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "leadColor":   "#685ad9",
  "followColor": "#10b981",
  "showGhostLabels": true,
  "compactWorkspace": false,
  "posterLayout": "stepped"
}/*EDITMODE-END*/;

function App() {
  // Fresh visits open on About (the manifesto). Hash-restored or room-bootstrapped
  // sessions override below — they jump straight to wherever the user left off.
  const [screen, setScreen] = useState("about");
  const [project, setProject] = useState("");
  const [mode, setMode] = useState("self");          // 'self' | 'workshop'
  const [session, setSession] = useState(() => emptySession());

  // ── Workshop room ────────────────────────────────────────────────────────
  // On boot: if URL has ?room=…, attempt to bootstrap. If sessionStorage has
  // a participantId for this room, silently reconnect. Otherwise prompt for
  // name + role via JoinRoomModal.
  const urlRoom = useMemo(() => (window.ROOM ? window.ROOM.fromUrl() : null), []);
  const [roomHandle, setRoomHandle] = useState(null);
  const [roomState, setRoomState] = useState(null);
  const [needsJoinPrompt, setNeedsJoinPrompt] = useState(
    !!(urlRoom && !urlRoom.participantId)
  );
  const [roomBootstrapError, setRoomBootstrapError] = useState(null);

  // Wire the handle's subscribe → React state and clean up on unmount/change.
  useEffect(() => {
    if (!roomHandle) return;
    const unsub = roomHandle.subscribe((s) => setRoomState(s));
    return () => { unsub(); };
  }, [roomHandle]);

  // Auto-bootstrap from URL when participantId is already known. Jump
  // straight to the workspace — the participant was mid-vote when they
  // refreshed, no reason to drop them back on the landing create/join cards.
  useEffect(() => {
    if (!urlRoom || !urlRoom.participantId || roomHandle) return;
    setMode("workshop");
    const h = window.ROOM.connect(urlRoom.code, urlRoom.participantId);
    setRoomHandle(h);
    setScreen("workspace");
    return () => { h.close(); };
    // run-once on mount; intentional
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Sync local project ↔ room project (room is source of truth when bound).
  useEffect(() => {
    if (roomState && typeof roomState.project === "string") {
      setProject(roomState.project);
    }
  }, [roomState && roomState.project]);

  // Seed local session from the room snapshot ONCE per room handle.
  // Without this, a reconnect (sessionStorage participantId already known)
  // would render Workspace with an empty session and the first click would
  // overwrite the participant's existing room votes with `unset`.
  const seededFromRoomRef = useRef(false);
  useEffect(() => {
    if (!roomHandle) { seededFromRoomRef.current = false; return; }
    if (seededFromRoomRef.current) return;
    if (!roomState) return;
    const me = roomState.participants && roomState.participants[roomHandle.participantId];
    if (!me) return;

    // Build a full priorities map: every leaf gets a value (room votes
    // override, everything else defaults to unset). Mirrors emptySession's
    // shape so the rest of the app keeps working.
    const seeded = { priorities: {}, notes: {} };
    for (const leaf of (window.ALL_LEAVES || [])) {
      seeded.priorities[leaf.id] = (me.priorities && me.priorities[leaf.id]) || "unset";
    }
    if (me.notes) seeded.notes = { ...me.notes };
    setSession(seeded);
    seededFromRoomRef.current = true;
  }, [roomHandle, roomState]);

  // Caller for the join modal — completes join then opens the socket and
  // jumps straight to the workspace.
  const completeJoin = async ({ name, role }) => {
    if (!urlRoom) return;
    try {
      setRoomBootstrapError(null);
      const { participantId } = await window.ROOM.join(urlRoom.code, { name, role });
      setMode("workshop");
      setNeedsJoinPrompt(false);
      const h = window.ROOM.connect(urlRoom.code, participantId);
      setRoomHandle(h);
      setScreen("workspace");
    } catch (err) {
      setRoomBootstrapError(err);
    }
  };
  const dismissJoinPrompt = () => {
    setNeedsJoinPrompt(false);
    if (window.ROOM) window.ROOM.clearRoomFromUrl();
  };

  // Share / restore — peek synchronously at load so the state-sync effect
  // never sees a "no restore yet, empty session" gap and clobbers the hash.
  // Suppressed entirely if we're booting into a room — the room IS the share.
  const [restorePrompt, setRestorePrompt] = useState(() => {
    if (typeof window === "undefined") return null;
    if (window.ROOM && window.ROOM.fromUrl()) return null;
    const encoded = readSessionFromHash();
    if (!encoded) return null;
    return summarizeEncoded(encoded);
  });
  const [shareOpen, setShareOpen] = useState(false);

  // If the hash was present but malformed, summarizeEncoded() returned null
  // above — strip it once on mount so the URL doesn't look broken.
  useEffect(() => {
    if (restorePrompt) return;
    if (readSessionFromHash()) clearSessionHash();
    // run once
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Keep the URL hash in lock-step with state, so a refresh preserves work.
  // Suspended while the restore modal is up, OR when a room is bound (the
  // ?room=… query is the share artifact and writing a hash on top is noise).
  // About + Landing both count as entry screens — no hash until the user has
  // actually picked something or moved on.
  useEffect(() => {
    if (restorePrompt) return;
    if (roomHandle) { clearSessionHash(); return; }
    const onEntryScreen = screen === "about" || screen === "landing";
    const hasMeaningfulState =
      !!project ||
      !onEntryScreen ||
      Object.values(session.priorities || {}).some(p => p && p !== "unset");
    if (!hasMeaningfulState) {
      clearSessionHash();
      return;
    }
    const encoded = encodeSession({ project, mode, screen, session });
    writeSessionToHash(encoded);
  }, [screen, project, mode, session, restorePrompt, roomHandle]);

  const applyRestore = (decoded) => {
    setProject(decoded.project);
    setMode(decoded.mode);
    setSession(decoded.session);
    setScreen(decoded.screen);
    setRestorePrompt(null);
  };
  const dismissRestore = () => {
    clearSessionHash();
    setRestorePrompt(null);
  };

  // Tweaks
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);

  // Apply tweak colours as CSS variables on root
  useEffect(() => {
    const r = document.documentElement;
    r.style.setProperty("--c-violet", tweaks.leadColor);
    r.style.setProperty("--c-emerald", tweaks.followColor);
    if (tweaks.showGhostLabels) {
      r.style.setProperty("--ghost-display", "inline-flex");
    } else {
      r.style.setProperty("--ghost-display", "none");
    }
  }, [tweaks.leadColor, tweaks.followColor, tweaks.showGhostLabels]);

  const onRoomCreated = ({ handle }) => {
    setRoomHandle(handle);
    setMode("workshop");
  };

  const screens = {
    about:     <About     setScreen={setScreen} />,
    landing:   <Landing   project={project} setProject={setProject} setSession={setSession} setScreen={setScreen} mode={mode} setMode={setMode} onRoomCreated={onRoomCreated} />,
    workspace: <Workspace project={project} session={session} setSession={setSession} setScreen={setScreen} mode={mode} roomHandle={roomHandle} roomState={roomState} />,
    pyramid:   <Pyramid   project={project} session={session} setScreen={setScreen} mode={mode} />,
    heatmap:   <Heatmap   project={project} session={session} setScreen={setScreen} mode={mode} />,
    workshop:  <Workshop  project={project} session={session} setScreen={setScreen} mode={mode} roomHandle={roomHandle} roomState={roomState} />,
  };

  return (
    <div className="app">
      {/* Mode banner (only when workshop). Shows room code + presence when a
          live room is bound. */}
      {mode === "workshop" && screen !== "landing" && screen !== "about" && (
        <div className="mode-banner">
          <span className="dot" />
          {roomHandle && roomState ? (
            <>
              Workshop room <strong style={{ letterSpacing: "0.16em" }}>{roomHandle.code}</strong>
              <span style={{ marginLeft: 12, opacity: 0.8 }}>
                {Object.values(roomState.participants || {}).length} here
              </span>
            </>
          ) : (
            <>Workshop mode · Projected to room</>
          )}
        </div>
      )}

      <header className="app-bar">
        <div className="app-bar-inner">
          <Brand />
          {project && screen !== "landing" && screen !== "about" && (
            <span className="project-pill">
              <span className="dot" />
              {project}
            </span>
          )}
          <ScreenNav screen={screen} setScreen={setScreen} mode={mode} />
          <ShareButton
            open={shareOpen}
            setOpen={setShareOpen}
            project={project}
            mode={mode}
            screen={screen}
            session={session}
          />
        </div>
      </header>

      <main className="app-main" data-screen-label={screen}>
        {screens[screen]}
      </main>

      {restorePrompt && (
        <RestoreModal
          summary={restorePrompt}
          onRestore={() => applyRestore(restorePrompt.decoded)}
          onDismiss={dismissRestore}
        />
      )}

      {needsJoinPrompt && urlRoom && (
        <JoinRoomModal
          code={urlRoom.code}
          onJoin={completeJoin}
          onDismiss={dismissJoinPrompt}
          error={roomBootstrapError}
        />
      )}

      <TweaksPanel title="Tweaks">
        <TweakSection title="Priority colors" subtitle="Recolor the lead and follow accents across all five screens.">
          <TweakColor
            label="Lead"
            value={tweaks.leadColor}
            options={["#685ad9", "#4338ca", "#d946ef", "#0ea5e9", "#dc2626"]}
            onChange={(v) => setTweak("leadColor", v)}
          />
          <TweakColor
            label="Follow"
            value={tweaks.followColor}
            options={["#10b981", "#059669", "#0d9488", "#84cc16", "#f59e0b"]}
            onChange={(v) => setTweak("followColor", v)}
          />
        </TweakSection>

        <TweakSection title="Poster" subtitle="What lands on the wall.">
          <TweakToggle
            label="Show ghost out-of-scope labels"
            checked={tweaks.showGhostLabels}
            onChange={(v) => setTweak("showGhostLabels", v)}
            help="The framework's durability is itself the message — out-of-scope leaves stay visible but de-emphasized."
          />
        </TweakSection>

        <TweakSection title="Jump to a screen" subtitle="Skip the flow.">
          <TweakRadio
            label="Screen"
            value={screen}
            options={[
              { value: "landing", label: "Landing" },
              { value: "workspace", label: "Workspace" },
              { value: "pyramid", label: "Pyramid" },
              { value: "heatmap", label: "Heatmap" },
              { value: "workshop", label: "Workshop" },
            ]}
            onChange={(v) => setScreen(v)}
          />
          <TweakRadio
            label="Mode"
            value={mode}
            options={[
              { value: "self", label: "Self-serve" },
              { value: "workshop", label: "Workshop" },
            ]}
            onChange={(v) => setMode(v)}
          />
        </TweakSection>

        <TweakSection title="Quick seed" subtitle="Populate with the canonical example.">
          <button className="btn btn-ghost" style={{ width: "100%", justifyContent: "center" }} onClick={() => {
            setProject(window.FRAMEWORK.sample.project);
            setSession(sessionFromSample());
            setScreen("workspace");
          }}>Load nanobody example</button>
        </TweakSection>
      </TweaksPanel>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
window.FRAMEWORK_READY.then(() => {
  root.render(<App />);
});

// ============================================================================
// Share button — top app bar. Opens a popover with the current sharable URL.
// ============================================================================
function ShareButton({ open, setOpen, project, mode, screen, session }) {
  const wrapRef = useRef(null);
  const [copied, setCopied] = useState(false);

  useEffect(() => {
    if (!open) return;
    const onDown = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    return () => document.removeEventListener("mousedown", onDown);
  }, [open]);

  // Recompute the URL every time the popover opens — so it reflects the
  // latest state even if the parent's state-sync effect hasn't fired yet.
  const url = useMemo(() => {
    const encoded = encodeSession({ project, mode, screen, session });
    return currentShareUrl(encoded);
  }, [project, mode, screen, session, open]);

  const ready = !!project || (screen !== "landing" && screen !== "about") ||
    Object.values(session.priorities || {}).some(p => p && p !== "unset");

  const copy = async () => {
    try {
      await navigator.clipboard.writeText(url);
      setCopied(true);
      setTimeout(() => setCopied(false), 1800);
    } catch (_) {
      // Fallback for non-clipboard environments — select the input.
      const el = document.getElementById("share-url-input");
      if (el) { el.focus(); el.select(); }
    }
  };

  return (
    <div ref={wrapRef} style={{ position: "relative", marginLeft: "auto" }}>
      <button
        onClick={() => ready && setOpen(o => !o)}
        disabled={!ready}
        title={ready ? "Share a link to this session" : "Pick at least one priority to share"}
        aria-expanded={open}
        style={{
          appearance: "none",
          display: "inline-flex", alignItems: "center", gap: 8,
          padding: "8px 14px",
          height: 36,
          borderRadius: 999,
          border: "1px solid var(--border-default)",
          background: open ? "var(--surface-navy)" : "rgba(255,255,255,0.6)",
          color: open ? "var(--fg-on-dark)" : "var(--fg-2)",
          fontFamily: "var(--font-jakarta)", fontWeight: 600, fontSize: 12,
          letterSpacing: "-0.005em",
          cursor: ready ? "pointer" : "not-allowed",
          opacity: ready ? 1 : 0.45,
          transition: "all 160ms",
        }}
      >
        <ShareIcon />
        <span>Share</span>
      </button>
      {open && (
        <div role="dialog" aria-label="Share session"
          style={{
            position: "absolute",
            top: "calc(100% + 10px)", right: 0,
            width: 420,
            background: "var(--surface-paper)",
            border: "1px solid var(--border-strong)",
            borderRadius: 16,
            boxShadow: "0 24px 60px rgba(34,49,83,0.18), 0 4px 14px rgba(34,49,83,0.06)",
            padding: 18, zIndex: 50,
          }}
        >
          <span style={{
            position: "absolute", top: -7, right: 22,
            width: 12, height: 12, transform: "rotate(45deg)",
            background: "var(--surface-paper)",
            borderTop: "1px solid var(--border-strong)",
            borderLeft: "1px solid var(--border-strong)",
          }} />
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.18em", color: "var(--c-violet-deep)" }}>
              SHARE · RESTORE
            </span>
            <button onClick={() => setOpen(false)} aria-label="Close"
              style={{ appearance: "none", border: 0, background: "transparent", color: "var(--fg-4)", cursor: "pointer", fontSize: 16, padding: 4 }}>×</button>
          </div>
          <p style={{
            fontFamily: "var(--font-serif)", fontSize: 13, color: "var(--fg-3)",
            lineHeight: 1.5, margin: "0 0 12px",
          }}>
            The whole session is encoded into the link. Send it to a collaborator
            or bookmark it for later — they’ll be asked before anything loads.
          </p>
          <div style={{
            display: "flex", gap: 8, alignItems: "stretch",
            background: "rgba(255,255,255,0.7)",
            border: "1px solid var(--border-default)",
            borderRadius: 10, padding: 6,
          }}>
            <input
              id="share-url-input"
              readOnly
              value={url}
              onFocus={(e) => e.target.select()}
              style={{
                flex: 1, border: 0, background: "transparent",
                fontFamily: "var(--font-mono)", fontSize: 11,
                color: "var(--fg-2)", outline: "none",
                padding: "6px 8px",
              }}
            />
            <button
              className="btn btn-primary"
              onClick={copy}
              style={{ padding: "6px 14px", fontSize: 12, whiteSpace: "nowrap" }}
            >{copied ? "Copied ✓" : "Copy"}</button>
          </div>
          <div style={{
            marginTop: 12, fontFamily: "var(--font-mono)", fontSize: 9.5,
            letterSpacing: "0.12em", color: "var(--fg-5)",
          }}>
            v1 · STATELESS · NO BACKEND
          </div>
        </div>
      )}
    </div>
  );
}

function ShareIcon() {
  return (
    <svg width="14" height="14" viewBox="0 0 16 16" fill="none" style={{ display: "block" }}>
      <circle cx="12" cy="3.5" r="2" fill="currentColor" />
      <circle cx="4" cy="8" r="2" fill="currentColor" />
      <circle cx="12" cy="12.5" r="2" fill="currentColor" />
      <path d="M5.7 7 L10.3 4.5 M5.7 9 L10.3 11.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
    </svg>
  );
}

// ============================================================================
// Restore modal — shown on initial load when the URL carries a session.
// Names the project, shows lead/follow counts, asks before loading.
// ============================================================================
function RestoreModal({ summary, onRestore, onDismiss }) {
  // Esc dismisses (treats as "start fresh") — friendly default.
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onDismiss();
      if (e.key === "Enter") onRestore();
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onRestore, onDismiss]);

  const { project, counts, screen, mode } = summary;
  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label="Restore saved session?"
      style={{
        position: "fixed", inset: 0, zIndex: 100,
        background: "rgba(34,49,83,0.42)",
        backdropFilter: "blur(4px)",
        WebkitBackdropFilter: "blur(4px)",
        display: "grid", placeItems: "center",
        padding: 24,
        animation: "fadeIn 180ms ease-out",
      }}
    >
      <div style={{
        width: "min(520px, 100%)",
        background: "var(--surface-paper)",
        border: "1px solid var(--border-strong)",
        borderRadius: 20,
        boxShadow: "0 40px 80px rgba(34,49,83,0.28), 0 8px 20px rgba(34,49,83,0.08)",
        padding: 28,
        animation: "popIn 200ms cubic-bezier(0.16, 1, 0.3, 1)",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16 }}>
          <span style={{
            width: 28, height: 28, borderRadius: 8,
            background: "var(--c-violet)", color: "#fff",
            display: "grid", placeItems: "center",
          }}>
            <ShareIcon />
          </span>
          <span className="eyebrow violet" style={{ margin: 0 }}>Saved session</span>
        </div>

        <h2 style={{
          fontFamily: "var(--font-jakarta)",
          fontWeight: 800,
          fontSize: 26,
          letterSpacing: "-0.02em",
          margin: "0 0 6px",
          color: "var(--fg-1)",
          textWrap: "balance",
        }}>
          Pick up where {project ? "you" : "this"} left off?
        </h2>
        <p style={{
          fontFamily: "var(--font-serif)", fontSize: 15,
          color: "var(--fg-3)", lineHeight: 1.5, margin: "0 0 18px",
        }}>
          This link carries a complete session for{" "}
          <strong style={{ color: "var(--fg-1)", fontFamily: "var(--font-jakarta)", fontWeight: 700 }}>
            {project || "an untitled project"}
          </strong>
          . Restore it, or start fresh.
        </p>

        <div style={{
          display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 10,
          marginBottom: 22,
        }}>
          <RestoreStat label="Lead" value={counts.lead} color="var(--c-violet)" />
          <RestoreStat label="Follow" value={counts.follow} color="var(--c-emerald)" />
          <RestoreStat label="Out-of-scope" value={counts.out} color="rgba(34,49,83,0.30)" />
        </div>

        {(mode === "workshop" || (screen && screen !== "landing")) && (
          <div style={{
            display: "flex", gap: 12, flexWrap: "wrap",
            marginBottom: 18, paddingBottom: 18,
            borderBottom: "1px dashed var(--border-default)",
          }}>
            {mode === "workshop" && (
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--c-violet-deep)", letterSpacing: "0.16em" }}>
                · WORKSHOP MODE
              </span>
            )}
            {screen && screen !== "landing" && (
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--fg-4)", letterSpacing: "0.16em" }}>
                · LAST ON: {String(screen).toUpperCase()}
              </span>
            )}
          </div>
        )}

        <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
          <button className="btn btn-ghost" onClick={onDismiss}>Start fresh</button>
          <button className="btn btn-primary" onClick={onRestore} autoFocus>
            Restore session
            <span aria-hidden style={{ fontFamily: "var(--font-mono)", marginLeft: 4 }}>↵</span>
          </button>
        </div>
      </div>
    </div>
  );
}

function RestoreStat({ label, value, color }) {
  return (
    <div style={{
      padding: "12px 14px",
      borderRadius: 12,
      background: "rgba(255,255,255,0.6)",
      border: "1px solid var(--border-default)",
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
        <span style={{ width: 6, height: 6, borderRadius: 999, background: color }} />
        <span style={{
          fontFamily: "var(--font-mono)", fontSize: 9.5,
          letterSpacing: "0.16em", textTransform: "uppercase",
          color: "var(--fg-4)",
        }}>{label}</span>
      </div>
      <div style={{
        fontFamily: "var(--font-jakarta)", fontWeight: 700, fontSize: 24,
        letterSpacing: "-0.02em", color: "var(--fg-1)", lineHeight: 1,
      }}>{value}</div>
    </div>
  );
}

// ============================================================================
// Join room modal — shown when ?room=… is in the URL but we have no
// participantId for it (i.e. fresh visitor following a shared link).
// ============================================================================
function JoinRoomModal({ code, onJoin, onDismiss, error }) {
  const [name, setName] = useState("");
  const [role, setRole] = useState("");
  const [submitting, setSubmitting] = useState(false);

  const submit = async () => {
    if (!name.trim()) return;
    setSubmitting(true);
    try {
      await onJoin({ name: name.trim(), role: role.trim() });
    } finally {
      setSubmitting(false);
    }
  };

  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onDismiss();
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onDismiss]);

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label="Join workshop room"
      style={{
        position: "fixed", inset: 0, zIndex: 100,
        background: "rgba(34,49,83,0.42)",
        backdropFilter: "blur(4px)",
        WebkitBackdropFilter: "blur(4px)",
        display: "grid", placeItems: "center",
        padding: 24, animation: "fadeIn 180ms ease-out",
      }}
    >
      <div style={{
        width: "min(480px, 100%)",
        background: "var(--surface-paper)",
        border: "1px solid var(--border-strong)",
        borderRadius: 20,
        boxShadow: "0 40px 80px rgba(34,49,83,0.28), 0 8px 20px rgba(34,49,83,0.08)",
        padding: 28,
        animation: "popIn 200ms cubic-bezier(0.16, 1, 0.3, 1)",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16 }}>
          <span style={{
            width: 28, height: 28, borderRadius: 8,
            background: "var(--c-violet)", color: "#fff",
            display: "grid", placeItems: "center",
            fontFamily: "var(--font-jakarta)", fontWeight: 800, fontSize: 14,
          }}>W</span>
          <span className="eyebrow violet" style={{ margin: 0 }}>Join workshop</span>
        </div>

        <h2 style={{
          fontFamily: "var(--font-jakarta)", fontWeight: 800, fontSize: 24,
          letterSpacing: "-0.02em", margin: "0 0 6px",
          color: "var(--fg-1)", textWrap: "balance",
        }}>
          You're joining room <span style={{ color: "var(--c-violet-deep)", letterSpacing: "0.04em" }}>{code}</span>
        </h2>
        <p style={{
          fontFamily: "var(--font-serif)", fontSize: 14,
          color: "var(--fg-3)", lineHeight: 1.5, margin: "0 0 18px",
        }}>
          Tell the room who you are. Your name shows up next to your votes; your role is optional but helps the facilitator read the disagreement view.
        </p>

        <div style={{ display: "grid", gap: 10, marginBottom: 18 }}>
          <input
            className="cb-input"
            placeholder="Your name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter" && name.trim()) submit(); }}
            autoFocus
          />
          <input
            className="cb-input"
            placeholder="Role (optional)  e.g. Protein engineer"
            value={role}
            onChange={(e) => setRole(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter" && name.trim()) submit(); }}
          />
        </div>

        {error && (
          <div style={{
            marginBottom: 14, padding: "10px 14px",
            background: "rgba(245,158,11,0.08)",
            border: "1px solid rgba(245,158,11,0.28)",
            borderRadius: 8,
            fontSize: 13, color: "var(--c-gold-deep)", lineHeight: 1.4,
          }}>
            {error.status === 404 ? "No room with this code (or it has been removed)."
              : error.status === 410 ? "This room has expired (older than 24h)."
              : error.status === 409 ? "Room is full (25 participant limit)."
              : `Couldn't join: ${error.message || "unknown error"}`}
          </div>
        )}

        <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
          <button className="btn btn-ghost" onClick={onDismiss} disabled={submitting}>Cancel</button>
          <button
            className="btn btn-primary"
            onClick={submit}
            disabled={!name.trim() || submitting}
            style={{ opacity: (!name.trim() || submitting) ? 0.5 : 1 }}
          >
            {submitting ? "Joining..." : "Join room"}
          </button>
        </div>
      </div>
    </div>
  );
}
