// Opportunity Analysis Report — interactive editorial-style page.
// Source: OPPORTUNITY_ANALYSIS.md (585 lines, 2026-04-25)
// Tab B (Reality Check + OST) layered 2026-04-24.

/* ------------------------------------------------------------------
   Small inline helpers (scoped to opp page)
------------------------------------------------------------------ */

function OppFadeIn({ children, delay = 0, threshold = 0.12, as: Tag = "div", className = "", style = {}, ...rest }) {
  const [ref, inView] = useInView({ threshold });
  return (
    <Tag
      ref={ref}
      className={"opp-fade " + (inView ? "is-in " : "") + className}
      style={{ transitionDelay: `${delay}ms`, ...style }}
      {...rest}
    >
      {children}
    </Tag>
  );
}

function OppSection({ id, eyebrow, num, title, lede, children }) {
  return (
    <section id={id} className="opp-section" data-opp-section={id}>
      <div className="opp-section-rail">
        <span className="opp-section-num">§ {num}</span>
      </div>
      <header className="opp-section-head">
        {eyebrow && <div className="opp-eyebrow">{eyebrow}</div>}
        <h2 className="opp-section-title">{title}</h2>
        {lede && <p className="opp-section-lede">{lede}</p>}
      </header>
      <div className="opp-section-body">{children}</div>
    </section>
  );
}

function OppPriBadge({ p }) {
  if (!p || p === "—") return <span className="opp-pri opp-pri-none">—</span>;
  return <span className={"opp-pri opp-pri-" + p.toLowerCase()}>{p}</span>;
}

function OppStateDot({ state }) {
  // ok / partial / warn / miss
  const map = { ok: "✓", partial: "◐", warn: "⚠", miss: "✗" };
  const lab = { ok: "구현 완료", partial: "부분 구현", warn: "확인 필요", miss: "미구현" };
  return <span className={"opp-state opp-state-" + state} title={lab[state] || ""}>{map[state] || "·"}</span>;
}

/* ------------------------------------------------------------------
   Sticky TOC (left rail) + reading progress bar (top)
------------------------------------------------------------------ */

function OppToc({ items, activeId, onJump }) {
  return (
    <aside className="opp-toc" aria-label="목차">
      <div className="opp-toc-mark">목차</div>
      <ul className="opp-toc-list">
        {items.map((it) => (
          <li key={it.id}>
            <button
              className={"opp-toc-item" + (activeId === it.id ? " is-active" : "")}
              onClick={() => onJump(it.id)}
            >
              <span className="opp-toc-num">{it.num}</span>
              <span className="opp-toc-label">{it.label}</span>
            </button>
          </li>
        ))}
      </ul>
    </aside>
  );
}

function OppProgressBar({ progress }) {
  return (
    <div className="opp-progress-top" aria-hidden="true">
      <div className="opp-progress-fill" style={{ width: `${progress * 100}%` }} />
    </div>
  );
}

/* ------------------------------------------------------------------
   Hero
------------------------------------------------------------------ */

function OppHero({ data, onScrollDown }) {
  const scrollY = useScrollY();
  const titleShift = Math.min(scrollY * 0.18, 80);
  const titleOpacity = Math.max(0, 1 - scrollY / 600);

  return (
    <section className="opp-hero">
      <div className="opp-hero-bg" aria-hidden="true">
        <div className="opp-hero-grid" />
        <div className="opp-hero-orb opp-orb-a" />
        <div className="opp-hero-orb opp-orb-b" />
        <div className="opp-hero-orb opp-orb-c" />
      </div>
      <div className="opp-hero-inner" style={{ transform: `translateY(${titleShift}px)`, opacity: titleOpacity }}>
        <div className="opp-hero-eyebrow">{data.meta.eyebrow}</div>
        <h1 className="opp-hero-title">
          미국 시장 <em>$100k MRR</em><br/>기회 분석
        </h1>
        <p className="opp-hero-sub">{data.meta.subtitle}</p>

        <div className="opp-hero-quote">
          <span className="opp-quote-mark">"</span>
          <p>{data.meta.oneSentence}</p>
        </div>

        <div className="opp-hero-thesis">
          {data.thesis.map((t, i) => (
            <OppFadeIn key={t.n} delay={i * 90} className="opp-thesis-card">
              <div className="opp-thesis-num">0{t.n}</div>
              <div className="opp-thesis-title">{t.title}</div>
              <div className="opp-thesis-body">{t.body}</div>
              <div className="opp-thesis-proof">{t.proof}</div>
            </OppFadeIn>
          ))}
        </div>

        <div className="opp-hero-meta">
          <div><span className="opp-mono">2026 · 04 · 25</span></div>
          <span className="opp-hero-dot" />
          <div className="opp-hero-meta-mut">{data.meta.authors}</div>
        </div>

        <button className="opp-scroll-hint" onClick={onScrollDown} aria-label="아래로 스크롤">
          ↓ 보고서 시작
        </button>
      </div>
    </section>
  );
}

/* ------------------------------------------------------------------
   §1 Legacy
------------------------------------------------------------------ */

function OppLegacy({ data }) {
  return (
    <OppSection
      id="legacy" num="01"
      eyebrow="The Decoded Heritage"
      title="레거시 유산 해독 — 10년의 2,200개 목소리"
      lede="레거시 투데잇은 '플래너'로 고용되지 않았다. '장기 시험 완주를 위한 감정 인프라'로 고용됐다. 미국의 MCAT/LSAT/BAR/CPA 같은 1+년 시험 맥락과 동형이다."
    >
      {/* JTBD 5 cards */}
      <h3 className="opp-h3">검증된 사용자 근본 니즈 — JTBD 5</h3>
      <div className="opp-jtbd-grid">
        {data.jtbds.map((j, i) => (
          <OppFadeIn key={j.id} delay={i * 80} className="opp-jtbd-card">
            <div className="opp-jtbd-head">
              <span className="opp-jtbd-name">{j.name}</span>
              <span className="opp-jtbd-freq">{j.freq}</span>
            </div>
            <div className="opp-jtbd-essence">{j.essence}</div>
            <blockquote className="opp-jtbd-quote">"{j.quote}"</blockquote>
          </OppFadeIn>
        ))}
      </div>

      {/* Loved vs Hated split */}
      <h3 className="opp-h3 opp-h3-spaced">사랑받은 기능 × 미움받은 경험</h3>
      <div className="opp-split">
        <div className="opp-split-pane opp-split-loved">
          <div className="opp-split-head">
            <span className="opp-split-icon">♡</span>
            <span>사랑받은 기능 (그대로 승계)</span>
          </div>
          <ul className="opp-split-list">
            {data.loved.map((l, i) => (
              <li key={i} className="opp-split-row">
                <OppStateDot state={l.state} />
                <div>
                  <div className="opp-split-feat">{l.feat}</div>
                  <div className="opp-split-meta"><span className="opp-mono">{l.evidence}</span> · {l.v2}</div>
                </div>
              </li>
            ))}
          </ul>
        </div>
        <div className="opp-split-pane opp-split-hated">
          <div className="opp-split-head">
            <span className="opp-split-icon">⨯</span>
            <span>미움받은 경험 (반드시 해결)</span>
          </div>
          <ul className="opp-split-list">
            {data.hated.map((h, i) => (
              <li key={i} className="opp-split-row">
                <span className="opp-hated-count">{h.count}</span>
                <div>
                  <div className="opp-split-feat">{h.issue}</div>
                  <div className="opp-split-meta">{h.meaning}</div>
                </div>
              </li>
            ))}
          </ul>
        </div>
      </div>

      {/* International signals */}
      <h3 className="opp-h3 opp-h3-spaced">의도치 않은 국제 시그널 — 192건</h3>
      <div className="opp-intl-grid">
        {data.intl.map((it) => (
          <div key={it.country} className="opp-intl-card">
            <div className="opp-intl-flag">{it.flag}</div>
            <div className="opp-intl-num"><CountUp end={it.count} format="exact" /></div>
            <div className="opp-intl-country">{it.country}</div>
            <p className="opp-intl-note">{it.note}</p>
          </div>
        ))}
      </div>

      <blockquote className="opp-pullquote">
        <span>"</span>
        I want to join beta for this app because of its <em>great potential</em>. The English grammar is not well done in some areas.
        <span className="opp-pullquote-attr">— lifegrad, USA, 2021</span>
      </blockquote>

      <div className="opp-callout">
        <div className="opp-callout-mark">시사점</div>
        <p>번역이 아니라 <strong>미국 정체성으로 처음부터 재설계</strong>해야 한다. 한국어 파일에서 영어로 옮기는 게 아니라, 영어가 원본이고 한국어가 번역.</p>
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §2 Market
------------------------------------------------------------------ */

function MarketMatrix({ items }) {
  const w = 560, h = 360;
  return (
    <div className="opp-matrix-wrap">
      <svg className="opp-matrix-svg" viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="xMidYMid meet">
        {/* axes */}
        <line x1={w/2} y1={20} x2={w/2} y2={h-30} stroke="var(--border-default)" strokeDasharray="3 4" />
        <line x1={20} y1={h/2} x2={w-30} y2={h/2} stroke="var(--border-default)" strokeDasharray="3 4" />
        {/* axis labels */}
        <text x={w/2} y={14} textAnchor="middle" fontSize="11" fontFamily="var(--font-mono)" fill="var(--fg-subtle)">↑ 구조 / 데이터 깊이</text>
        <text x={w/2} y={h-12} textAnchor="middle" fontSize="11" fontFamily="var(--font-mono)" fill="var(--fg-subtle)">↓ 가벼움 / 게임화</text>
        <text x={20} y={h/2 - 8} fontSize="11" fontFamily="var(--font-mono)" fill="var(--fg-subtle)">← 개인 도구</text>
        <text x={w-30} y={h/2 - 8} textAnchor="end" fontSize="11" fontFamily="var(--font-mono)" fill="var(--fg-subtle)">소셜 →</text>

        {items.map((c, i) => {
          const cx = 40 + c.x * (w - 80);
          const cy = 40 + c.y * (h - 80);
          return (
            <g key={c.app} className={"opp-matrix-pt" + (c.hero ? " is-hero" : "")}>
              <circle cx={cx} cy={cy} r={c.hero ? 18 : 12} className="opp-matrix-halo" />
              <circle cx={cx} cy={cy} r={c.hero ? 8 : 6} className="opp-matrix-dot" />
              <text x={cx + 14} y={cy + 4} fontSize="12" fontFamily="var(--font-display)" fontWeight={c.hero ? 700 : 500} fill="var(--fg-default)">
                {c.app}
              </text>
            </g>
          );
        })}
      </svg>
    </div>
  );
}

function OppMarket({ data }) {
  return (
    <OppSection
      id="market" num="02"
      eyebrow="The Battlefield"
      title="미국 시장 지형 — 공백과 벤치마크"
      lede="$13B 글로벌 Productivity 시장의 38%가 북미. 그러나 '시간 측정 + 자동 계획 + 소셜 + 시험 데드라인'을 한 앱에 묶은 미국향 제품이 없다."
    >
      <h3 className="opp-h3">시장 규모</h3>
      <div className="opp-kpi-row">
        {data.marketKpi.map((k, i) => (
          <OppFadeIn key={i} delay={i * 70} className="opp-kpi-card">
            <div className="opp-kpi-value">{k.v}</div>
            <div className="opp-kpi-label">{k.l}</div>
            <div className="opp-kpi-annot">{k.a}</div>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">4축 경쟁 포지셔닝</h3>
      <p className="opp-section-sub">투데잇은 4축이 만나는 중심에 있다. 다른 앱들은 각자 한 면만 잘한다.</p>
      <MarketMatrix items={data.competitors} />
      <div className="opp-comp-grid">
        {data.competitors.filter(c => !c.hero).map((c) => (
          <div key={c.app} className="opp-comp-card">
            <div className="opp-comp-axis">{c.axis}</div>
            <div className="opp-comp-name">{c.app}</div>
            <div className="opp-comp-row"><span className="opp-comp-good">잘함</span> {c.good}</div>
            <div className="opp-comp-row"><span className="opp-comp-bad">약점</span> {c.bad}</div>
          </div>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">치명적 공백 3개 — 투데잇만 커버 가능</h3>
      <div className="opp-gap-grid">
        {data.gaps.map((g, i) => (
          <OppFadeIn key={i} delay={i * 100} className="opp-gap-card">
            <div className="opp-gap-num">0{i + 1}</div>
            <div className="opp-gap-title">{g.title}</div>
            <p className="opp-gap-body">{g.body}</p>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">$100k MRR 현실 궤적</h3>
      <div className="opp-table opp-table-traj">
        <div className="opp-tr opp-th">
          <div>유사 앱</div><div>월 매출</div><div>도달 시점</div><div>전략</div>
        </div>
        {data.trajectory.map((t, i) => (
          <div key={i} className={"opp-tr" + (t.hero ? " is-hero" : "")}>
            <div className="opp-mono">{t.app}</div>
            <div className="opp-mono opp-num">{t.mo}</div>
            <div>{t.time}</div>
            <div>{t.strat}</div>
          </div>
        ))}
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §3 v2 status
------------------------------------------------------------------ */

function OppV2({ data }) {
  return (
    <OppSection
      id="v2" num="03"
      eyebrow="Internal Inventory"
      title="현재 todait-ios v2 상태 진단"
      lede="이미 가진 자산은 강력하다. 그러나 레거시에서 계승되지 못한 3가지 — 폰 뒤집기, 스터디그룹, 1년 히트맵 — 는 P0/P1 복원이 필요하다."
    >
      <div className="opp-split">
        <div className="opp-split-pane opp-split-loved">
          <div className="opp-split-head">
            <span className="opp-split-icon">▲</span>
            <span>강점 — 이미 경쟁자 대비 앞섬</span>
          </div>
          <ul className="opp-split-list">
            {data.v2Strengths.map((s, i) => (
              <li key={i} className="opp-split-row">
                <OppStateDot state="ok" />
                <div>
                  <div className="opp-split-feat">{s.name}</div>
                  <div className="opp-split-meta">{s.edge}</div>
                </div>
              </li>
            ))}
          </ul>
        </div>
        <div className="opp-split-pane opp-split-hated">
          <div className="opp-split-head">
            <span className="opp-split-icon">▼</span>
            <span>격차 — 우선순위 복원 필요</span>
          </div>
          <ul className="opp-split-list">
            {data.v2Gaps.map((g, i) => (
              <li key={i} className="opp-split-row">
                <OppPriBadge p={g.pri} />
                <div>
                  <div className="opp-split-feat">{g.name}</div>
                  <div className="opp-split-meta">{g.body}</div>
                </div>
              </li>
            ))}
          </ul>
        </div>
      </div>

      <h3 className="opp-h3 opp-h3-spaced">v2 기능 재고 항목</h3>
      <div className="opp-table opp-table-v2">
        <div className="opp-tr opp-th">
          <div>기능</div><div>상태</div><div>우선순위</div><div>권고</div>
        </div>
        {data.v2Items.map((it, i) => (
          <div key={i} className="opp-tr">
            <div className="opp-feat">{it.feat}</div>
            <div className="opp-mono opp-state-text">{it.state}</div>
            <div><OppPriBadge p={it.pri} /></div>
            <div>{it.note}</div>
          </div>
        ))}
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §4 JTBD US + Beachhead
------------------------------------------------------------------ */

function OppJtbd({ data }) {
  return (
    <OppSection
      id="jtbd" num="04"
      eyebrow="The Buyer Persona"
      title="JTBD & Beachhead 세그먼트"
      lede="레거시 5개 JTBD를 미국 맥락으로 재작성. Beachhead는 단일 선택 — 미국 LSAT/MCAT 1st-year attempt."
    >
      <div className="opp-jtbd-primary">
        <div className="opp-jtbd-primary-mark">Primary JTBD</div>
        <p className="opp-jtbd-primary-text">{data.jtbdPrimary}</p>
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Secondary JTBDs</h3>
      <ol className="opp-secondary-jtbd">
        {data.jtbdSecondary.map((j, i) => <li key={i}>{j}</li>)}
      </ol>

      <h3 className="opp-h3 opp-h3-spaced">Beachhead — 미국 LSAT/MCAT 프렙생</h3>
      <div className="opp-beachhead-card">
        <div className="opp-beachhead-bullseye">
          <div className="opp-bullseye-ring opp-ring-3" />
          <div className="opp-bullseye-ring opp-ring-2" />
          <div className="opp-bullseye-ring opp-ring-1" />
          <div className="opp-bullseye-core">
            <div className="opp-bullseye-flag">🇺🇸</div>
            <div className="opp-bullseye-label">LSAT / MCAT</div>
            <div className="opp-bullseye-sub">1st-year attempt</div>
          </div>
        </div>
        <div className="opp-beachhead-reasons">
          <div className="opp-bh-title">왜 LSAT / MCAT인가</div>
          <ul>
            {data.beachheadReasons.map((r, i) => (
              <li key={i}>
                <span className="opp-bh-k">{r.k}</span>
                <span className="opp-bh-v">{r.v}</span>
              </li>
            ))}
          </ul>
        </div>
      </div>

      <h3 className="opp-h3 opp-h3-spaced">한국 ↔ 미국 매핑</h3>
      <div className="opp-table opp-table-mapping">
        <div className="opp-tr opp-th">
          <div>한국</div><div>미국 대응</div><div>판단</div>
        </div>
        {data.korusMap.map((k, i) => (
          <div key={i} className={"opp-tr opp-mapping-" + k.verdict}>
            <div>{k.kr}</div>
            <div>{k.us}</div>
            <div>
              {k.verdict === "primary" && <span className="opp-verdict opp-verdict-primary">Beachhead</span>}
              {k.verdict === "later"   && <span className="opp-verdict opp-verdict-later">12개월 후 확장</span>}
              {k.verdict === "skip"    && <span className="opp-verdict opp-verdict-skip">건너뛰기</span>}
            </div>
          </div>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Secondary 세그먼트 (12개월 뒤)</h3>
      <ol className="opp-numlist">
        {data.secondary.map((s, i) => <li key={i}>{s}</li>)}
      </ol>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §5 Value Proposition
------------------------------------------------------------------ */

function OppValueProp({ data }) {
  return (
    <OppSection
      id="vp" num="05"
      eyebrow="Value Proposition"
      title="누구에게, 왜, 무엇을, 언제, 어디서, 얼마에"
      lede="6-part JTBD 템플릿으로 미국 LSAT/MCAT 1st-year attempt 페르소나에게 약속하는 가치를 정리한다."
    >
      <div className="opp-vp-grid">
        {data.vpBlocks.map((b, i) => (
          <OppFadeIn key={b.k} delay={i * 70} className="opp-vp-card">
            <div className="opp-vp-key">{b.k}</div>
            <div className="opp-vp-title">{b.t}</div>
            <p className="opp-vp-body">{b.body}</p>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Why Us — 경쟁보다 우리를 선택하는 이유</h3>
      <ul className="opp-whyus">
        {data.whyUs.map((w, i) => (
          <li key={i} className={"opp-whyus-row" + (w.hero ? " is-hero" : "")}>
            <span className="opp-whyus-app">{w.app}</span>
            <span className="opp-whyus-arrow">→</span>
            <span className="opp-whyus-limit">{w.limit}</span>
          </li>
        ))}
      </ul>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §6 Fit
------------------------------------------------------------------ */

function StarBar({ score, max = 5 }) {
  return (
    <span className="opp-stars" aria-label={`${score} / ${max}`}>
      {Array.from({ length: max }).map((_, i) => (
        <span key={i} className={"opp-star" + (i < score ? " is-on" : "")}>★</span>
      ))}
    </span>
  );
}

function OppFit({ data }) {
  return (
    <OppSection
      id="fit" num="06"
      eyebrow="Product–Opportunity Fit"
      title="제품–기회 정합성 분석"
      lede="현재 v2 기능이 미국 LSAT/MCAT 사용자 요구를 얼마나 충족하는지. 그리고 각 기능의 Market-Fit 기여도를 점수화."
    >
      <h3 className="opp-h3">v2 × Beachhead 요구 매트릭스</h3>
      <div className="opp-table opp-table-fit">
        <div className="opp-tr opp-th">
          <div>미국 LSAT/MCAT 사용자 요구</div><div>v2 상태</div><div>격차 / 권고</div>
        </div>
        {data.fitMatrix.map((f, i) => (
          <div key={i} className="opp-tr">
            <div className="opp-feat">{f.req}</div>
            <div><OppStateDot state={f.state} /></div>
            <div>{f.gap}</div>
          </div>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">기능별 Market-Fit 점수</h3>
      <div className="opp-table opp-table-score">
        <div className="opp-tr opp-th">
          <div>기능</div>
          <div>시장</div>
          <div>레거시</div>
          <div>공백</div>
          <div>비용</div>
          <div>종합</div>
        </div>
        {data.fitScores.map((s, i) => (
          <div key={i} className={"opp-tr" + (s.exclude ? " is-excluded" : "")}>
            <div className="opp-feat">{s.feat}{s.exclude && <span className="opp-excluded-tag">배제</span>}</div>
            <div className="opp-fire">{"🔥".repeat(s.market)}</div>
            <div className="opp-fire">{s.legacy ? "🔥".repeat(s.legacy) : "—"}</div>
            <div className="opp-fire">{s.gap ? "🔥".repeat(s.gap) : "낮음"}</div>
            <div className="opp-mono opp-cost">{s.cost}</div>
            <div><StarBar score={s.score} /></div>
          </div>
        ))}
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §7 Money / $100k MRR
------------------------------------------------------------------ */

function OppMoney({ data }) {
  const maxMrr = Math.max(...data.pricingMath.map(p => p.mrr));
  return (
    <OppSection
      id="money" num="07"
      eyebrow="Monetization Math"
      title="$100k MRR 설계"
      lede="$12.99 / $59.99 / $149 — 3-tier 프리미엄 포지셔닝. 12–18개월 도달 경로 5단계."
    >
      <h3 className="opp-h3">가격 구조 — 4 Tier</h3>
      <div className="opp-tiers">
        {data.tiers.map((t, i) => (
          <OppFadeIn key={t.name} delay={i * 80} className={"opp-tier" + (t.hero ? " is-hero" : "")}>
            <div className="opp-tier-head">
              <div className="opp-tier-tag">{t.tag}</div>
              <h4 className="opp-tier-name">{t.name}</h4>
              <div className="opp-tier-price">{t.price}</div>
            </div>
            <ul className="opp-tier-features">
              {t.features.map((f, idx) => <li key={idx}>{f}</li>)}
            </ul>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">$100k MRR 도달 경로 — Timeline</h3>
      <div className="opp-mrr-timeline">
        {data.pricingMath.map((p, i) => {
          const pct = (p.mrr / maxMrr) * 100;
          return (
            <OppFadeIn key={i} delay={i * 90} className={"opp-mrr-step" + (p.hero ? " is-hero" : "")}>
              <div className="opp-mrr-phase">{p.phase}</div>
              <div className="opp-mrr-months opp-mono">{p.months}</div>
              <div className="opp-mrr-bar-wrap">
                <div className="opp-mrr-bar" style={{ width: `${pct}%` }}>
                  <span className="opp-mrr-mrr opp-mono">${(p.mrr / 1000).toFixed(0)}k</span>
                </div>
              </div>
              <div className="opp-mrr-stats">
                <span><span className="opp-mrr-k">MAU</span> <span className="opp-mono">{(p.mau >= 1000 ? (p.mau / 1000).toFixed(0) + "k" : p.mau)}</span></span>
                <span><span className="opp-mrr-k">유료</span> <span className="opp-mono">{p.paid.toLocaleString("en-US")}</span></span>
              </div>
            </OppFadeIn>
          );
        })}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Hard Paywall 플로우</h3>
      <p className="opp-section-sub">RevenueCat 2025 벤치마크: Hard paywall이 soft 대비 전환율 <strong>5배</strong>. LSAT/MCAT 세그먼트는 노출 즉시 가치 약속.</p>
      <div className="opp-paywall-flow">
        {data.paywallFlow.map((s, i) => (
          <React.Fragment key={i}>
            <div className={"opp-flow-step" + (s.hero ? " is-hero" : "")}>
              <div className="opp-flow-num">{i + 1}</div>
              <div className="opp-flow-step-name">{s.step}</div>
              <div className="opp-flow-desc">{s.desc}</div>
            </div>
            {i < data.paywallFlow.length - 1 && <span className="opp-flow-arrow">→</span>}
          </React.Fragment>
        ))}
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §8 Growth
------------------------------------------------------------------ */

function LoopDiagram({ steps }) {
  const n = steps.length;
  const r = 110;
  const cx = 150, cy = 150;
  return (
    <svg viewBox="0 0 300 300" className="opp-loop-svg">
      <defs>
        <marker id="opp-loop-arrow" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M 0 0 L 10 5 L 0 10 z" fill="var(--accent)" />
        </marker>
      </defs>
      <circle cx={cx} cy={cy} r={r} fill="none" stroke="var(--border-default)" strokeDasharray="3 5" />
      {steps.map((_, i) => {
        const a = (i / n) * Math.PI * 2 - Math.PI / 2;
        const x = cx + Math.cos(a) * r;
        const y = cy + Math.sin(a) * r;
        return <circle key={i} cx={x} cy={y} r={6} fill="var(--accent)" />;
      })}
      {/* arc segments connecting nodes */}
      {steps.map((_, i) => {
        const a1 = (i / n) * Math.PI * 2 - Math.PI / 2;
        const a2 = ((i + 1) / n) * Math.PI * 2 - Math.PI / 2;
        const x1 = cx + Math.cos(a1) * r;
        const y1 = cy + Math.sin(a1) * r;
        const x2 = cx + Math.cos(a2) * r;
        const y2 = cy + Math.sin(a2) * r;
        return (
          <path key={i} d={`M ${x1} ${y1} A ${r} ${r} 0 0 1 ${x2} ${y2}`}
                fill="none" stroke="var(--accent)" strokeWidth="1.4" markerEnd="url(#opp-loop-arrow)" opacity="0.7" />
        );
      })}
    </svg>
  );
}

function OppGrowth({ data }) {
  return (
    <OppSection
      id="growth" num="08"
      eyebrow="Growth Loops"
      title="성장 루프 & 채널 전략"
      lede="레거시 인사이트로 검증된 3개 바이럴 루프. CAC 효율 기준 채널 우선순위."
    >
      <h3 className="opp-h3">3개 바이럴 루프</h3>
      <div className="opp-loops">
        {data.loops.map((l) => (
          <div key={l.id} className="opp-loop-card">
            <div className="opp-loop-id">루프 {l.id}</div>
            <h4 className="opp-loop-title">{l.title}</h4>
            <div className="opp-loop-viz">
              <LoopDiagram steps={l.steps} />
              <ol className="opp-loop-steps">
                {l.steps.map((s, i) => <li key={i}>{s}</li>)}
              </ol>
            </div>
            <div className="opp-loop-note">{l.note}</div>
          </div>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">채널 우선순위 — CAC 효율</h3>
      <div className="opp-table opp-table-channel">
        <div className="opp-tr opp-th">
          <div>채널</div><div>CAC</div><div>확장성</div><div>우선순위</div>
        </div>
        {data.channels.map((c, i) => (
          <div key={i} className="opp-tr">
            <div className="opp-feat">{c.name}</div>
            <div className="opp-mono">{c.cac}</div>
            <div>{c.scale}</div>
            <div><OppPriBadge p={c.pri} /></div>
          </div>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Don't do — 초기 6개월</h3>
      <ul className="opp-dontdo">
        {data.dontDo.map((d, i) => <li key={i}>{d}</li>)}
      </ul>

      <h3 className="opp-h3 opp-h3-spaced">ASO 키워드 타겟팅</h3>
      <div className="opp-aso">
        <div className="opp-aso-row">
          <div className="opp-aso-k">Primary</div>
          <div className="opp-aso-tags">
            {data.asoKeywords.primary.map((k, i) => <span key={i} className="opp-aso-tag opp-aso-tag-primary">{k}</span>)}
          </div>
        </div>
        <div className="opp-aso-row">
          <div className="opp-aso-k">Long-tail</div>
          <div className="opp-aso-tags">
            {data.asoKeywords.longtail.map((k, i) => <span key={i} className="opp-aso-tag">{k}</span>)}
          </div>
        </div>
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §9 Pre-mortem
------------------------------------------------------------------ */

function FailureCard({ f, i }) {
  const [open, setOpen] = React.useState(i === 0);
  return (
    <div className={"opp-failure" + (open ? " is-open" : "")}>
      <button className="opp-failure-head" onClick={() => setOpen(!open)} aria-expanded={open}>
        <span className="opp-failure-title">{f.title}</span>
        <span className="opp-failure-toggle">{open ? "—" : "+"}</span>
      </button>
      <div className="opp-failure-story">{f.story}</div>
      <div className="opp-failure-counter">
        <div className="opp-failure-counter-label">대응</div>
        <div>{f.counter}</div>
      </div>
    </div>
  );
}

function OppRisk({ data }) {
  return (
    <OppSection
      id="risk" num="09"
      eyebrow="Pre-mortem"
      title="위험 · 가정 · 12개월 후 실패 시나리오"
      lede="런칭 전 검증해야 할 5개 핵심 가정. 그리고 1년 뒤 '실패했다면 왜?' 시나리오 5개와 사전 대응."
    >
      <h3 className="opp-h3">반드시 검증해야 할 핵심 가정 5</h3>
      <div className="opp-table opp-table-assume">
        <div className="opp-tr opp-th">
          <div>#</div><div>가정</div><div>검증 방법</div><div>실패 비용</div>
        </div>
        {data.assumptions.map((a) => (
          <div key={a.id} className="opp-tr">
            <div className="opp-mono opp-assume-id">{a.id}</div>
            <div>{a.text}</div>
            <div className="opp-assume-method">{a.method}</div>
            <div><span className={"opp-cost-pill opp-cost-" + a.cost.toLowerCase().replace(/[^a-z]/g, "")}>{a.cost}</span></div>
          </div>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Pre-mortem 시나리오 5</h3>
      <div className="opp-failures">
        {data.failures.map((f, i) => <FailureCard key={i} f={f} i={i} />)}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">금지 사항 — Don'ts</h3>
      <ul className="opp-donts">
        {data.donts.map((d, i) => (
          <li key={i}><span className="opp-x">✗</span><span>{d}</span></li>
        ))}
      </ul>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §10 Roadmap
------------------------------------------------------------------ */

function OppRoadmap({ data }) {
  return (
    <OppSection
      id="roadmap" num="10"
      eyebrow="Execution"
      title="12개월 로드맵 — 분기별"
      lede="MVP → Public Launch → Retention → $100k Push. 분기 단위로 목표를 명확히, 우선순위 P0부터."
    >
      <div className="opp-roadmap">
        {data.roadmap.map((q, i) => (
          <OppFadeIn key={q.q} delay={i * 80} className="opp-quarter">
            <div className="opp-quarter-mark">
              <div className="opp-quarter-q">{q.q}</div>
              <div className="opp-quarter-months">{q.months}</div>
            </div>
            <div className="opp-quarter-body">
              <h4 className="opp-quarter-title">{q.title}</h4>
              <div className="opp-quarter-goal">{q.goal}</div>
              <ul className="opp-quarter-items">
                {q.items.map((it, idx) => (
                  <li key={idx}>
                    <OppPriBadge p={it.p} />
                    <span>{it.t}</span>
                  </li>
                ))}
              </ul>
            </div>
          </OppFadeIn>
        ))}
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §11 Metrics
------------------------------------------------------------------ */

function OppMetrics({ data }) {
  return (
    <OppSection
      id="metrics" num="11"
      eyebrow="Success Metrics"
      title="성공 지표 — North Star + Inputs"
      lede="MAU는 허영. 유료 전환 + 실제 사용 + 핵심 기능 경험을 모두 담은 단일 지표를 추적한다."
    >
      <div className="opp-northstar">
        <div className="opp-northstar-glow" aria-hidden="true" />
        <div className="opp-northstar-mark">North Star</div>
        <div className="opp-northstar-code">{data.northStar.code}</div>
        <div className="opp-northstar-name">{data.northStar.name}</div>
        <p className="opp-northstar-why">{data.northStar.why}</p>
        <div className="opp-northstar-targets">
          {data.northStar.targets.map((t) => (
            <div key={t.time} className="opp-target">
              <div className="opp-target-time">{t.time}</div>
              <div className="opp-target-num"><CountUp end={t.n} format="exact" /></div>
            </div>
          ))}
        </div>
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Input Metrics — North Star 구동 5개</h3>
      <div className="opp-inputs">
        {data.inputs.map((m, i) => (
          <OppFadeIn key={i} delay={i * 80} className="opp-input">
            <div className="opp-input-head">
              <div className="opp-input-name">{m.name}</div>
              <div className="opp-input-def">{m.def}</div>
            </div>
            <div className="opp-input-bars">
              <div className="opp-input-bar-row">
                <span className="opp-input-bar-label">M6</span>
                <div className="opp-input-bar-track">
                  <div className="opp-input-bar opp-input-bar-m6" style={{ width: `${m.m6}%` }}>
                    <span>{m.m6}{m.unit}</span>
                  </div>
                </div>
              </div>
              <div className="opp-input-bar-row">
                <span className="opp-input-bar-label">M12</span>
                <div className="opp-input-bar-track">
                  <div className="opp-input-bar opp-input-bar-m12" style={{ width: `${m.m12}%` }}>
                    <span>{m.m12}{m.unit}</span>
                  </div>
                </div>
              </div>
            </div>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">Guardrail Metrics</h3>
      <div className="opp-guardrails">
        {data.guardrails.map((g, i) => (
          <div key={i} className="opp-guardrail">
            <div className="opp-guardrail-k">{g.k}</div>
            <div className="opp-guardrail-v">{g.v}</div>
            <div className="opp-guardrail-why">{g.why}</div>
          </div>
        ))}
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   §12 Decisions
------------------------------------------------------------------ */

function OppDecisions({ data }) {
  return (
    <OppSection
      id="decisions" num="12"
      eyebrow="Hand-off"
      title="결정이 필요한 7가지"
      lede="이 문서를 넘겨받는 사람에게. 각 결정은 트레이드오프를 포함한다."
    >
      <div className="opp-decisions">
        {data.decisions.map((d, i) => (
          <OppFadeIn key={d.n} delay={i * 60} className="opp-decision">
            <div className="opp-decision-num">{String(d.n).padStart(2, "0")}</div>
            <div className="opp-decision-body">
              <div className="opp-decision-q">
                <span className="opp-decision-mark">?</span>
                {d.q}
              </div>
              <p className="opp-decision-tradeoff">{d.body}</p>
            </div>
          </OppFadeIn>
        ))}
      </div>
    </OppSection>
  );
}

/* ------------------------------------------------------------------
   Appendix A — Top 10 quotes
------------------------------------------------------------------ */

function OppAppendix({ data }) {
  return (
    <OppSection
      id="appendix" num="A"
      eyebrow="Appendix"
      title="레거시 리뷰 Top 10 — 벽에 붙여둘 것"
      lede="이 차기작이 누구를 위해 만들어지는지 잊지 않기 위해, 10년 동안 사용자들이 남긴 가장 강력한 목소리들."
    >
      <div className="opp-quotes-grid">
        {data.topQuotes.map((q, i) => (
          <OppFadeIn key={i} delay={i * 50} className="opp-quote-card">
            <div className="opp-quote-num">{String(i + 1).padStart(2, "0")}</div>
            <blockquote className="opp-quote-text">"{q.q}"</blockquote>
            <div className="opp-quote-foot">
              <span className="opp-quote-flag">{q.flag}</span>
              <span className="opp-quote-who">{q.who}</span>
              <span className="opp-quote-year opp-mono">{q.year}</span>
              <span className="opp-quote-stars">{"★".repeat(q.stars)}</span>
            </div>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">부록 B — 출처 파일</h3>
      <ul className="opp-sources">
        {data.appendixSources.map((s, i) => <li key={i} className="opp-mono">{s}</li>)}
      </ul>

      <div className="opp-credit">
        <div>작성: <span className="opp-mono">2026-04-25</span> · {data.meta.authors}</div>
        <div className="opp-credit-next">다음 단계: 위 "결정 7가지"에 대한 파운더 판단 → Q1 MVP 범위 확정 → 2026-06 Private Beta 시작</div>
      </div>
    </OppSection>
  );
}

/* ==================================================================
   STRATEGIC REPORT — Tab A (existing 1차 리포트)
================================================================== */

function StrategicReport({ data, activeId, setActiveId, sectionRefs, jump, scrollToFirst }) {
  return (
    <React.Fragment>
      <OppHero data={data} onScrollDown={scrollToFirst} />

      {/* Executive summary anchor target (so toc 00 jumps near hero) */}
      <div id="exec" style={{ position: "relative", top: -60 }} />

      <div className="opp-layout">
        <OppToc items={data.toc} activeId={activeId} onJump={jump} />

        <article className="opp-article">
          {/* Risks (part of executive summary) */}
          <section className="opp-section opp-risks-section">
            <header className="opp-section-head">
              <div className="opp-eyebrow">Executive Summary · 핵심 리스크</div>
              <h2 className="opp-section-title">3가지 리스크가 모든 결정에 깔려 있다</h2>
            </header>
            <div className="opp-risks">
              {data.risks.map((r, i) => (
                <OppFadeIn key={i} delay={i * 80} className="opp-risk">
                  <div className="opp-risk-tag">{r.tag}</div>
                  <p className="opp-risk-text">{r.text}</p>
                </OppFadeIn>
              ))}
            </div>
          </section>

          <OppLegacy data={data} />
          <OppMarket data={data} />
          <OppV2 data={data} />
          <OppJtbd data={data} />
          <OppValueProp data={data} />
          <OppFit data={data} />
          <OppMoney data={data} />
          <OppGrowth data={data} />
          <OppRisk data={data} />
          <OppRoadmap data={data} />
          <OppMetrics data={data} />
          <OppDecisions data={data} />
          <OppAppendix data={data} />
        </article>
      </div>
    </React.Fragment>
  );
}

/* ==================================================================
   REALITY CHECK REPORT — Tab B (새 비판적 + OST 리포트)
================================================================== */

/* ----- Cold Hero ----- */
function V2Hero({ data, onScrollDown }) {
  const scrollY = useScrollY();
  const titleShift = Math.min(scrollY * 0.18, 80);
  const titleOpacity = Math.max(0, 1 - scrollY / 600);

  return (
    <section className="opp-hero opp-v2-hero">
      <div className="opp-hero-bg" aria-hidden="true">
        <div className="opp-hero-grid" />
        <div className="opp-hero-orb opp-orb-b" />
        <div className="opp-hero-orb opp-orb-c" />
      </div>
      <div className="opp-hero-inner" style={{ transform: `translateY(${titleShift}px)`, opacity: titleOpacity }}>
        <div className="opp-hero-eyebrow opp-v2-hero-eyebrow">{data.meta.eyebrow}</div>
        <h1 className="opp-hero-title">
          현실 검증 +<br/><em>Opportunity Solution Tree</em>
        </h1>
        <p className="opp-hero-sub">{data.meta.subtitle}</p>

        <div className="opp-hero-quote opp-v2-cold-quote">
          <span className="opp-quote-mark">"</span>
          <p>{data.meta.coldThesis}</p>
        </div>

        <div className="opp-hero-thesis">
          {data.coldFindings.map((t, i) => (
            <OppFadeIn key={t.n} delay={i * 90} className="opp-thesis-card opp-v2-finding">
              <div className="opp-thesis-num opp-v2-finding-num">{t.tag}</div>
              <div className="opp-thesis-title">{t.title}</div>
              <div className="opp-thesis-body">{t.body}</div>
              <div className="opp-thesis-proof opp-v2-finding-proof">{t.proof}</div>
            </OppFadeIn>
          ))}
        </div>

        <div className="opp-hero-meta">
          <div><span className="opp-mono">2026 · 04 · 25</span></div>
          <span className="opp-hero-dot" />
          <div className="opp-hero-meta-mut">{data.meta.authors}</div>
        </div>

        <button className="opp-scroll-hint" onClick={onScrollDown} aria-label="아래로 스크롤">
          ↓ 차가운 진단 시작
        </button>
      </div>
    </section>
  );
}

/* ----- §1 MRR Ceiling ----- */
function V2Mrr({ data }) {
  const m = data.mrr;
  const maxMau = Math.max(...m.conversionScenarios.map(s => s.mauRequired));
  const ceiling = m.ceilingCurve;
  const targetMrr = ceiling.target;
  const W = 640, H = 240;
  const padX = 36, padY = 22;
  const xs = ceiling.points.map(p => p.m);
  const xMax = Math.max(...xs);
  const yMax = Math.max(targetMrr, ...ceiling.points.map(p => p.mrr)) * 1.05;
  const sx = (m) => padX + (m / xMax) * (W - padX * 2);
  const sy = (v) => H - padY - (v / yMax) * (H - padY * 2);

  const linePath = ceiling.points.map((p, i) => {
    return (i === 0 ? "M" : "L") + sx(p.m) + " " + sy(p.mrr);
  }).join(" ");

  const areaPath = linePath +
    " L" + sx(ceiling.points[ceiling.points.length - 1].m) + " " + (H - padY) +
    " L" + sx(ceiling.points[0].m) + " " + (H - padY) + " Z";

  return (
    <OppSection
      id="v2-mrr" num="01"
      eyebrow="달콤한 가정의 종말"
      title="$100k MRR 산수 — 새 가격에서의 천장"
      lede="$4.99 / $29.99 가격에서 $100k MRR은 28,500명. 1차 리포트 ($12.99/$59.99)의 14,000명 대비 2배. 자동 재분배 단독 기능으로는 도달 불가."
    >
      {/* ARPU side-by-side */}
      <h3 className="opp-h3">가격 비교 — 1차 vs 2차 ARPU</h3>
      <div className="opp-v2-price-compare">
        {m.priceCompare.map((p, i) => (
          <OppFadeIn key={i} delay={i * 80} className={"opp-v2-price-card" + (p.hero ? " is-hero" : "")}>
            <div className="opp-v2-price-label">{p.label}</div>
            <div className="opp-v2-price-row">
              <div>
                <div className="opp-v2-price-k">월간</div>
                <div className="opp-v2-price-v opp-mono">${p.monthly.toFixed(2)}</div>
              </div>
              <div>
                <div className="opp-v2-price-k">연간</div>
                <div className="opp-v2-price-v opp-mono">${p.annual.toFixed(2)}</div>
              </div>
              <div>
                <div className="opp-v2-price-k">평균 ARPU</div>
                <div className="opp-v2-price-v opp-v2-price-arpu opp-mono">${p.arpu.toFixed(2)}/월</div>
              </div>
              <div>
                <div className="opp-v2-price-k">$100k MRR 필요 구독자</div>
                <div className="opp-v2-price-v opp-v2-price-subs opp-mono">{p.requiredSubs.toLocaleString("en-US")}</div>
              </div>
            </div>
          </OppFadeIn>
        ))}
      </div>
      <p className="opp-section-sub opp-v2-emph">
        <strong className="opp-v2-warn-strong">→ 새 가격에서 2배 이상의 활성 유료 구독자가 필요하다.</strong>
      </p>

      {/* Conversion Scenario Bars */}
      <h3 className="opp-h3 opp-h3-spaced">전환율 시나리오 — 필요 MAU</h3>
      <p className="opp-section-sub">
        28,500명 유료 구독자에 도달하려면 전환율별로 필요한 MAU. 1%(산업 평균)는 <strong>비현실적</strong>, 8%(best-in-class)만이 현실적.
      </p>
      <div className="opp-v2-scenarios">
        {m.conversionScenarios.map((s, i) => {
          const pct = (s.mauRequired / maxMau) * 100;
          return (
            <OppFadeIn key={i} delay={i * 80} className={"opp-v2-scenario opp-v2-via-" + s.viabilityDot}>
              <div className="opp-v2-scenario-rate">{s.label}</div>
              <div className="opp-v2-scenario-bar-wrap">
                <div className="opp-v2-scenario-bar" style={{ width: `${pct}%` }}>
                  <span className="opp-mono">{(s.mauRequired / 1000).toFixed(0)}k</span>
                </div>
              </div>
              <div className="opp-v2-scenario-meta">
                <OppStateDot state={s.viabilityDot} />
                <span className="opp-v2-scenario-viability">{s.viability}</span>
                <span className="opp-v2-scenario-note">{s.note}</span>
              </div>
            </OppFadeIn>
          );
        })}
      </div>

      {/* Ceiling curve */}
      <h3 className="opp-h3 opp-h3-spaced">현실적 천장 곡선 — 24개월 plateau</h3>
      <p className="opp-section-sub">
        자동 재분배 단독 기능 + $4.99/$29.99 가격 가정. 24개월 후 천장 도달 추정: <strong className="opp-v2-warn-strong">$25k–$45k MRR</strong>.
      </p>
      <div className="opp-v2-ceiling-wrap">
        <svg className="opp-v2-ceiling-svg" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet">
          <defs>
            <linearGradient id="opp-v2-ceiling-grad" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%"  stopColor="var(--accent)" stopOpacity="0.3" />
              <stop offset="100%" stopColor="var(--accent)" stopOpacity="0" />
            </linearGradient>
          </defs>

          {/* gridlines */}
          {[0, 0.25, 0.5, 0.75, 1].map((p, i) => {
            const y = padY + (1 - p) * (H - padY * 2);
            return (
              <g key={i}>
                <line x1={padX} y1={y} x2={W - padX} y2={y}
                      stroke="var(--border-default)" strokeDasharray="2 4" opacity="0.5" />
                <text x={padX - 6} y={y + 4} textAnchor="end" fontSize="10"
                      fontFamily="var(--font-mono)" fill="var(--fg-subtle)">
                  ${Math.round(p * yMax / 1000)}k
                </text>
              </g>
            );
          })}

          {/* x-axis tick labels */}
          {[0, 6, 12, 18, 24].map((mo) => (
            <text key={mo} x={sx(mo)} y={H - 4} textAnchor="middle" fontSize="10"
                  fontFamily="var(--font-mono)" fill="var(--fg-subtle)">M{mo}</text>
          ))}

          {/* Target line ($100k) */}
          <line x1={padX} y1={sy(targetMrr)} x2={W - padX} y2={sy(targetMrr)}
                stroke="var(--accent-coral-500)" strokeDasharray="4 4" strokeWidth="1.5" opacity="0.7" />
          <text x={W - padX - 4} y={sy(targetMrr) - 6} textAnchor="end" fontSize="10"
                fontFamily="var(--font-mono)" fill="var(--accent-coral-500)" fontWeight="600">
            $100k 목표 (도달 불가)
          </text>

          {/* Ceiling band */}
          <rect x={padX} y={sy(ceiling.ceilingHigh)}
                width={W - padX * 2}
                height={Math.max(0, sy(ceiling.ceilingLow) - sy(ceiling.ceilingHigh))}
                fill="var(--accent-amber-500)" opacity="0.12" />
          <text x={W - padX - 6} y={sy(ceiling.ceilingHigh) + 14} textAnchor="end" fontSize="10"
                fontFamily="var(--font-mono)" fill="var(--accent-amber-700)" fontWeight="600">
            추정 천장 $25k–$45k
          </text>

          {/* curve */}
          <path d={areaPath} fill="url(#opp-v2-ceiling-grad)" />
          <path d={linePath} fill="none" stroke="var(--accent)" strokeWidth="2.4" strokeLinejoin="round" />
          {ceiling.points.map((p, i) => (
            <circle key={i} cx={sx(p.m)} cy={sy(p.mrr)} r="3.5"
                    fill="var(--accent)" stroke="var(--bg-canvas)" strokeWidth="1.5" />
          ))}
        </svg>
      </div>

      <h3 className="opp-h3 opp-h3-spaced">왜 천장이 낮은가 — 4가지 이유</h3>
      <div className="opp-v2-reasons">
        {m.ceilingReasons.map((r, i) => (
          <OppFadeIn key={i} delay={i * 60} className="opp-v2-reason">
            <div className="opp-v2-reason-k">{r.k}</div>
            <div className="opp-v2-reason-v">{r.v}</div>
          </OppFadeIn>
        ))}
      </div>

      <div className="opp-callout opp-v2-callout-warn">
        <div className="opp-callout-mark">결론</div>
        <p>
          $100k MRR은 <strong>현재 기능셋만으로는 도달 불가</strong>. 추가 모듈(Multi-resource Integrator, FL Score Tracker, 18-month Window Optimizer 등) 또는 가격 상향 ($9.99/월) 둘 중 하나는 반드시 필요하다.
        </p>
      </div>
    </OppSection>
  );
}

/* ----- §2 Asymmetry ----- */
function V2Asymmetry({ data }) {
  const a = data.asymmetry;
  return (
    <OppSection
      id="v2-asymmetry" num="02"
      eyebrow="한국에서 작동했다는 증거의 한계"
      title="한국 ≠ 미국 — 5가지 비대칭"
      lede="단일 절대시험, 공부 시간 = 사회적 통화 — 한국 검증을 떠받친 두 기둥은 미국에 없다. 5축 중 핵심 3축이 불리하다."
    >
      <h3 className="opp-h3">5축 비대칭 매트릭스</h3>
      <div className="opp-table opp-v2-asym-table">
        <div className="opp-tr opp-th">
          <div>#</div><div>차원</div><div>한국 (검증된 곳)</div><div>미국 (가정해야 할 곳)</div><div>비대칭도</div>
        </div>
        {a.rows.map((r) => (
          <div key={r.n} className={"opp-tr opp-v2-asym-row opp-v2-asym-" + r.level}>
            <div className="opp-mono opp-v2-asym-n">{r.n}</div>
            <div className="opp-v2-asym-dim">{r.dim}</div>
            <div className="opp-v2-asym-cell">{r.kr}</div>
            <div className="opp-v2-asym-cell">{r.us}</div>
            <div>
              <span className={"opp-v2-asym-pill opp-v2-asym-pill-" + r.level}>{r.levelLabel}</span>
            </div>
          </div>
        ))}
      </div>

      <div className="opp-callout opp-v2-callout-cold">
        <div className="opp-callout-mark">차가운 진단</div>
        <p>{data.meta.coldThesis}</p>
      </div>

      <h3 className="opp-h3 opp-h3-spaced">검증해야 살아남는 5가지 핵심 가정</h3>
      <div className="opp-v2-assume-grid">
        {a.assumptions.map((as, i) => (
          <OppFadeIn key={as.id} delay={i * 70} className={"opp-v2-assume opp-v2-assume-" + as.risk}>
            <div className="opp-v2-assume-id opp-mono">{as.id}</div>
            <div className="opp-v2-assume-text">{as.text}</div>
            <div className={"opp-v2-assume-risk opp-v2-assume-risk-" + as.risk}>{as.riskLabel}</div>
          </OppFadeIn>
        ))}
      </div>
    </OppSection>
  );
}

/* ----- Reusable: Persona + Pain coverage ----- */
function V2PersonaBlock({ persona, pains, opportunities, coverage, accent }) {
  const maxWeight = Math.max(...pains.map(p => p.weight));
  return (
    <React.Fragment>
      <div className={"opp-v2-persona opp-v2-persona-" + accent}>
        <div className="opp-v2-persona-flag">{persona.flag}</div>
        <div className="opp-v2-persona-meta">
          <div className="opp-v2-persona-label">{persona.label}</div>
          <div className="opp-v2-persona-stats">
            {persona.stats.map((s, i) => (
              <div key={i} className="opp-v2-persona-stat">
                <span className="opp-v2-persona-stat-k">{s.k}</span>
                <span className="opp-v2-persona-stat-v">{s.v}</span>
              </div>
            ))}
          </div>
        </div>
      </div>

      <h3 className="opp-h3 opp-h3-spaced">진짜 페인 5가지 — 가중치순</h3>
      <div className="opp-v2-pain-list">
        {pains.map((p, i) => (
          <OppFadeIn key={p.n} delay={i * 70} className="opp-v2-pain">
            <div className="opp-v2-pain-rank opp-mono">#{p.n}</div>
            <div className="opp-v2-pain-body">
              <div className="opp-v2-pain-key">{p.key}</div>
              <div className="opp-v2-pain-text">{p.body}</div>
            </div>
            <div className="opp-v2-pain-weight-wrap">
              <div className="opp-v2-pain-weight-bar"
                   style={{ width: `${(p.weight / maxWeight) * 100}%` }} />
              <span className="opp-v2-pain-weight-num opp-mono">{p.weight}</span>
            </div>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">진짜 기회 5가지</h3>
      <div className="opp-v2-opp-grid">
        {opportunities.map((o, i) => (
          <OppFadeIn key={o.n} delay={i * 60} className="opp-v2-opp-card">
            <div className="opp-v2-opp-icon">{o.icon}</div>
            <div className="opp-v2-opp-num opp-mono">기회 {String(o.n).padStart(2, "0")}</div>
            <div className="opp-v2-opp-title">{o.t}</div>
            <p className="opp-v2-opp-body">{o.body}</p>
            <div className="opp-v2-opp-paid">{o.paid}</div>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">v2 (자동 재분배) 커버리지</h3>
      <p className="opp-section-sub">
        현재 v2 단일 기능이 위 5대 페인을 얼마나 덮는가. <strong>총 커버: <span className={"opp-v2-cover-num opp-v2-cover-" + accent}>{coverage.currentV2Cover}%</span></strong> · 격차: {coverage.gapPercent}%
      </p>
      <div className="opp-v2-cover-grid">
        {coverage.breakdown.map((c, i) => (
          <div key={i} className={"opp-v2-cover-row opp-v2-cover-state-" + c.state}>
            <OppStateDot state={c.state} />
            <div className="opp-v2-cover-pain">{c.pain}</div>
            <div className="opp-v2-cover-meter">
              <div className="opp-v2-cover-meter-bar"
                   style={{ width: `${c.cover}%` }} />
            </div>
            <div className="opp-v2-cover-pct opp-mono">{c.cover}%</div>
          </div>
        ))}
      </div>

      {coverage.verdict && (
        <div className="opp-callout opp-v2-callout-good">
          <div className="opp-callout-mark">권고</div>
          <p>{coverage.verdict}</p>
        </div>
      )}
    </React.Fragment>
  );
}

/* ----- §3 MCAT ----- */
function V2Mcat({ data }) {
  return (
    <OppSection
      id="v2-mcat" num="03"
      eyebrow="진짜 돈 내는 사람들의 진짜 페인 — MCAT"
      title="MCAT 학생의 진짜 기회"
      lede="MCAT은 4-6개월 풀타임 학습. 자료 분산이 가장 큰 페인이지만 v2 자동 재분배는 이 페인을 거의 못 푼다 (커버 20%)."
    >
      <V2PersonaBlock
        persona={data.mcat.persona}
        pains={data.mcat.pains}
        opportunities={data.mcat.opportunities}
        coverage={data.mcat.coverage}
        accent="coral"
      />
    </OppSection>
  );
}

/* ----- §4 CPA ----- */
function V2Cpa({ data }) {
  return (
    <OppSection
      id="v2-cpa" num="04"
      eyebrow="숨어있던 진짜 적합 시장"
      title="CPA 학생의 진짜 기회"
      lede="CPA는 18개월 윈도우, 75% 직장인. 평일 야간 + 주말 변동이 핵심. v2 Working Pro Mode + Rescue Coach가 정확히 이 페인을 푼다 (커버 40%)."
    >
      <V2PersonaBlock
        persona={data.cpa.persona}
        pains={data.cpa.pains}
        opportunities={data.cpa.opportunities}
        coverage={data.cpa.coverage}
        accent="mint"
      />
    </OppSection>
  );
}

/* ----- §5 OST Tree ----- */
function OstNode({ children, type = "O", expanded, onToggle, hasChildren = true, badge, dim = false }) {
  return (
    <div className={"opp-v2-ost-node opp-v2-ost-node-" + type + (expanded ? " is-expanded" : "") + (dim ? " is-dim" : "")}>
      <button
        className="opp-v2-ost-node-head"
        onClick={hasChildren ? onToggle : undefined}
        aria-expanded={expanded}
      >
        <span className="opp-v2-ost-node-type">{type}</span>
        <span className="opp-v2-ost-node-content">{children}</span>
        {badge}
        {hasChildren && (
          <span className="opp-v2-ost-node-chev">{expanded ? "−" : "+"}</span>
        )}
      </button>
    </div>
  );
}

function OstSolution({ s }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div className="opp-v2-ost-solution">
      <OstNode
        type="S"
        expanded={open}
        onToggle={() => setOpen(!open)}
        hasChildren={s.experiments && s.experiments.length > 0}
        badge={(
          <span className="opp-v2-ost-meta">
            <OppPriBadge p={s.pri} />
            <OppStateDot state={s.state} />
          </span>
        )}
      >
        <span className="opp-v2-ost-id opp-mono">{s.id}</span>
        <span className="opp-v2-ost-title">{s.title}</span>
        {s.note && <span className="opp-v2-ost-note">{s.note}</span>}
      </OstNode>

      {open && s.experiments && (
        <div className="opp-v2-ost-experiments">
          {s.experiments.map((e) => (
            <div key={e.id} className="opp-v2-ost-experiment opp-v2-ost-node opp-v2-ost-node-E">
              <span className="opp-v2-ost-node-type">E</span>
              <div className="opp-v2-ost-exp-body">
                <span className="opp-v2-ost-id opp-mono">{e.id}</span>
                <span className="opp-v2-ost-title">{e.title}</span>
                <span className="opp-v2-ost-exp-goal">목표: {e.goal}</span>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function OstOpportunity({ o, defaultOpen = false }) {
  const [open, setOpen] = React.useState(defaultOpen);
  return (
    <div className={"opp-v2-ost-opportunity" + (open ? " is-open" : "")}>
      <OstNode
        type="O"
        expanded={open}
        onToggle={() => setOpen(!open)}
        hasChildren={o.solutions && o.solutions.length > 0}
      >
        <span className="opp-v2-ost-id opp-mono">{o.id}</span>
        <span className="opp-v2-ost-title">{o.title}</span>
        {o.sub && <span className="opp-v2-ost-sub">{o.sub}</span>}
      </OstNode>

      {open && (
        <div className="opp-v2-ost-solutions">
          {o.solutions.map((s) => <OstSolution key={s.id} s={s} />)}
        </div>
      )}
    </div>
  );
}

function V2Ost({ data }) {
  const ost = data.ost;
  const [allOpen, setAllOpen] = React.useState(false);
  // We rely on per-node state; expand-all is a hint for users.

  return (
    <OppSection
      id="v2-ost" num="05"
      eyebrow="Opportunity Solution Tree"
      title="OST — Outcome → Opportunity → Solution → Experiment"
      lede="6개 사용자 페인(O), 각 페인을 푸는 Solution(S) 15개, 각 Solution을 검증하는 Experiment(E) 10+개. 노드를 펼쳐서 전체 트리를 탐색하라."
    >
      {/* Outcome */}
      <div className="opp-v2-ost-outcome">
        <div className="opp-v2-ost-outcome-mark">Outcome</div>
        <div className="opp-v2-ost-outcome-text">{ost.outcome.label}</div>
        <div className="opp-v2-ost-outcome-hint">{ost.outcome.hint}</div>
      </div>

      {/* Legend */}
      <div className="opp-v2-ost-legend">
        <div className="opp-v2-ost-legend-item">
          <span className="opp-v2-ost-legend-pill opp-v2-ost-legend-O">O</span>
          <span>{ost.legend.O}</span>
        </div>
        <div className="opp-v2-ost-legend-item">
          <span className="opp-v2-ost-legend-pill opp-v2-ost-legend-S">S</span>
          <span>{ost.legend.S}</span>
        </div>
        <div className="opp-v2-ost-legend-item">
          <span className="opp-v2-ost-legend-pill opp-v2-ost-legend-E">E</span>
          <span>{ost.legend.E}</span>
        </div>
      </div>

      {/* Tree */}
      <div className="opp-v2-ost-tree">
        <div className="opp-v2-ost-trunk-line" aria-hidden="true" />
        <div className="opp-v2-ost-opportunities">
          {ost.opportunities.map((o, i) => (
            <OstOpportunity key={o.id} o={o} defaultOpen={i === 0} />
          ))}
        </div>
      </div>

      <p className="opp-section-sub opp-v2-ost-hint">
        각 노드를 클릭해서 펼치고 접을 수 있습니다. <strong>P0</strong> 솔루션은 우선순위 핵심, <strong>E</strong> 노드는 가설 검증 실험.
      </p>
    </OppSection>
  );
}

/* ----- §6 Roadmap ----- */
function V2Roadmap({ data }) {
  const r = data.roadmap;
  return (
    <OppSection
      id="v2-roadmap" num="06"
      eyebrow="현실주의 12개월 재구성"
      title="12개월 로드맵 — CPA 우선 → MCAT 확장"
      lede="1차 리포트의 LSAT/MCAT 동시 진입을 폐기. CPA 베타를 1차 검증축으로, 가격 인상 실험을 분기 단위로 명시화. M12에 천장 도달 여부로 분기 결정."
    >
      <div className="opp-v2-roadmap">
        {r.quarters.map((q, i) => (
          <OppFadeIn key={q.q} delay={i * 80} className="opp-v2-quarter">
            <div className="opp-v2-quarter-mark">
              <div className="opp-v2-quarter-q">{q.q}</div>
              <div className="opp-v2-quarter-months opp-mono">{q.months}</div>
            </div>
            <div className="opp-v2-quarter-body">
              <h4 className="opp-v2-quarter-title">{q.title}</h4>
              <div className="opp-v2-quarter-goal">{q.goal}</div>
              <div className="opp-v2-quarter-verify">
                <span className="opp-v2-quarter-verify-mark">검증 가정</span>
                {q.verifies.map((v) => (
                  <span key={v} className="opp-v2-quarter-verify-tag opp-mono">{v}</span>
                ))}
                <span className="opp-v2-quarter-verify-text">{q.verifyText}</span>
              </div>
              <ul className="opp-v2-quarter-items">
                {q.items.map((it, idx) => (
                  <li key={idx}>
                    <OppPriBadge p={it.p} />
                    <span>{it.t}</span>
                  </li>
                ))}
              </ul>
            </div>
          </OppFadeIn>
        ))}
      </div>

      {/* M12 decision tree */}
      <h3 className="opp-h3 opp-h3-spaced">{r.decision.title}</h3>
      <div className="opp-v2-decision-branches">
        {r.decision.branches.map((b, i) => (
          <OppFadeIn key={i} delay={i * 80} className="opp-v2-decision-branch">
            <div className="opp-v2-decision-cond">
              <span className="opp-v2-decision-mark">IF</span>
              {b.cond}
            </div>
            <div className="opp-v2-decision-action">
              <span className="opp-v2-decision-mark">→</span>
              {b.action}
            </div>
          </OppFadeIn>
        ))}
      </div>

      <h3 className="opp-h3 opp-h3-spaced">1차 리포트 vs 2차 리포트 — 톤 변화</h3>
      <div className="opp-v2-tone-shift">
        <div className="opp-v2-tone-row opp-v2-tone-head">
          <div>1차 (낙관)</div><div>2차 (현실)</div>
        </div>
        {data.toneShift.map((t, i) => (
          <div key={i} className="opp-v2-tone-row">
            <div className="opp-v2-tone-v1">{t.v1}</div>
            <div className="opp-v2-tone-v2">{t.v2}</div>
          </div>
        ))}
      </div>
    </OppSection>
  );
}

/* ----- Reality Check Report wrapper ----- */
function RealityCheckReport({ data, activeId, jump, scrollToFirst }) {
  return (
    <React.Fragment>
      <V2Hero data={data} onScrollDown={scrollToFirst} />

      <div id="v2-exec" style={{ position: "relative", top: -60 }} />

      <div className="opp-layout">
        <OppToc items={data.toc} activeId={activeId} onJump={jump} />

        <article className="opp-article opp-v2-article">
          {/* Cold Executive */}
          <section className="opp-section opp-risks-section opp-v2-cold-exec">
            <header className="opp-section-head">
              <div className="opp-eyebrow opp-v2-cold-eyebrow">차가운 진단 · Executive</div>
              <h2 className="opp-section-title">낙관적이지 마라 — 3가지 차가운 발견</h2>
            </header>
            <div className="opp-risks">
              {data.coldFindings.map((f, i) => (
                <OppFadeIn key={i} delay={i * 80} className="opp-risk opp-v2-risk-cold">
                  <div className="opp-risk-tag opp-v2-finding-tag">{f.tag}</div>
                  <p className="opp-risk-text"><strong>{f.title}.</strong> {f.body}</p>
                  <div className="opp-v2-risk-proof">{f.proof}</div>
                </OppFadeIn>
              ))}
            </div>
          </section>

          <V2Mrr data={data} />
          <V2Asymmetry data={data} />
          <V2Mcat data={data} />
          <V2Cpa data={data} />
          <V2Ost data={data} />
          <V2Roadmap data={data} />
        </article>
      </div>
    </React.Fragment>
  );
}

/* ==================================================================
   Page — Tab system wrapping both reports
================================================================== */

const OPP_TAB_STORAGE_KEY = "todait-opp-tab-v2";

function OppTabSwitcher({ activeTab, onSwitch }) {
  return (
    <div className="opp-tab-switcher" role="tablist" aria-label="리포트 선택">
      <button
        role="tab"
        aria-selected={activeTab === "strategic"}
        className={"opp-tab" + (activeTab === "strategic" ? " is-active" : "")}
        onClick={() => onSwitch("strategic")}
      >
        <span className="opp-tab-mark">A</span>
        <span className="opp-tab-label">전략 리포트</span>
        <span className="opp-tab-sub">낙관적 기회 · $12.99 · 1차</span>
      </button>
      <button
        role="tab"
        aria-selected={activeTab === "reality"}
        className={"opp-tab opp-tab-reality" + (activeTab === "reality" ? " is-active" : "")}
        onClick={() => onSwitch("reality")}
      >
        <span className="opp-tab-mark">B</span>
        <span className="opp-tab-label">현실 검증 · OST</span>
        <span className="opp-tab-sub">차가운 검증 · $4.99 · 2차</span>
      </button>
    </div>
  );
}

function OpportunityPage() {
  const dataA = window.OPPORTUNITY_DATA;
  const dataB = window.OPPORTUNITY_V2_DATA;

  // Initial tab from URL hash or localStorage; default strategic.
  const initialTab = (() => {
    if (typeof window === "undefined") return "strategic";
    const hash = (window.location.hash || "").toLowerCase();
    if (hash === "#reality" || hash === "#tab-b") return "reality";
    if (hash === "#strategic" || hash === "#tab-a") return "strategic";
    const stored = localStorage.getItem(OPP_TAB_STORAGE_KEY);
    if (stored === "reality" || stored === "strategic") return stored;
    return "strategic";
  })();

  const [activeTab, setActiveTab] = React.useState(initialTab);
  const activeData = activeTab === "strategic" ? dataA : dataB;
  const tocItems = activeData.toc;

  const [activeId, setActiveId] = React.useState(tocItems[0].id);
  const [progress, setProgress] = React.useState(0);
  const sectionRefs = React.useRef({});

  // when tab changes, scroll to top, reset section refs, persist
  React.useEffect(() => {
    try { localStorage.setItem(OPP_TAB_STORAGE_KEY, activeTab); } catch (e) {}
    sectionRefs.current = {};
    setActiveId(tocItems[0].id);
    setProgress(0);
    window.scrollTo({ top: 0, behavior: "smooth" });
  }, [activeTab]);

  // Hash sync (one-way: write user choice into hash)
  React.useEffect(() => {
    if (typeof window === "undefined") return;
    const desired = activeTab === "reality" ? "#reality" : "#strategic";
    if (window.location.hash !== desired) {
      try { history.replaceState(null, "", desired); } catch (e) {}
    }
  }, [activeTab]);

  // Reading progress + active section (re-runs when tab/items change)
  React.useEffect(() => {
    const onScroll = () => {
      const doc = document.documentElement;
      const total = doc.scrollHeight - doc.clientHeight;
      const sc = total > 0 ? window.scrollY / total : 0;
      setProgress(Math.max(0, Math.min(1, sc)));

      const y = window.scrollY + 140;
      let current = tocItems[0].id;
      for (const it of tocItems) {
        const el = sectionRefs.current[it.id] || document.getElementById(it.id);
        if (!el) continue;
        const top = el.offsetTop;
        if (top <= y) current = it.id;
      }
      setActiveId(current);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [tocItems, activeTab]);

  // Populate ref map after tab paint
  React.useEffect(() => {
    const t = setTimeout(() => {
      tocItems.forEach((it) => {
        sectionRefs.current[it.id] = document.getElementById(it.id);
      });
    }, 60);
    return () => clearTimeout(t);
  }, [tocItems, activeTab]);

  const jump = (id) => {
    const el = document.getElementById(id);
    if (!el) return;
    const top = el.getBoundingClientRect().top + window.scrollY - 80;
    window.scrollTo({ top, behavior: "smooth" });
  };

  const scrollToFirst = () => jump(tocItems[1] ? tocItems[1].id : tocItems[0].id);

  return (
    <div className="opp-root" data-page="opportunity" data-opp-tab={activeTab}>
      <OppProgressBar progress={progress} />

      <OppTabSwitcher activeTab={activeTab} onSwitch={setActiveTab} />

      {activeTab === "strategic" ? (
        <StrategicReport
          data={dataA}
          activeId={activeId}
          setActiveId={setActiveId}
          sectionRefs={sectionRefs}
          jump={jump}
          scrollToFirst={scrollToFirst}
        />
      ) : (
        <RealityCheckReport
          data={dataB}
          activeId={activeId}
          jump={jump}
          scrollToFirst={scrollToFirst}
        />
      )}
    </div>
  );
}

Object.assign(window, { OpportunityPage });
