// fc-screens-b.jsx, Your Camp (Plan), Plan Preview, Menstrual Cycle · v2
// iOS grouped lists, plain-language section intros, full-length.

const { FC, TONE, SHADOW, computePace, Phone, Eyebrow, Num, ToneBadge, HeroMotif,
        Card, SectionHeader, Stat, StatusBar, HomeIndicator, LargeTitle, CompactNav,
        NavAction, GearButton, TabBar, PrimaryButton, TextButton, GroupedList, Row,
        Toggle, ProgressBar, getStyle } = window;
const BodyB = ({ children, gap = 18, pad = '4px 20px 26px' }) => (
  <div style={{ flex: 1, padding: pad, display: 'flex', flexDirection: 'column', gap }}>{children}</div>
);

// ── PROOF BAND ───────────────────────────────────────────────────────────────
// Shared social-proof treatment, used everywhere proof appears (paywall + Coach's
// Read) so it reads consistently — and deliberately NOT the rounded card component.
// A full-bleed editorial band: mono eyebrow → sport stat (hero) → supporting line →
// optional real fighter quote (serif italic, tap to cycle) → safety footnote.
// Content from FC_SOCIAL_PROOF / FC_FIGHTER_QUOTES (see Social Proof Research.md).
// Assumes a 20px-horizontal-padded parent (BodyB / the Coach's Read scroll).
function ProofBand({ sport, quote = false, compact = false }) {
  const sp = window.socialProofFor ? window.socialProofFor(sport) : { stat: '60–80%', statOf: 'of combat-sport athletes cut weight', line: '', src: '' };
  const Q = (quote && window.FC_FIGHTER_QUOTES) ? window.FC_FIGHTER_QUOTES : [];
  const [qi, setQi] = React.useState(() => Q.length ? Math.floor(Math.random() * Q.length) : 0);
  const q = Q.length ? Q[qi % Q.length] : null;
  return (
    <div style={{ margin: '6px -20px 0', padding: '0 20px' }}>
      {/* editorial section rule + scientific label */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, borderTop: '1.5px solid var(--ink)', paddingTop: 13 }}>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--ink)', fontWeight: 600 }}>The evidence</span>
        <span style={{ flex: 1 }} />
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600 }}>{sport}</span>
      </div>
      {/* stat hero — big light numeral (scientific/modern) */}
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 13, marginTop: 14 }}>
        <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: compact ? 46 : 56, lineHeight: 0.78, letterSpacing: '-0.04em', color: 'var(--accent)', whiteSpace: 'nowrap' }}>{sp.stat}</span>
        <span style={{ flex: 1, fontFamily: 'var(--sans)', fontSize: 13.5, fontWeight: 600, color: 'var(--ink)', lineHeight: 1.25 }}>{sp.statOf}</span>
      </div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-2)', lineHeight: 1.5, marginTop: 11, textWrap: 'pretty' }}>{sp.line}</div>
      {!compact && sp.edge && (
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginTop: 15, paddingTop: 14, borderTop: '1px solid var(--rule)' }}>
          <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 36, lineHeight: 0.78, letterSpacing: '-0.03em', color: 'var(--accent)', whiteSpace: 'nowrap' }}>{sp.edge.stat}</span>
          <span style={{ flex: 1 }}>
            <span style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-2)', lineHeight: 1.4, textWrap: 'pretty' }}>{sp.edge.line}</span>
          </span>
        </div>
      )}
      {q && (
        <figure onClick={() => setQi((qi + 1) % Q.length)} style={{ margin: '17px 0 0', paddingTop: 16, borderTop: '1px solid var(--rule)', cursor: 'pointer', position: 'relative' }}>
          <div aria-hidden="true" style={{ position: 'absolute', top: 4, left: -2, fontFamily: 'var(--serif)', fontSize: 52, lineHeight: 1, color: 'var(--accent)', opacity: 0.16, pointerEvents: 'none' }}>”</div>
          <blockquote style={{ margin: 0, paddingLeft: 26, fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 16.5, lineHeight: 1.45, color: 'var(--ink)', textWrap: 'pretty' }}>“{q.quote}”</blockquote>
          <figcaption style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10, marginTop: 12, paddingLeft: 26 }}>
            <span>
              <span style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 13, fontWeight: 700, color: 'var(--ink)' }}>{q.who}</span>
              <span style={{ display: 'block', fontFamily: 'var(--mono)', fontSize: 9.5, letterSpacing: '0.03em', color: 'var(--ink-3)', marginTop: 2 }}>{q.cred}</span>
            </span>
            {Q.length > 1 && <span style={{ display: 'flex', gap: 4, flexShrink: 0, paddingBottom: 2 }}>{Q.map((_, i) => <span key={i} style={{ width: 5, height: 5, borderRadius: '50%', background: i === qi ? 'var(--accent)' : 'var(--rule-2)' }} />)}</span>}
          </figcaption>
        </figure>
      )}
      {!compact && (
        <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start', marginTop: 16 }}>
          <svg width="13" height="14" viewBox="0 0 16 16" fill="none" style={{ flexShrink: 0, marginTop: 1 }}><path d="M8 1.5 L13.5 4 V8 C13.5 11.3 11 13.7 8 14.5 C5 13.7 2.5 11.3 2.5 8 V4 Z" stroke="var(--t-green)" strokeWidth="1.4"/><path d="M5.7 8 L7.3 9.6 L10.4 6.2" stroke="var(--t-green)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
          <span style={{ fontFamily: 'var(--sans)', fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.5, textWrap: 'pretty' }}>{window.FC_SOCIAL_PROOF_AUTHORITY}</span>
        </div>
      )}
      {/* citation pinned to the bottom of the card, with a source indicator */}
      {window.SourceTag ? <window.SourceTag src={(sp.edge && !compact && sp.edge.src) ? `${sp.src} · ${sp.edge.src}` : sp.src} /> : null}
    </div>
  );
}

const _WEIGH_PHRASE = {
  'day-before': 'the day before', 'same-day': 'the same day',
  'within-2h': 'in the gi, just before you compete', 'same-or-day-before': 'same day (amateur) or the day before (pro)',
};
const _PLAN_LABEL = { camp: 'Camp cut + fight-week water', walk: 'Walk-it-down (real weight only)', minimal: 'Conservative (minimal water)' };
// sportKey (display) → bespoke sport-glyph name (fc-icons.jsx)
const _GLYPH = { MMA: 'mma', Boxing: 'boxing', BJJ: 'bjj', 'BJJ (no-gi)': 'bjj', Judo: 'judo', 'Muay Thai': 'muaythai', Taekwondo: 'taekwondo', Wrestling: 'wrestling' };

// PlanGlide, the focal VISUAL the plan screen was missing: the planned descent
// from start weight to body target across camp, with today's position and the
// fight-week segment marked. Labels live at the edges with start/end anchors so
// nothing runs off the side.
function PlanGlide({ startW, targetW, campWeeks, todayWeek, water = true, width = 331, axis = false }) {
  const W = width, H = 150, padL = axis ? 32 : 8, padR = 8, padT = 30, padB = 22;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  const drop = startW - targetW;
  const tx = (t) => padL + t * innerW;
  const vy = (v) => padT + ((startW - v) / (drop || 1)) * innerH;
  const N = 40;
  // Simple plan line: a steady descent from start weight to the WEIGH-IN target
  // (the lowest point of the cut). Mirrors the trend chart, stripped to just the plan.
  const pts = Array.from({ length: N + 1 }, (_, i) => { const t = i / N; return [tx(t), vy(startW - drop * t)]; });
  const line = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
  const area = `${line} L ${tx(1).toFixed(1)} ${(padT + innerH).toFixed(1)} L ${tx(0).toFixed(1)} ${(padT + innerH).toFixed(1)} Z`;
  const tNow = Math.min(1, todayWeek / campWeeks);
  const nowX = tx(tNow), nowY = vy(startW - drop * tNow);
  return (
    <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{ display: 'block' }} role="img" aria-label={`Planned cut from ${startW} to ${targetW} kg at weigh-in, over ${campWeeks} weeks.`}>
      <defs><linearGradient id="plg" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="var(--accent)" stopOpacity="0.12" /><stop offset="1" stopColor="var(--accent)" stopOpacity="0" /></linearGradient></defs>
      {[0, 0.5, 1].map(g => <line key={g} x1={padL} x2={W - padR} y1={padT + g * innerH} y2={padT + g * innerH} stroke="var(--rule)" strokeWidth="0.8" strokeDasharray="2 3" />)}
      {/* y-axis: weight ticks + unit */}
      {axis && <line x1={padL} x2={padL} y1={padT} y2={padT + innerH} stroke="var(--rule-2)" strokeWidth="0.8" />}
      {axis && [0, 0.5, 1].map(g => (
        <text key={'y' + g} x={padL - 6} y={padT + g * innerH + 3.5} textAnchor="end" fontFamily="var(--num)" fontSize="9" fill={g === 1 ? 'var(--accent)' : 'var(--ink-3)'} fontWeight={g === 1 ? '600' : '500'}>{(startW - g * drop).toFixed(1)}</text>
      ))}
      {axis && <text x={padL - 6} y={padT - 11} textAnchor="end" fontFamily="var(--mono)" fontSize="7.5" letterSpacing="0.06em" fill="var(--ink-3)" fontWeight="600">KG</text>}
      <path d={area} fill="url(#plg)" />
      <path d={line} fill="none" stroke="var(--accent)" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
      {/* start + weigh-in dots */}
      <circle cx={tx(0)} cy={vy(startW)} r="4" fill="var(--ink)" />
      <circle cx={tx(1)} cy={vy(targetW)} r="4.5" fill="var(--accent)" stroke="var(--paper)" strokeWidth="1.5" />
      {/* today marker — line only; no dot (their actual weight today isn't necessarily on plan).
          On a fresh plan today === start, so suppress the marker and just label the start anchor TODAY. */}
      {tNow > 0.04 && <line x1={nowX} x2={nowX} y1={padT} y2={padT + innerH} stroke="var(--ink)" strokeWidth="1" opacity="0.4" />}
      {/* value labels, nudged clear of the line (suppressed when the y-axis shows them) */}
      {!axis && <text x={tx(0)} y={vy(startW) - 10} textAnchor="start" fontFamily="var(--num)" fontSize="12" fill="var(--ink)" fontWeight="500">{startW.toFixed(1)}</text>}
      {!axis && <text x={tx(1)} y={vy(targetW) - 12} textAnchor="end" fontFamily="var(--num)" fontSize="12" fill="var(--accent)" fontWeight="600">{targetW.toFixed(1)}</text>}
      <text x={padL} y={H - 7} textAnchor="start" fontFamily="var(--mono)" fontSize="8.5" letterSpacing="0.04em" fill={tNow > 0.04 ? 'var(--ink-3)' : 'var(--ink-2)'} fontWeight={tNow > 0.04 ? '600' : '700'}>{tNow > 0.04 ? 'START' : 'TODAY'}</text>
      {tNow > 0.04 && <text x={nowX} y={H - 7} textAnchor="middle" fontFamily="var(--mono)" fontSize="8.5" letterSpacing="0.04em" fill="var(--ink-2)" fontWeight="700">TODAY</text>}
      <text x={W - padR} y={H - 7} textAnchor="end" fontFamily="var(--mono)" fontSize="8.5" letterSpacing="0.04em" fill="var(--accent)" fontWeight="700">WEIGH-IN</text>
    </svg>
  );
}

function CampPlan({ styleKey = 'paper', sportKey = 'MMA' }) {
  const st = getStyle(styleKey);
  const [manage, setManage] = React.useState(false);
  const S = window.FC_SPORTS[sportKey] || window.FC_SPORTS.MMA;
  const amateur = true;
  const planType = window.planTypeFor(sportKey, amateur);
  const weighPhrase = _WEIGH_PHRASE[S.weighIn] || 'the day before';
  const unverified = S.conf === 'structure';
  return (
    <Phone styleKey={styleKey} label="Your Camp">
      <StatusBar />
      <LargeTitle title="Your Camp" trailing={<NavAction onClick={() => setManage(true)}>Manage</NavAction>} />
      <BodyB>
        {/* summary hero */}
        <Card inverted={st.heroInverted} pad={22}>
          <HeroMotif motif={st.motif} />
          {window.SportGlyph && _GLYPH[sportKey] && (
            <div style={{ position: 'absolute', top: 20, right: 20, opacity: st.heroInverted ? 0.9 : 1 }}>
              <window.SportGlyph name={_GLYPH[sportKey]} size={34} ink={st.heroInverted ? 'var(--paper)' : 'var(--ink)'} accent={st.heroInverted ? '#f0a08f' : 'var(--accent)'} />
            </div>
          )}
          <Eyebrow color={st.heroInverted ? 'rgba(246,244,239,0.6)' : 'var(--accent)'}>About this camp</Eyebrow>
          <div style={{ fontFamily: 'var(--display)', fontSize: 28, letterSpacing: '0.01em', fontWeight: 600, marginTop: 9, color: st.heroInverted ? 'var(--paper)' : 'var(--ink)' }}>{FC.division.toUpperCase()} · {sportKey.toUpperCase()}</div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 14, color: st.heroInverted ? 'rgba(246,244,239,0.7)' : 'var(--ink-2)', marginTop: 8 }}>Fight night {FC.fightDateShort}, 2026 · weigh-in {weighPhrase}.</div>
          <div style={{ height: 1, background: st.heroInverted ? 'rgba(246,244,239,0.16)' : 'var(--rule)', margin: '18px 0 16px' }} />
          <div style={{ display: 'flex' }}>
            {S.equipmentReq
              ? <Stat label="Class limit" value={FC.maxWithGi.toFixed(1)} unit="kg" sub="in gear" i={0} />
              : <Stat label="Class limit" value={FC.goalWeight.toFixed(1)} unit="kg" sub="on the scale" i={0} />}
            <Stat label="Body target" value={FC.goalWeight.toFixed(1)} unit="kg" color="var(--accent)" sub={S.equipmentReq ? `−${S.equipmentKg} gear` : 'underwear'} i={1} />
          </div>
        </Card>

        {/* THE CUT, focal visual + scannable key numbers (hierarchy + a visual) */}
        {(() => {
          const startW = FC.planStart, targetW = FC.goalWeight;
          const drop = startW - targetW;
          const campWeeks = Math.max(1, Math.round(FC.campDays / 7));
          const todayWeek = FC.todayDay / 7;
          const water = planType !== 'walk';
          const stats = [
            ['To lose', drop.toFixed(1), 'kg'],
            ['Timeline', String(campWeeks), 'wks'],
            ['Pace', (drop / campWeeks).toFixed(2), 'kg/wk'],
          ];
          return (
            <Card pad={20}>
              <Eyebrow color="var(--accent)">Your cut</Eyebrow>
              <div style={{ display: 'flex', marginTop: 12, marginBottom: 4 }}>
                {stats.map(([lb, v, u], i) => (
                  <div key={lb} style={{ flex: 1, paddingLeft: i ? 16 : 0, borderLeft: i ? '1px solid var(--rule)' : 'none' }}>
                    <div style={{ fontFamily: 'var(--sans)', fontSize: 11, fontWeight: 600, color: 'var(--ink-3)' }}>{lb}</div>
                    <div style={{ display: 'flex', alignItems: 'baseline', gap: 3, marginTop: 6 }}>
                      <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 26, letterSpacing: '-0.02em', color: i === 0 ? 'var(--accent)' : 'var(--ink)' }}>{v}</span>
                      <span style={{ fontFamily: 'var(--mono)', fontSize: 9.5, color: 'var(--ink-3)', fontWeight: 600 }}>{u}</span>
                    </div>
                  </div>
                ))}
              </div>
              <div style={{ marginTop: 16, paddingTop: 14, borderTop: '1px solid var(--rule)' }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 13.5, fontWeight: 600, color: 'var(--ink-2)' }}>Your descent to fight day</div>
                <div style={{ marginTop: 12 }}>
                  {window.PlanCutStrategy
                    ? <window.PlanCutStrategy startW={startW} targetW={targetW} water={water} width={291} />
                    : <PlanGlide startW={startW} targetW={targetW} campWeeks={campWeeks} todayWeek={todayWeek} water={water} width={291} />}
                </div>
              </div>
            </Card>
          );
        })()}

        {/* per-sport coaching read, the bespoke "why" */}
        <CoachTip chip={`${sportKey} · ${_PLAN_LABEL[planType].split(' ')[0]}`} headline={`Your plan is built for ${sportKey}.`} body={S.coach} tone={planType === 'walk' ? 'amber' : 'accent'} />

        {/* rules, driven by sport data */}
        <div>
          <div style={{ borderTop: '1px solid var(--rule)', margin: '2px 4px 14px' }} />
          <SectionHeader>Competition rules</SectionHeader>
          <GroupedList>
            <Row label="Combat sport" value={sportKey} />
            <Row label="Fight date" value="1 Jan 2026" />
            <Row label="Weigh-in" value={S.weighIn === 'within-2h' ? 'Same day (in gi)' : S.weighIn === 'same-or-day-before' ? 'Same day / day before' : S.weighIn === 'same-day' ? 'Same day' : 'Day before'} />
            {S.equipmentReq
              ? <Row label="Gear allowance" value={`+${S.equipmentKg} kg`} note={`You weigh in dressed, so we set your body target ${S.equipmentKg} kg under the class limit.`} last />
              : <Row label="Gear allowance" value="None" note="Underwear-only weigh-in. Your body target is the class limit." last />}
          </GroupedList>
        </div>

        {/* plan type + rehydration, bespoke */}
        <div>
          <SectionHeader>Cut strategy</SectionHeader>
          <GroupedList>
            <Row label="Plan type" value={_PLAN_LABEL[planType]} chevron={false} />
            {S.reweigh
              ? <Row label="Random re-weigh" value={`≤ +${S.reweighPct}% of class`} note={`A surprise scale check on fight day caps you at ${S.reweighPct}% over your class, so you can't over-dehydrate to make weight.`} last />
              : planType === 'walk'
                ? <Row label="Fight-week water" value="None" note="Same-day weigh-in, no time to rehydrate. The whole cut is real weight, walked down across camp." last />
                : <Row label="Fight-week water" value={planType === 'minimal' ? 'Minimal' : 'Up to ~5%'} note={planType === 'minimal' ? 'Barely any rehydration window. Keep any water cut small.' : 'Day-before weigh-in gives you the night to rehydrate, so a moderate water cut is safe.'} last />}
          </GroupedList>
        </div>

        {unverified && (
          <div style={{ fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.5, padding: '0 4px', textWrap: 'pretty' }}>
            ⓘ These {sportKey} figures follow the standard structure. Always check your promotion's current rulebook, limits vary by sanctioning body.
          </div>
        )}

        {/* customise */}
        <div>
          <SectionHeader>Daily weigh-in</SectionHeader>
          <GroupedList>
            <Row label="Weigh-in time" value={FC.weighInTime} note="First thing fasted is most accurate, but consistency beats the exact hour. Pick one you can hit daily." />
            <Row label="Morning reminder" chevron={false} control={<Toggle on={true} />} last />
          </GroupedList>
        </div>

        {/* customise */}
        <div>
          <SectionHeader>Personalise your plan</SectionHeader>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.5, padding: '0 4px 11px', textWrap: 'pretty' }}>
            Toggle a factor and your targets, trend line and alerts adjust on the spot.
          </div>
          <GroupedList>
            {[['Menstrual cycle', true], ['Gear allowance', S.equipmentReq], ['Random re-weigh cap', !!S.reweigh], ['Hydration testing', false]].map((it, i, a) => (
              <Row key={it[0]} label={it[0]} chevron={false} control={<Toggle on={it[1]} />} last={i === a.length - 1} />
            ))}
          </GroupedList>
        </div>
      </BodyB>
      <TabBar active="plan" />
      <HomeIndicator />
      {manage && window.ManageFightSheet && <window.ManageFightSheet onClose={() => setManage(false)} />}
    </Phone>
  );
}

// ── WEEK-BY-WEEK ROADMAP ─────────────────────────────────────────────────────
// The plan made concrete: every week from camp start to fight night, with its
// target weight, the drop that week, and the milestones (camp start, fight-week
// water taper, weigh-in, fight day). Driven by the SAME math as the Strategy
// card and Coach's Read — real weight walked down across camp, fight-week water
// held for the final week — so the three screens never disagree.
//   teaser = paywall mode: first `free` weeks clear, the rest blurred + locked.
function WeekRoadmap({ startW, goal, weeks = 8, water = true, fwWater = 0, weighInLabel = 'Weigh-in', teaser = false, free = 2, width = 309 }) {
  const cut = Math.abs(startW - goal);
  const campKg = +(cut - fwWater).toFixed(1);          // real weight, across camp
  const realWeeks = water ? Math.max(1, weeks - 1) : weeks;
  const targetAt = (w) => {
    if (w <= 0) return startW;
    if (water && w >= weeks) return goal;              // fight week ends on goal
    return +(startW - campKg * Math.min(1, w / realWeeks)).toFixed(1);
  };
  const rows = [];
  for (let w = 1; w <= weeks; w++) {
    const t = targetAt(w), prev = targetAt(w - 1);
    const isFight = w === weeks;
    const milestone = w === 1 ? 'Camp starts'
      : isFight ? (water ? 'Fight week · water cut' : 'Fight week')
      : null;
    rows.push({ w, out: weeks - w, target: t, drop: +(prev - t).toFixed(1), isFight, milestone, waterWeek: isFight && water });
  }

  const ACC = 'var(--accent)';
  const Row = ({ r }) => {
    const locked = teaser && r.w > free;
    return (
      <div style={{ display: 'flex', gap: 13, alignItems: 'stretch' }}>
        {/* rail */}
        <div style={{ flexShrink: 0, width: 22, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
          <span style={{ width: 11, height: 11, borderRadius: '50%', marginTop: 4, background: r.isFight ? ACC : 'var(--paper)', border: '2px solid ' + (r.isFight ? ACC : 'var(--rule-2)'), boxShadow: r.isFight ? '0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent)' : 'none' }} />
          {r.w < weeks && <span style={{ flex: 1, width: 2, background: 'var(--rule)', marginTop: 2 }} />}
        </div>
        {/* content */}
        <div style={{ flex: 1, paddingBottom: r.w < weeks ? 16 : 2, filter: locked ? 'blur(4.5px)' : 'none', opacity: locked ? 0.6 : 1, userSelect: locked ? 'none' : 'auto' }}>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
            <span style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
              <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.08em', textTransform: 'uppercase', color: r.isFight ? ACC : 'var(--ink-3)', fontWeight: 700 }}>Wk {r.w}</span>
              <span style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.04em', color: 'var(--rule-2)', fontWeight: 600 }}>{r.out === 0 ? 'FIGHT' : r.out + ' WK' + (r.out > 1 ? 'S' : '') + ' OUT'}</span>
            </span>
            <span style={{ display: 'flex', alignItems: 'baseline', gap: 3 }}>
              <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 20, letterSpacing: '-0.02em', color: r.isFight ? ACC : 'var(--ink)' }}>{r.target.toFixed(1)}</span>
              <span style={{ fontFamily: 'var(--mono)', fontSize: 9, color: 'var(--ink-3)', fontWeight: 600 }}>kg</span>
            </span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, marginTop: 3 }}>
            <span style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', fontWeight: r.milestone ? 600 : 400 }}>
              {r.milestone || (r.drop > 0 ? 'Steady week' : 'Hold')}
              {r.waterWeek && <span style={{ color: ACC }}> · {weighInLabel}</span>}
            </span>
            {r.drop > 0 && <span style={{ flexShrink: 0, fontFamily: 'var(--mono)', fontSize: 11, color: r.waterWeek ? ACC : 'var(--ink-2)', fontWeight: 600 }}>{'\u2212'}{r.drop.toFixed(1)} kg</span>}
          </div>
        </div>
      </div>
    );
  };

  return (
    <div style={{ width }}>
      <div style={{ position: 'relative' }}>
        {rows.map(r => <Row key={r.w} r={r} />)}
        {teaser && (
          <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, top: '32%', display: 'flex', alignItems: 'flex-end', justifyContent: 'center', paddingBottom: 6, background: 'linear-gradient(180deg, transparent 0%, var(--paper) 78%)', pointerEvents: 'none' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, background: 'var(--ink)', borderRadius: 100, padding: '8px 15px' }}>
              <svg width="11" height="13" viewBox="0 0 15 17" fill="none"><rect x="1.5" y="7" width="12" height="9" rx="2" stroke="var(--paper)" strokeWidth="1.5"/><path d="M4 7 V5 a3.5 3.5 0 0 1 7 0 V7" stroke="var(--paper)" strokeWidth="1.5"/></svg>
              <span style={{ fontFamily: 'var(--sans)', fontSize: 12.5, fontWeight: 600, color: 'var(--paper)' }}>{weeks - free} more weeks in your full plan</span>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ── PLAN PREVIEW (onboarding / paywall) ──────────────────────────────────────
function PlanPreview({ styleKey = 'paper', paceKey = 'onTrack', sportKey, variant }) {
  const st = getStyle(styleKey);
  const p = computePace(paceKey);
  const [plan, setPlan] = React.useState('annual');
  const sport = sportKey || window.__sport || (window.FC && window.FC.sport) || 'MMA';
  const v = variant || window.__paywallVariant || 'dashboard';
  const today = p.hist[p.hist.length - 1][1];
  const targetW = FC.goalWeight;
  const startW = (FC.planStart != null ? FC.planStart : today);
  const cut = Math.abs(startW - targetW);
  // Onboarding paywall — fresh plan, 8 wks out (matches loading + Coach's Read).
  // In production this is the real weeks-to-weigh-in.
  const weeks = 8;
  const ratePct = (cut / weeks / startW * 100);
  const S = (window.FC_SPORTS && window.FC_SPORTS[sport]) || {};
  const planType = window.planTypeFor ? window.planTypeFor(sport, true) : 'camp';
  const division = (FC && FC.division) || null;
  const campName = division ? ('Your ' + division + ' camp') : ('Your ' + sport + ' camp');
  const water = planType !== 'walk';
  const stratName = { camp: 'Camp cut + fight-week water', walk: 'Walk-it-down — real weight only', minimal: 'Conservative — minimal water' }[planType] || 'Camp cut + fight-week water';
  const stratLine = water
    ? 'Most of the drop is true weight across camp, with a controlled water cut held for fight week.'
    : 'No rehydration window, so the whole cut is real weight, walked down across camp.';
  const fwWater = water ? +(cut * 0.28).toFixed(1) : 0;
  const campKg = +(cut - fwWater).toFixed(1);

  // ── shared pieces ──────────────────────────────────────────────────────────
  const Header = (
    <div style={{ paddingTop: 6 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--accent)', fontWeight: 600 }}>{campName}</span>
        <span style={{ flex: 1, height: 1, background: 'var(--rule)' }} />
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9.5, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600 }}>{weeks} wks out</span>
      </div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, lineHeight: 1.12, letterSpacing: '-0.02em', color: 'var(--ink)', marginTop: 12, textWrap: 'balance' }}>{FC.firstName}{FC.firstName ? ',' : ''} here’s your road to fight night.</div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 15, color: 'var(--ink-2)', marginTop: 9, lineHeight: 1.5, textWrap: 'pretty' }}>A {cut.toFixed(1)} kg cut over {weeks} weeks, at {ratePct.toFixed(2)}%/wk — paced to keep your power and make weight clean.</div>
    </div>
  );

  // intelligence strip — the read, as data (scientific)
  const Intel = (
    <div style={{ display: 'flex', border: '1px solid var(--rule)', borderRadius: 'var(--radius)', overflow: 'hidden', background: 'var(--paper)' }}>
      {[['CUT', '\u2212' + cut.toFixed(1), 'kg'], ['RATE', ratePct.toFixed(2), '%/wk'], ['CAMP', String(weeks), 'wks'], ['MAKE', targetW.toFixed(1), 'kg']].map((c, i) => (
        <div key={c[0]} style={{ flex: 1, padding: '12px 11px', borderLeft: i ? '1px solid var(--rule)' : 'none' }}>
          <div style={{ fontFamily: 'var(--mono)', fontSize: 8.5, letterSpacing: '0.1em', color: 'var(--ink-3)', fontWeight: 600 }}>{c[0]}</div>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 2, marginTop: 6 }}>
            <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 22, letterSpacing: '-0.02em', color: i === 0 ? 'var(--accent)' : 'var(--ink)' }}>{c[1]}</span>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 8.5, color: 'var(--ink-3)', fontWeight: 600 }}>{c[2]}</span>
          </div>
        </div>
      ))}
    </div>
  );

  // the week-by-week roadmap — the plan made concrete, the thing they're buying
  const weighInLabel = water ? 'water taper' : 'weigh-in';
  const Roadmap = (
    <Card pad={18}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10, marginBottom: 16 }}>
        <Eyebrow color="var(--accent)">Your week-by-week road</Eyebrow>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600 }}>Start → fight night</span>
      </div>
      <window.WeekRoadmap startW={startW} goal={targetW} weeks={weeks} water={water} fwWater={fwWater} weighInLabel={weighInLabel} teaser={true} free={2} width={283} />
    </Card>
  );

  // the plan graph — the projection they're buying
  const Graph = (
    <Card pad={16}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 2 }}>
        <Eyebrow color="var(--ink-3)" style={{ fontSize: 10 }}>Projection to weigh-in</Eyebrow>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9.5, letterSpacing: '0.04em', color: 'var(--ink-3)', fontWeight: 600 }}>{weeks} WKS OUT</span>
      </div>
      <PlanGlide startW={startW} targetW={targetW} campWeeks={weeks} todayWeek={0} water={water} width={299} />
    </Card>
  );

  // a REAL plan module (cut strategy), shown — not a ticked feature
  const Strategy = (
    <Card pad={18}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
        <Eyebrow color="var(--accent)">Your cut strategy</Eyebrow>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600 }}>{sport}</span>
      </div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 17, fontWeight: 700, lineHeight: 1.28, color: 'var(--ink)', marginTop: 10, textWrap: 'balance' }}>{stratName}</div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-2)', lineHeight: 1.5, marginTop: 7, textWrap: 'pretty' }}>{stratLine}</div>
      <div style={{ display: 'flex', marginTop: 15, paddingTop: 14, borderTop: '1px solid var(--rule)' }}>
        {[['True-weight cut', campKg.toFixed(1), 'kg', 'var(--ink)'], ['Fight-week water', fwWater.toFixed(1), 'kg', water ? 'var(--accent)' : 'var(--ink-3)'], ['Cut pace', ratePct.toFixed(2), '%/wk', 'var(--ink)']].map(([lb, vv, u, c], i) => (
          <div key={lb} style={{ flex: 1, paddingLeft: i ? 14 : 0, borderLeft: i ? '1px solid var(--rule)' : 'none' }}>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 10, fontWeight: 600, color: 'var(--ink-3)' }}>{lb}</div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 2, marginTop: 6 }}><span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 22, letterSpacing: '-0.02em', color: c }}>{vv}</span><span style={{ fontFamily: 'var(--mono)', fontSize: 8.5, color: 'var(--ink-3)', fontWeight: 600 }}>{u}</span></div>
          </div>
        ))}
      </div>
    </Card>
  );

  const Feas = window.FeasibilityCard ? <window.FeasibilityCard currentWeight={startW} bodyTarget={targetW} weeksToWeighIn={weeks} /> : null;

  const Plans = (
    <div style={{ marginTop: 4, display: 'flex', flexDirection: 'column', gap: 9 }}>
      {[['annual', 'Annual', '\u00a359.99/yr', '\u00a35.00/mo', 'Save 50%'], ['monthly', 'Monthly', '\u00a39.99/mo', null, null]].map(([key, label, price, per, badge]) => {
        const sel = plan === key;
        return (
          <button key={key} onClick={() => setPlan(key)} style={{ display: 'flex', alignItems: 'center', gap: 12, width: '100%', textAlign: 'left', cursor: 'pointer', padding: '13px 15px', borderRadius: 13, background: sel ? 'var(--paper)' : 'transparent', border: '1.5px solid ' + (sel ? 'var(--paper)' : 'rgba(246,244,239,0.32)') }}>
            <span style={{ flexShrink: 0, width: 18, height: 18, borderRadius: '50%', border: '2px solid ' + (sel ? 'var(--accent)' : 'rgba(246,244,239,0.5)'), display: 'flex', alignItems: 'center', justifyContent: 'center' }}>{sel && <span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--accent)' }} />}</span>
            <span style={{ flex: 1, minWidth: 0 }}>
              <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <span style={{ fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, color: sel ? 'var(--ink)' : 'var(--paper)' }}>{label}</span>
                {badge && <span style={{ fontFamily: 'var(--mono)', fontSize: 8.5, letterSpacing: '0.06em', textTransform: 'uppercase', fontWeight: 700, color: '#fff', background: 'var(--accent)', borderRadius: 100, padding: '2px 7px' }}>{badge}</span>}
              </span>
              {per && <span style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 12, color: sel ? 'var(--ink-3)' : 'rgba(246,244,239,0.6)', marginTop: 2 }}>Just {per}, billed yearly</span>}
            </span>
            <span style={{ flexShrink: 0, fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 700, color: sel ? 'var(--ink)' : 'var(--paper)' }}>{price}</span>
          </button>
        );
      })}
    </div>
  );

  const Unlock = ({ showList }) => (
    <Card pad={20} inverted>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <svg width="15" height="17" viewBox="0 0 15 17" fill="none"><rect x="1.5" y="7" width="12" height="9" rx="2" stroke="var(--paper)" strokeWidth="1.5"/><path d="M4 7 V5 a3.5 3.5 0 0 1 7 0 V7" stroke="var(--paper)" strokeWidth="1.5"/></svg>
        <span style={{ fontFamily: 'var(--display)', fontSize: 16, letterSpacing: '0.04em', fontWeight: 600, color: 'var(--paper)' }}>{showList ? 'GET YOUR FULL PLAN' : 'UNLOCK YOUR PLAN'}</span>
      </div>
      {showList ? (
        <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 12 }}>
          {['Week-by-week breakdown to fight day', 'Fight-week water taper, built in', 'A cut strategy tuned to your sport', 'Targets that adapt to your cycle'].map(tx => (
            <div key={tx} style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
              <svg width="13" height="11" viewBox="0 0 13 11" fill="none"><path d="M1 5.5 L4.7 9 L12 1.5" stroke="#f4a299" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
              <span style={{ fontFamily: 'var(--sans)', fontSize: 14, color: 'rgba(246,244,239,0.9)' }}>{tx}</span>
            </div>
          ))}
        </div>
      ) : (
        <div style={{ marginTop: 12, fontFamily: 'var(--sans)', fontSize: 13.5, color: 'rgba(246,244,239,0.82)', lineHeight: 1.5, textWrap: 'pretty' }}>Everything above goes live — week-by-week targets that adapt daily, your fight-week taper, and the verdict engine that reads your trend.</div>
      )}
      <div style={{ marginTop: 16, paddingTop: 14, borderTop: '1px solid rgba(246,244,239,0.16)', display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12 }}>
        <span style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'rgba(246,244,239,0.7)', lineHeight: 1.4 }}>A fight-camp nutritionist runs <span style={{ color: 'var(--paper)', fontWeight: 600 }}>£80–150</span> a session.</span>
        <span style={{ flexShrink: 0, fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--paper)', fontWeight: 600 }}>Yours from £5/mo</span>
      </div>
      <div style={{ marginTop: 16 }}>{Plans}</div>
      <button style={{ width: '100%', marginTop: 14, background: 'var(--paper)', color: 'var(--ink)', border: 'none', borderRadius: 14, minHeight: 50, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600 }}>Start 14-day free trial</button>
      <div style={{ textAlign: 'center', marginTop: 11, fontFamily: 'var(--sans)', fontSize: 12, color: 'rgba(246,244,239,0.78)', lineHeight: 1.45 }}>{plan === 'annual' ? 'Then \u00a359.99/yr (\u00a35.00/mo)' : 'Then \u00a39.99/mo'} · cancel anytime.<br />We’ll remind you 2 days before your trial ends.</div>
    </Card>
  );

  // ── legacy 'features' hero card (kept for comparison) ────────────────────────
  const LegacyHero = (
    <Card inverted={st.heroInverted} pad={22}>
      <HeroMotif motif={st.motif} />
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <div style={{ flex: 1 }}>
          <Eyebrow color={st.heroInverted ? 'rgba(246,244,239,0.55)' : 'var(--ink-3)'} style={{ fontSize: 10 }}>TODAY</Eyebrow>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginTop: 8 }}><Num size={32} color={st.heroInverted ? 'var(--paper)' : 'var(--ink)'}>{startW.toFixed(1)}</Num><span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: st.heroInverted ? 'rgba(246,244,239,0.55)' : 'var(--ink-3)', fontWeight: 600 }}>kg</span></div>
        </div>
        <svg width="22" height="14" viewBox="0 0 22 14" fill="none" style={{ opacity: 0.5 }}><path d="M1 7 H19 M14 2 L20 7 L14 12" stroke={st.heroInverted ? '#fff' : 'var(--ink)'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
        <div style={{ flex: 1, textAlign: 'right' }}>
          <Eyebrow color={st.heroInverted ? 'rgba(246,244,239,0.55)' : 'var(--ink-3)'} style={{ fontSize: 10 }}>FIGHT WEIGHT</Eyebrow>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginTop: 8, justifyContent: 'flex-end' }}><Num size={32} color={st.heroInverted ? '#f4a299' : 'var(--accent)'}>{targetW.toFixed(1)}</Num><span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: st.heroInverted ? 'rgba(246,244,239,0.55)' : 'var(--ink-3)', fontWeight: 600 }}>kg</span></div>
        </div>
      </div>
      <div style={{ height: 1, background: st.heroInverted ? 'rgba(246,244,239,0.16)' : 'var(--rule)', margin: '16px 0' }} />
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'center', gap: 8 }}>
        <Num size={40} color={st.heroInverted ? 'var(--paper)' : 'var(--ink)'}>{'\u2212'}{cut.toFixed(1)}</Num>
        <span style={{ fontFamily: 'var(--display)', fontSize: 13, letterSpacing: '0.1em', fontWeight: 600, color: st.heroInverted ? 'rgba(246,244,239,0.7)' : 'var(--ink-3)' }}>KG IN {weeks} WEEKS</span>
      </div>
    </Card>
  );

  let body;
  if (v === 'reveal') {
    // Merged read + plan — the single screen that lands after "Building your camp".
    const sex = window.__sex || 'M';
    const weekly = cut / weeks;
    const PlanDivider = (
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 26, marginBottom: 2 }}>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'var(--accent)', fontWeight: 600 }}>Now — here’s the plan</span>
        <span style={{ flex: 1, height: 1, background: 'var(--rule)' }} />
      </div>
    );
    // Bespoke sport × sex social proof — moved here from the biological-sex step,
    // because the "in your class, fighters like you" framing only earns the claim
    // once the division is set (which happens by the time the plan is revealed).
    const bproof = window.bespokeProof ? window.bespokeProof(sport, sex) : null;
    const sportLabel = sport === 'BJJ' ? 'BJJ' : sport;
    const Bespoke = bproof ? (
      <div style={{ borderRadius: 'var(--radius)', border: '1px solid color-mix(in srgb, var(--accent) 28%, transparent)', background: 'var(--paper)', overflow: 'hidden' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 14px', background: 'color-mix(in srgb, var(--accent) 9%, transparent)', borderBottom: '1px solid color-mix(in srgb, var(--accent) 18%, transparent)' }}>
          {window.CoachMark && <window.CoachMark sport={sport} size={26} />}
          <div style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.13em', textTransform: 'uppercase', color: 'var(--accent)', fontWeight: 600 }}>In {division ? division + ' ' : ''}{sportLabel}, {sex === 'F' ? 'women' : 'men'} like you</div>
        </div>
        <div style={{ padding: '12px 14px 14px' }}>
          <div style={{ fontFamily: 'var(--display)', fontSize: 19, fontWeight: 600, letterSpacing: '0.01em', color: 'var(--ink)', lineHeight: 1.05 }}>{bproof.who}</div>
          <div style={{ fontFamily: 'var(--mono)', fontSize: 9.5, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--accent)', marginTop: 4 }}>{bproof.cred}</div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 13.5, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 9, textWrap: 'pretty' }}>{bproof.fact}</div>
        </div>
      </div>
    ) : null;
    body = <React.Fragment>
      {window.CoachReadBody ? <window.CoachReadBody sportKey={sport} sex={sex} variant="insight" /> : null}
      {PlanDivider}
      {Intel}{Roadmap}{Strategy}
      {window.FirstWeekCard ? <window.FirstWeekCard startW={startW} weekly={weekly} weighInTime={FC.weighInTime} /> : null}
      <ProofBand sport={sport} quote />
      <Unlock showList={false} />
    </React.Fragment>;
  } else if (v === 'roadmap') {
    body = <React.Fragment>{Header}{Intel}{Roadmap}{Strategy}
      <ProofBand sport={sport} quote />
      <Unlock showList={false} />
    </React.Fragment>;
  } else if (v === 'features') {
    body = <React.Fragment>{Header}{LegacyHero}
      <CoachTip chip={sport + ' \u00b7 ' + weeks + ' weeks'} headline="This pace guards your gas tank." bullets={['Won on round-to-round recovery, not one big moment', 'A gradual cut keeps your cardio and power intact', 'You walk in dangerous, not drained']} />
      <ProofBand sport={sport} quote />
      <Unlock showList={true} />
    </React.Fragment>;
  } else if (v === 'spotlight') {
    body = <React.Fragment>{Header}{Graph}{Intel}
      <ProofBand sport={sport} quote />
      <Unlock showList={false} />
    </React.Fragment>;
  } else { // 'dashboard' (default)
    body = <React.Fragment>{Header}{Intel}{Graph}{Feas}{Strategy}
      <ProofBand sport={sport} quote />
      <Unlock showList={false} />
    </React.Fragment>;
  }

  return (
    <Phone styleKey={styleKey} label="Plan Preview">
      <StatusBar />
      <CompactNav title="Your Plan" onBack={false} trailing={<NavAction>Skip</NavAction>} />
      <BodyB gap={16}>{body}</BodyB>
      <HomeIndicator />
    </Phone>
  );
}

// CycleCurve, simple wave of expected water-weight across the cycle, with a
// "you're here" marker. Dips through follicular, peaks in late luteal.
function CycleCurve({ width = 309 }) {
  const H = 104, padL = 8, padR = 8, padT = 12, padB = 24, days = 28, yMax = 1.3;
  const off = (d) => -Math.cos(2 * Math.PI * (d - 10) / days);   // -1 follicular low → +1 luteal peak
  const xs = (d) => padL + (d / days) * (width - padL - padR);
  const ys = (o) => padT + (1 - (o + yMax) / (2 * yMax)) * (H - padT - padB);
  const pts = Array.from({ length: days + 1 }, (_, d) => [xs(d), ys(off(d))]);
  const line = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
  const area = `${line} L ${xs(days).toFixed(1)} ${ys(-yMax).toFixed(1)} L ${xs(0).toFixed(1)} ${ys(-yMax).toFixed(1)} Z`;
  const baseY = ys(0), curD = 24, curX = xs(curD), curY = ys(off(curD));
  const bands = [['Menses', 0, 5], ['Follicular', 5, 13], ['Ovul', 13, 16], ['Luteal', 16, 28]];
  return (
    <svg width={width} height={H} viewBox={`0 0 ${width} ${H}`} style={{ display: 'block', overflow: 'visible' }}>
      <defs><linearGradient id="cycgrad" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0" stopColor="var(--ink)" stopOpacity="0.08" />
        <stop offset="1" stopColor="var(--ink)" stopOpacity="0" />
      </linearGradient></defs>
      <line x1={padL} x2={width - padR} y1={baseY} y2={baseY} stroke="var(--rule)" strokeWidth="1" strokeDasharray="3 3" />
      {bands.map((b, i) => {
        const x0 = xs(b[1]), mid = (xs(b[1]) + xs(b[2])) / 2, on = b[0] === 'Luteal';
        return (
          <g key={b[0]}>
            {i > 0 && <line x1={x0} x2={x0} y1={padT} y2={H - padB} stroke="var(--rule)" strokeWidth="0.7" opacity="0.45" />}
            <text x={mid} y={H - 8} textAnchor="middle" fontFamily="var(--mono)" fontSize="8" letterSpacing="0.04em" fill={on ? 'var(--accent)' : 'var(--ink-3)'} fontWeight={on ? 700 : 600}>{b[0].toUpperCase()}</text>
          </g>
        );
      })}
      <path d={area} fill="url(#cycgrad)" />
      <path d={line} fill="none" stroke="var(--ink)" strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round" />
      <line x1={curX} x2={curX} y1={curY} y2={H - padB} stroke="var(--accent)" strokeWidth="1" strokeDasharray="2 2" />
      <circle cx={curX} cy={curY} r="4.5" fill="var(--paper)" stroke="var(--accent)" strokeWidth="2" />
    </svg>
  );
}

// ── MENSTRUAL CYCLE ──────────────────────────────────────────────────────────
// Composes the §15 cycle-intelligence blocks by mode. `mode` / `flag` props let
// the canvas show the natural / contraception / absent states; an internal
// selector also lets you flip between them live.
function MenstrualCycle({ styleKey = 'paper', mode: modeProp = 'natural', flag = false }) {
  const st = getStyle(styleKey);
  const [mode, setMode] = React.useState(modeProp);
  React.useEffect(() => setMode(modeProp), [modeProp]);
  const natural = mode === 'natural';
  // absent-cycle state: forced via prop, or shown in the 'absent' mode by default
  const reds = flag || mode === 'absent';

  return (
    <Phone styleKey={styleKey} label="Menstrual Cycle">
      <StatusBar />
      <CompactNav title="Your Cycle" trailing={<NavAction>Save</NavAction>} />
      <BodyB>
        <window.CycleModeSeg value={mode} onChange={setMode} />

        {reds && <window.AbsentCycleCard severity={mode === 'absent' ? 'amenorrhea' : 'watch'} />}

        {natural && (
          <React.Fragment>
            <window.PhaseIndicator inverted={st.heroInverted} />
            <window.FCCycleForecastCard width={309} />
            <window.SymptomCheckCard />
            <window.FCCyclePhaseCard paceKey="onTrack" width={309} />
            <window.LearnedCard />

            <div>
              <SectionHeader>Expected weight by phase</SectionHeader>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.5, padding: '0 4px 11px', textWrap: 'pretty' }}>
                Folded into your weekly targets so a normal hormonal swing never reads as falling behind. Tuned to your own history where we have it.
              </div>
              <GroupedList>
                <Row label="Luteal" value="+1.4 kg" valueColor="var(--accent)" note="Your learned value (typical is +0.5)." />
                <Row label="Menstruation" value="+0.4 kg" />
                <Row label="Follicular" value="−1.0 kg" valueColor="#3a6b2e" />
                <Row label="Ovulation" value="0.0 kg" last />
              </GroupedList>
            </div>
          </React.Fragment>
        )}

        {mode === 'contraception' && (
          <React.Fragment>
            <window.ContraceptionCard />
            <window.SymptomCheckCard />
          </React.Fragment>
        )}

        {mode === 'absent' && (
          <window.SymptomCheckCard />
        )}

        <div>
          <SectionHeader>Cycle setup</SectionHeader>
          <GroupedList>
            <Row label="Cycle mode" value={window.CYCLE_MODES[mode].label} />
            {natural && <Row label="Cycle length" value="28 days" />}
            {natural && <Row label="Last period" value="14 Dec" />}
            <Row label="Cycle insights" chevron={false} control={<Toggle on={true} />} last />
          </GroupedList>
        </div>
      </BodyB>
      <HomeIndicator />
    </Phone>
  );
}

Object.assign(window, { CampPlan, PlanPreview, WeekRoadmap, MenstrualCycle, CycleCurve, ProofBand, PlanGlide });
