/* ============================================================
   act3.jsx — Act 3 · 深度（掀开盖子）高保真重写
   五屏：智能路由 / 评测飞轮 / Runner 经济 / 无 session / 估值
   （团队“凭什么是我们”已合并到 team.jsx，紧接第一幕之后）
   设计：Apple 发布页质感 · Liquid Glass · tier 色板 · 真实 logo
   ============================================================ */
window.AP_SCREENS = window.AP_SCREENS || [];

/* ============================================================
   屏 1 · 智能路由，不是模型路由
   ============================================================ */
/* 任务被识别出的四维信号 —— 智能路由的「权衡维度」。
   每一维用「与它语义匹配」的量度，而不是统一的进度条：
   难度=阶梯条 · 隐私=公开/内部/敏感分级 · 成本=预算档（¥）· 质量=星级。 */
const SIGNALS = [
  { key: "diff", label: "难度", level: "偏高", meter: "steps",   val: 4, max: 5,                       note: "多步调研 + 交叉验证" },
  { key: "priv", label: "隐私", level: "敏感", meter: "privacy", val: 2, scale: ["公开", "内部", "敏感"], note: "涉及私有财务数据" },
  { key: "cost", label: "成本", level: "敏感", meter: "cost",    val: 1, max: 3,                       note: "预算有限，够用即可" },
  { key: "qual", label: "质量", level: "中等", meter: "stars",   val: 3, max: 5,                       note: "可自动校验" },
];

/* 路由 icon：中枢 → 分发（share / route 意象）—— Lucide Share2 */
function RouteIcon() {
  return <Icon name="Share2" />;
}

/* 四维量度：每一维一种贴合语义的可视形式（取代统一进度条）。判定后逐格点亮（.in 触发）。 */
function SigMeter({ s }) {
  if (s.meter === "steps") {
    return (
      <div className="sm sm-steps" aria-hidden="true">
        {Array.from({ length: s.max }).map((_, i) => (
          <i key={i} className={"smg" + (i < s.val ? " on" : "")}
             style={{ height: (38 + i * 15) + "%", "--d": (i * 0.06) + "s" }} />
        ))}
      </div>
    );
  }
  if (s.meter === "privacy") {
    return (
      <div className="sm sm-priv" aria-hidden="true">
        {s.scale.map((seg, i) => (
          <span key={seg} className={"smp" + (i === s.val ? " on" : (i < s.val ? " pass" : ""))}>
            {i === s.val && <Icon name="Lock" size={10} />}
            {seg}
          </span>
        ))}
      </div>
    );
  }
  if (s.meter === "cost") {
    return (
      <div className="sm sm-cost" aria-hidden="true">
        <span className="sm-pips">
          {Array.from({ length: s.max }).map((_, i) => (
            <em key={i} className={"smc" + (i < s.val ? " on" : "")} style={{ "--d": (i * 0.07) + "s" }}>¥</em>
          ))}
        </span>
        <span className="sm-cap">够用即可</span>
      </div>
    );
  }
  /* stars */
  return (
    <div className="sm sm-star" aria-hidden="true">
      {Array.from({ length: s.max }).map((_, i) => (
        <span key={i} className={"sms" + (i < s.val ? " on" : "")} style={{ "--d": (i * 0.07) + "s" }}>
          <Icon name="Star" size={13} />
        </span>
      ))}
    </div>
  );
}
/* 多个备选 Agent —— 据四维权衡，选中最匹配的「那一个」，其余排除。
   每个 Agent 自带模型 + 工具 + harness，被选中后端到端独立交付。 */
const CANDIDATES = [
  { id: "remote",   brand: "claude",     name: "远端 · 通用旗舰",  note: "能力最强、最通用 —— 但私有财务要外传、单价偏高", fit: false, verdict: "隐私 / 成本 不符" },
  { id: "local",    brand: "localagent", name: "本机 · Agent",     note: "私有数据留在本机，接本地高性价比模型，够用且能自动校验", fit: true,  verdict: "四维最匹配" },
  { id: "vertical", brand: "rogo",       name: "云端 · 垂直 Agent", note: "够不到你本机的私有财务文件", fit: false, verdict: "隐私 不符" },
];
const SEL = CANDIDATES.findIndex((c) => c.fit);   // 被选中的那个

function RouteVsModel({ active, bus }) {
  /* 逐步揭示：1-4 逐一判定四维信号 · 5 核心权衡 · 6 备选 Agent 出现 ·
     7 评估排除不匹配 · 8 选中最匹配 · 9 由它端到端执行 · 10 结论 */
  const [step, setStep] = useState(0);
  const scrub = useScrub({
    bus,
    duration: 3050,
    reset: () => { setStep(0); bus.setReady(false); bus.setHint(null); },
    /* 阅读节奏：四维信号是「逐条判定」的信息拍(~260ms)，权衡处稍停让人消化，
       然后备选出现→排除→选中是主线，最后落结论。点击=整段连播；方向键逐拍。 */
    frames: [
      { t: 160,  label: "难度",     apply: () => setStep(1) },
      { t: 420,  label: "隐私",     apply: () => setStep(2) },
      { t: 680,  label: "成本",     apply: () => setStep(3) },
      { t: 940,  label: "质量",     apply: () => setStep(4) },
      { t: 1280, label: "权衡",     stop: true, apply: () => setStep(5) },
      { t: 1650, label: "备选 Agent", apply: () => setStep(6) },
      { t: 2050, label: "逐一评估",   apply: () => setStep(7) },
      { t: 2450, label: "选中",       apply: () => setStep(8) },
      { t: 2750, label: "端到端执行", apply: () => setStep(9) },
      { t: 3000, label: "完成",     apply: () => { setStep(10); bus.setReady(true);
        bus.setHint("难度 · 隐私 · 成本 · 质量 —— 智能路由在多个 Agent 间选中最匹配的一个，由它端到端交付"); } },
    ],
  });

  /* 测量节点中心，把连线锚到真实位置（沿用第一/二幕的连线手法） */
  const worldRef = useRef(null);
  const nodeRefs = useRef({});
  const setNode = (id) => (el) => { if (el) nodeRefs.current[id] = el; };
  const [pts, setPts] = useState({});
  const [box, setBox] = useState({ w: 0, h: 0 });
  useLayoutEffect(() => {
    const measure = () => {
      const wb = worldRef.current && worldRef.current.getBoundingClientRect();
      if (!wb) return;
      const next = {};
      Object.keys(nodeRefs.current).forEach((id) => {
        const el = nodeRefs.current[id]; if (!el || !el.isConnected) return;
        const r = el.getBoundingClientRect();
        next[id] = {
          cy: r.top + r.height / 2 - wb.top,
          left: r.left - wb.left,
          right: r.right - wb.left,
        };
      });
      setPts(next); setBox({ w: wb.width, h: wb.height });
    };
    measure();
    const t = setTimeout(measure, 120);
    window.addEventListener("resize", measure);
    return () => { clearTimeout(t); window.removeEventListener("resize", measure); };
  }, [active]);

  /* 进屏即自动整段播放（四维判定 → 选中 → 端到端），无需手动推进；等转场动画结束再 play，
     避免与 .screen.enter 的 blur/scale 转场叠加导致连线滤镜抖动。倒着进来已由 useScrub 跳末态。 */
  const playRef = useRef(scrub.play);
  playRef.current = scrub.play;
  useEffect(() => {
    if (bus && bus.entryDir === "back") return;
    const t = setTimeout(() => playRef.current(), 280);
    return () => clearTimeout(t);
  }, []);

  const hub = pts.hub;

  return (
    <div className={"rvm reng s" + step}>
      <Scrubber ctl={scrub} label="智能路由 · 逐维判定 → 选中最匹配的 Agent" />
      <div className="rvm-head fade-up">
        <h2 className="h-title">智能路由，不是模型路由</h2>
        <p className="rvm-sub">读懂任务的<b>难度 · 隐私 · 成本 · 质量</b>，从多个备选 Agent 里选中最匹配的<b>那一个</b> —— 由它端到端交付</p>
      </div>

      <div className="reng-stage" ref={worldRef}>
        {/* 连线层：信号逐条汇入核心（柔光小点沿线流动）→ 核心只连向被选中的 Agent */}
        <svg className="reng-wires" width={Math.max(1, box.w)} height={Math.max(1, box.h)}>
          {hub && SIGNALS.map((s, i) => {
            const p = pts["sig" + i]; if (!p) return null;
            const on = step >= i + 1;
            const d = wirePath(p.right, p.cy, hub.left, hub.cy);
            return (
              <g key={"c" + i} className={"rw" + (on ? " in" : "")}>
                <path id={"rw-c" + i} className="rw-line" d={d} pathLength="1" />
                {on && (
                  <circle className="rw-dot" r="3.2">
                    <animateMotion dur="2.2s" begin={(-0.35 * i) + "s"} repeatCount="indefinite"
                                   keyPoints="0;1" keyTimes="0;1" calcMode="linear">
                      <mpath href={"#rw-c" + i} />
                    </animateMotion>
                  </circle>
                )}
              </g>
            );
          })}
          {/* 只向被选中的那个 Agent 连一条线（step≥8 选中后出现）—— 路由是「选一个」，不是分发多路 */}
          {hub && step >= 8 && pts["ch" + SEL] && (() => {
            const p = pts["ch" + SEL];
            const d = wirePath(hub.right, hub.cy, p.left, p.cy);
            return (
              <g className="rw in">
                <path id="rw-selected-agent" className="rw-line" d={d} pathLength="1" />
                <circle className="rw-dot" r="3.2">
                  <animateMotion dur="2.2s" repeatCount="indefinite"
                                 keyPoints="0;1" keyTimes="0;1" calcMode="linear">
                    <mpath href="#rw-selected-agent" />
                  </animateMotion>
                </circle>
              </g>
            );
          })()}
        </svg>

        {/* ① 四维信号 —— 逐条判定（量表填充 + 判定词） */}
        <div className="reng-col left">
          {SIGNALS.map((s, i) => (
            <div key={s.key} ref={setNode("sig" + i)}
                 className={"sig-node sig-" + s.key + (step >= i + 1 ? " in" : "")}>
              <span className="sig-top">
                <b>{s.label}</b>
                <em className="sig-verdict">{step >= i + 1 ? s.level : "判定中…"}</em>
              </span>
              <SigMeter s={s} />
              <span className="sig-note">{s.note}</span>
            </div>
          ))}
        </div>

        {/* 核心：智能路由（真实 icon） */}
        <div className="reng-mid">
          <div ref={setNode("hub")}
               className={"reng-hub" + (step >= 1 ? " live" : "") + (step >= 5 ? " decide" : "")}>
            <span className="rh-ring" />
            <span className="rh-icon"><RouteIcon /></span>
            <span className="rh-title">智能路由</span>
            <span className="rh-sub">四维权衡 · 选定 Agent</span>
          </div>
        </div>

        {/* ② 多个备选 Agent —— 出现 → 排除不匹配 → 选中那一个 */}
        <div className="reng-col right cand-col">
          {CANDIDATES.map((c, i) => {
            const shown = step >= 6;
            const evaluated = step >= 7;
            const selected = c.fit && step >= 8;
            const rejected = !c.fit && evaluated;
            return (
              <div key={c.id} ref={setNode("ch" + i)}
                   className={"cand-node" + (shown ? " in" : "") + (selected ? " sel" : "") + (rejected ? " rej" : "")}>
                <span className="cnd-ic"><BrandTile brand={c.brand} size={30} radius={9} soft={false} /></span>
                <div className="cnd-body">
                  <span className="cnd-name">{c.name}</span>
                  <span className="cnd-note">
                    {selected && step >= 9 ? "选中 —— 由它端到端执行：多次检索 · 交叉验证 · 写入" : c.note}
                  </span>
                </div>
                <span className="cnd-flag">
                  {selected ? "✓ 选中" : rejected ? <em className="cnd-x">✕ {c.verdict}</em> : (evaluated ? "评估中…" : "")}
                </span>
              </div>
            );
          })}
        </div>
      </div>

      <p className={"reng-foot" + (step >= 10 ? " in" : "")}>
        OpenRouter 在一堆<b>模型</b>里替你挑一个；我们在一堆 <b>Agent</b> 之间选中最匹配的<b>那一个</b> —— 它自带模型、工具与 harness，端到端把活干完。<b>选得准</b>，才是智能路由真正难的地方。
      </p>
    </div>
  );
}

/* ============================================================
   屏 2 · 任务越多，路由越准（评测飞轮）
   左：真实任务流，持续涌入、向下渐隐
   右：任务累积量 × 路由满意度 的右肩上升曲线
   ============================================================ */
/* 路由的目标不只是「模型」，更是不同的 Agent —— 本机用户接入的 Cursor / Codex / Claude Code 等，
   也可能是本机部署的大模型，或远端通用模型、人机协同。logo 用各 Agent / 模型的真实标识。 */
const STREAM = [
  { type: "写代码",   priv: "低敏", path: "本机 · Cursor",      brand: "cursor",     pass: true },
  { type: "修复缺陷", priv: "低敏", path: "本机 · Codex",       brand: "codex",      pass: true },
  { type: "重构模块", priv: "私有", path: "本机 · Claude Code", brand: "claudecode", pass: true },
  { type: "补单测",   priv: "低敏", path: "本机 · Windsurf",    brand: "windsurf",   pass: true },
  { type: "调研竞品", priv: "私有", path: "本机 · Ollama 部署", brand: "ollama",     pass: true },
  { type: "财务对账", priv: "私有", path: "本机 · 部署大模型",  brand: "deepseek",   pass: true },
  { type: "做周报",   priv: "低敏", path: "远端 · 通用模型",    brand: "gemini",     pass: false },
  { type: "排期协调", priv: "低敏", path: "人机 · 协同把关",    brand: "slack",      pass: true },
];
/* 累积量 → 路由满意度（冷启动 72% → 规模化 98%） */
const CURVE = [
  { n: "10",    acc: 72 },
  { n: "100",   acc: 81 },
  { n: "1K",    acc: 88 },
  { n: "1万",   acc: 93 },
  { n: "10万",  acc: 96 },
  { n: "100万", acc: 98 },
];
/* 图表几何 */
const CW = 400, CH = 248, PADL = 30, PADR = 18, PADT = 20, PADB = 30;
const PLW = CW - PADL - PADR, PLH = CH - PADT - PADB;
const A_MIN = 66, A_MAX = 99;
const xOf = (i) => PADL + (PLW * i) / (CURVE.length - 1);
const yOf = (a) => PADT + PLH * (1 - (a - A_MIN) / (A_MAX - A_MIN));
const LINE_PTS = CURVE.map((d, i) => xOf(i) + "," + yOf(d.acc).toFixed(1));
const LINE_D = "M" + LINE_PTS.join(" L");
const AREA_D = "M" + xOf(0) + "," + (PADT + PLH) + " L" + LINE_PTS.join(" L") + " L" + xOf(CURVE.length - 1) + "," + (PADT + PLH) + " Z";
/* 实线=已发生（冷启动→当前规模），虚线=规模化预测段（末两点）。
   在 PRED_FROM 处把曲线一分为二：前段实线 draw-on，后段虚线表「预测」语义 */
const PRED_FROM = 4;
const SOLID_D = "M" + LINE_PTS.slice(0, PRED_FROM + 1).join(" L");
const PRED_D = "M" + LINE_PTS.slice(PRED_FROM).join(" L");
const GRID = [70, 80, 90];

function EvalFlywheel({ active, bus }) {
  const [step, setStep] = useState(0);          // 已揭示的曲线点数

  const N = CURVE.length;
  const acc = step >= 1 ? CURVE[step - 1].acc : A_MIN + 6;
  const accN = step >= 1 ? CURVE[step - 1].n : "0";
  /* 实线段只覆盖「已发生」的 0..PRED_FROM；其后为虚线预测段 */
  const solidProgress = PRED_FROM > 0 ? Math.min(1, Math.max(0, (step - 1)) / PRED_FROM) : 0;  // 0..1 实线已画比例
  const progress = N > 1 ? Math.max(0, (step - 1)) / (N - 1) : 0;   // 0..1 整条已揭示比例
  /* 画线用 SVG 归一化 pathLength="1"（与全站 .rw-line 一致），不再靠 getTotalLength 量长。
     之前进场时父层 .screen.enter 带 transform:scale + filter:blur 动画，会让 getTotalLength
     量出错误的长度 → strokeDasharray 与真实路径长不符 → 虚线图案平铺/错位，于是线条「疯狂上下抽动」。
     归一化后 dasharray 恒为 1，dashoffset 从 1(全隐)→0(全画)，彻底消除测量误差这一抖动来源。 */
  const dashOff = 1 - solidProgress;
  const areaIn = solidProgress >= 0.6;                              // 线画到 ~60% 才淡入面积
  const predIn = step - 1 > PRED_FROM;                              // 实线画完后才显预测虚线
  // 任务流揭示：跟随步进，比曲线快一截，铺满后底部出现「持续涌入」
  const rowsShown = step === 0 ? 0 : Math.min(STREAM.length, 2 + step);

  const LEAD = 160;   // 进场后多快画出第一个曲线点（原 520，留白太久）
  const scrub = useScrub({
    bus,
    duration: LEAD + N * 520 + 600,
    reset: () => { setStep(0); bus.setReady(false); bus.setHint(null); },
    frames: [
      ...CURVE.map((d, i) => ({
        t: LEAD + i * 520, label: d.n + " 条", apply: () => setStep(i + 1),
      })),
      { t: LEAD + N * 520 + 300, label: "完成", apply: () => { bus.setReady(true); bus.setHint("任务越多，路由越准 — 这是别人拿不到的护城河"); } },
    ],
  });

  /* 进场即播放，不再延迟 —— 旧版延后 600ms 是为绕开当年「整屏 blur+scale 转场」叠加
     曲线 drop-shadow 滤镜导致的逐帧重栅格化抽搐；如今 .screen.enter 已改为纯 10px 位移
     （无 filter），曲线也改用归一化 pathLength（不再 getTotalLength 量长），两个抖动根因都没了。
     于是进屏立刻 play()，配合 LEAD=160 让首点很快画出，消除「进来要等一会才动」的留白。
     倒着进来已被 useScrub 直接跳到末态，这里不重复触发。 */
  const playRef = useRef(scrub.play);
  playRef.current = scrub.play;
  useEffect(() => {
    if (bus && bus.entryDir === "back") return;
    const raf = requestAnimationFrame(() => playRef.current());
    return () => cancelAnimationFrame(raf);
  }, []);

  return (
    <div className="flywheel">
      <Scrubber ctl={scrub} label="评测飞轮 · 累积量 × 满意度" />
      <div className="fw-head fade-up">
        <h2 className="h-title">任务越多，路由越准</h2>
      </div>

      <div className="fw-main">
        {/* 左：真实任务流 */}
        <div className="fw-stream">
          <div className="fws-cap"><span>真实任务持续涌入</span><em>每条都记录：路径 · 验收</em></div>
          <div className="fws-list">
            {STREAM.map((r, i) => {
              const on = i < rowsShown;
              const depth = Math.max(0, i - 2);                 // 前 3 条清晰，之后渐隐
              return (
                <div key={i} className={"fws-row" + (on ? " in" : "")}
                  style={{ transitionDelay: (i % 4) * 0.04 + "s", filter: `blur(${depth * 0.7}px)`, opacity: on ? Math.max(0.18, 1 - depth * 0.16) : 0 }}>
                  <BrandTile brand={r.brand} size={26} radius={8} soft={false} />
                  <span className="fws-type">{r.type}</span>
                  <Tag tone={r.priv === "私有" ? "amber" : "blue"}>{r.priv}</Tag>
                  <em className="fws-path mono">{r.path}</em>
                  <span className={r.pass ? "fws-pass" : "fws-fail"}>{r.pass ? "通过" : "淘汰"}</span>
                </div>
              );
            })}
            <div className={"fws-more" + (rowsShown >= STREAM.length ? " on" : "")}>
              <span className="fws-more-dots"><i /><i /><i /></span>更多任务持续涌入…
            </div>
          </div>
        </div>

        {/* 右：累积量 × 满意度 曲线 */}
        <div className="fw-chart glass">
          <div className="fwc-top">
            <div className="fwc-read">
              <span className="fwc-num mono">{acc}<i>%</i></span>
              <span className="fwc-cap">路由满意度</span>
            </div>
            <div className="fwc-delta">
              <span className="mono">▲ {acc - (A_MIN + 6)}%</span>
              <em>较冷启动</em>
            </div>
          </div>
          <svg className="fwc-svg" viewBox={`0 0 ${CW} ${CH}`} preserveAspectRatio="xMidYMid meet">
            <defs>
              {/* 面积垂直渐变：accent 0.18 → 0（Vercel/Apple 克制做法） */}
              <linearGradient id="fwcArea" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stopColor="var(--ch)" stopOpacity=".18" />
                <stop offset="100%" stopColor="var(--ch)" stopOpacity="0" />
              </linearGradient>
            </defs>
            {/* 网格 + y 标签：仅 2–3 条水平参考线、无竖网格、无外框 */}
            {GRID.map((g) => (
              <g key={g}>
                <line className="fwc-grid" x1={PADL} y1={yOf(g)} x2={CW - PADR} y2={yOf(g)} />
                <text className="fwc-ylab" x={PADL - 6} y={yOf(g) + 3} textAnchor="end">{g}</text>
              </g>
            ))}
            {/* 面积：线画到 ~60% 时才淡入 */}
            <path className="fwc-area" d={AREA_D} fill="url(#fwcArea)" style={{ opacity: areaIn ? 1 : 0 }} />
            {/* 已发生段：实线 draw-on（归一化 pathLength=1，dashoffset 1→0 生长） */}
            <path className="fwc-line" d={SOLID_D} pathLength="1"
              style={{ strokeDasharray: 1, strokeDashoffset: dashOff }} />
            {/* 规模化预测段：虚线（已发生段画完后淡入） */}
            <path className="fwc-line fwc-pred" d={PRED_D}
              style={{ opacity: predIn ? 1 : 0 }} />
            {/* 数据点 + x 标签 */}
            {CURVE.map((d, i) => {
              const shown = i < step;
              const isLast = i === step - 1;
              const isPred = i > PRED_FROM;          // 预测段数据点：空心弱化
              return (
                <g key={i} style={{ opacity: shown ? 1 : 0 }} className="fwc-pt">
                  <circle className={"fwc-dot" + (isLast ? " head" : "") + (isPred ? " pred" : "")} cx={xOf(i)} cy={yOf(d.acc)} r={isLast ? 5.5 : 3.5} />
                  <text className="fwc-xlab" x={xOf(i)} y={CH - 10} textAnchor="middle">{d.n}</text>
                </g>
              );
            })}
          </svg>
          <div className="fwc-foot">
            <span>累积真实任务 <b className="mono">{accN}</b> 条 · 横轴对数刻度</span>
            <span className="fwc-legend">
              <i className="leg-line solid" />实测
              <i className="leg-line dash" />规模化预测
            </span>
          </div>
        </div>
      </div>

      <p className="fw-line">
        沉淀的不是用户语料，是<b>真实任务的执行经验</b>。任务越多、曲线越往上 — 同一任务两条路径跑、用户选更好的那个，这就是 <b>Agent Arena</b>。
      </p>
    </div>
  );
}

/* macOS 窗口 chrome：三个低饱和中性灰「红绿灯」点 + 可选居中标题。
   纯展示，把「模拟产品界面」做成桌面浮窗（Cursor/Linear 手法），遵守单一强调色铁律。 */
function MacChrome({ title }) {
  return (
    <div className="mac-chrome">
      <span className="mac-dots"><i /><i /><i /></span>
      {title && <span className="mac-title">{title}</span>}
    </div>
  );
}

/* ============================================================
   屏 3 · 闲置 Token，安全接单（Runner 经济）
   设计：Apple Liquid Glass · 蓝色系（不用紫）· SVG icon（不用 emoji）
   两张玻璃卡（高度紧凑，左侧只露出部分接单流，知道它在变化即可）：
   左=持续接单流（每条 = 等级徽标 + 类型 + 本次用到的本机东西 Icon + 收益）；
   右=沙盒：用户选 L0–L3 授权深度，逐级把本机权限放开；
   运行中的任务展示它调用了哪些本机东西（Claude Code / Codex / 本地模型 / 网络 / 文件 / Token / Computer Use）。
   ============================================================ */
/* 线性 icon —— 直接传 Lucide 名（PascalCase） */
function RIcon({ name, size = 16 }) {
  return <Icon className="ricon" name={name || "Check"} size={size} />;
}

// 本机资源 / 工具：任务在沙盒里实际调用到的本地东西（全部 Icon 呈现）
const RN_TOOLS = {
  net:   { icon: "Globe",    name: "联网" },
  model: { icon: "Box",      name: "本地模型" },
  cc:    { icon: "Terminal", name: "Claude Code" },
  codex: { icon: "Code",     name: "Codex" },
  file:  { icon: "Folder",   name: "本地文件" },
  cpu:   { icon: "Cpu",      name: "本机 Token" },
  agent: { icon: "Bot",      name: "Computer Use" },
};
// 授权深度 L0–L3：分享者把本机权限放开到多深，逐级递进
// L0 仅文字进出 → L1 读写本地文件 → L2 文件上传下载 + 调用本机工具 → L3 完全托管（调度 Agent · 完整本机权限）
const RN_LEVELS = [
  { id: "L0", icon: "MessageSquare", name: "纯文本",  grant: "本地模型 · 仅文字进出，不碰文件" },
  { id: "L1", icon: "FolderOpen",    name: "读写文件", grant: "+ 读写本地文件 · Claude Code / Codex" },
  { id: "L2", icon: "Wrench",        name: "工具调用", grant: "+ 文件上传下载 · 调用本机工具" },
  { id: "L3", icon: "Bot",           name: "完全托管", grant: "+ 调度 Agent · 完整本机权限（Computer Use）" },
];
const RN_ACCEPT = 2;   // 分享者把授权深度设到 L2 为止（更深的 L3 自动不接）
// 任务池：lv=任务等级；uses=本次在沙盒里调用到的本机东西（仅 ≤ 所接等级）
const RN_POOL = [
  { type: "调研报告", lv: 0, cost: 0.03, uses: ["net", "model"] },
  { type: "网页摘要", lv: 0, cost: 0.04, uses: ["net", "model"] },
  { type: "文档润色", lv: 1, cost: 0.06, uses: ["file", "cc"] },
  { type: "数据整理", lv: 1, cost: 0.05, uses: ["file", "codex"] },
  { type: "代码重构", lv: 2, cost: 0.21, uses: ["cc", "file", "cpu"] },
  { type: "批量翻译", lv: 1, cost: 0.03, uses: ["model", "cpu"] },
  { type: "风险预警", lv: 2, cost: 0.08, uses: ["net", "file", "cpu"] },
  { type: "日程整理", lv: 1, cost: 0.02, uses: ["file", "cc"] },
];
const RN_BASE_EARN = 11.8;   // 今日已累计（开场即在跑，体现量大）
const RN_BASE_DONE = 248;

function RunnerEcon({ active, bus }) {
  const [feed, setFeed] = useState([]);     // 左侧已完成的接单流（最新在上）
  const [cur, setCur] = useState(null);      // 右侧沙盒里正在执行的任务
  const [earn, setEarn] = useState(RN_BASE_EARN);
  const [done, setDone] = useState(RN_BASE_DONE);
  const idRef = useRef(100);
  const poolRef = useRef(0);
  const feedRef = useRef([]);
  const curRef = useRef(null);
  const tickRef = useRef(null);

  function nextTask() {
    const t = RN_POOL[poolRef.current % RN_POOL.length];
    poolRef.current += 1;
    return { ...t, id: idRef.current++ };
  }
  // 一拍：沙盒里这条跑完 → 落到左侧接单流并计费 → 沙盒里换下一条
  function tick() {
    const finished = curRef.current;
    if (finished) {
      const next = [finished, ...feedRef.current].slice(0, 5);
      feedRef.current = next; setFeed(next);
      setEarn((e) => +(e + finished.cost).toFixed(2));
      setDone((d) => d + 1);
    }
    const fresh = nextTask();
    curRef.current = fresh; setCur(fresh);
  }

  const scrub = useScrub({
    bus,
    duration: 2200,
    autoStart: true,   // 进屏即呈现末态（接单流已在独立 interval 里跑），ready 立刻为真、单击即翻页
    reset: () => { bus.setReady(false); bus.setHint(null); },
    frames: [
      { t: 400,  label: "节点上线", apply: () => {} },
      { t: 1300, label: "沙盒接单", apply: () => {} },
      { t: 1900, label: "持续运行", apply: () => {
        bus.setReady(true);
        bus.setHint("全程跑在安全沙盒里 —— 接什么、给多少权限，你说了算"); } },
    ],
  });

  // 连续运行：随屏激活启动、离屏清理
  useEffect(() => {
    if (!active) return;
    poolRef.current = 0; idRef.current = 100;
    setEarn(RN_BASE_EARN); setDone(RN_BASE_DONE);
    // 预填左侧一截「已接」，沙盒里放一条「执行中」
    const seed = [];
    for (let i = 0; i < 4; i++) seed.unshift(nextTask());
    feedRef.current = seed; setFeed(seed);
    const first = nextTask();
    curRef.current = first; setCur(first);
    const start = setTimeout(() => { tickRef.current = setInterval(tick, 1600); }, 700);
    return () => { clearTimeout(start); if (tickRef.current) clearInterval(tickRef.current); };
  }, [active]);

  return (
    <div className="runner">
      <Scrubber ctl={scrub} label="Runner · 安全沙盒接单" />
      <div className="rn-head fade-up">
        <h2 className="h-title">闲置 Token，安全接单</h2>
        <p className="rn-sub">远端任务自动流进来，<b>全程跑在安全沙盒里</b> —— 接什么单、放开多深，<b>你说了算</b></p>
      </div>

      <div className="rn-stage">
        {/* 左卡 · 持续接单流（只露出部分）：等级徽标 + 类型 + 本次用到的本机东西 + 收益 */}
        <div className="rn-card rn-stream glass">
          <MacChrome title="实时接单" />
          <div className="rs-head">
            <div className="rs-title">实时接单中</div>
            <div className="rs-metric">
              <b className="mono">+${earn.toFixed(2)}</b>
              <em>今日 · 已接 <i className="mono">{done}</i> 单</em>
            </div>
          </div>
          <div className="rs-list">
            {feed.map((r) => (
              <div key={r.id} className="rs-row">
                <span className={"lvl-badge sm lvl-r" + r.lv}>{RN_LEVELS[r.lv].id}</span>
                <span className="rsr-type">{r.type}</span>
                <span className="rsr-tools">
                  {r.uses.map((k) => <RIcon key={k} name={RN_TOOLS[k].icon} size={13} />)}
                </span>
                <span className="rsr-earn mono">+${r.cost.toFixed(2)}</span>
              </div>
            ))}
          </div>
          <div className="rs-foot">任务持续涌入 · 越接越多</div>
        </div>

        {/* 右卡 · 沙盒：选 L0–L3 —— 你愿意承担多少风险，自己说了算 */}
        <div className="rn-card sandbox glass">
          <div className="sb-head">
            <span className="sb-shield"><RIcon name="ShieldCheck" size={15} /></span>
            <b className="sb-name">安全沙盒</b>
            <span className="sb-state">运行中</span>
          </div>

          {/* 授权深度控制条：L0–L3，分享者把本机权限逐级放开 */}
          <div className="sb-level">
            <div className="sbl-top">
              <span>授权深度<i className="sbl-hint">可随时调</i></span>
              <b className={"lvl-badge lvl-r" + RN_ACCEPT}>
                {RN_LEVELS[RN_ACCEPT].id} · {RN_LEVELS[RN_ACCEPT].name}
              </b>
            </div>
            <div className="sbl-bar">
              <span className="sbl-fill" style={{ width: (RN_ACCEPT / (RN_LEVELS.length - 1)) * 100 + "%" }} />
              <span className="sbl-thumb" style={{ left: (RN_ACCEPT / (RN_LEVELS.length - 1)) * 100 + "%" }} />
            </div>
            <div className="sbl-ticks">
              {RN_LEVELS.map((l, i) => (
                <span key={l.id} className={"sbl-tick " + (i <= RN_ACCEPT ? "on" : "off")}>
                  <span className={"lvl-badge sm " + (i <= RN_ACCEPT ? "lvl-r" + i : "lvl-off")}>{l.id}</span>
                </span>
              ))}
            </div>
            <div className="sbl-note">{RN_LEVELS[RN_ACCEPT].grant} —— <b>L3 不接</b></div>
          </div>

          {/* 沙盒内运行：当前任务等级 + 它调用了哪些本机东西（Icon）+ 确定性进度条 */}
          <div className="sb-run" key={cur ? cur.id : "none"}>
            <div className="sb-now">正在隔离运行</div>
            {cur && (
              <div className="sbr-task">
                <span className={"lvl-badge lvl-r" + cur.lv}>{RN_LEVELS[cur.lv].id}</span>
                <div className="sbr-meta"><b>{cur.type}</b><em>远端派发 · 沙盒内执行</em></div>
              </div>
            )}
            <div className="sbr-use">
              <span className="use-label">本次调用了</span>
              {cur && cur.uses.map((k) => (
                <span key={k} className="use-chip on">
                  <RIcon name={RN_TOOLS[k].icon} size={12} />{RN_TOOLS[k].name}
                </span>
              ))}
            </div>
            <div className="sbr-bar"><i /></div>
          </div>
        </div>
      </div>

      {/* 利润率：非卡片，一行说清 */}
      <p className="rn-margin fade-up">
        Token 由分享者<b>自备</b>（自带 Claude Code 额度 · 本地模型 · 自有 API 额度），
        平台几乎以<b>零边际成本</b>调度 —— 因此有<b>更高的利润率</b>。
      </p>
    </div>
  );
}

/* ============================================================
   屏 4 · 用户只知道在聊天，事就办成了
   传统 Agent 把复杂度「暴露」给用户：会话 / 上下文要自己管、
   入口要自己填 key/token/id、工具连接好几步。
   AgentPilot 把这些全吃进黑盒 —— 用户侧只剩一个输入框。
   ============================================================ */
/* BEAT 0 · 发现的问题 —— 过去「光是能用起来」要自己扛的一桌真复杂
   卡片仿照 Claude Code / Codex 等真实工具的呈现：上下文窗口使用率 + 自动压缩、
   会话靠 /resume 自己管、每个模型一套参数、每个工具一串 Key + 一轮 OAuth +
   会失效的 Token。3 列网格整齐陈列（不靠乱旋转），靠「密度 + 真实细节」体现复杂。 */
const PILE = [
  { kind: "context", pos: { x: -250, y: -40, r: -4, z: 3 } },
  { kind: "creds", t: "接入微信", fields: ["AppID", "AppSecret", "Token", "EncodingAESKey", "回调 URL"],
    reauth: "Access Token 已失效 · 需重新授权", pos: { x: -15, y: -66, r: 2, z: 6 } },
  { kind: "model", pos: { x: 235, y: -34, r: 3, z: 4 } },
  { kind: "sessions", pos: { x: -235, y: 120, r: 3, z: 2 } },
  { kind: "creds", t: "接入 Slack", fields: ["Bot Token", "Signing Secret", "OAuth Scopes", "Event 订阅 URL"],
    pos: { x: 5, y: 135, r: -3, z: 5 } },
  { kind: "oauth", t: "连接 Notion · Gmail", steps: ["OAuth 授权", "选择页面", "配置 Scopes"],
    note: "凭据每 90 天过期，重新授权", pos: { x: 245, y: 118, r: -3, z: 3 } },
];
/* 上下文窗口的分段占用（仿 Claude Code /context：系统/工具/MCP/记忆/对话） */
const CTX_SEG = [
  { k: "sys", label: "系统", w: 12 },
  { k: "tool", label: "工具", w: 17 },
  { k: "mcp", label: "MCP", w: 23 },
  { k: "mem", label: "记忆", w: 13 },
  { k: "msg", label: "对话", w: 29 },
];
/* BEAT 2 · 解决办法 —— 收进黑盒后的两类连接：工作软件一键连、聊天入口扫码连 */
const SOLVE_WORK = ["notion", "gmail", "gcal", "linear", "slack"];   // 常用工作软件 · 一键连接
const SOLVE_CHAT = ["wechat", "telegram", "feishu"];                 // 聊天入口 · 扫码即用
function NoSession({ active, bus }) {
  // 0 发现的问题（一桌配置） · 1 凝结为范式陈述 · 2 解决办法（统一窗口）
  const [phase, setPhase] = useState(0);
  const scrub = useScrub({
    bus,
    duration: 4200,
    /* phase 0 是手动触发的静态首拍：进屏即置 ready，露出「轻点屏幕任意处继续」提示，
       否则用户不知道这一桌复杂需要点一下才会收进黑盒。推进到第一拍后再撤掉提示。 */
    reset: () => { setPhase(0); bus.setReady(true); bus.setHint(null); },
    /* 三拍叙事：发现问题 → 逐一划除、凝结为范式 → 解决办法。
       键盘右键逐拍停；点击屏幕则整段连贯播完。 */
    frames: [
      { t: 1500, label: "凝结为范式", stop: true,
        apply: () => { setPhase(1); bus.setReady(false); bus.setHint(null); } },
      { t: 3300, label: "解决办法", stop: true,
        apply: () => { setPhase(2); bus.setReady(true);
          bus.setHint("一个对话框 —— 会话、上下文、授权、连接，背后全包"); } },
    ],
  });
  return (
    <div className="nosession">
      <Scrubber ctl={scrub} label="发现问题 · 凝结范式 · 解决办法" />
      <div className="ns-head fade-up">
        <h2 className="h-title">用户只知道在聊天，事就办成了</h2>
        <p className="ns-sub">过去那一整套复杂 —— 全收进黑盒，<b>用户侧只剩一个对话框</b></p>
      </div>

      <div className={"m2 ph" + phase}>
        {/* BEAT 0 · 发现的问题：一桌仿真的「能用起来之前」 */}
        <div className="m2-pile">
          <div className="pile-scatter">
            {/* 纵深幽灵卡：暗示「还有十几个，每个都这样」 */}
            <i className="pile-ghost g1" /><i className="pile-ghost g2" />
            {PILE.map((c, i) => (
              <div key={i} className={"pile-card pc-" + c.kind}
                   style={{ "--d": (i * 0.07) + "s", "--x": c.pos.x + "px", "--y": c.pos.y + "px",
                            "--r": c.pos.r + "deg", zIndex: c.pos.z }}>

                {c.kind === "context" && (
                  <>
                    <div className="pc-h">上下文窗口<span className="pc-pct">94%</span></div>
                    <div className="pc-meter">
                      {CTX_SEG.map((s) => <i key={s.k} className={"seg " + s.k} style={{ width: s.w + "%" }} />)}
                    </div>
                    <div className="pc-legend">
                      {CTX_SEG.map((s) => <span key={s.k}><i className={"dot " + s.k} />{s.label}</span>)}
                    </div>
                    <div className="pc-warn">6% 剩余 · 自动压缩将触发，更早的对话被截断丢失</div>
                  </>
                )}

                {c.kind === "sessions" && (
                  <>
                    <div className="pc-h">会话管理<span className="pc-mini">手动</span></div>
                    <div className="pc-sess">
                      {["会话 1", "会话 2", "会话 3", "会话 4", "会话 5"].map((s) => <span key={s}>{s}</span>)}
                      <span className="add"><Icon name="Plus" size={12} stroke={2.4} /> 新建</span><span className="more">+3</span>
                    </div>
                    <div className="pc-note">切换 / 恢复全靠 <code>/resume</code>，自己记着在哪一条</div>
                  </>
                )}

                {c.kind === "model" && (
                  <>
                    <div className="pc-h">模型配置<span className="pc-req">必填</span></div>
                    <div className="pc-select">模型<b>claude-sonnet-4-5</b><em><Icon name="ChevronDown" size={14} /></em></div>
                    <div className="pc-field"><span>base_url</span><i /></div>
                    <div className="pc-field"><span>API Key</span><i className="filled">••••••••••••••</i></div>
                    <div className="pc-slider"><span>temperature</span><i><b style={{ left: "68%" }} /></i><em>0.7</em></div>
                    <div className="pc-pair"><span>max_tokens · 4096</span><span>top_p · 1.0</span></div>
                  </>
                )}

                {c.kind === "creds" && (
                  <>
                    <div className="pc-h">{c.t}<span className="pc-req">必填</span></div>
                    <div className="pc-fields">
                      {c.fields.map((f) => <div key={f} className="pc-field"><span>{f}</span><i /></div>)}
                    </div>
                    {c.reauth && <div className="pc-reauth"><span className="pc-x">!</span>{c.reauth}</div>}
                  </>
                )}

                {c.kind === "oauth" && (
                  <>
                    <div className="pc-h">{c.t}<span className="pc-req">必填</span></div>
                    <div className="pc-steps">
                      {c.steps.map((s, j) => (
                        <React.Fragment key={s}>{j > 0 && <em><Icon name="ChevronRight" size={13} /></em>}<span>{s}</span></React.Fragment>
                      ))}
                    </div>
                    {c.note && <div className="pc-note">{c.note}</div>}
                  </>
                )}
              </div>
            ))}
          </div>
        </div>

        {/* BEAT 1 · 凝结为范式陈述：复杂度本就不该交给用户 */}
        <div className="m2-shift">
          <p className="ms-line">这些复杂度，<b>本就不该交给用户</b></p>
          <p className="ms-sub">会话、上下文、授权、连接 —— 全部收进黑盒</p>
        </div>

        {/* BEAT 2 · 解决办法：一个统一对话框（背后全包） */}
        <div className="m2-solve">
          <div className="sv-conns">
            <div className="sv-cgrp">
              <span className="sv-ctiles">
                {SOLVE_WORK.map((b) => <BrandTile key={b} brand={b} size={20} radius={6} soft={false} />)}
              </span>
              <span className="sv-ctxt"><b>常用工作软件 · 一键连接</b>无需自己配 key / base_url</span>
              <DoneTick size={18} />
            </div>
            <div className="sv-cgrp">
              <span className="sv-ctiles">
                {SOLVE_CHAT.map((b) => <BrandTile key={b} brand={b} size={20} radius={6} soft={false} />)}
              </span>
              <span className="sv-ctxt"><b>聊天入口 · 扫码即用</b>不用 AppID / Token / 回调</span>
              <span className="sv-qr"><Icon name="QrCode" size={16} /></span>
            </div>
          </div>
          <div className="sv-chat">
            <div className="sv-row out"><div className="sv-bubble">帮我把这件事办了</div></div>
            <div className="sv-row in"><div className="sv-bubble">好的，已经办完了</div></div>
          </div>
          <div className="sv-input">
            <span className="sv-plus"><Icon name="Plus" size={18} /></span>
            <span className="sv-ph">发消息…</span>
            <span className="sv-send" />
          </div>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   屏 7 · The Ask：$6M 怎么分 —— 横向分段比例条
   金额放标题区；一条玻璃分段条按相对权重表达「钱主要花在哪」
   （招聘最宽 = 大头），段宽即优先级，不出现百分比 / 具体数字；
   下方四列图例承载文字说明。底部说明「为什么是这个数」。
   ============================================================ */
const SPEND = [
  { id: "hire",   label: "招聘专业人才", note: "更专业的人，加快迭代、把质量拉满",     w: 38, big: true },
  { id: "cold",   label: "冷启动补贴",   note: "给早期用户的冷启动额度，先把飞轮转起来", w: 27 },
  { id: "office", label: "办公与设备",   note: "办公空间 · 团队开发设备",             w: 18 },
  { id: "tools",  label: "团队 AI 工具", note: "给团队配齐 AI 生产力工具",            w: 17 },
];
function Ask({ active, bus }) {
  // step：0 空 · 1 金额 · 2 落版句　｜　reveal：已从左到右展开的段数 0..4
  const [step, setStep] = useState(0);
  const [reveal, setReveal] = useState(0);
  const [num, setNum] = useState(0);
  const ivRef = useRef(null);
  function count() {
    if (ivRef.current) clearInterval(ivRef.current);
    let v = 0;
    ivRef.current = setInterval(() => {
      v += 1;
      if (v >= 6) { v = 6; clearInterval(ivRef.current); ivRef.current = null; }
      setNum(v);
    }, 120);
  }
  const scrub = useScrub({
    bus,
    autoplay: true,        // 进屏即自动连播（不管按键 / 点击进入）
    duration: 2900,
    reset: () => {
      if (ivRef.current) { clearInterval(ivRef.current); ivRef.current = null; }
      setStep(0); setReveal(0); setNum(0); bus.setReady(false); bus.setHint(null);
    },
    // 金额 → 四段从左到右逐级展开（条与说明一起出场）→ 落版句
    frames: [
      { t: 400,  label: "我们在融",   apply: () => { setStep(1); count(); } },
      { t: 950,  label: "招聘",       apply: () => setReveal(1) },
      { t: 1280, label: "冷启动",     apply: () => setReveal(2) },
      { t: 1610, label: "办公",       apply: () => setReveal(3) },
      { t: 1940, label: "AI 工具",    apply: () => setReveal(4) },
      { t: 2500, label: "为什么是这个数", apply: () => { setStep(2); bus.setReady(true);
        bus.setHint("$6M · 约 10 个月 runway —— 把已被验证的需求，加速跑成高质量的产品"); } },
    ],
  });
  useEffect(() => () => { if (ivRef.current) clearInterval(ivRef.current); }, []);

  return (
    <div className="ask">
      <Scrubber ctl={scrub} label="The Ask · 这笔钱怎么分" />
      <div className="ask-head fade-up">
        <h2 className="h-title">这一笔钱，花在刀刃上</h2>
      </div>

      <div className="ask-stage">
        {/* 金额：随居中的舞台浮在中部，紧贴分段条上方 */}
        <div className={"ask-amount" + (step >= 1 ? " on" : "")}>
          <span className="ask-num mono">$<b>{step >= 1 ? num : 0}</b><i>M</i></span>
          <span className="ask-amount-meta"><b>首轮融资</b> · 约 <b>10 个月</b> runway</span>
        </div>
        {/* 分段比例条：段宽 = 相对权重（招聘最宽），从左到右逐段展开 */}
        <div className="ask-bar" role="img" aria-label="资金按相对权重分配，招聘为大头">
          {SPEND.map((s, i) => (
            <div key={s.id}
                 className={"ask-seg seg-" + s.id + (s.big ? " big" : "") + (reveal > i ? " in" : "")}
                 style={{ flexGrow: s.w }}>
              {s.big && <span className="ask-seg-flag">大头</span>}
            </div>
          ))}
        </div>

        {/* 图例：每列宽度 = 对应段权重 → 与上方分段对齐，随该段一起出场 */}
        <div className="ask-legend">
          {SPEND.map((s, i) => (
            <div key={s.id} className={"ask-leg seg-" + s.id + (reveal > i ? " in" : "")}
                 style={{ flexGrow: s.w }}>
              <span className="askl-head">
                <span className="askl-dot" />
                <span className="askl-label">{s.label}</span>
              </span>
              <span className="askl-note">{s.note}</span>
            </div>
          ))}
        </div>
      </div>

      <p className={"ask-foot" + (step >= 2 ? " in" : "")}>
        为什么是这个数 —— 一支精干团队 × 约 10 个月，足够把<b>已被验证的需求</b>，加速成<b>高质量的产品</b>。
      </p>
    </div>
  );
}

/* 枢纽编排：从「为什么是现在」总览出发，四件事逐个「高亮 → 深讲」，
   讲完四点再到市场、融资。WhyNow(focus=N) 是回到枢纽屏的轻量高亮，
   紧跟其后的深讲屏即该点的「我们的解法」。WhyNow / MarketSize 在 act2.jsx 定义（全局）。 */
const WhyFocusN = (n) => (p) => <WhyNow {...p} focus={n} />;
window.AP_SCREENS.push(
  /* 点 1 的高亮并入「为什么是现在」枢纽屏自身（就地高亮，不换屏），故此处不单列 why-1 */
  { id: "act3-nosession", chapter: "机会", Comp: NoSession },
  { id: "why-2", chapter: "机会", Comp: WhyFocusN(2) },
  { id: "act3-route", chapter: "机会", Comp: RouteVsModel },
  { id: "why-3", chapter: "机会", Comp: WhyFocusN(3) },
  { id: "act3-runner", chapter: "机会", Comp: RunnerEcon },
  { id: "why-4", chapter: "机会", Comp: WhyFocusN(4) },
  { id: "act3-eval", chapter: "机会", Comp: EvalFlywheel },
  { id: "act2-market", chapter: "机会", Comp: MarketSize },
  { id: "act3-ask", chapter: "融资", Comp: Ask },
);
