/* ============================================================
   secret.jsx — ひみつけっしゃ（サポーター / スポンサー募集）
   Claude-code ダークターミナル調
   キャラ: window.PixelPanda（団長）
   証システム: rg_tokens から所持枚数を読み込んでパーソナライズ
   ============================================================ */

/* --- localStorage ヘルパー --- */
const sload = (k, d) => { try { return JSON.parse(localStorage.getItem(k)) ?? d; } catch { return d; } };
const ssave = (k, v) => { try { localStorage.setItem(k, JSON.stringify(v)); } catch {} };
const sToday = () => new Date().toLocaleDateString("ja-JP");

/* 一回再生のタイプライター（text を一度だけ打つ） */
function OneShot({ text, speed = 45 }) {
  const [n, setN] = React.useState(0);
  React.useEffect(() => {
    if (n >= text.length) return;
    const t = setTimeout(() => setN(n + 1), speed);
    return () => clearTimeout(t);
  }, [n, text]);
  const done = n >= text.length;
  return <span>{text.slice(0, n)}{!done && <span className="ss-cursor" style={{ height: 11, marginLeft: 1, verticalAlign: -1 }} />}</span>;
}

/* ============================================================
   団長パンダのセリフ（タイプライター）
   ============================================================ */
const BOSS_LINES = [
  "ようこそ。ここは表に出ない、しずかな部室みたいなとこ。",
  "表ではレリゴー楽しく。でも裏は、けっこうガチなんだ。",
  "支えてほしいのは、ひとり親じゃない人。当事者は、ただ休んでて。",
  "コーヒー一杯ぶんで、誰かの逃げ場が守れる。",
  "合言葉さえ言えれば、もう団員だよ。",
];

function Typer({ lines, speed = 48 }) {
  const [idx, setIdx] = React.useState(() => Math.floor(Math.random() * lines.length));
  const [shown, setShown] = React.useState("");
  const [phase, setPhase] = React.useState("type");
  React.useEffect(() => {
    const full = lines[idx]; let t;
    if (phase === "type") {
      if (shown.length < full.length) t = setTimeout(() => setShown(full.slice(0, shown.length + 1)), speed);
      else t = setTimeout(() => setPhase("hold"), 2000);
    } else if (phase === "hold") { t = setTimeout(() => setPhase("next"), 500); }
    else { let n; do { n = Math.floor(Math.random() * lines.length); } while (n === idx && lines.length > 1); setIdx(n); setShown(""); setPhase("type"); }
    return () => clearTimeout(t);
  }, [shown, phase, idx]);
  return <span>{shown}<span className="ss-cursor" style={{ height: 12, marginLeft: 1, verticalAlign: -1 }} /></span>;
}

/* 団長パンダ */
function Boss() {
  const C = window.PixelPanda;
  return (
    <div className="ss-rec-stage">
      <div style={{ transform: "translateX(-11px) scale(0.5)" }}>
        {C ? <C pose="wave" /> : null}
      </div>
    </div>
  );
}

/* ============================================================
   データ
   ============================================================ */
const ROADMAP = [
  { ver: "v4.0.5", now: true, label: "サイトと、気持ちの庭", sub: "いまここ。ちいさな置き場所と、証で育つ庭。" },
  { ver: "v5", label: "みんなの庭がつながる", sub: "ひとりの庭から、みんなの庭へ。証が合流する。" },
  { ver: "v6", label: "ひとり親の仕事をつくる", sub: "支えられる側から、稼げる側へ。小さな経済をつくる。" },
  { ver: "v7", label: "オフラインの集会所", sub: "画面の外にも、空いた時間に集える場所をつくる。" },
  { ver: "v∞", inf: true, label: "虐待も、孤立もない子ども時代へ", sub: "親がひとりにならない、その先。未来ある子どもたちを守る。" },
];

const TIPS = [
  { amount: 300, label: "コーヒー一杯、ぶん" },
  { amount: 500, label: "お昼ごはん、ぶん" },
  { amount: 1000, label: "絵本、一冊ぶん" },
  { amount: 3000, label: "サーバー代、ちょっと" },
];

/* コラボの例（企業・店舗向け ・ 上のアイコンパターンに揃える） */
const COLLABS = [
  {
    cmd: "discount", icon: "％", title: "割引・サービス",
    body: "ひとり親向けの割引やサービスを、一緒に。",
    subject: "割引・サービスの相談",
  },
  {
    cmd: "space", icon: "⌂", title: "集会所・イベント",
    body: "オフラインで集える場やイベントを、一緒に。",
    subject: "集会所・イベントの相談",
  },
  {
    cmd: "job", icon: "⊕", title: "仕事をつくる",
    body: "ひとり親が稼げる仕組みを、一緒に。",
    subject: "仕事づくりの相談",
  },
  {
    cmd: "collab", icon: "❖", title: "商品・コンテンツ",
    body: "商品やコンテンツの、コラボ。",
    subject: "コラボの相談",
  },
];

const WAYS = [
  {
    cmd: "cheer", icon: "♥", title: "応援する",
    body: "結局全ての起源と根本、人の「気持ち」が人を動かす。",
    cheer: true, action: "エールを送る",
  },
  {
    cmd: "idea", icon: "✦", title: "アイデアを出す",
    body: "「こうしたら面白いかも」を投げてほしい。突飛なほど歓迎。みんなで案を転がしたい。",
    href: "mailto:hello@405justfound.example?subject=アイデアあります", action: "メールで送る",
  },
  {
    cmd: "intel", icon: "⌖", title: "情報をくれる",
    body: "「黙々と頑張ってる人がいる」「こんな事例がある」を教えて。点と点を、つなぎます。",
    href: "mailto:hello@405justfound.example?subject=情報提供", action: "メールで送る",
  },
];

const WHY = [
  ["広告も、スポンサー漬けの記事も、", "貼りたくない"],
  ["数字に追われて、", "発信を盛りたくない"],
  ["だから、おなじ気持ちの仲間に、", "すこしずつ支えてほしい"],
];

/* ============================================================
   タイプライターの順送り制御
   登録された全タイパーを縦位置の上から順に1つずつ打鍵。
   ・現在の行が終わるまで次は始まらない
   ・まだ画面に入っていない行は、見えるまで待機
   ・スクロールで通り過ぎた（打ち損ねた）行は即完了して次へ（詰まり防止）
   ============================================================ */
const PREFERS_REDUCE = typeof window !== "undefined"
  && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

/* 展開内のローカル順送り用コンテキスト（null ならページ本体のスクロール順送り） */
const FoldContext = React.createContext(null);

const __items = [];
function __rectTop(it) {
  const el = it.ref && it.ref.current;
  if (!el) return Infinity;
  return el.getBoundingClientRect().top + window.scrollY;
}
let __pumpQueued = false;
function __schedulePump() {
  if (__pumpQueued) return;
  __pumpQueued = true;
  requestAnimationFrame(() => { __pumpQueued = false; __pump(); });
}
function __pump() {
  const sorted = __items.slice().sort((a, b) => __rectTop(a) - __rectTop(b));
  for (const it of sorted) {
    if (it.done) continue;
    if (it.started) break;                 // 現在打鍵中 → 待つ
    const el = it.ref.current;
    const rect = el && el.getBoundingClientRect();
    if (rect && rect.bottom < 48) { it.finishInstant(); continue; } // 通り過ぎた → 即完了
    if (it.inView) { it.started = true; it.start(); }
    break;                                 // 先頭の未完了でゲート
  }
}

/* 画面タップで、入場タイプライターを全部いっきに完了（スキップ） */
function __skipAllTypers() {
  let skipped = 0;
  for (const it of __items) {
    if (!it.done && it.finishInstant) { it.finishInstant(); skipped++; }
  }
  return skipped;
}

function useTypeGate(ref) {
  const fold = React.useContext(FoldContext);
  const [go, setGo] = React.useState(PREFERS_REDUCE);
  const [instant, setInstant] = React.useState(PREFERS_REDUCE);
  const item = React.useRef({ ref, idx: null, inView: PREFERS_REDUCE, started: PREFERS_REDUCE, done: PREFERS_REDUCE }).current;

  React.useEffect(() => {
    /* --- 展開内：ローカル順送り（スクロール非依存・開いた瞬間に上から打鍵） --- */
    if (fold) {
      item.idx = fold.register();
      if (PREFERS_REDUCE || fold.instant) { setInstant(true); setGo(true); }
      return;
    }

    /* --- ページ本体：スクロール順送り --- */
    item.start = () => setGo(true);
    item.finishInstant = () => { item.started = true; item.done = true; setInstant(true); setGo(true); };
    __items.push(item);

    let io;
    if (!PREFERS_REDUCE && ref.current) {
      io = new IntersectionObserver((es) => {
        if (es[0].isIntersecting) { item.inView = true; }
        __schedulePump();
      }, { threshold: 0.6 });
      io.observe(ref.current);
    }
    window.addEventListener("scroll", __schedulePump, { passive: true });
    __schedulePump();
    return () => {
      io && io.disconnect();
      window.removeEventListener("scroll", __schedulePump);
      const i = __items.indexOf(item); if (i >= 0) __items.splice(i, 1);
    };
  }, []);

  /* fold モード：自分の順番が来たら開始（active を毎レンダリング監視） */
  React.useEffect(() => {
    if (fold && !PREFERS_REDUCE && item.idx != null && fold.active >= item.idx && !item.started) {
      item.started = true; setGo(true);
    }
  });

  const markDone = React.useCallback(() => {
    if (item.done) return;
    item.done = true;
    if (fold) fold.advance(item.idx);
    else __schedulePump();
  }, []);

  return [go, markDone, instant];
}

/* タイプライター見出し（順送り制御つき）
   parts: [{ t:"テキスト", em:true?, b:true?, br:true? }] */
function TypeHeadline({ parts, speed = 52 }) {
  const total = parts.reduce((s, p) => s + p.t.length, 0);
  const ref = React.useRef(null);
  const [go, markDone, instant] = useTypeGate(ref);
  const [n, setN] = React.useState(PREFERS_REDUCE ? total : 0);

  React.useEffect(() => {
    if (instant) { if (n !== total) setN(total); return; }
    if (!go) return;
    if (n >= total) { markDone(); return; }
    const t = setTimeout(() => setN(n + 1), speed);
    return () => clearTimeout(t);
  }, [go, n, total, instant]);

  let used = 0;
  const out = [];
  parts.forEach((p, i) => {
    const remain = Math.max(0, n - used);
    const show = p.t.slice(0, remain);
    const partEnd = used + p.t.length;
    if (show) {
      if (p.em) out.push(<span className="em" key={"t" + i}>{show}</span>);
      else if (p.b) out.push(<b key={"t" + i}>{show}</b>);
      else if (p.cls) out.push(<span className={p.cls} key={"t" + i}>{show}</span>);
      else out.push(<React.Fragment key={"t" + i}>{show}</React.Fragment>);
    }
    if (p.br && n >= partEnd) out.push(<br key={"b" + i} />);
    used = partEnd;
  });
  const typing = go && !instant && n < total;

  return (
    <span ref={ref}>
      {out}
      {typing && <span className="type-caret" />}
    </span>
  );
}

/* ブートプロンプト（一番上・最初に打鍵） */
function BootTyper({ segments, speed = 26 }) {
  const total = segments.reduce((s, p) => s + p.t.length, 0);
  const ref = React.useRef(null);
  const [go, markDone, instant] = useTypeGate(ref);
  const [n, setN] = React.useState(PREFERS_REDUCE ? total : 0);

  React.useEffect(() => {
    if (instant) { if (n !== total) setN(total); return; }
    if (!go) return;
    if (n >= total) { markDone(); return; }
    const t = setTimeout(() => setN(n + 1), speed);
    return () => clearTimeout(t);
  }, [go, n, total, instant]);

  let used = 0;
  const out = [];
  segments.forEach((p, i) => {
    const remain = Math.max(0, n - used);
    const partEnd = used + p.t.length;
    const show = p.t.slice(0, remain);
    if (show) out.push(p.cls ? <span className={p.cls} key={"s" + i}>{show}</span> : <React.Fragment key={"s" + i}>{show}</React.Fragment>);
    if (p.br && n >= partEnd) out.push(<br key={"br" + i} />);
    used = partEnd;
  });
  return <span ref={ref}>{out}<span className="ss-cursor" /></span>;
}

/* 章扉（順送り：順番が来たら出現＋日本語ラベルを打鍵） */
function Chapter({ num, jp, en }) {
  const ref = React.useRef(null);
  const [go, markDone, instant] = useTypeGate(ref);
  const [n, setN] = React.useState(instant ? jp.length : 0);

  React.useEffect(() => {
    if (instant) { if (n !== jp.length) setN(jp.length); markDone(); return; }
    if (!go) return;
    if (n >= jp.length) { markDone(); return; }
    const t = setTimeout(() => setN(n + 1), 95);
    return () => clearTimeout(t);
  }, [go, n, instant]);

  const shown = go || instant;
  const typing = go && !instant && n < jp.length;
  return (
    <div ref={ref} className={"ss-chapter " + (shown ? "gate-shown" : "gate-hidden")}>
      <div className="ch-num">{num}</div>
      <div className="ch-lb">
        <span className="ch-jp jp">{jp.slice(0, n)}{typing && <span className="type-caret" />}</span>
        <span className="ch-en">{en}</span>
      </div>
    </div>
  );
}

/* 枠（順番が来たら出現。中のテキスト打鍵の直前に枠を見せる） */
function GateBox({ className = "", children, hold = 55, tag = "div", ...rest }) {
  const ref = React.useRef(null);
  const [go, markDone, instant] = useTypeGate(ref);
  React.useEffect(() => {
    if (instant) { markDone(); return; }
    if (go) { const t = setTimeout(markDone, hold); return () => clearTimeout(t); }
  }, [go, instant]);
  const shown = go || instant;
  const Tag = tag;
  return (
    <Tag ref={ref} className={className + " " + (shown ? "gate-shown" : "gate-hidden")} {...rest}>
      {children}
    </Tag>
  );
}

/* 折りたたみ（ターミナル風の展開ボタン・順送り対応） */
function Fold({ cmd, label, children }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  const [go, markDone, instant] = useTypeGate(ref);
  React.useEffect(() => {
    if (instant) { markDone(); return; }
    if (go) { const t = setTimeout(markDone, 55); return () => clearTimeout(t); }
  }, [go, instant]);
  const shown = go || instant;

  /* 展開内のローカル順送り（初回だけ打鍵。再展開は即表示） */
  const [active, setActive] = React.useState(0);
  const [instantOpen, setInstantOpen] = React.useState(false);
  const counter = React.useRef(0);
  const playedRef = React.useRef(false);
  const seq = {
    active,
    instant: instantOpen,
    register() { return counter.current++; },
    advance(i) { setActive((a) => Math.max(a, i + 1)); },
  };

  const handleOpen = () => {
    const wasPlayed = playedRef.current;
    playedRef.current = true;
    setInstantOpen(wasPlayed);   // 2回目以降は即表示
    counter.current = 0; setActive(0); setOpen(true);
  };
  const handleClose = () => { setOpen(false); counter.current = 0; setActive(0); };

  return (
    <div ref={ref} className={"ss-fold " + (shown ? "gate-shown" : "gate-hidden")}>
      {!open && (
        <button className="ss-fold-btn" onClick={handleOpen}>
          <span className="fb-caret">▸</span>
          <span className="fb-tx">
            <span className="fb-cmd">$ {cmd}</span>
            {label && <span className="fb-label jp">{label}</span>}
          </span>
          <span className="fb-hint jp">展開<span className="fb-arr">▸</span></span>
        </button>
      )}
      {open && (
        <div className="ss-fold-open">
          <div className="ss-fold-cmd"><span className="fb-caret on">▾</span><span className="fb-cmd">$ {cmd}</span></div>
          <div className="ss-fold-body">
            <FoldContext.Provider value={seq}>{children}</FoldContext.Provider>
          </div>
          <button className="ss-fold-collapse jp" onClick={handleClose}>▲ とじる</button>
        </div>
      )}
    </div>
  );
}

/* ============================================================
   ページ本体
   ============================================================ */
/* ============================================================
   インライン問い合わせフォーム（カテゴリ付き・サイト内で送信）
   送信内容は rg_contacts に { category, title, nick, text, date } で保存。
   将来 WordPress につなぐときは、この配列を送信するだけ。
   ============================================================ */
function InlineContactForm({ category, title, action, icon, accent }) {
  const SERVER = (typeof window !== "undefined" && window.GardenAPI && window.GardenAPI.enabled);

  // このカテゴリの既存スレッドを読む（rg_contacts に1カテゴリ1スレッド）
  // 旧形式（msgsなし・textのみ）も読めるよう正規化
  const findThread = () => {
    const raw = sload("rg_contacts", []).find((t) => t.category === category);
    if (!raw) return null;
    if (Array.isArray(raw.msgs)) return raw;
    // 旧形式 → msgs配列へ変換
    return {
      id: raw.id || ("k" + Date.now()), category, title: raw.title || title,
      nick: raw.nick || "ななしさん",
      msgs: [{ by: "me", text: raw.text || "", date: raw.date || "" }],
    };
  };
  const [thread, setThread] = React.useState(SERVER ? null : findThread);
  const [open, setOpen] = React.useState(false);
  const [nick, setNick] = React.useState(() => sload("rg_nick", ""));
  const [text, setText] = React.useState("");
  const [draft, setDraft] = React.useState("");
  const [undo, setUndo] = React.useState(false);      // 送信直後の取り消し
  const [confirmDel, setConfirmDel] = React.useState(false);
  const undoTimer = React.useRef(null);
  const undoSentText = React.useRef("");

  // 接続中：サーバーからこのカテゴリのスレッドを取得
  React.useEffect(() => {
    if (!SERVER) return;
    window.GardenAPI.contactList().then((res) => {
      if (res && Array.isArray(res.threads)) {
        const mine = res.threads.find((t) => t.who === category);
        if (mine) setThread({ ...mine, category, title });
      }
    });
  }, []);

  const removeThread = () => {
    // 接続中は表示を連れるだけ（サーバーの記録は残る）
    if (!SERVER) {
      const all = sload("rg_contacts", []).filter((t) => t.category !== category);
      ssave("rg_contacts", all);
    }
    setThread(null); setConfirmDel(false); setOpen(false);
  };

  const persist = (updated) => {
    if (SERVER) { setThread(updated); return; }   // サーバー接続時は localStorage に書かない
    const all = sload("rg_contacts", []);
    const idx = all.findIndex((t) => t.category === category);
    let next;
    if (idx >= 0) { next = all.slice(); next[idx] = updated; }
    else { next = [updated, ...all]; }
    ssave("rg_contacts", next);
    setThread(updated);
  };

  const today = () => new Date().toLocaleDateString("ja-JP", { month: "numeric", day: "numeric" });

  // 初回送信
  const submit = (e) => {
    e.preventDefault();
    if (!text.trim()) return;
    const body = text.trim();
    const nk = nick.trim() || "ななしさん";
    if (nick.trim()) ssave("rg_nick", nick.trim());
    if (SERVER) {
      // 楽観的に表示し、サーバーに送信
      const optimistic = { id: "tmp", category, title, nick: nk, msgs: [{ by: "me", text: body, date: today() }] };
      setThread(optimistic);
      window.GardenAPI.contactSend({ category, nick: nk, text: body }).then((res) => {
        if (res && res.thread) setThread({ ...res.thread, category, title });
      });
    } else {
      persist({ id: "k" + Date.now(), category, title, nick: nk, msgs: [{ by: "me", text: body, date: today() }] });
    }
    undoSentText.current = body;   // 取り消し用に保持
    setText(""); setOpen(false);
    // 送信直後の取り消し（10秒）
    setUndo(true);
    clearTimeout(undoTimer.current);
    undoTimer.current = setTimeout(() => setUndo(false), 10000);
  };

  const cancelSend = () => {
    clearTimeout(undoTimer.current);
    setUndo(false);
    // 書いた内容をフォームに戻す
    const txt = undoSentText.current || "";
    removeThread();
    setText(txt); setOpen(true);
  };

  // 続けて送る
  const sendFollow = () => {
    if (!draft.trim() || !thread) return;
    const body = draft.trim();
    if (SERVER) {
      const tid = (thread.id && String(thread.id).indexOf("srv") === 0) ? thread.id : null;
      const optimistic = { ...thread, msgs: [...thread.msgs, { by: "me", text: body, date: today() }] };
      setThread(optimistic);
      window.GardenAPI.contactSend({ category, nick: thread.nick, text: body, thread_id: tid }).then((res) => {
        if (res && res.thread) setThread({ ...res.thread, category, title });
      });
    } else {
      persist({ ...thread, msgs: [...thread.msgs, { by: "me", text: body, date: today() }] });
    }
    setDraft("");
  };

  // ===== スレッドがある（送信済み）=====
  if (thread) {
    const hasReply = thread.msgs.some((m) => m.by === "them");
    const last = thread.msgs[thread.msgs.length - 1];
    return (
      <div className="kthread jp">
        {thread.msgs.map((m, i) => (
          m.by === "me" ? (
            <div className="kbubble me" key={i}>{m.text}<span className="kby">{thread.nick}・{m.date}</span></div>
          ) : (
            <div className="kbubble them" key={i}>{m.text}<span className="kby">405 運営・{m.date}</span></div>
          )
        ))}
        {!hasReply && last.by === "me" && !undo && (
          <div className="kpending">✓ 受け取りました。お返事は、このスレッドに届きます。</div>
        )}
        {undo && (
          <div className="kundo">
            <span>送信しました</span>
            <button className="kundo-btn" onClick={cancelSend}>↩ 取り消す</button>
          </div>
        )}
        <div className="kfollow">
          <input
            className="kform-in" type="text" maxLength={600}
            placeholder="つづけて送る…（連絡先を渡すのもここで）"
            value={draft} onChange={(e) => setDraft(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") sendFollow(); }}
          />
          <button className="kfollow-send" onClick={sendFollow} disabled={!draft.trim()}>▶</button>
        </div>
        {confirmDel ? (
          <div className="kdel-confirm">
            <span>このやりとりを消す？</span>
            <button className="kdel-yes" onClick={removeThread}>消す</button>
            <button className="kdel-no" onClick={() => setConfirmDel(false)}>やめる</button>
          </div>
        ) : (
          <button className="kdel-link" onClick={() => setConfirmDel(true)}>やりとりを消す</button>
        )}
      </div>
    );
  }

  // ===== 未送信：ボタン or フォーム =====
  if (!open) {
    return (
      <GateBox className={"way-action jp" + (accent ? " amber" : "")} tag="button" onClick={() => setOpen(true)}>
        {action}
        <span className="way-arr">→</span>
        <span className="way-ico">{icon}</span>
      </GateBox>
    );
  }

  return (
    <form className="kform jp" onSubmit={submit}>
      <input
        className="kform-in" type="text" maxLength={20}
        placeholder="ニックネーム（任意）"
        value={nick} onChange={(e) => setNick(e.target.value)}
      />
      <textarea
        className="kform-text" rows={3} maxLength={600}
        placeholder={"「" + title + "」について、自由にどうぞ。"}
        value={text} onChange={(e) => setText(e.target.value)}
      ></textarea>
      <div className="kform-row">
        <button type="button" className="kform-cancel" onClick={() => setOpen(false)}>やめる</button>
        <button type="submit" className="kform-send" disabled={!text.trim()}>▶ 送信する</button>
      </div>
    </form>
  );
}

function SecretPage() {
  // ひみつけっしゃを開いた＝運営からの返信を既読に（トップのバッジを消す）
  React.useEffect(() => {
    if (window.GardenAPI && window.GardenAPI.enabled && window.GardenAPI.contactList) {
      window.GardenAPI.contactList().then((res) => {
        if (!res || !Array.isArray(res.threads)) return;
        const them = res.threads.reduce((n, th) => n + (th.msgs || []).filter((m) => m.by === "them").length, 0);
        ssave("rg_contact_seen", them);
      });
    }
  }, []);
  const tokenCount = React.useState(() => {
    const w = sload("rg_tokens", []);
    return Array.isArray(w) ? w.length : 0;
  })[0];

  const [tipSel, setTipSel] = React.useState(500);
  const [custom, setCustom] = React.useState("");
  const [toast, setToast] = React.useState(null);
  const [cheerCount, setCheerCount] = React.useState(0);
  const [cheerTotal, setCheerTotal] = React.useState(() => sload("rg_cheer_total", 0));
  const [cheeredToday, setCheeredToday] = React.useState(() => sload("rg_cheer_today", null) === sToday());

  // 本番接続中は、みんなのエール総数をサーバーから取得して表示
  React.useEffect(() => {
    if (window.GardenAPI && window.GardenAPI.enabled) {
      window.GardenAPI.getState().then((s) => {
        if (s && typeof s.cheers === "number") { ssave("rg_cheer_total", s.cheers); setCheerTotal(s.cheers); }
      });
    }
  }, []);

  /* リロード時もページ先頭から入場エフェクトを再生する */
  React.useEffect(() => {
    if ("scrollRestoration" in window.history) window.history.scrollRestoration = "manual";
    window.scrollTo(0, 0);
  }, []);

  /* 画面ワンタップで入場エフェクトをスキップ（毎回有効） */
  React.useEffect(() => {
    const onTap = (e) => {
      // ボタン・入力・リンクのタップは通常動作（スキップしない）
      if (e.target.closest && e.target.closest("button, a, input, textarea, select, label")) return;
      __skipAllTypers();
    };
    document.addEventListener("pointerdown", onTap, true);
    return () => document.removeEventListener("pointerdown", onTap, true);
  }, []);

  const sendCheer = (e) => {
    e.preventDefault();
    if (cheeredToday) { showToast("♥ 本日分のエールは送信済みです"); return; }
    // エールは「気持ちの証」にはならない。みんなの庭の空に星を灯す“もと”になる
    const nextTotal = sload("rg_cheer_total", 0) + 1;
    ssave("rg_cheer_total", nextTotal);
    setCheerTotal(nextTotal);
    ssave("rg_cheer_today", sToday());
    setCheeredToday(true);
    setCheerCount((c) => c + 1);
    // 本番接続中は、みんなの合計に加算して本物の総数を表示
    if (window.GardenAPI && window.GardenAPI.enabled) {
      window.GardenAPI.cheer().then((res) => {
        if (res && typeof res.cheers === "number") { ssave("rg_cheer_total", res.cheers); setCheerTotal(res.cheers); }
      });
    }
  };

  const showToast = (msg) => {
    setToast(msg);
    setTimeout(() => setToast(null), 2600);
  };

  const chosen = custom ? parseInt(custom, 10) : tipSel;
  const selectChip = (amt) => { setTipSel(amt); setCustom(""); };
  const sendTip = () => {
    if (!chosen || chosen <= 0) { showToast("$ 金額を選んでね"); return; }
    showToast(`¥${chosen.toLocaleString()} を投げ銭 — ありがとう。決済は準備中だよ`);
  };

  return (
    <div className="ss-wrap">
      <div className="ss-phone">

        {/* ターミナル chrome */}
        <div className="ss-chrome">
          <div className="ss-dots"><i /><i /><i /></div>
          <span className="ss-path">~/405/secret-society — zsh</span>
          <a className="ss-back" href="index.html">◀ exit</a>
        </div>

        {/* ブート / プロンプト */}
        <div className="ss-pad">
          <div className="ss-boot-type jp">
            <BootTyper segments={[
              { t: "➜ ", cls: "pr" },
              { t: "405 ", cls: "fl" },
              { t: "git:(", cls: "cm" },
              { t: "main" },
              { t: ") ", cls: "cm" },
              { t: "./join --secret-society", br: true },
              { t: "[ ok ] ", cls: "ok" },
              { t: "表向きの顔を、そっと閉じています…", br: true },
              { t: "[ ok ] ", cls: "ok" },
              { t: "裏口を、ひらきました" },
            ]} />
          </div>
        </div>

        {/* タイトル */}
        <div className="ss-title">
          <div className="kanji jp"><TypeHeadline parts={[{t:"ひみつ",br:true},{t:"けっしゃ"}]} speed={90} /></div>
          <div className="roman"><TypeHeadline speed={40} parts={[{t:"405 SECRET SOCIETY"}]} /></div>
          <p className="tagline jp">
            <TypeHeadline speed={32} parts={[
              {t:"表向きは"},{t:"レリゴー楽しく",b:true},{t:"、",br:true},
              {t:"でも裏では"},{t:"割とガチ",b:true},{t:"で",br:true},
              {t:"ひとり親世界をなんとかしたいんです。"},
            ]} />
          </p>
        </div>

        {/* ============ 01 理由 ============ */}
        <Chapter num="01" jp="理由" en="WHY — なぜ要るのか" />

        <GateBox className="ss-mission">
          <div className="big jp"><TypeHeadline parts={[{t:"「いいね」より、",br:true},{t:"目の前の人",em:true},{t:"を。"}]} /></div>
          <p className="body jp">
            <TypeHeadline speed={28} parts={[
              {t:"向き合うのは、スマホじゃない。",br:true},
              {t:"伝えるべき相手に、何をすべきか考える場所。"},
            ]} />
          </p>
        </GateBox>

        <Fold cmd="cat WHY.md" label="なぜ、この場所が要るのか">
        <div className="ss-manifesto">
          <p className="jp"><TypeHeadline speed={18} parts={[
            {t:"いまやSNSは、社会の常識。気がつかないうちに、スマホから目を離せなくさせるコンテンツばかり。"},
          ]} /></p>
          <p className="jp"><TypeHeadline speed={18} parts={[
            {t:"だけど私たちには、"},{t:"時間がない、お金がない、心の余裕もない",b:true},
            {t:"。ふと、そんな社会から置いてけぼりを食らったような気分になる。"},
          ]} /></p>
          <p className="jp"><TypeHeadline speed={18} parts={[
            {t:"でも、それってそんなに大事なことかな？目の前の家族や、自分の時間を割いてまで、することかな？"},
          ]} /></p>
          <p className="jp em"><TypeHeadline speed={16} parts={[
            {t:"アカウントも、個人登録もいらない。長居もしなくていい。気軽に立ち寄って、おんなじチャンネルの人とつながりが感じられて、「よし、明日も頑張ろ！」って仕事や育児に向き合える。そんな活力になる場所を、つくりたいと思いました。"},
          ]} /></p>
          <p className="jp"><TypeHeadline speed={18} parts={[
            {t:"おんなじ気持ちだけど、どうすればいいか分からない人。すでにやってるけど、"},
            {t:"寡黙に黙々と、人知れず頑張ってる人",b:true},{t:"も、絶対いる。"},
          ]} /></p>
          <p className="jp last"><TypeHeadline speed={22} parts={[
            {t:"そんな人たちと横でつながって、"},{t:"輪を広げていきたい",b:true},{t:"。"},
          ]} /></p>
        </div>
        </Fold>

        {/* ============ 02 目的 ============ */}
        <Chapter num="02" jp="目的" en="GOAL — どこを目指すか" />

        <GateBox className="ss-mission">
          <div className="big jp"><TypeHeadline parts={[{t:"ひとり親が、",br:true},{t:"「ひとり」にならない",em:true},{t:"世界をつくる。"}]} /></div>
          <p className="body jp">
            <TypeHeadline speed={26} parts={[
              {t:"いまは、ちいさなサイトと、ちいさな庭。",br:true},
              {t:"でも遠い未来、ここを本物の「居場所」にしたい。",br:true},
              {t:"画面の中だけじゃなく、現実にも。"},
            ]} />
          </p>
        </GateBox>

        {/* 未来ロードマップ */}
        <Fold cmd="git log --future" label="これから目指す未来の地図">
        <div className="ss-road">
          {ROADMAP.map((r, i) => (
            <div className={"ss-rd" + (r.now ? " now" : "") + (r.inf ? " inf" : "")} key={r.ver}>
              <div className="gut">
                <span className="node" />
                <span className="line" />
              </div>
              <div className="rd-body">
                <div className="ver">
                  {r.ver}
                  {r.now && <span className="here">いまここ</span>}
                </div>
                <div className="label jp"><TypeHeadline speed={26} parts={[{t:r.label}]} /></div>
                <div className="sub jp"><TypeHeadline speed={16} parts={[{t:r.sub}]} /></div>
              </div>
            </div>
          ))}
        </div>
        </Fold>

        {/* ============ 03 参加 ============ */}
        <Chapter num="03" jp="参加" en="JOIN — どう混ざれるか" />

        <GateBox className="ss-mission">
          <div className="big jp"><TypeHeadline parts={[{t:"いちばん欲しいのは、",br:true},{t:"あなたの声",em:true},{t:"。"}]} /></div>
          <p className="body jp">
            <TypeHeadline speed={24} parts={[
              {t:"仲間で案を出し合って、いつか自分たちでマネタイズして、自走していく。",br:true},
              {t:"それが、この結社の目標。だからまずは、こんな形で混ざってほしい。"},
            ]} />
          </p>
        </GateBox>
        <Fold cmd="ls ./contribute" label="あなたにできること、3つ">
        <div className="ss-ways">
          {WAYS.map((w) => {
            const inner = (
              <React.Fragment>
              <div className="way-title jp"><TypeHeadline speed={26} parts={[{t:w.cmd+"() ",cls:"way-cmd"},{t:w.title}]} /></div>
              <p className="way-body jp"><TypeHeadline speed={16} parts={[{t:w.body}]} /></p>
              {w.cheer && <div className="cheer-total jp">みんなのエール <b>{cheerTotal}</b></div>}
              {w.cheer ? (
                (cheerCount > 0 || cheeredToday) ? (
                <span className="way-cheer-done jp" key={cheerCount}>
                  <span className="cd-ico">♥</span>
                  {cheerCount > 0
                    ? <OneShot text={`みんなのエール、いま ${cheerTotal}。エール10ごとに、庭の空に星がひとつ灯ります。`} />
                    : <span>本日分のエールは、送信完了しています。<br />みんなのエール、いま <b>{cheerTotal}</b>。エール10ごとに、庭の空に星がひとつ灯ります。</span>}
                </span>
                ) : (
                <GateBox className="way-action jp" tag="span">
                  {w.action}
                  <span className="way-arr">→</span>
                  <span className="way-ico">{w.icon}</span>
                </GateBox>
                )
              ) : (
                <InlineContactForm category={w.cmd} title={w.title} action="送る" icon={w.icon} />
              )}
              </React.Fragment>
            );
            return w.cheer
              ? <a className="ss-way" key={w.cmd} href="#" onClick={sendCheer}>{inner}</a>
              : <div className="ss-way" key={w.cmd}>{inner}</div>;
          })}
        </div>
        </Fold>

        {/* コラボ募集（企業・店舗・チーム） */}
        <GateBox className="ss-subsec jp"><span className="c">$ ./collab --partner</span>　企業・お店・チーム</GateBox>
        <GateBox className="ss-mission amber">
          <div className="big jp"><TypeHeadline parts={[{t:"一緒に、",br:true},{t:"何かを生み出したい",em:true},{t:"。"}]} /></div>
          <p className="body jp">
            <TypeHeadline speed={24} parts={[
              {t:"ひとり親や、その子どもたちのために、一緒に動けそうな企業・お店・チームを探しています。"},
            ]} />
          </p>
        </GateBox>
        <Fold cmd="ls ./collab" label="一緒にできること、4つ">
        <div className="ss-ways">
          {COLLABS.map((c) => (
            <div className="ss-way amber" key={c.cmd}>
              <div className="way-title jp"><TypeHeadline speed={26} parts={[{t:c.cmd+"() ",cls:"way-cmd"},{t:c.title}]} /></div>
              <p className="way-body jp"><TypeHeadline speed={16} parts={[{t:c.body}]} /></p>
              <InlineContactForm category={c.cmd} title={c.title} action="相談する" icon={c.icon} accent />
            </div>
          ))}
        </div>
        <p className="collab-note jp"><TypeHeadline speed={20} parts={[{t:"「こんなの面白そう」だけでも大歓迎。まずは雑談から。"}]} /></p>
        </Fold>

        {/* 合言葉ブロックは削除 */}

        {/* 下部 EXIT */}
        <a className="ss-exit-btn jp" href="index.html">
          <span className="ex-pr">$</span> exit — トップへもどる
        </a>

        <div className="ss-foot">© 2026 405 JUST FOUND — secret society<br />exit code 0</div>

        {toast && <div className="ss-toast jp">{toast}</div>}
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<SecretPage />);
