/* COMPANIONS — Roles, Handoffs, Difficult, Friday — single-file viewer */

const { useState, useMemo, useEffect } = React;

// ============================================================
// ROLES — Operating manual for five positions
// ============================================================
function RolesApp({ tokens, accent }) {
  const [roleId, setRoleId] = useChairsideStored("ch.roles.id", "tc");
  const [viewId, setViewId] = useChairsideStored("ch.roles.view", "overview");
  const [pendingScriptId, setPendingScriptId] = useState(null);
  const [autoLaunchScript, setAutoLaunchScript] = useState(null);
  const autoLaunchConsumed = React.useRef(false);
  const didMountView = React.useRef(false);

  // Deep-link reader: #role=own&view=practice&script=own-script-1
  useEffect(() => {
    function readHash() {
      const params = new URLSearchParams(window.location.hash.replace(/^#/, ""));
      const hashRoleId = params.get("role");
      const hashViewId = params.get("view");
      const scriptId = params.get("script");
      const wantsLaunch = params.get("launch") === "1";
      const validViews = ["overview", "practice", "metrics", "praxia"];

      const validRole =
        hashRoleId ? ROLES_DATA.roles.find(r => r.id === hashRoleId) || null : null;
      if (validRole) setRoleId(validRole.id);

      if (scriptId) {
        setViewId("practice");
        setPendingScriptId(scriptId);
      } else if (validViews.includes(hashViewId)) {
        setViewId(hashViewId);
      }

      // Deep-link auto-launch: open the Practice-with-AI modal directly, used
      // by the course home's "Practice with AI" CTAs (#...&launch=1). Picks the
      // named script, else the role's first roleplay-ready script. Fires once
      // per page load; reuses the engine-exported window.RoleplaySession.
      if (wantsLaunch && !autoLaunchConsumed.current) {
        const targetRole = validRole || ROLES_DATA.roles.find(r => r.id === roleId);
        const api = window.RoleplayAPI;
        const isReady = (s) => !!(api && typeof api.isRoleplayReady === "function" && api.isRoleplayReady(s));
        const scripts = (targetRole && targetRole.scripts) || [];
        const chosen =
          (scriptId && scripts.find(s => s.id === scriptId && isReady(s))) ||
          scripts.find(isReady) ||
          null;
        if (chosen) {
          autoLaunchConsumed.current = true;
          setViewId("practice");
          setPendingScriptId(chosen.id);
          setAutoLaunchScript(chosen);
        }
      }
    }
    readHash();
    window.addEventListener("hashchange", readHash);
    return () => window.removeEventListener("hashchange", readHash);
  }, []);

  const role = ROLES_DATA.roles.find(r => r.id === roleId);
  const accentColor = role.color;
  const hasPractice = Boolean((role.scripts && role.scripts.length) || (role.failureModes && role.failureModes.length));
  const hasMetrics = Boolean((role.weeklyMetrics && role.weeklyMetrics.length) || (role.metrics && role.metrics.length) || (role.week && role.week.length));
  const hasPraxia = Boolean(role.praxiaBridge && role.praxiaBridge.length);
  const availableViews = [
    { id: "overview", label: "Overview", available: true },
    { id: "practice", label: "Practice", available: hasPractice },
    { id: "metrics", label: "Metrics", available: hasMetrics },
    { id: "praxia", label: "Praxia", available: hasPraxia },
  ].filter(v => v.available);
  const activeViewId = availableViews.some(v => v.id === viewId) ? viewId : "overview";

  useEffect(() => {
    if (activeViewId !== viewId) setViewId("overview");
  }, [activeViewId, viewId]);

  useEffect(() => {
    if (!didMountView.current) {
      didMountView.current = true;
      return;
    }
    window.scrollTo({ top: 0, behavior: "smooth" });
  }, [viewId]);

  const viewIsAvailableForRole = (id, nextRole) => {
    if (id === "overview") return true;
    if (id === "practice") return Boolean((nextRole.scripts && nextRole.scripts.length) || (nextRole.failureModes && nextRole.failureModes.length));
    if (id === "metrics") return Boolean((nextRole.weeklyMetrics && nextRole.weeklyMetrics.length) || (nextRole.metrics && nextRole.metrics.length) || (nextRole.week && nextRole.week.length));
    if (id === "praxia") return Boolean(nextRole.praxiaBridge && nextRole.praxiaBridge.length);
    return false;
  };

  const selectRole = (nextRoleId) => {
    const nextRole = ROLES_DATA.roles.find(r => r.id === nextRoleId);
    if (nextRole && !viewIsAvailableForRole(viewId, nextRole)) setViewId("overview");
    setRoleId(nextRoleId);
  };

  const renderEmptyState = (message) => (
    <div style={{ color: tokens.soft, fontSize: 15, lineHeight: 1.6, padding: "24px 0" }}>
      {message}
    </div>
  );

  const renderMetricsThatMatterCard = (style) => (
    <div className="card" style={{ padding: 28, ...style }}>
      <div className="eyebrow" style={{ color: accentColor, marginBottom: 14 }}>Metrics that matter</div>
      <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
        {role.metrics.map((m, i) => (
          <li key={i} style={{ padding: "10px 0", borderBottom: i === role.metrics.length - 1 ? "none" : `1px solid ${tokens.line}`, fontSize: 14, color: tokens.text }}>
            {m}
          </li>
        ))}
      </ul>
    </div>
  );

  const renderTypicalWeekCard = (style) => (
    <div className="card" style={{ padding: 28, background: tokens.surface2, ...style }}>
      <div className="eyebrow" style={{ color: accentColor, marginBottom: 14 }}>A typical week</div>
      <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
        {role.week.map((w, i) => {
          const [day, ...rest] = w.split(":");
          return (
            <li key={i} style={{ padding: "10px 0", display: "flex", gap: 12, fontSize: 13, color: tokens.text, borderBottom: i === role.week.length - 1 ? "none" : `1px solid ${tokens.line}` }}>
              <span className="mono" style={{ color: accentColor, minWidth: 40, fontSize: 10, paddingTop: 2 }}>{day}</span>
              <span style={{ flex: 1, lineHeight: 1.5 }}>{rest.join(":").trim()}</span>
            </li>
          );
        })}
      </ul>
    </div>
  );

  return (
    <div style={{ background: tokens.bg, minHeight: "100vh" }}>
      <ChairsideHeader product="Roles" tokens={tokens} accent={accent} />

      {/* Hero */}
      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "80px 28px 48px" }}>
        <div className="pill" style={{ marginBottom: 20 }}>Chairside · Roles</div>
        <h1 className="display" style={{ maxWidth: 980 }}>The five jobs in a dental practice — written like jobs, not like job descriptions.</h1>
        <p style={{ fontSize: 19, color: tokens.soft, maxWidth: 720, marginTop: 20, lineHeight: 1.55 }}>
          Most dental job descriptions are HR documents — duties, qualifications, reports-to. These are operating manuals: what the job is for, what good looks like, the conversations you'll need to be fluent in, and what isn't yours to carry.
        </p>
      </section>

      {/* Role tabs */}
      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "0 28px 32px" }}>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", borderBottom: `1px solid ${tokens.line}`, paddingBottom: 16 }}>
          {ROLES_DATA.roles.map(r => (
            <button key={r.id} onClick={() => selectRole(r.id)}
              className="role-pill"
              style={{
                borderRadius: 999,
                background: roleId === r.id ? r.color : "transparent",
                color: roleId === r.id ? "white" : tokens.text,
                border: `1px solid ${roleId === r.id ? r.color : tokens.line}`,
                fontSize: 14, fontWeight: 500,
              }}>
              {r.name}
            </button>
          ))}
        </div>
      </section>

      {/* Role sub-tabs */}
      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "0 28px" }}>
        <div style={{ display: "flex", gap: 28, borderBottom: `1px solid ${tokens.line}`, marginBottom: 32 }}>
          {availableViews.map(v => {
            const active = activeViewId === v.id;
            return (
              <button
                key={v.id}
                onClick={() => setViewId(v.id)}
                onMouseOver={(e) => {
                  if (!active) e.currentTarget.style.color = tokens.text;
                }}
                onMouseOut={(e) => {
                  if (!active) e.currentTarget.style.color = tokens.soft;
                }}
                className="role-subtab"
                style={{
                  background: "transparent",
                  border: "none",
                  borderBottom: active ? `2px solid ${accentColor}` : "2px solid transparent",
                  color: active ? tokens.text : tokens.soft,
                  fontSize: 15,
                  fontWeight: active ? 500 : 400,
                  cursor: "pointer",
                }}
              >
                {v.label}
              </button>
            );
          })}
        </div>
      </section>

      {/* Role detail */}
      {activeViewId === "overview" && (
        <section style={{ maxWidth: 1320, margin: "0 auto", padding: "16px 28px 120px" }}>
          <div style={{ display: "grid", gridTemplateColumns: "1.6fr 1fr", gap: 48, alignItems: "start" }}>
            <div>
              <div className="eyebrow" style={{ color: accentColor, marginBottom: 12 }}>Purpose</div>
              <h2 className="display" style={{ marginBottom: 28, lineHeight: 1.1 }}>{role.purpose}</h2>

              <Section title="What good looks like" tokens={tokens}>
                <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                  {role.outcomes.map((o, i) => (
                    <li key={i} style={{ display: "flex", gap: 14, padding: "12px 0", borderBottom: `1px solid ${tokens.line}` }}>
                      <span style={{ color: accentColor, fontFamily: "JetBrains Mono, monospace", fontSize: 12, minWidth: 22, paddingTop: 2 }}>{String(i + 1).padStart(2, "0")}</span>
                      <span style={{ fontSize: 16, lineHeight: 1.5 }}>{o}</span>
                    </li>
                  ))}
                </ul>
              </Section>

              {role.mastery && <MasterySection role={role} tokens={tokens} accentColor={accentColor} />}

              <Section title="The conversations you'll need to be fluent in" tokens={tokens}>
                <ul style={{ listStyle: "none", padding: 0, margin: 0 }}>
                  {role.conversations.map((c, i) => (
                    <li key={i} style={{ padding: "14px 0", borderBottom: `1px solid ${tokens.line}`, fontSize: 16, color: tokens.text }}>
                      <span style={{ fontFamily: "JetBrains Mono, monospace", fontSize: 11, color: tokens.mute, marginRight: 12 }}>·</span>
                      {c}
                    </li>
                  ))}
                </ul>
              </Section>

              <Section title="Handoffs you own" tokens={tokens}>
                {role.handoffs.map((h, i) => (
                  <div key={i} style={{ padding: "14px 0", borderBottom: `1px solid ${tokens.line}`, display: "flex", gap: 16 }}>
                    <span className="mono" style={{ color: accentColor, minWidth: 80, paddingTop: 2 }}>{h.split(":")[0]}</span>
                    <span style={{ fontSize: 16, color: tokens.text, flex: 1 }}>{h.split(":").slice(1).join(":").trim()}</span>
                  </div>
                ))}
              </Section>

              <Section title="What this role is not for" tokens={tokens}>
                <div style={{ background: tokens.surface2, borderRadius: 14, padding: "20px 24px", border: `1px solid ${tokens.line}` }}>
                  <ul style={{ margin: 0, paddingLeft: 18, lineHeight: 1.7, color: tokens.soft }}>
                    {role.notFor.map((n, i) => <li key={i} style={{ marginBottom: 6 }}>{n}</li>)}
                  </ul>
                </div>
              </Section>
            </div>

            <aside style={{ position: "sticky", top: 96 }}>
              {renderMetricsThatMatterCard({ marginBottom: 20 })}
              {renderTypicalWeekCard()}
            </aside>
          </div>
        </section>
      )}

      {activeViewId === "practice" && (
        <section style={{ maxWidth: 860, margin: "0 auto", padding: "16px 28px 120px" }}>
          {role.scripts && role.scripts.length > 0 && (
            <PracticeHub
              role={role}
              tokens={tokens}
              accentColor={accentColor}
              pendingScriptId={pendingScriptId}
              setPendingScriptId={setPendingScriptId}
            />
          )}
          {role.scripts && role.scripts.length > 0 && <ScriptsLibrary role={role} tokens={tokens} accentColor={accentColor} pendingScriptId={pendingScriptId} setPendingScriptId={setPendingScriptId} />}
          {role.failureModes && role.failureModes.length > 0 && <FailureModesSection role={role} tokens={tokens} accentColor={accentColor} />}
          {!hasPractice && renderEmptyState("No practice material for this role yet.")}
        </section>
      )}

      {activeViewId === "metrics" && (
        <section style={{ maxWidth: 860, margin: "0 auto", padding: "16px 28px 120px" }}>
          {role.weeklyMetrics && role.weeklyMetrics.length > 0 ? <WeeklyMetricsSection role={role} tokens={tokens} accentColor={accentColor} /> : renderEmptyState("No weekly metrics defined for this role yet.")}
          {renderMetricsThatMatterCard({ marginBottom: 20 })}
          {renderTypicalWeekCard()}
        </section>
      )}

      {activeViewId === "praxia" && (
        <section style={{ maxWidth: 860, margin: "0 auto", padding: "16px 28px 120px" }}>
          {role.praxiaBridge && role.praxiaBridge.length > 0 ? <PraxiaBridgeSection role={role} tokens={tokens} accentColor={accentColor} /> : renderEmptyState("No Praxia cross-references for this role yet.")}
        </section>
      )}

      {/* Deep-link auto-launch modal — the same RoleplaySession the
          "Practice with AI" button opens, mounted directly from a #...&launch=1
          link. The accordion below is also opened (pendingScriptId) so closing
          the modal lands on the open script. */}
      {autoLaunchScript && window.RoleplaySession && (
        <window.RoleplaySession
          script={autoLaunchScript}
          tokens={tokens}
          accent={{ c: accentColor, c2: accentColor, soft: `color-mix(in srgb, ${accentColor} 18%, ${tokens.surface})` }}
          onClose={() => {
            setAutoLaunchScript(null);
            // Strip launch flag so a refresh doesn't reopen the modal.
            try {
              const p = new URLSearchParams(window.location.hash.replace(/^#/, ""));
              p.delete("launch");
              const s = p.toString();
              window.history.replaceState(null, "", s ? `#${s}` : window.location.pathname);
            } catch (e) { /* ignore */ }
          }}
        />
      )}
    </div>
  );
}

// ============================================================
// HANDOFFS — Library of every handoff in the practice
// ============================================================
function HandoffsApp({ tokens, accent }) {
  const [openId, setOpenId] = useState(HANDOFFS_DATA.handoffs[0].id);

  return (
    <div style={{ background: tokens.bg, minHeight: "100vh" }}>
      <ChairsideHeader product="Handoffs" tokens={tokens} accent={accent} />

      <section style={{ maxWidth: 1100, margin: "0 auto", padding: "80px 28px 48px" }}>
        <div className="pill" style={{ marginBottom: 20 }}>Chairside · Handoffs</div>
        <h1 className="display">Every patient is handed off six times. Most practices script none of them.</h1>
        <p style={{ fontSize: 19, color: tokens.soft, maxWidth: 720, marginTop: 20 }}>
          A handoff is the moment a patient leaves one team member's care and enters another's. They are the most fragile minute in the visit — and the most overlooked. This is the library.
        </p>
      </section>

      <section style={{ maxWidth: 1100, margin: "0 auto", padding: "0 28px 120px" }}>
        {HANDOFFS_DATA.handoffs.map((h, i) => {
          const open = openId === h.id;
          return (
            <article key={h.id} style={{ borderTop: `1px solid ${tokens.line}`, borderBottom: i === HANDOFFS_DATA.handoffs.length - 1 ? `1px solid ${tokens.line}` : "none" }}>
              <button onClick={() => setOpenId(open ? null : h.id)}
                style={{ width: "100%", textAlign: "left", padding: "28px 0", background: "transparent", border: "none", display: "flex", alignItems: "center", gap: 24, color: tokens.text }}>
                <span className="mono" style={{ color: tokens.mute, minWidth: 36 }}>{String(i + 1).padStart(2, "0")}</span>
                <div style={{ flex: 1 }}>
                  <div style={{ display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap" }}>
                    <span className="mono" style={{ color: accent.c, fontSize: 11 }}>{h.from}</span>
                    <span style={{ color: tokens.mute }}>→</span>
                    <span className="mono" style={{ color: accent.c, fontSize: 11 }}>{h.to}</span>
                  </div>
                  <h3 className="display" style={{ marginTop: 6, fontSize: 28 }}>{h.title}</h3>
                </div>
                <span style={{ fontSize: 24, color: tokens.mute, transition: "transform .2s", transform: open ? "rotate(45deg)" : "none" }}>+</span>
              </button>

              {open && (
                <div style={{ padding: "0 0 48px 60px", display: "grid", gridTemplateColumns: "1fr 1fr", gap: 40 }}>
                  <div>
                    <SubHeading tokens={tokens} accent={accent.c}>What transfers</SubHeading>
                    <ul style={{ paddingLeft: 18, lineHeight: 1.6, color: tokens.text, marginTop: 8 }}>
                      {h.whatTransfers.map((w, j) => <li key={j} style={{ marginBottom: 6 }}>{w}</li>)}
                    </ul>

                    <SubHeading tokens={tokens} accent={accent.c} style={{ marginTop: 28 }}>What stays</SubHeading>
                    <ul style={{ paddingLeft: 18, lineHeight: 1.6, color: tokens.soft, marginTop: 8 }}>
                      {h.whatStays.map((w, j) => <li key={j} style={{ marginBottom: 6 }}>{w}</li>)}
                    </ul>

                    <SubHeading tokens={tokens} accent={accent.c} style={{ marginTop: 28 }}>Common failures</SubHeading>
                    <ul style={{ paddingLeft: 18, lineHeight: 1.6, color: tokens.soft, marginTop: 8 }}>
                      {h.common.map((w, j) => <li key={j} style={{ marginBottom: 6 }}>{w}</li>)}
                    </ul>
                  </div>

                  <div>
                    <SubHeading tokens={tokens} accent={accent.c}>What it sounds like</SubHeading>
                    <div style={{ background: tokens.surface, border: `1px solid ${tokens.line}`, borderRadius: 16, padding: 24, marginTop: 8 }}>
                      {h.script.map((line, j) => (
                        <div key={j} style={{ marginBottom: j === h.script.length - 1 ? 0 : 18 }}>
                          <div className="mono" style={{ color: accent.c, fontSize: 10, marginBottom: 6, letterSpacing: "0.12em" }}>{line.speaker.toUpperCase()}</div>
                          <div style={{ fontFamily: "Fraunces, serif", fontSize: 17, lineHeight: 1.5, color: tokens.text }}>"{line.text}"</div>
                        </div>
                      ))}
                    </div>
                  </div>
                </div>
              )}
            </article>
          );
        })}
      </section>
    </div>
  );
}

// ============================================================
// DIFFICULT — Difficult patient conversations
// ============================================================
function DifficultApp({ tokens, accent }) {
  const [scenarioId, setScenarioId] = useChairsideStored("ch.diff.id", "d1");
  const s = DIFFICULT_DATA.scenarios.find(x => x.id === scenarioId);

  return (
    <div style={{ background: tokens.bg, minHeight: "100vh" }}>
      <ChairsideHeader product="Difficult" tokens={tokens} accent={accent} />

      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "80px 28px 32px" }}>
        <div className="pill" style={{ marginBottom: 20 }}>Chairside · Difficult</div>
        <h1 className="display" style={{ maxWidth: 1000 }}>Eight conversations no one teaches in dental school.</h1>
        <p style={{ fontSize: 19, color: tokens.soft, maxWidth: 720, marginTop: 20 }}>
          The angry patient at the front desk. The treatment that failed. The bad review you didn't deserve. The chair-side cry. Each one is rehearseable. None of them are scripted — they're disciplined.
        </p>
      </section>

      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "16px 28px 120px", display: "grid", gridTemplateColumns: "320px 1fr", gap: 40 }}>
        <aside>
          <div className="eyebrow" style={{ color: tokens.mute, marginBottom: 14 }}>Scenarios</div>
          <ul style={{ listStyle: "none", padding: 0, margin: 0, position: "sticky", top: 96 }}>
            {DIFFICULT_DATA.scenarios.map((x, i) => {
              const active = x.id === scenarioId;
              return (
                <li key={x.id}>
                  <button onClick={() => setScenarioId(x.id)}
                    style={{
                      width: "100%", textAlign: "left", padding: "14px 16px", borderRadius: 12,
                      background: active ? tokens.surface : "transparent",
                      border: `1px solid ${active ? tokens.line : "transparent"}`,
                      color: tokens.text, marginBottom: 4, display: "flex", gap: 14, alignItems: "flex-start",
                    }}>
                    <span className="mono" style={{ color: active ? accent.c : tokens.mute, minWidth: 22, fontSize: 11, paddingTop: 3 }}>{String(i + 1).padStart(2, "0")}</span>
                    <span style={{ fontSize: 14, lineHeight: 1.4, fontWeight: active ? 500 : 400 }}>{x.title}</span>
                  </button>
                </li>
              );
            })}
          </ul>
        </aside>

        <div style={{ maxWidth: 820 }}>
          <div className="eyebrow" style={{ color: accent.c, marginBottom: 12 }}>Scenario {String(DIFFICULT_DATA.scenarios.findIndex(x => x.id === s.id) + 1).padStart(2, "0")}</div>
          <h2 className="display" style={{ marginBottom: 24 }}>{s.title}</h2>

          <div style={{ fontFamily: "Fraunces, serif", fontStyle: "italic", fontSize: 19, lineHeight: 1.55, color: tokens.soft, padding: "20px 28px", borderLeft: `3px solid ${accent.c}`, background: tokens.surface, marginBottom: 36 }}>
            {s.scenario}
          </div>

          <div style={{ marginBottom: 36 }}>
            <SubHeading tokens={tokens} accent={accent.c}>The principle</SubHeading>
            <p style={{ fontSize: 18, fontFamily: "Fraunces, serif", lineHeight: 1.5, marginTop: 10, color: tokens.text }}>{s.principle}</p>
          </div>

          <SubHeading tokens={tokens} accent={accent.c}>The moves</SubHeading>
          <div style={{ marginTop: 14, marginBottom: 36 }}>
            {s.moves.map((m, i) => (
              <div key={i} style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 0, marginBottom: 16, border: `1px solid ${tokens.line}`, borderRadius: 14, overflow: "hidden" }}>
                <div style={{ padding: "20px 22px", background: tokens.surface, borderRight: `1px solid ${tokens.line}` }}>
                  <div className="mono" style={{ color: "#2A8A88", fontSize: 10, marginBottom: 8 }}>DO</div>
                  <div style={{ fontSize: 15, lineHeight: 1.55 }}>{m.do}</div>
                </div>
                <div style={{ padding: "20px 22px", background: tokens.surface2 }}>
                  <div className="mono" style={{ color: "#B5536A", fontSize: 10, marginBottom: 8 }}>DON'T</div>
                  <div style={{ fontSize: 15, lineHeight: 1.55, color: tokens.soft }}>{m.dont}</div>
                </div>
              </div>
            ))}
          </div>

          <div style={{ background: "#1A1612", color: "#F5EDDC", borderRadius: 18, padding: 32 }}>
            <div className="mono" style={{ color: "#E89A4A", fontSize: 11, marginBottom: 14, letterSpacing: "0.16em" }}>NEVER</div>
            <ul style={{ margin: 0, paddingLeft: 18, lineHeight: 1.7 }}>
              {s.neverDo.map((n, i) => <li key={i} style={{ marginBottom: 8, fontSize: 15 }}>{n}</li>)}
            </ul>
          </div>
        </div>
      </section>
    </div>
  );
}

// ============================================================
// FRIDAY — 52-week roleplay kit
// ============================================================
function FridayApp({ tokens, accent }) {
  const [weekIdx, setWeekIdx] = useChairsideStored("ch.friday.week", 0);
  const [filter, setFilter] = useState("all");
  const [completed, setCompleted] = useChairsideStored("ch.friday.done", []);

  const themes = useMemo(() => Array.from(new Set(FRIDAY_DATA.weeks.map(w => w.theme))), []);
  const visible = useMemo(() => FRIDAY_DATA.weeks.filter(w => filter === "all" || w.theme === filter), [filter]);
  const w = FRIDAY_DATA.weeks[weekIdx];

  const toggle = (i) => setCompleted(c => c.includes(i) ? c.filter(x => x !== i) : [...c, i]);

  return (
    <div style={{ background: tokens.bg, minHeight: "100vh" }}>
      <ChairsideHeader product="Friday Roleplay" tokens={tokens} accent={accent} />

      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "80px 28px 32px" }}>
        <div className="pill" style={{ marginBottom: 20 }}>Chairside · Friday</div>
        <h1 className="display" style={{ maxWidth: 1080 }}>A year of Friday roleplay. Thirty minutes. Same time, every week.</h1>
        <p style={{ fontSize: 19, color: tokens.soft, maxWidth: 720, marginTop: 20 }}>
          Skill compounds when it's practiced under pressure, in front of peers, on a schedule. Fifty-two prepared sessions. Pick where you are. Run it.
        </p>
      </section>

      {/* Active week panel */}
      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "16px 28px 48px" }}>
        <div className="card" style={{ padding: "32px 36px", display: "grid", gridTemplateColumns: "1fr 1.4fr", gap: 48, background: tokens.surface }}>
          <div>
            <div className="eyebrow" style={{ color: accent.c, marginBottom: 12 }}>Week {String(w.week).padStart(2, "0")} · {w.theme}</div>
            <h2 className="display" style={{ fontSize: 38, lineHeight: 1.1 }}>{w.scenario}</h2>
            <div style={{ display: "flex", gap: 10, marginTop: 24 }}>
              <button onClick={() => setWeekIdx(Math.max(0, weekIdx - 1))} className="btn">← Previous</button>
              <button onClick={() => setWeekIdx(Math.min(51, weekIdx + 1))} className="btn">Next →</button>
              <button onClick={() => toggle(weekIdx)} className={`btn ${completed.includes(weekIdx) ? "accent" : "primary"}`}>
                {completed.includes(weekIdx) ? "✓ Done" : "Mark complete"}
              </button>
            </div>
          </div>

          <div>
            <div className="eyebrow" style={{ color: tokens.mute, marginBottom: 14 }}>30-minute structure</div>
            <ol style={{ listStyle: "none", padding: 0, margin: 0, counterReset: "step" }}>
              {w.structure.map((s, i) => (
                <li key={i} style={{ display: "flex", gap: 16, padding: "12px 0", borderBottom: i === w.structure.length - 1 ? "none" : `1px solid ${tokens.line}` }}>
                  <span className="mono" style={{ color: accent.c, minWidth: 24, fontSize: 11, paddingTop: 3 }}>{String(i + 1).padStart(2, "0")}</span>
                  <span style={{ fontSize: 15, lineHeight: 1.5 }}>{s}</span>
                </li>
              ))}
            </ol>
            <div className="eyebrow" style={{ color: tokens.mute, marginTop: 24, marginBottom: 10 }}>Facilitator notes</div>
            <ul style={{ paddingLeft: 18, color: tokens.soft, fontSize: 14, lineHeight: 1.6, margin: 0 }}>
              {w.facilitatorNotes.map((n, i) => <li key={i} style={{ marginBottom: 6 }}>{n}</li>)}
            </ul>
          </div>
        </div>
      </section>

      {/* Year grid */}
      <section style={{ maxWidth: 1320, margin: "0 auto", padding: "32px 28px 120px" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", marginBottom: 24, flexWrap: "wrap", gap: 16 }}>
          <h3 className="display" style={{ fontSize: 28 }}>The year</h3>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            <button onClick={() => setFilter("all")} className={`chip ${filter === "all" ? "active" : ""}`}>All themes</button>
            {themes.map(t => (
              <button key={t} onClick={() => setFilter(t)} className={`chip ${filter === t ? "active" : ""}`}>{t}</button>
            ))}
          </div>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: 12 }}>
          {visible.map(week => {
            const isActive = week.week - 1 === weekIdx;
            const isDone = completed.includes(week.week - 1);
            return (
              <button key={week.week} onClick={() => setWeekIdx(week.week - 1)}
                style={{
                  textAlign: "left", padding: "16px 18px", borderRadius: 14,
                  background: isActive ? accent.c : tokens.surface,
                  color: isActive ? "white" : tokens.text,
                  border: `1px solid ${isActive ? accent.c : tokens.line}`,
                  cursor: "pointer", transition: "all .2s ease",
                  position: "relative",
                }}>
                <div className="mono" style={{ fontSize: 10, color: isActive ? "rgba(255,255,255,0.7)" : tokens.mute, marginBottom: 6 }}>
                  WK {String(week.week).padStart(2, "0")} · {week.theme}
                </div>
                <div style={{ fontFamily: "Fraunces, serif", fontSize: 15, lineHeight: 1.3 }}>{week.scenario}</div>
                {isDone && (
                  <span style={{ position: "absolute", top: 10, right: 12, color: isActive ? "white" : "#2A8A88", fontSize: 14 }}>✓</span>
                )}
              </button>
            );
          })}
        </div>
      </section>
    </div>
  );
}

// ============================================================
// SHARED helpers
// ============================================================
function Section({ title, tokens, children }) {
  return (
    <div style={{ marginBottom: 40 }}>
      <h3 style={{ fontFamily: "Fraunces, serif", fontSize: 24, marginBottom: 16, color: tokens.text }}>{title}</h3>
      {children}
    </div>
  );
}

function SubHeading({ children, tokens, accent, style }) {
  return (
    <div className="eyebrow" style={{ color: accent || tokens.mute, ...style }}>{children}</div>
  );
}

function isRoleplayReadyForHub(script) {
  const api = window.RoleplayAPI;
  return !!(api && typeof api.isRoleplayReady === "function" && api.isRoleplayReady(script));
}

function roleInitials(name) {
  return String(name || "")
    .split(/\s+/)
    .filter(Boolean)
    .map(part => part[0])
    .join("")
    .slice(0, 2)
    .toUpperCase();
}

function rolePracticeLabel(role) {
  if (!role || !role.name) return "team";
  return role.name === "Practice Owner" ? "owner" : role.name.split(" ")[0].toLowerCase();
}

function truncateRoleText(text, max = 118) {
  if (!text) return "";
  return text.length <= max ? text : `${text.slice(0, max - 1).trimEnd()}…`;
}

function PracticeHub({ role, tokens, accentColor, pendingScriptId, setPendingScriptId }) {
  const isPhone = (window.useIsPhone || (() => false))();
  const scripts = role.scripts || [];
  const readyScripts = scripts.filter(isRoleplayReadyForHub);
  const firstReady = readyScripts[0] || scripts[0];
  const [selectedId, setSelectedId] = useState(pendingScriptId || (firstReady && firstReady.id));

  useEffect(() => {
    const pendingInRole = pendingScriptId && scripts.some(s => s.id === pendingScriptId);
    setSelectedId(pendingInRole ? pendingScriptId : (firstReady && firstReady.id));
  }, [role.id, pendingScriptId]);

  const selected = scripts.find(s => s.id === selectedId) || firstReady;
  const selectedReady = isRoleplayReadyForHub(selected);
  const openScript = () => {
    if (!selected) return;
    setPendingScriptId && setPendingScriptId(selected.id);
    setTimeout(() => {
      const el = document.getElementById(`script-${selected.id}`);
      if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
    }, 80);
  };

  if (!scripts.length || !selected) return null;

  return (
    <Section title="Practice Hub" tokens={tokens}>
      <div style={{
        border: `1px solid ${tokens.line}`,
        borderRadius: 18,
        overflow: "hidden",
        background: tokens.surface,
      }}>
        <div style={{
          padding: isPhone ? "20px 18px" : "24px 24px 22px",
          background: `color-mix(in srgb, ${accentColor} 9%, ${tokens.surface})`,
          borderBottom: `1px solid ${tokens.line}`,
          display: "grid",
          gridTemplateColumns: isPhone ? "auto 1fr" : "auto 1fr auto",
          gap: isPhone ? 12 : 18,
          alignItems: "center",
        }}>
          <div style={{
            width: 54,
            height: 54,
            borderRadius: 16,
            background: accentColor,
            color: "white",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            fontWeight: 600,
            flexShrink: 0,
          }}>
            {roleInitials(role.name)}
          </div>
          <div style={{ minWidth: 0 }}>
            <div className="eyebrow" style={{ color: accentColor, marginBottom: 6 }}>Practice with AI</div>
            <h2 className="display" style={{ fontSize: 26, lineHeight: 1.12, marginBottom: 6 }}>Practice as {rolePracticeLabel(role)}.</h2>
            <div style={{ color: tokens.soft, fontSize: 14, lineHeight: 1.45 }}>
              {readyScripts.length}/{scripts.length} scenarios ready · choose a rep, run it, then review the report.
            </div>
          </div>
          <a
            className="btn"
            href="Chairside-My-Practice.html"
            style={{
              textDecoration: "none",
              justifyContent: "center",
              whiteSpace: "nowrap",
              gridColumn: isPhone ? "1 / -1" : "auto",
            }}
          >
            My Practice →
          </a>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: isPhone ? "1fr" : "minmax(0, 1.1fr) minmax(260px, 0.9fr)", gap: 0 }}>
          <div style={{ padding: isPhone ? 18 : 24, borderRight: isPhone ? "none" : `1px solid ${tokens.line}`, borderBottom: isPhone ? `1px solid ${tokens.line}` : "none" }}>
            <div className="eyebrow" style={{ color: accentColor, marginBottom: 10 }}>Selected rep</div>
            <h3 className="display" style={{ fontSize: 24, lineHeight: 1.16, marginBottom: 10 }}>{selected.title}</h3>
            <p style={{ color: tokens.soft, fontSize: 14, lineHeight: 1.6, margin: "0 0 18px" }}>
              {truncateRoleText(selected.setup || selected.trigger || "Open the script below, read the setup, then run the rep.")}
            </p>
            <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
              {window.RoleplayLauncher && (
                <window.RoleplayLauncher
                  script={selected}
                  tokens={tokens}
                  accent={{ c: accentColor, c2: accentColor, soft: `color-mix(in srgb, ${accentColor} 18%, ${tokens.surface})` }}
                />
              )}
              <button type="button" className="btn ghost" onClick={openScript} style={{ padding: "10px 16px", fontSize: 13 }}>
                Open script →
              </button>
            </div>
            {!selectedReady && (
              <div style={{ marginTop: 12, color: tokens.mute, fontSize: 12 }}>
                This script is in the library, but AI practice is not ready for it yet.
              </div>
            )}
          </div>

          <div style={{ padding: isPhone ? 14 : 18, display: "grid", gap: 8, alignContent: "start", maxHeight: isPhone ? "none" : 420, overflowY: isPhone ? "visible" : "auto" }}>
            {scripts.map((script, index) => {
              const ready = isRoleplayReadyForHub(script);
              const active = script.id === selected.id;
              return (
                <button
                  key={script.id}
                  type="button"
                  onClick={() => setSelectedId(script.id)}
                  style={{
                    width: "100%",
                    minHeight: 66,
                    display: "grid",
                    gridTemplateColumns: "32px 1fr auto",
                    gap: 12,
                    alignItems: "center",
                    padding: "12px 12px",
                    borderRadius: 12,
                    border: `1px solid ${active ? accentColor : tokens.line}`,
                    background: active ? `color-mix(in srgb, ${accentColor} 8%, ${tokens.surface})` : tokens.surface,
                    color: tokens.text,
                    textAlign: "left",
                    fontFamily: "inherit",
                  }}
                >
                  <span className="mono" style={{ color: active ? accentColor : tokens.mute, fontSize: 11 }}>
                    {String(index + 1).padStart(2, "0")}
                  </span>
                  <span style={{ minWidth: 0 }}>
                    <span style={{ display: "block", fontSize: 14, lineHeight: 1.25, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{script.title}</span>
                    <span style={{ display: "block", marginTop: 4, color: tokens.mute, fontSize: 11 }}>{ready ? "AI ready" : "Script only"}</span>
                  </span>
                  <span style={{
                    width: 10,
                    height: 10,
                    borderRadius: 999,
                    background: ready ? accentColor : tokens.line,
                  }} />
                </button>
              );
            })}
          </div>
        </div>
      </div>
    </Section>
  );
}

// ─── Mastery progression: 30 / 90 / 365
function MasterySection({ role, tokens, accentColor }) {
  const stages = [
    { key: "day30", label: "Day 30", kicker: "Tactical" },
    { key: "day90", label: "Day 90", kicker: "Relational" },
    { key: "day365", label: "Day 365", kicker: "Structural" },
  ];
  return (
    <Section title="Mastery progression" tokens={tokens}>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 14 }}>
        {stages.map((s, i) => (
          <div key={s.key} style={{ background: tokens.surface, border: `1px solid ${tokens.line}`, borderRadius: 16, padding: "22px 22px 24px", display: "flex", flexDirection: "column", gap: 12 }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
              <span className="mono" style={{ color: accentColor, fontSize: 11, letterSpacing: "0.16em" }}>{s.label.toUpperCase()}</span>
              <span className="mono" style={{ color: tokens.mute, fontSize: 10 }}>{String(i + 1).padStart(2, "0")} / 03</span>
            </div>
            <div style={{ fontFamily: "Fraunces, serif", fontSize: 22, lineHeight: 1.15, color: tokens.text }}>{s.kicker}</div>
            <div style={{ height: 1, background: tokens.line, margin: "2px 0 4px" }} />
            <p style={{ fontSize: 14, lineHeight: 1.55, color: tokens.soft, margin: 0 }}>{role.mastery[s.key]}</p>
          </div>
        ))}
      </div>
    </Section>
  );
}

// ─── Weekly metrics with editable input fields
function WeeklyMetricsSection({ role, tokens, accentColor }) {
  const storeKey = `ch.metrics.${role.id}`;
  const [values, setValues] = useChairsideStored(storeKey, {});
  const update = (name, v) => setValues({ ...values, [name]: v });

  return (
    <Section title="Weekly metrics" tokens={tokens}>
      <div style={{ display: "grid", gap: 12 }}>
        {role.weeklyMetrics.map((m, i) => (
          <div key={i} style={{ background: tokens.surface, border: `1px solid ${tokens.line}`, borderRadius: 14, padding: "18px 22px" }}>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 200px", gap: 24, alignItems: "center" }}>
              <div>
                <div style={{ fontFamily: "Fraunces, serif", fontSize: 19, color: tokens.text, marginBottom: 4 }}>{m.name}</div>
                <div style={{ fontSize: 13, color: tokens.soft, lineHeight: 1.5 }}>{m.definition}</div>
              </div>
              <div>
                <label className="mono" style={{ color: tokens.mute, fontSize: 10, letterSpacing: "0.14em", display: "block", marginBottom: 6 }}>THIS WEEK</label>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <input
                    type="number"
                    inputMode="decimal"
                    placeholder="—"
                    value={values[m.name] ?? ""}
                    onChange={(e) => update(m.name, e.target.value)}
                    style={{ flex: 1, padding: "10px 12px" }}
                  />
                  <span className="mono" style={{ color: tokens.mute, fontSize: 12 }}>{m.unit || ""}</span>
                </div>
              </div>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16, marginTop: 14, paddingTop: 14, borderTop: `1px solid ${tokens.line}` }}>
              <div>
                <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 4 }}>BASELINE</div>
                <div style={{ fontSize: 13, color: tokens.soft, lineHeight: 1.5 }}>{m.baseline}</div>
              </div>
              <div>
                <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 4 }}>CADENCE</div>
                <div style={{ fontSize: 13, color: tokens.soft, lineHeight: 1.5 }}>{m.cadence}</div>
              </div>
              <div>
                <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 4 }}>OFF-TRACK</div>
                <div style={{ fontSize: 13, color: tokens.soft, lineHeight: 1.5 }}>{m.offTrack}</div>
              </div>
            </div>
          </div>
        ))}
      </div>
    </Section>
  );
}

// ─── Scripts library — accordion of verbatim scripts
function ScriptsLibrary({ role, tokens, accentColor, pendingScriptId, setPendingScriptId }) {
  const [openId, setOpenId] = useState(null);
  useEffect(() => {
    if (pendingScriptId && role.scripts.some(s => s.id === pendingScriptId)) {
      setOpenId(pendingScriptId);
      // scroll once mounted
      setTimeout(() => {
        const el = document.getElementById(`script-${pendingScriptId}`);
        if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
        setPendingScriptId && setPendingScriptId(null);
      }, 80);
    }
  }, [pendingScriptId, role.id]);
  return (
    <Section title="Scripts library" tokens={tokens}>
      <div style={{ borderTop: `1px solid ${tokens.line}` }}>
        {role.scripts.map((s, i) => {
          const open = openId === s.id;
          return (
            <article key={s.id} id={`script-${s.id}`} style={{ borderBottom: `1px solid ${tokens.line}`, scrollMarginTop: 80 }}>
              <button onClick={() => setOpenId(open ? null : s.id)}
                style={{ width: "100%", textAlign: "left", padding: "20px 0", background: "transparent", border: "none", display: "flex", alignItems: "flex-start", gap: 18, color: tokens.text }}>
                <span className="mono" style={{ color: accentColor, minWidth: 36, fontSize: 11, paddingTop: 6 }}>{String(i + 1).padStart(2, "0")}</span>
                <span style={{ flex: 1, fontFamily: "Fraunces, serif", fontSize: 21, lineHeight: 1.25 }}>{s.title}</span>
                <span style={{ fontSize: 22, color: tokens.mute, transition: "transform .2s", transform: open ? "rotate(45deg)" : "none", paddingTop: 2 }}>+</span>
              </button>
              {open && (
                <div className="script-body-open" style={{ paddingTop: 4, paddingRight: 0, paddingBottom: 32 }}>
                  <PatternChipRow patternIds={s.patternsUsed} tokens={tokens} accentColor={accentColor} />
                  {window.RoleplayLauncher && (
                    <div style={{ marginTop: 4, marginBottom: 20, display: "flex", justifyContent: "flex-end" }}>
                      <window.RoleplayLauncher
                        script={s}
                        tokens={tokens}
                        accent={{ c: accentColor, c2: accentColor, soft: `color-mix(in srgb, ${accentColor} 18%, ${tokens.surface})` }}
                      />
                    </div>
                  )}
                  <ScriptBlock label="Trigger" body={s.trigger} tokens={tokens} accentColor={accentColor} />
                  <ScriptBlock label="Setup" body={s.setup} tokens={tokens} accentColor={accentColor} />
                  <div style={{ margin: "18px 0" }}>
                    <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 8 }}>OPENING ({role.id.toUpperCase()})</div>
                    <div style={{ background: tokens.surface, border: `1px solid ${tokens.line}`, borderLeft: `3px solid ${accentColor}`, borderRadius: 12, padding: "16px 20px", fontFamily: "Fraunces, serif", fontSize: 16, lineHeight: 1.55, color: tokens.text }}>
                      "{s.opening}"
                    </div>
                  </div>
                  {Array.isArray(s.patientResponses) && s.patientResponses.length > 0 && (
                    <CollapsibleSubsection
                      label="PATIENT RESPONSES & REPLIES"
                      tokens={tokens}
                      accentColor={accentColor}
                      defaultOpen={false}
                    >
                      <div style={{ display: "grid", gap: 10 }}>
                        {s.patientResponses.map((pr, j) => (
                          <div key={j} style={{ background: tokens.surface, border: `1px solid ${tokens.line}`, borderRadius: 12, overflow: "hidden" }}>
                            <div style={{ padding: "14px 18px", background: tokens.surface2, borderBottom: `1px solid ${tokens.line}` }}>
                              <div className="mono" style={{ color: tokens.mute, fontSize: 10, letterSpacing: "0.14em", marginBottom: 6 }}>PATIENT</div>
                              <div style={{ fontStyle: "italic", fontSize: 14, color: tokens.text, lineHeight: 1.5 }}>"{pr.response}"</div>
                            </div>
                            <div style={{ padding: "14px 18px" }}>
                              <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 6 }}>{role.id.toUpperCase()} REPLY</div>
                              <div style={{ fontSize: 14, color: tokens.text, lineHeight: 1.55, fontFamily: "Fraunces, serif" }}>"{pr.reply}"</div>
                            </div>
                          </div>
                        ))}
                      </div>
                    </CollapsibleSubsection>
                  )}
                  <ScriptBlock label="Exit" body={s.exit} tokens={tokens} accentColor={accentColor} marginTop={20} />
                  <ScriptBlock label="Recovery" body={s.recovery} tokens={tokens} accentColor={accentColor} />
                  <div style={{ marginTop: 18, padding: "14px 18px", background: tokens.surface2, borderRadius: 12, border: `1px dashed ${tokens.line}` }}>
                    <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 6 }}>PRAXIA LINK ↗</div>
                    <PraxiaLessonLink lessonId={s.praxiaLessonId} tokens={tokens} accentColor={accentColor}>
                      <span style={{ fontSize: 13, color: tokens.soft, lineHeight: 1.55 }}>{s.praxiaLink}</span>
                    </PraxiaLessonLink>
                  </div>
                  {s.leadHcId && (
                    <div style={{ marginTop: 12, padding: "14px 18px", background: tokens.surface2, borderRadius: 12, border: `1px dashed ${tokens.line}` }}>
                      <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 6 }}>CHAIRSIDE: LEAD ↗</div>
                      <LeadHcLink hcId={s.leadHcId} tokens={tokens} accentColor={accentColor}>
                        <span style={{ fontSize: 13, color: tokens.soft, lineHeight: 1.55 }}>{s.leadLink || `See the in-the-room script in Chairside: Lead — Hard Conversation ${s.leadHcId.replace(/^hc/, '')}.`}</span>
                      </LeadHcLink>
                    </div>
                  )}
                </div>
              )}
            </article>
          );
        })}
      </div>
    </Section>
  );
}

function ScriptBlock({ label, body, tokens, accentColor, marginTop }) {
  return (
    <div style={{ marginTop: marginTop || 14 }}>
      <div className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em", marginBottom: 6 }}>{label.toUpperCase()}</div>
      <div style={{ fontSize: 14, color: tokens.soft, lineHeight: 1.6 }}>{body}</div>
    </div>
  );
}

// Collapsible labelled subsection — used inside open script accordions for
// reference material that pushes the primary action down when always-on.
function CollapsibleSubsection({ label, tokens, accentColor, children, defaultOpen = false }) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <div style={{ marginTop: 18 }}>
      <button
        type="button"
        onClick={() => setOpen(o => !o)}
        aria-expanded={open}
        style={{
          width: "100%",
          background: "transparent",
          border: "none",
          padding: 0,
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          cursor: "pointer",
          color: tokens.text,
        }}
      >
        <span className="mono" style={{ color: accentColor, fontSize: 10, letterSpacing: "0.14em" }}>
          {label}
        </span>
        <span
          aria-hidden="true"
          className="accordion-plus"
          style={{
            color: tokens.mute,
            transition: "transform .2s",
            transform: open ? "rotate(45deg)" : "none",
            lineHeight: 1,
          }}
        >
          +
        </span>
      </button>
      {open && <div style={{ marginTop: 10 }}>{children}</div>}
    </div>
  );
}

// ─── Phase: Language Patterns chip row — appears at the top of an open
// script accordion body when the script has `patternsUsed`. Each chip is a
// real cross-page link to the Language Patterns library, since the suite is
// a multi-HTML-shell app, not an SPA. Empty / missing `patternsUsed` renders
// nothing (silent absence, per spec).
function PatternChipRow({ patternIds, tokens, accentColor }) {
  if (!Array.isArray(patternIds) || patternIds.length === 0) return null;
  const lib = (typeof window !== "undefined" && window.LANGUAGE_PATTERNS) || [];
  const found = patternIds
    .map(id => lib.find(p => p.id === id))
    .filter(Boolean);
  if (found.length === 0) return null;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap", marginBottom: 14 }}>
      <span className="mono" style={{ color: tokens.mute, fontSize: 10, letterSpacing: "0.14em" }}>
        Patterns in this script:
      </span>
      {found.map(p => (
        <a
          key={p.id}
          href={`Chairside-Language-Patterns.html#${encodeURIComponent(p.id)}`}
          className="pattern-chip"
          style={{
            borderRadius: 999,
            border: `1px solid ${tokens.line}`,
            background: tokens.surface,
            color: tokens.soft,
            fontSize: 12,
            textDecoration: "none",
            whiteSpace: "nowrap",
            transition: "all .15s ease",
          }}
          onMouseEnter={e => { e.currentTarget.style.borderColor = accentColor; e.currentTarget.style.color = accentColor; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = tokens.line; e.currentTarget.style.color = tokens.soft; }}
        >
          {p.name}
        </a>
      ))}
    </div>
  );
}

// ─── Failure modes — accordion
function FailureModesSection({ role, tokens, accentColor }) {
  const [openIdx, setOpenIdx] = useState(null);
  return (
    <Section title="Failure modes" tokens={tokens}>
      <div style={{ borderTop: `1px solid ${tokens.line}` }}>
        {role.failureModes.map((fm, i) => {
          const open = openIdx === i;
          return (
            <article key={i} style={{ borderBottom: `1px solid ${tokens.line}` }}>
              <button onClick={() => setOpenIdx(open ? null : i)}
                style={{ width: "100%", textAlign: "left", padding: "18px 0", background: "transparent", border: "none", display: "flex", alignItems: "center", gap: 18, color: tokens.text }}>
                <span className="mono" style={{ color: accentColor, minWidth: 36, fontSize: 11 }}>{String(i + 1).padStart(2, "0")}</span>
                <span style={{ flex: 1, fontFamily: "Fraunces, serif", fontSize: 20 }}>{fm.name}</span>
                <span style={{ fontSize: 22, color: tokens.mute, transition: "transform .2s", transform: open ? "rotate(45deg)" : "none" }}>+</span>
              </button>
              {open && (
                <div style={{ padding: "4px 0 28px 54px", display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24 }}>
                  <div>
                    <div className="mono" style={{ color: "#B5536A", fontSize: 10, letterSpacing: "0.14em", marginBottom: 8 }}>SIGNATURE</div>
                    <div style={{ fontSize: 14, color: tokens.text, lineHeight: 1.6 }}>{fm.signature}</div>
                  </div>
                  <div>
                    <div className="mono" style={{ color: "#2A8A88", fontSize: 10, letterSpacing: "0.14em", marginBottom: 8 }}>RECOVERY</div>
                    <div style={{ fontSize: 14, color: tokens.text, lineHeight: 1.6 }}>{fm.recovery}</div>
                  </div>
                </div>
              )}
            </article>
          );
        })}
      </div>
    </Section>
  );
}

// ─── Praxia bridge — footer card with cross-links
function PraxiaBridgeSection({ role, tokens, accentColor }) {
  return (
    <Section title="Praxia bridge" tokens={tokens}>
      <div style={{ background: "#1A1612", color: "#F5EDDC", borderRadius: 18, padding: "32px 32px 24px" }}>
        <div className="mono" style={{ color: "#E89A4A", fontSize: 11, letterSpacing: "0.16em", marginBottom: 10 }}>THE CROSS-REFERENCE</div>
        <div style={{ fontFamily: "Fraunces, serif", fontSize: 22, lineHeight: 1.3, marginBottom: 22, color: "#F5EDDC", maxWidth: 640 }}>
          The lessons in Praxia that power this role. Each link opens the underlying mental model.
        </div>
        <div style={{ display: "grid", gap: 0 }}>
          {role.praxiaBridge.map((p, i) => (
            <div key={p.lessonId} style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 24, padding: "16px 0", borderTop: i === 0 ? "none" : `1px solid #3A3329` }}>
              <div>
                <PraxiaLessonLink lessonId={p.lessonId} tokens={tokens} accentColor="#E89A4A" dark>
                  <span className="mono" style={{ color: "#E89A4A", fontSize: 12, letterSpacing: "0.12em" }}>{p.lessonId.toUpperCase()} ↗</span>
                </PraxiaLessonLink>
                <div style={{ fontFamily: "Fraunces, serif", fontSize: 16, color: "#F5EDDC", marginTop: 4 }}>{p.lessonTitle}</div>
              </div>
              <div style={{ fontSize: 14, color: "#B5AB95", lineHeight: 1.6 }}>{p.relevance}</div>
            </div>
          ))}
        </div>
      </div>
    </Section>
  );
}

// ─── Praxia cross-link — opens Praxia.html with a deep-link hash
function PraxiaLessonLink({ lessonId, children, tokens, accentColor, dark }) {
  const href = `Praxia.html#lesson=${encodeURIComponent(lessonId)}`;
  return (
    <a href={href} target="_blank" rel="noopener" style={{ color: "inherit", textDecoration: "none", display: "inline-block" }}
      onMouseOver={(e) => e.currentTarget.style.opacity = 0.7}
      onMouseOut={(e) => e.currentTarget.style.opacity = 1}>
      {children}
    </a>
  );
}

// ─── Chairside: Lead cross-link — opens Lead.html with a deep-link hash to a specific hard conversation
function LeadHcLink({ hcId, children, tokens, accentColor }) {
  const href = `Chairside-Lead.html#hc=${encodeURIComponent(hcId)}`;
  return (
    <a href={href} target="_blank" rel="noopener" style={{ color: "inherit", textDecoration: "none", display: "inline-block" }}
      onMouseOver={(e) => e.currentTarget.style.opacity = 0.7}
      onMouseOut={(e) => e.currentTarget.style.opacity = 1}>
      {children}
    </a>
  );
}

// ============================================================
// ROUTING — single file, four products via ?app= query
// ============================================================
function CompanionsRoot() {
  const params = new URLSearchParams(location.search);
  const app = params.get("app") || "roles";
  const tokens = CHAIRSIDE_THEMES.warm;
  const accent = CHAIRSIDE_ACCENTS.amber;

  if (app === "handoffs") return <><ChairsideStyle tokens={tokens} accent={accent} /><HandoffsApp tokens={tokens} accent={accent} /></>;
  if (app === "difficult") return <><ChairsideStyle tokens={tokens} accent={accent} /><DifficultApp tokens={tokens} accent={accent} /></>;
  if (app === "friday") return <><ChairsideStyle tokens={tokens} accent={accent} /><FridayApp tokens={tokens} accent={accent} /></>;
  return <><ChairsideStyle tokens={tokens} accent={accent} /><RolesApp tokens={tokens} accent={accent} /></>;
}

Object.assign(window, { CompanionsRoot });
