// fc-onboarding.jsx, Two first-run flows hinged on the paywall, native iOS patterns.
//
//   FLOW 1 · ONBOARDING ("OnWeight Setup"), pre-paywall signup that produces a plan.
//     Launch → Account → Sport → Fight date → Sex → Current wt → Goal wt →
//     Cut rate → Cut strategies → Building → Plan Preview · Paywall
//
//   FLOW 2 · REFINE PLAN, post-paywall refinement, launched from the
//     paywall or from Plan. Adds the precision the first pass can't:
//     Intro → Weigh-in time → Morning reminder → [female] Cycle on? → Period date →
//     Cycle length → Phase weight changes → Competition rules → Check details →
//     Refining → Home   (plan-shape choice is future state, see fc-future)
//
// Shared template OnbStep: back + progress + flow eyebrow, large title, content, CTA.
// Reuses the shared kit (GroupedList, Row, Stat, Toggle, IOSNumberPad, etc).

const FB = '#a32a18';

// Champion fact with its leading name removed, so it reads as a clean fragment
// under the name hero (which already shows the name). Drops a leading linking
// verb too ("Amanda Nunes is the only…" → "The only…"); keeps action verbs
// ("…won…" → "Won…"). Returns null when the fact isn't name-led, so the caller
// falls back to the credential line.
function championFactFragment(fact, who) {
  if (fact && who && fact.startsWith(who + ' ')) {
    let rest = fact.slice(who.length + 1).replace(/^(is|was|are)\s+/i, '');
    return rest.charAt(0).toUpperCase() + rest.slice(1);
  }
  return null;
}

// Shared tick row for the toned onboarding coach cards (sport / sex). Accent check
// + plain-sans line, matching the resolved card system.
function OnbTick({ children }) {
  return (
    <div style={{ display: 'flex', gap: 9, alignItems: 'flex-start', marginTop: 10 }}>
      <svg width="15" height="15" viewBox="0 0 16 16" style={{ marginTop: 1, flexShrink: 0 }} aria-hidden="true"><path d="M2 8 L6 12 L14 3" stroke={FB} strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
      <span style={{ fontFamily: 'var(--sans)', fontSize: 12.5, lineHeight: 1.45, color: 'var(--ink-2)' }}>{children}</span>
    </div>
  );
}

// Per-sport coach tip, shown the moment a sport is picked (OnbSport). Each frames
// the cut around that sport's weigh-in reality + the one thing it must protect.
const SPORT_PICK_TIP = {
  BJJ:         { chip: 'Weigh in dressed', headline: 'You weigh in dressed, right before you roll.',
    bullets: ['No time to rehydrate after', 'Keep the cut real and conservative', 'Guards grip and gas tank'] },
  Boxing:      { chip: 'Same-day weigh-in', headline: 'Amateur weigh-ins are same day.',
    bullets: ['Whole cut is real weight, not water', 'No water cut to lean on', 'Guards your reach and hand speed'] },
  Judo:        { chip: '+5% morning check', headline: 'Evening weigh-in, random +5% morning check.',
    bullets: ['The cap stops you over-dehydrating', 'Gradual is the only way', 'Guards power and grip'] },
  MMA:         { chip: 'Day-before weigh-in', headline: 'Day-before weigh-in buys time to rebuild.',
    bullets: ['A moderate water cut works', 'Pace it to protect recovery', 'Guards your round-to-round engine'] },
  'Muay Thai': { chip: 'No fixed classes', headline: 'No universal class table here.',
    bullets: ['Set the limit your promotion gave', 'Where same-day, cut real weight only', 'Guards clinch strength and pace'] },
  Taekwondo:   { chip: '+5% same-day control', headline: 'Day-before, with a +5% same-day control.',
    bullets: ['The cap rewards a steady cut', 'Guards your explosive kicks', 'Power off both legs, every round'] },
  Wrestling:   { chip: 'Same-day weigh-in', headline: 'You weigh in and wrestle same day.',
    bullets: ['No time to rehydrate', 'Make it real weight, not water', 'Guards scramble power and gas tank'] },
  Other:       { chip: 'Your rules', headline: 'Not listed? We’ll work from your numbers.',
    bullets: ['Set your promotion’s limit and weigh-in timing', 'We keep the cut gradual and real', 'Guards your strength and stamina'] },
};

// Per-sport size/leverage edge you gain dropping below your natural class.
const SPORT_EDGE = {
  Boxing:        'reach over smaller opponents',
  MMA:           'a size and strength edge',
  BJJ:           'strength and grip on the mat',
  'BJJ (no-gi)': 'grip and scramble speed',
  Taekwondo:     'reach and leverage on kicks',
  Judo:          'grip and strength in the exchange',
  Wrestling:     'leverage in the scramble',
};

// Short phrase for the performance the cut must protect, per sport.
const SPORT_GUARD = {
  Boxing:        'your reach and hand speed',
  MMA:           'your round-to-round engine',
  'Muay Thai':   'your clinch and pace',
  BJJ:           'your grip and gas tank',
  'BJJ (no-gi)': 'your grip and scramble speed',
  Taekwondo:     'your explosive kicks',
  Judo:          'your power and grip',
  Wrestling:     'your scramble power and gas',
  Other:         'your strength and stamina',
};

// ── Limit-screen coaching tip (OnbGoalWeight) ────────────────────────────────
// The limit screen's job is to REACT to the number just chosen. So the tip's
// headline is the sport × pace read (SPORT_PACE_TIP), and the bullets carry the
// hard rate + where the limit sits vs the natural class. The deeper synthesis —
// real vs water, cycle, what to protect, social proof — is the Coach's Read
// screen's job, not this tip. (The old standalone "Cut Rate (derived)" screen
// is folded in here: with a fight date, paceVerdict() derives %bw/wk and the
// coach's verdict the instant a limit is picked; with no date it defers pace.)
function paceVerdict(cut, current, weeks) {
  if (!weeks || cut <= 0) return null;
  const kgWk = cut / weeks;
  const pct = (kgWk / current) * 100;
  let tone, tier, word;
  if (pct >= 1.0)       { tone = 'red';   tier = 'veryfast'; word = 'past the 1%/wk ceiling — that’s muscle, not water'; }
  else if (pct > 0.75)  { tone = 'amber'; tier = 'fast';     word = 'aggressive — doable, but fuel hard'; }
  else if (pct >= 0.45) { tone = 'green'; tier = 'ideal';    word = 'right in the 0.5%/wk pocket'; }
  else                  { tone = 'green'; tier = 'ideal';    word = 'gentle — runway to spare'; }
  return { tone, pct, kgWk, tier, line: cut.toFixed(1) + ' kg over ' + weeks + ' wks → ' + pct.toFixed(2) + '%/wk, ' + word };
}
const TONE_RANK = { green: 0, amber: 1, red: 2 };
// The limit card reads as one of three surfaces: green when the cut is recommended,
// beige when it's workable but worth care, red when it's past the safe ceiling.
// The verdict tone is passed through untouched so an amber caution keeps its
// quiet-dot markers instead of the green endorsement ticks.
const goalCardSurface = (t) => (t === 'amber' ? 'accent' : t);
const worseTone = (a, b) => (TONE_RANK[b] > TONE_RANK[a] ? b : a);
const TIER_LABEL = { ideal: 'Steady', fast: 'Fast', veryfast: 'Very fast' };

// Sport × pace headline matrix — the emotional read on the chosen limit, in the
// sport's own currency (punch power, scrambles, kicks…). Three pace tiers map to
// paceVerdict's tier: ideal (< 0.75%/wk), fast (0.75–1.0), veryfast (≥ 1.0).
const SPORT_PACE_TIP = {
  Boxing:        { ideal: 'This steady cut buys time to keep your punch power.', fast: 'This quick cut works, but you’ll fight to stay sharp late.', veryfast: 'This aggressive cut can bring range, but it costs endurance.' },
  MMA:           { ideal: 'This balanced cut keeps you ready in every phase.', fast: 'A fast cut trades comfort for competitiveness.', veryfast: 'This steep cut gives size, but you’ll feel the drain.' },
  'Muay Thai':   { ideal: 'This steady cut keeps your kicks and clinch sharp.', fast: 'This fast cut can test your conditioning and rhythm.', veryfast: 'This aggressive cut can sap explosiveness and timing.' },
  BJJ:           { ideal: 'This steady cut lets you roll with stamina and control.', fast: 'You’ll make the division, but scrambles may feel heavier.', veryfast: 'This aggressive cut can cost you strength and guard work.' },
  'BJJ (no-gi)': { ideal: 'This steady cut lets you roll with stamina and control.', fast: 'You’ll make the division, but scrambles may feel heavier.', veryfast: 'This aggressive cut can cost you strength and guard work.' },
  Taekwondo:     { ideal: 'This steady cut protects your kicking speed.', fast: 'This fast cut may tighten up your flexibility.', veryfast: 'This steep cut risks slowing your reactions.' },
  Judo:          { ideal: 'This gradual cut protects your power and grip.', fast: 'This fast cut can stiffen your grip and spike injury risk.', veryfast: 'This steep cut can drain the power your throws need.' },
  Wrestling:     { ideal: 'This steady cut keeps your scramble power and gas.', fast: 'This fast cut can leave your scrambles flat.', veryfast: 'This steep cut risks gassing you in the first period.' },
  Other:         { ideal: 'This steady cut protects your strength and stamina.', fast: 'This fast cut gets you lighter, but it tests endurance.', veryfast: 'This steep cut gets you down, but performance may dip.' },
};
function sportPaceTip(sportKey, tier) {
  const m = SPORT_PACE_TIP[sportKey] || SPORT_PACE_TIP.Other;
  return m[tier] || m.ideal;
}

function goalTip(sportKey, current, entered, rec, weeks) {
  if (!entered || isNaN(entered) || entered <= 0) return null;
  const note = 'Just to get started. We refine with your rules and fight-week strategies next.';
  const cut = +(current - entered).toFixed(1);
  if (cut <= 0) {
    return { tone: 'amber', surface: goalCardSurface('amber'), chip: rec ? rec.name : 'Custom limit', headline: 'At or above your walk-around weight.',
      bullets: ['No cut needed for this', 'You give up size to bigger athletes'], note };
  }
  // Where the limit sits vs the athlete's natural class.
  let cls;
  if (rec) {
    if (entered < rec.bodyTarget - 0.05) cls = { tone: 'green', headline: 'Below ' + rec.name + ', your natural class.', detail: 'Bigger and stronger than smaller opponents' };
    else if (entered > rec.limitKg + 0.05) cls = { tone: 'green', headline: 'Above ' + rec.name + ', your natural class.', detail: 'Easy weight to hold — may give up size' };
    else cls = { tone: 'green', headline: rec.name + ' is your natural class.', detail: 'Make weight without crashing' };
  } else {
    cls = { tone: 'green', headline: 'Set your promotion’s limit.', detail: 'No universal class table for your sport' };
  }
  const pv = paceVerdict(cut, current, weeks);
  if (!pv) {
    // No fight date: the rate can't be derived — lead on class, defer pace.
    return { tone: cls.tone, surface: goalCardSurface(cls.tone), chip: rec ? rec.name : 'Custom limit', headline: cls.headline,
      bullets: [cls.detail, 'You set the cut pace next — no fight date yet'], note };
  }
  // Dated: headline is the sport × pace read; bullets carry the rate + class.
  return {
    tone: pv.tone,
    surface: goalCardSurface(pv.tone),
    chip: (rec ? rec.name : 'Custom limit') + ' · ' + TIER_LABEL[pv.tier],
    headline: sportPaceTip(sportKey, pv.tier),
    bullets: [pv.line, cls.headline],
    note,
  };
}

// ── shared chrome ────────────────────────────────────────────────────────────
function OnbHeader({ step, total }) {
  return (
    <div style={{ padding: '4px 20px 0', display: 'flex', alignItems: 'center', gap: 14 }}>
      <button aria-label="Back" style={{ width: 34, height: 34, marginLeft: -6, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <svg width="11" height="18" viewBox="0 0 11 18" fill="none"><path d="M9 1 L2 9 L9 17" stroke="var(--ink)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
      </button>
      <div style={{ flex: 1, height: 4, borderRadius: 2, background: 'var(--rule)', overflow: 'hidden' }}>
        <div style={{ width: `${(step / total) * 100}%`, height: '100%', background: 'var(--ink)', borderRadius: 2 }} />
      </div>
    </div>
  );
}

// Flow eyebrow, the single clearest signal of WHICH flow you're in.
function FlowTag({ flow }) {
  const goal = flow === 'goal';
  const text = goal ? 'Refine your plan' : '';
  if (!text) return null;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <span style={{ fontFamily: 'var(--display)', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', fontWeight: 600, color: 'var(--ink-3)' }}>
        {text}
      </span>
    </div>
  );
}

function OnbStep({ flow = 'setup', label, step = 1, total = 9, title, subtitle, children, cta = 'Continue', ctaDisabled = false, skip = null, secondary = null, footer = null }) {
  return (
    <Phone styleKey="balanced" label={label}>
      <StatusBar />
      <OnbHeader step={step} total={total} />
      <div style={{ flex: 1, padding: '14px 20px 18px', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        <FlowTag flow={flow} />
        <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.15, color: 'var(--ink)', textWrap: 'balance', marginTop: 10 }}>{title}</div>
        {subtitle && <div style={{ fontFamily: 'var(--sans)', fontSize: 15, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>{subtitle}</div>}
        <div style={{ marginTop: 20, flex: 1 }}>{children}</div>
        {footer}
        <button style={{ width: '100%', background: ctaDisabled ? 'var(--rule)' : 'var(--ink)', color: ctaDisabled ? 'var(--ink-3)' : 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, marginTop: 14 }}>{cta}</button>
        {secondary && <button style={{ width: '100%', background: 'transparent', color: 'var(--ink)', border: '1px solid var(--rule-2)', borderRadius: 'var(--radius-ctl)', minHeight: 50, fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, marginTop: 10 }}>{secondary}</button>}
        {skip && <button style={{ width: '100%', background: 'transparent', color: 'var(--ink-3)', border: 'none', minHeight: 44, fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 500, marginTop: 4 }}>{skip}</button>}
      </div>
      <HomeIndicator />
    </Phone>
  );
}

// selectable list row (checkmark = selected)
function SelRow({ label, sub, on, last, icon, onClick }) {
  const body = (
    <div style={{ display: 'flex', alignItems: 'center', gap: 13, padding: '0 18px', minHeight: sub ? 62 : 54 }}>
      {icon && <span style={{ flexShrink: 0 }}>{icon}</span>}
      <div style={{ flex: 1 }}>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 16, fontWeight: on ? 600 : 400, color: 'var(--ink)' }}>{label}</div>
        {sub && <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', marginTop: 2 }}>{sub}</div>}
      </div>
      <span style={{ width: 22, height: 22, borderRadius: '50%', flexShrink: 0, border: on ? 'none' : '1.6px solid var(--rule-2)', background: on ? FB : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        {on && <svg width="11" height="9" viewBox="0 0 11 9" fill="none"><path d="M1 4.5 L4 7.5 L10 1" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>}
      </span>
    </div>
  );
  return (
    <div>
      {onClick
        ? <button type="button" onClick={onClick} style={{ display: 'block', width: '100%', textAlign: 'left', background: 'none', border: 'none', padding: 0, font: 'inherit', color: 'inherit', cursor: 'pointer' }}>{body}</button>
        : body}
      {!last && <div style={{ height: 1, background: 'var(--rule)', marginLeft: icon ? 49 : 18 }} />}
    </div>
  );
}

// ══════════════════════════════════════════════════════════════════════════════
//  FLOW 1 · ONBOARDING, "Your Camp"
// ══════════════════════════════════════════════════════════════════════════════

// ── 0 · LAUNCH ───────────────────────────────────────────────────────────────
function OnbLaunch() {
  return (
    <Phone styleKey="balanced" label="Launch">
      {/* dark, continues from the app-open splash */}
      <div style={{ position: 'absolute', inset: 0, background: '#15140f' }} />
      <div style={{ position: 'relative', display: 'flex', flexDirection: 'column', flex: 1, minHeight: '100%', color: '#f6f4ef' }}>
        {/* light status bar (dark surface) */}
        <div style={{ height: 50, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 30px', flexShrink: 0 }}>
          <span style={{ fontFamily: 'var(--num)', fontSize: 15, fontWeight: 600, color: '#f6f4ef' }}>9:41</span>
          <div style={{ display: 'flex', gap: 6 }}>
            <svg width="17" height="11" viewBox="0 0 17 11" fill="none"><rect x="0.5" y="3" width="3" height="8" rx="1" fill="#f6f4ef"/><rect x="4.8" y="1.5" width="3" height="9.5" rx="1" fill="#f6f4ef"/><rect x="9.1" y="0" width="3" height="11" rx="1" fill="#f6f4ef"/><rect x="13.4" y="0" width="3" height="11" rx="1" fill="rgba(246,244,239,0.4)"/></svg>
            <svg width="22" height="11" viewBox="0 0 24 12" fill="none"><rect x="1" y="1" width="20" height="10" rx="3" stroke="#f6f4ef" strokeOpacity="0.5"/><rect x="2.5" y="2.5" width="15" height="7" rx="1.5" fill="#f6f4ef"/></svg>
          </div>
        </div>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', padding: '12px 26px 22px' }}>
          {/* mark holds the splash's centre position, wordmark + tagline settle in below */}
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
            <div style={{ width: 92, height: 92, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              {window.AppMark ? <window.AppMark ink="#f6f4ef" accent="var(--accent)" size={92} /> : null}
            </div>
            <div style={{ fontFamily: 'var(--display)', fontSize: 46, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.02em', lineHeight: 0.95, color: '#f6f4ef', marginTop: 22 }}>OnWeight</div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 17, color: 'rgba(246,244,239,0.7)', marginTop: 14, lineHeight: 1.4, maxWidth: '20ch' }}>Make weight.<br />Stay sharp.<br />Every camp.</div>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
            <button style={{ width: '100%', background: '#f6f4ef', color: '#15140f', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600 }}>LOCK IN</button>
            <button style={{ width: '100%', background: 'transparent', color: 'rgba(246,244,239,0.78)', border: 'none', minHeight: 44, fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 500 }}>I already have an account</button>
          </div>
          <div style={{ display: 'flex', justifyContent: 'center', paddingTop: 16 }}>
            <div style={{ width: 140, height: 5, borderRadius: 3, background: 'rgba(246,244,239,0.85)' }} />
          </div>
        </div>
      </div>
    </Phone>
  );
}

// ── 1 · CREATE ACCOUNT ───────────────────────────────────────────────────────
// DEPRECATED (B5): superseded by the social-first `CreateAccount` (fc-account-
// safety.jsx), which is now the single canonical sign-up. Kept only so older
// references don't break; do not route new flows here.
function OnbAccount() {
  const field = (label, val, ph) => (
    <div style={{ marginBottom: 14 }}>
      <div style={{ fontFamily: 'var(--display)', fontSize: 10.5, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600, marginBottom: 8 }}>{label}</div>
      <div style={{ background: 'var(--paper)', border: '1px solid var(--rule)', borderRadius: 'var(--radius-ctl)', minHeight: 52, display: 'flex', alignItems: 'center', padding: '0 16px', fontFamily: 'var(--sans)', fontSize: 16, color: val ? 'var(--ink)' : 'var(--rule-2)' }}>{val || ph}</div>
    </div>
  );
  return (
    <OnbStep flow="setup" label="Create Account" step={1} total={8} title="Create your account" subtitle="One login for every camp. We keep your history so each cut starts smarter.">
      {field('Email', 'alex.mercer@gmail.com', 'you@email.com')}
      {field('Password', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022', 'At least 8 characters')}
      <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 4 }}>By continuing you agree to the Terms and Privacy Policy.</div>
    </OnbStep>
  );
}

// ── 2 · SPORT ────────────────────────────────────────────────────────────────
// Sport picker. Opens with NOTHING selected; tapping a sport selects it, enables
// the CTA, and reveals that sport's bespoke coach tip. Listed alphabetically.
// `sportKey` seeds a preset selection (used by the design-canvas variant artboards).
function OnbSport({ sportKey = null }) {
  const sports = [
    ['BJJ', 'BJJ', 'bjj'],
    ['Boxing', 'Boxing', 'boxing'],
    ['Judo', 'Judo', 'judo'],
    ['MMA', 'MMA', 'mma'],
    ['Muay Thai', 'Muay Thai', 'muaythai'],
    ['Taekwondo', 'Taekwondo', 'taekwondo'],
    ['Wrestling', 'Wrestling', 'wrestling'],
    ['Other sport', 'Other', 'other'],
  ];
  const [sel, setSel] = React.useState(sportKey);
  React.useEffect(() => { window.__sport = sel; }, [sel]);
  const Glyph = window.SportGlyph;
  const tip = sel ? SPORT_PICK_TIP[sel] : null;
  const proof = sel && window.socialProofFor ? window.socialProofFor(sel) : null;
  const footer = tip ? (
    <div style={{ marginTop: 16, borderRadius: 'var(--radius)', background: '#f1ddd5', padding: '20px 20px 22px' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
        <span style={{ fontFamily: 'var(--display)', fontSize: 10.5, fontWeight: 600, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>Your sport</span>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9.5, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--rule-2)' }}>{sel}</span>
      </div>
      {proof && (
        <React.Fragment>
          <div style={{ fontFamily: 'var(--sans)', fontWeight: 300, fontSize: 46, letterSpacing: '-0.03em', lineHeight: 0.9, color: 'var(--accent)', marginTop: 14 }}>{proof.stat}</div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 14, lineHeight: 1.45, color: 'var(--ink-2)', marginTop: 11, textWrap: 'pretty' }}>{proof.statOf}.</div>
        </React.Fragment>
      )}
      <div style={{ height: 1, background: 'rgba(21,20,15,0.12)', margin: '18px 0 6px' }} />
      <div style={{ fontFamily: 'var(--sans)', fontSize: 13.5, fontWeight: 600, color: 'var(--ink)' }}>{tip.headline}</div>
      {tip.bullets.map((b, i) => <OnbTick key={i}>{b}</OnbTick>)}
      {proof && <window.SourceTag src={proof.src} rule="rgba(21,20,15,0.12)" />}
    </div>
  ) : null;

  // Avatar list: the shared standard for icon-bearing select lists. Each option is
  // led by its bespoke sport glyph in a tinted tile.
  return (
    <OnbStep flow="setup" label="Select Sport" step={1} total={7}
      title="What do you fight?"
      subtitle="Your sport sets your weigh-in rules and what the cut protects. You can change this at any time."
      ctaDisabled={!sel} footer={footer}>
      <window.AvatarList>
        {sports.map(s => {
          const on = s[1] === sel;
          // "Other" has no bespoke glyph — give it a neutral target mark so the row
          // still carries an icon tile like every other option.
          const ic = s[1] === 'Other'
            ? <svg width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2" /><circle cx="12" cy="12" r="2.6" fill="currentColor" /></svg>
            : (Glyph ? <Glyph name={s[2]} size={26} variant="mono" ink={on ? 'var(--paper)' : 'var(--ink-2)'} /> : null);
          return <window.AvatarRow key={s[1]} label={s[0]} on={on} onClick={() => setSel(s[1])} icon={ic} />;
        })}
      </window.AvatarList>
    </OnbStep>
  );
}

// ── 3 · FIGHT DATE ───────────────────────────────────────────────────────────
function Calendar({ month = 'January 2026', start = 4, days = 31, sel = 11 }) {
  const cells = [...Array(start).fill(null), ...Array.from({ length: days }, (_, i) => i + 1)];
  return (
    <div style={{ background: 'var(--paper)', border: '1px solid var(--rule)', borderRadius: 'var(--radius)', padding: '16px 14px' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 4px 12px' }}>
        <span style={{ fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, color: 'var(--ink)' }}>{month}</span>
        <div style={{ display: 'flex', gap: 22 }}>
          <svg width="9" height="15" viewBox="0 0 9 15" fill="none"><path d="M7 1 L1.5 7.5 L7 14" stroke="var(--ink-3)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>
          <svg width="9" height="15" viewBox="0 0 9 15" fill="none"><path d="M2 1 L7.5 7.5 L2 14" stroke={FB} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '2px 0' }}>
        {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((d, i) => <div key={i} style={{ textAlign: 'center', fontFamily: 'var(--mono)', fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, paddingBottom: 6 }}>{d}</div>)}
        {cells.map((d, i) => (
          <div key={i} style={{ aspectRatio: '1', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            {d && <span style={{ width: 32, height: 32, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--sans)', fontSize: 15, fontWeight: d === sel ? 600 : 400, background: d === sel ? FB : 'transparent', color: d === sel ? '#fff' : 'var(--ink)' }}>{d}</span>}
          </div>
        ))}
      </div>
    </div>
  );
}
function OnbFightDate() {
  const sport = window.__sport || (window.FC && window.FC.sport) || 'MMA';
  return (
    <OnbStep flow="setup" label="Fight Date" step={3} total={7} title="When do you weigh in?" subtitle="We build the plan around this date. No fight yet? Set a target pace instead."
      footer={<div style={{ marginTop: 16, borderRadius: 'var(--radius)', background: 'var(--t-green-soft)', border: '1px solid color-mix(in srgb, var(--t-green) 26%, transparent)', padding: '20px 20px 22px' }}>
        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
          <span style={{ fontFamily: 'var(--display)', fontSize: 10.5, fontWeight: 600, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--t-green)' }}>Your fight</span>
        </div>
        <div style={{ fontFamily: 'var(--sans)', fontWeight: 300, fontSize: 50, letterSpacing: '-0.03em', lineHeight: 0.9, color: 'var(--ink)', marginTop: 14 }}>8 weeks</div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 14, lineHeight: 1.45, color: 'var(--ink-2)', marginTop: 11 }}>until you step on the scale.</div>
        <div style={{ height: 1, background: 'color-mix(in srgb, var(--t-green) 20%, transparent)', margin: '18px 0' }} />
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12 }}>
          <span style={{ fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, color: 'var(--ink)', maxWidth: '68%' }}>We read your trend, not the daily scale.</span>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, flexShrink: 0, fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700, color: 'var(--t-green)', background: 'var(--paper)', border: '1px solid color-mix(in srgb, var(--t-green) 30%, transparent)', borderRadius: 100, padding: '4px 9px' }}>
            <span style={{ width: 5, height: 5, borderRadius: '50%', background: 'var(--t-green)' }} />On track
          </span>
        </div>
        <svg viewBox="0 0 320 104" style={{ marginTop: 14, display: 'block', width: '100%', height: 'auto' }}>
          {/* gridlines */}
          {[20, 52, 84].map(y => <line key={y} x1="8" x2="312" y1={y} y2={y} stroke="color-mix(in srgb, var(--t-green) 12%, transparent)" strokeWidth="1" />)}
          {/* typical-swing band around the trend */}
          <polygon points="8,23 60,31 112,39 164,48 216,57 268,66 312,71 312,89 268,84 216,75 164,66 112,57 60,49 8,41" fill="color-mix(in srgb, var(--t-green) 13%, transparent)" />
          {/* daily = the noise */}
          <polyline points="8,36 30,28 52,46 74,35 96,52 118,42 140,58 162,47 184,30 206,60 228,70 250,62 272,78 294,72 312,82" fill="none" stroke="var(--rule-2)" strokeWidth="1.4" strokeLinejoin="round" strokeLinecap="round" opacity="0.7" />
          {[[8,36],[30,28],[52,46],[74,35],[96,52],[118,42],[140,58],[162,47],[206,60],[228,70],[250,62],[272,78],[294,72],[312,82]].map(([x,y],i) => <circle key={i} cx={x} cy={y} r="1.9" fill="var(--rule-2)" />)}
          {/* the smoothed trend */}
          <polyline points="8,32 60,40 112,48 164,57 216,66 268,75 312,80" fill="none" stroke="var(--t-green)" strokeWidth="3.5" strokeLinecap="round" strokeLinejoin="round" />
          {/* the water spike + callout */}
          <circle cx="184" cy="30" r="5" fill="none" stroke="var(--t-green)" strokeWidth="1.6" />
          <circle cx="184" cy="30" r="2.4" fill="var(--t-green)" />
          <text x="194" y="24" fontFamily="var(--num)" fontSize="13" fontWeight="700" fill="var(--ink)">+1.1 kg</text>
          <text x="194" y="35" fontFamily="var(--mono)" fontSize="7.5" letterSpacing="0.06em" fontWeight="700" fill="var(--ink-3)">WATER · TREND HELD</text>
        </svg>
        <div style={{ display: 'flex', gap: 14, marginTop: 10, fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.04em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600 }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}><span style={{ width: 9, height: 3, borderRadius: 2, background: 'var(--rule-2)' }} />Daily noise</span>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}><span style={{ width: 9, height: 3, borderRadius: 2, background: 'var(--t-green)' }} />Real trend</span>
        </div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 12, textWrap: 'pretty' }}>A morning up <strong style={{ fontWeight: 700, color: 'var(--ink)' }}>+1.1 kg</strong> is water, not the cut. The trend didn’t budge — you’re still on track.</div>
      </div>} secondary="No fight booked yet">
      <Calendar />
    </OnbStep>
  );
}

// ── 4 · WEIGH-IN WINDOW ──────────────────────────────────────────────────────
// The rehydration window is what decides how much of the cut can be water, so we
// ask it rather than infer it. Most sports have a probable rule (§FC_SPORTS), so
// we pre-select it and say so — the athlete only has to correct us.
const WEIGH_WINDOW_OPTS = [
  ['multiday', 'Multi-day event', 'You re-weigh each morning you compete'],
  ['sameday-lt2', 'Same day — under 2 hours', 'Almost no time to rehydrate'],
  ['sameday-gte2', 'Same day — 2 hours or more', 'A small rehydration window'],
  ['daybefore', 'Day before', 'Around 24 hours to rebuild'],
  ['unsure', 'Not sure yet', 'We’ll plan conservatively until you confirm'],
];
const SPORT_WEIGH_RULE = {
  MMA: { likely: 'daybefore', chip: 'MMA', line: 'Nearly all MMA promotions weigh in the day before — that ~24h is why a measured water cut is on the table at all.' },
  BJJ: { likely: 'sameday-lt2', chip: 'IBJJF', line: 'Most BJJ comps use this rule: you weigh in, in your gi, minutes before you roll. No rehydration window, so the cut has to be real weight.' },
  'BJJ (no-gi)': { likely: 'sameday-lt2', chip: 'IBJJF', line: 'Most no-gi comps use this rule: you weigh in, in your rashguard, right before you roll. No time to rebuild.' },
  Wrestling: { likely: 'sameday-gte2', chip: 'UWW', line: 'UWW weighs in the morning you wrestle. If your event runs across days, pick multi-day — you’ll re-weigh each morning.' },
  Judo: { likely: 'daybefore', chip: 'IJF', line: 'IJF weighs in the evening before, then a random morning control caps you at +5% of your category.' },
  Taekwondo: { likely: 'daybefore', chip: 'WT', line: 'Day-before weigh-in, plus a random same-day control that caps you at +5% of your category.' },
  Boxing: { likely: null, chip: 'Boxing', line: 'This one splits: amateur boxing weighs in the same day, pro is the day before. Check your bout sheet.' },
  'Muay Thai': { likely: null, chip: 'Muay Thai', line: 'No single standard — pros weigh in around 24 hours out, IFMA amateurs weigh in same-day in fight attire.' },
  Other: { likely: null, chip: 'Your promotion', line: 'We don’t have your promotion’s rule book, so tell us the timing and we’ll build the cut around it.' },
};
function OnbWeighInWindow({ sportKey = (window.__sport || 'MMA') }) {
  const sport = sportKey || 'MMA';
  const rule = SPORT_WEIGH_RULE[sport] || SPORT_WEIGH_RULE.Other;
  const [sel, setSel] = React.useState(rule.likely || 'daybefore');
  React.useEffect(() => { window.__weighWindow = sel; }, [sel]);
  return (
    <OnbStep flow="setup" label="Weigh-in Window" step={4} total={7} title="How soon after weigh-in do you compete?" subtitle="This sets your fight week cut strategy."
      footer={<div style={{ marginTop: 16 }}><CoachTip coach={false} sport={sport} chip={rule.chip} headline={rule.likely ? 'We’ve pre-filled your sport’s usual rule.' : 'Your sport doesn’t have one standard.'} bullets={[rule.line]} note="Change it any time — your plan recalculates." /></div>}>
      <GroupedList>
        {WEIGH_WINDOW_OPTS.map((o, i) => {
          const on = sel === o[0], likely = rule.likely === o[0];
          return (
            <div key={o[0]}>
              <button type="button" onClick={() => setSel(o[0])} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '15px 18px', width: '100%', textAlign: 'left', border: 'none', cursor: 'pointer', background: on ? 'var(--accent-soft)' : 'transparent' }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <span style={{ fontFamily: 'var(--sans)', fontSize: 15.5, fontWeight: 600, color: 'var(--ink)' }}>{o[1]}</span>
                    {likely && <span style={{ flexShrink: 0, fontFamily: 'var(--mono)', fontSize: 8.5, letterSpacing: '0.06em', textTransform: 'uppercase', fontWeight: 700, color: FB, background: on ? 'var(--paper)' : 'var(--accent-soft)', borderRadius: 100, padding: '2px 7px' }}>Usual</span>}
                  </div>
                  <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.4, marginTop: 3 }}>{o[2]}</div>
                </div>
                <span style={{ width: 22, height: 22, borderRadius: '50%', flexShrink: 0, border: on ? 'none' : '1.6px solid var(--rule-2)', background: on ? FB : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                  {on && <svg width="11" height="9" viewBox="0 0 11 9" fill="none"><path d="M1 4.5 L4 7.5 L10 1" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>}
                </span>
              </button>
              {i < WEIGH_WINDOW_OPTS.length - 1 && <div style={{ height: 1, background: 'var(--rule)', marginLeft: 18 }} />}
            </div>
          );
        })}
      </GroupedList>
    </OnbStep>
  );
}

// Recommended vs not-recommended cut strategies + social proof, surfaced in the
// biological-sex read. Strategy set transcribed from the Figma cut-strategy
// frames; Water Load is the only sex-split item (not advised for female athletes).
// Exact strategy is refined later in the Plan. (StratRow / SubLabel are the shared
// read-card primitives from fc-shared, so this card and the coach cards match.)
function fighterQuoteFor(sex) {
  const Q = window.FC_FIGHTER_QUOTES || [];
  const who = sex === 'F' ? 'Kayla Harrison' : 'Robert Whittaker';
  return Q.find(x => x.who === who) || Q[0] || null;
}
function SexCutRead({ sex, sport }) {
  const female = sex === 'F';
  const rec = female
    ? ['Low-fibre & low-sodium days', 'Sweat-out — capped at 5% of bodyweight']
    : ['Low-fibre & low-sodium days', 'Sweat-out — capped at 5% of bodyweight', 'Water load — a temporary ~0.5% drop'];
  const avoid = female
    ? ['Low-carb — carbs fuel your performance', 'Water loading — not advised for female athletes']
    : ['Low-carb — carbs fuel your performance'];
  return (
    <div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 11 }}>
        <div>
          <div style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.12em', textTransform: 'uppercase', color: '#2f5a24', fontWeight: 600, marginBottom: 6 }}>Recommended</div>
          <ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>{rec.map((r, i) => <StratRow key={i} ok label={r} />)}</ul>
        </div>
        <div>
          <div style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600, marginBottom: 6 }}>Not recommended</div>
          <ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>{avoid.map((r, i) => <StratRow key={i} ok={false} label={r} />)}</ul>
        </div>
      </div>
      {female && <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, lineHeight: 1.45, color: 'var(--ink-3)', marginTop: 11 }}>Got a menstrual cycle? We build your weigh-in water swings into the plan when you refine.</div>}
    </div>
  );
}

// ── 4 · BIOLOGICAL SEX ───────────────────────────────────────────────────────
// Biological sex. Value-focused: we optimise the cut for THIS athlete — cutting
// methods chosen for their physiology, and (carefully — never assuming every
// female athlete currently cycles) menstrual-cycle weigh-in swings built into the
// targets later when the plan is refined.
// Per-sport fun fact — shown on the biological-sex step when the athlete picks
// "Prefer not to say" (no sex to match a champion on). Light, true, sourced from
// sport history (ancient-Olympics dates per Britannica / Olympics.com; Pankration
// 648 BC; judo first Olympic martial art 1964).
const SPORT_FUN_FACT = {
  MMA: 'Pankration — boxing and wrestling with kicks, holds and chokes — was an Olympic event back in 648 BC. MMA’s ancestor is over 2,600 years old.',
  Boxing: 'Boxing has been an Olympic sport since 688 BC — fought bare-knuckle, with no rounds and no weight classes.',
  BJJ: 'Brazilian jiu-jitsu grew out of judo — brought to Brazil and reworked by the Gracie family in the 1920s.',
  'BJJ (no-gi)': 'Brazilian jiu-jitsu grew out of judo — brought to Brazil and reworked by the Gracie family in the 1920s.',
  Judo: 'Judo means “the gentle way” — and in 1964 it became the first martial art ever contested at the Olympics.',
  'Muay Thai': 'Muay Thai is “the art of eight limbs” — it scores with fists, elbows, knees and shins.',
  Taekwondo: 'Taekwondo means “the way of the foot and fist” — and a spinning head kick scores the most points in the sport.',
  Wrestling: 'Wrestling is the oldest combat sport at the Olympics — contested since 708 BC, just 32 years after the very first Games.',
  Other: 'Combat sports punch above their weight at the Olympics — they account for around a quarter of all the medals on offer.',
};

// Biological-sex coach card. Class isn't known yet at this step, so the social
// proof is matched on SPORT + SEX only (no division claim), framed warmly
// ("You're in good company"). Carries one tick on the physiology-specialised cut
// strategy, and — only for female athletes, and never assuming a cycle — a
// conditional line about cycle-aware targets.
// ── Cycle water-weight graph for the biological-sex screen ───────────────────
// Three tweakable treatments, all built on the same model as the §15 CycleCurve
// (water sits low through follicular, peaks in late luteal) and styled to match
// the clay proof cards: ink curve, accent for the luteal emphasis, mono micro
// labels, hairlines at rgba(21,20,15,0.12). No "you're here" marker — tracking
// isn't set up yet, so this shows the shape we plan around, not their position.
const SEX_CYCLE_PHASES = [['Menses', 0, 5], ['Follicular', 5, 13], ['Ovul', 13, 16], ['Luteal', 16, 28]];
const SEX_CYCLE_DAYS = 28;

function sexCycleGeom(width, H, padT, padB, padL = 2, padR = 2, yMax = 1.25) {
  const off = (d) => -Math.cos(2 * Math.PI * (d - 10) / SEX_CYCLE_DAYS);
  const xs = (d) => padL + (d / SEX_CYCLE_DAYS) * (width - padL - padR);
  const ys = (o) => padT + (1 - (o + yMax) / (2 * yMax)) * (H - padT - padB);
  const pts = Array.from({ length: SEX_CYCLE_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(SEX_CYCLE_DAYS).toFixed(1)} ${ys(-yMax).toFixed(1)} L ${xs(0).toFixed(1)} ${ys(-yMax).toFixed(1)} Z`;
  return { off, xs, ys, line, area, baseY: ys(0), yMax };
}

// A · PHASED CURVE — the CycleCurve treatment, phase bands named underneath.
// Most consistent with the cycle screens the athlete meets later.
function SexCycleA({ width = 305 }) {
  // Generous top padding is the annotation lane — the callout sits ABOVE the
  // peak with a leader down to it, so nothing overlaps the curve.
  const H = 134, padT = 36, padB = 22, padL = 6, padR = 6, yMax = 1.15;
  const g = sexCycleGeom(width, H, padT, padB, padL, padR, yMax);
  const peakX = g.xs(24), peakY = g.ys(1);
  return (
    <svg width="100%" viewBox={`0 0 ${width} ${H}`} style={{ display: 'block' }} role="img" aria-label="Water weight across a 28-day cycle: lowest through the follicular phase, peaking about 0.8 kg above baseline in the late luteal phase, where the target adjusts.">
      {/* one reference: the baseline we plan against */}
      <line x1={padL} x2={width - padR} y1={g.baseY} y2={g.baseY} stroke="rgba(21,20,15,0.22)" strokeWidth="1" strokeDasharray="3 3" />
      <text x={width - padR} y={g.baseY - 5} textAnchor="end" fontFamily="var(--mono)" fontSize="7" letterSpacing="0.06em" fill="var(--ink-3)" fontWeight="600">BASELINE</text>
      <path d={g.line} fill="none" stroke={FB} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" />
      {/* the luteal read — leader up into the annotation lane */}
      <line x1={peakX} x2={peakX} y1={peakY - 4.5} y2={30} stroke={FB} strokeWidth="1" strokeDasharray="2 2" opacity="0.55" />
      <circle cx={peakX} cy={peakY} r="4.5" fill={FB} />
      <text x={peakX} y={17} textAnchor="middle" fontFamily="var(--mono)" fontSize="10" fontWeight="700" fill={FB}>+0.8 kg</text>
      <text x={peakX} y={27} textAnchor="middle" fontFamily="var(--mono)" fontSize="7" letterSpacing="0.06em" fill="var(--ink-3)" fontWeight="600">TARGET ADJUSTS</text>
      {/* phase axis */}
      <line x1={padL} x2={width - padR} y1={H - padB} y2={H - padB} stroke="rgba(21,20,15,0.18)" strokeWidth="1" />
      {SEX_CYCLE_PHASES.map((b, i) => (i > 0 ? <line key={b[0]} x1={g.xs(b[1])} x2={g.xs(b[1])} y1={H - padB} y2={H - padB + 4} stroke="rgba(21,20,15,0.22)" strokeWidth="1" /> : null))}
      {SEX_CYCLE_PHASES.map((b) => {
        const on = b[0] === 'Luteal';
        return <text key={b[0]} x={(g.xs(b[1]) + g.xs(b[2])) / 2} y={H - 6} textAnchor="middle" fontFamily="var(--mono)" fontSize="7" letterSpacing="0.05em" fill={on ? FB : 'var(--ink-3)'} fontWeight={on ? 700 : 600}>{b[0].toUpperCase()}</text>;
      })}
    </svg>
  );
}

// B · ANNOTATED — adds the magnitude, so the value prop is quantified rather
// than only shaped. Phases become a slim segmented rail above the curve.
function SexCycleB({ width = 305 }) {
  const H = 108, padT = 22, padB = 18;
  const g = sexCycleGeom(width, H, padT, padB);
  const peakX = g.xs(24), peakY = g.ys(g.off(24));
  return (
    <svg width="100%" viewBox={`0 0 ${width} ${H}`} style={{ display: 'block', overflow: 'visible' }} role="img" aria-label="Across a 28-day cycle, water weight can swing up to about 0.8 kg, peaking in the late luteal phase.">
      <defs><linearGradient id="sexcycB" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0" stopColor="var(--ink)" stopOpacity="0.10" />
        <stop offset="1" stopColor="var(--ink)" stopOpacity="0" />
      </linearGradient></defs>
      {SEX_CYCLE_PHASES.map((b) => {
        const on = b[0] === 'Luteal';
        return <rect key={b[0]} x={g.xs(b[1]) + 0.6} y="0" width={g.xs(b[2]) - g.xs(b[1]) - 1.2} height="4" rx="2" fill={on ? FB : 'rgba(21,20,15,0.16)'} />;
      })}
      {SEX_CYCLE_PHASES.map((b) => {
        const on = b[0] === 'Luteal';
        return <text key={b[0]} x={(g.xs(b[1]) + g.xs(b[2])) / 2} y="15" textAnchor="middle" fontFamily="var(--mono)" fontSize="7" letterSpacing="0.05em" fill={on ? FB : 'var(--ink-3)'} fontWeight={on ? 700 : 600}>{b[0].toUpperCase()}</text>;
      })}
      <line x1={g.xs(0)} x2={g.xs(SEX_CYCLE_DAYS)} y1={g.baseY} y2={g.baseY} stroke="rgba(21,20,15,0.18)" strokeWidth="1" strokeDasharray="3 3" />
      <path d={g.area} fill="url(#sexcycB)" />
      <path d={g.line} fill="none" stroke="var(--ink)" strokeWidth="1.9" strokeLinejoin="round" strokeLinecap="round" />
      <line x1={peakX} x2={peakX} y1={peakY} y2={g.baseY} stroke={FB} strokeWidth="1" strokeDasharray="2 2" />
      <circle cx={peakX} cy={peakY} r="3.4" fill={FB} stroke="#e7d9c6" strokeWidth="1.8" />
      <text x={peakX - 7} y={peakY - 5} textAnchor="end" fontFamily="var(--mono)" fontSize="9" fontWeight="700" fill={FB}>+0.8 KG</text>
      <text x={g.xs(0)} y={g.baseY + 11} fontFamily="var(--mono)" fontSize="7" letterSpacing="0.05em" fill="var(--ink-3)" fontWeight="600">YOUR BASELINE</text>
    </svg>
  );
}

// C · PHASE BARS — no curve. Water level per phase as a column, which reads
// fastest at card scale and keeps the card's hierarchy on the copy.
function SexCycleC({ width = 305 }) {
  const H = 92, padT = 6, padB = 20, gap = 5;
  const levels = { Menses: 0.42, Follicular: 0.18, Ovul: 0.5, Luteal: 1 };
  const plotH = H - padT - padB;
  const unit = (width - gap * (SEX_CYCLE_PHASES.length - 1)) / SEX_CYCLE_DAYS;
  let x = 0;
  return (
    <svg width="100%" viewBox={`0 0 ${width} ${H}`} style={{ display: 'block', overflow: 'visible' }} role="img" aria-label="Water weight by cycle phase: lowest through follicular, highest through the luteal phase.">
      {SEX_CYCLE_PHASES.map((b) => {
        const on = b[0] === 'Luteal';
        const w = (b[2] - b[1]) * unit, lvl = levels[b[0]], h = Math.max(4, plotH * lvl);
        const el = (
          <g key={b[0]}>
            <rect x={x} y={padT + (plotH - h)} width={w} height={h} rx="3" fill={on ? FB : 'rgba(21,20,15,0.14)'} />
            <text x={x + w / 2} y={H - 6} textAnchor="middle" fontFamily="var(--mono)" fontSize="7.5" letterSpacing="0.05em" fill={on ? FB : 'var(--ink-3)'} fontWeight={on ? 700 : 600}>{b[0].toUpperCase()}</text>
          </g>
        );
        x += w + gap;
        return el;
      })}
      <line x1="0" x2={width} y1={padT + plotH} y2={padT + plotH} stroke="rgba(21,20,15,0.18)" strokeWidth="1" />
    </svg>
  );
}

function SexCycleGraph({ variant = 'phased', width = 305 }) {
  if (variant === 'annotated') return <SexCycleB width={width} />;
  if (variant === 'bars') return <SexCycleC width={width} />;
  return <SexCycleA width={width} />;
}

function SexProofCard({ sex, sport, cycleVariant = 'phased' }) {
  const female = sex === 'F';
  const proof = (sex === 'F' || sex === 'M') && window.bespokeProof ? window.bespokeProof(sport, sex) : null;
  const funFact = SPORT_FUN_FACT[sport] || SPORT_FUN_FACT.Other;
  const Tick = ({ children }) => (
    <div style={{ display: 'flex', gap: 9, alignItems: 'flex-start', marginTop: 10 }}>
      <svg width="15" height="15" viewBox="0 0 16 16" style={{ marginTop: 1, flexShrink: 0 }} aria-hidden="true"><path d="M2 8 L6 12 L14 3" stroke={FB} strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
      <span style={{ fontFamily: 'var(--sans)', fontSize: 12.5, lineHeight: 1.45, color: 'var(--ink-2)' }}>{children}</span>
    </div>
  );
  return (
    <div style={{ borderRadius: 'var(--radius)', background: '#e7d9c6', padding: '20px 20px 22px' }}>
      <div style={{ fontFamily: 'var(--display)', fontSize: 10.5, fontWeight: 600, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>{proof ? 'You’re in good company' : 'Did you know'}</div>
      {proof ? (
        <React.Fragment>
          <div style={{ fontFamily: 'var(--sans)', fontWeight: 300, fontSize: 42, letterSpacing: '-0.02em', lineHeight: 0.95, color: 'var(--accent)', marginTop: 14 }}>{proof.who}</div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 14, lineHeight: 1.45, color: 'var(--ink-2)', marginTop: 11, textWrap: 'pretty' }}>{championFactFragment(proof.fact, proof.who) || (proof.cred + '.')}</div>
        </React.Fragment>
      ) : (
        <div style={{ fontFamily: 'var(--sans)', fontSize: 16, lineHeight: 1.5, color: 'var(--ink)', marginTop: 14, textWrap: 'pretty' }}>{funFact}</div>
      )}
      {female && <React.Fragment>
        <div style={{ height: 1, background: 'rgba(21,20,15,0.12)', margin: '20px 0 16px' }} />
        <div style={{ fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, color: 'var(--ink)', textWrap: 'pretty' }}>Two things change in your plan.</div>
        <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 18 }}>
          <div>
            <SexCycleGraph variant={cycleVariant} width={281} />
            <div style={{ display: 'flex', gap: 11, marginTop: 10 }}>
              <span style={{ flexShrink: 0, fontFamily: 'var(--mono)', fontSize: 9.5, fontWeight: 700, letterSpacing: '0.06em', color: FB, paddingTop: 3 }}>01</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 600, color: 'var(--ink)', letterSpacing: '-0.005em' }}>Cycle-aware targets</div>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 4, textWrap: 'pretty' }}>Athletes who menstruate carry more water some weeks. We plan around it.</div>
              </div>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 11 }}>
            <span style={{ flexShrink: 0, fontFamily: 'var(--mono)', fontSize: 9.5, fontWeight: 700, letterSpacing: '0.06em', color: FB, paddingTop: 3 }}>02</span>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 600, color: 'var(--ink)', letterSpacing: '-0.005em' }}>A strategy for {sport} and your body</div>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 4, textWrap: 'pretty' }}>We leave water loading out of your fight week — the evidence doesn’t support it for female athletes.</div>
            </div>
          </div>
        </div>
      </React.Fragment>}
    </div>
  );
}

function OnbSex({ sportKey = (window.__sport || 'MMA'), cycleVariant = 'phased' }) {
  const sport = sportKey || 'MMA';
  const sportLabel = sport === 'BJJ' ? 'BJJ' : sport;
  const opts = ['Female', 'Male', 'Prefer not to say'];
  const [sel, setSel] = React.useState('Female');
  React.useEffect(() => { window.__sex = sel === 'Female' ? 'F' : sel === 'Male' ? 'M' : 'N'; }, [sel]);
  const sexCode = sel === 'Female' ? 'F' : sel === 'Male' ? 'M' : 'N';
  const chip = sexCode === 'F' ? sportLabel + ' · ♀' : sexCode === 'M' ? sportLabel + ' · ♂' : sportLabel;
  const headline = sexCode === 'F' ? 'Built for female ' + sportLabel + ' fighters.'
    : sexCode === 'M' ? 'Built for male ' + sportLabel + ' fighters.' : 'Tuned to you either way.';
  const note = sexCode === 'N' ? 'If you menstruate, switch on cycle tracking when you refine — we’ll tune the strategy then too.'
    : 'We refine your exact strategy with you when you set up the plan.';
  // Custom Phone (not OnbStep): options + the coach’s read flow together in a
  // scroll region, with the CTA pinned — so the read never clips on a real device
  // and there’s no dead gap when the list is short.
  return (
    <Phone styleKey="balanced" label="Biological Sex">
      <StatusBar />
      <OnbHeader step={2} total={7} />
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
        <div style={{ padding: '14px 20px 0' }}>
          <FlowTag flow="setup" />
          <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.15, color: 'var(--ink)', textWrap: 'balance', marginTop: 10 }}>Your biological sex</div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 15, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>It sets your cutting strategy and how we read your weight near weigh-in.</div>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '16px 20px 8px', display: 'flex', flexDirection: 'column', gap: 16 }}>
          <window.AvatarList>
            {opts.map(o => <window.AvatarRow key={o} label={o} on={sel === o} onClick={() => setSel(o)} />)}
          </window.AvatarList>
          <SexProofCard sex={sexCode} sport={sport} cycleVariant={cycleVariant} />
        </div>
        <div style={{ padding: '6px 20px 12px' }}>
          <button style={{ width: '100%', background: 'var(--ink)', color: 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600 }}>Continue</button>
        </div>
      </div>
      <HomeIndicator />
    </Phone>
  );
}

// ── 5/6 · WEIGHT INPUTS (number pad) ─────────────────────────────────────────
function WeightEntry({ flow = 'setup', label, step, total, title, subtitle, val, cta = 'Continue', tip = null }) {
  return (
    <Phone styleKey="balanced" label={label}>
      <StatusBar />
      <OnbHeader step={step} total={total} />
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
        <div style={{ padding: '14px 20px 0' }}>
          <FlowTag flow={flow} />
          <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.15, color: 'var(--ink)', textWrap: 'balance', marginTop: 10 }}>{title}</div>
          {subtitle && <div style={{ fontFamily: 'var(--sans)', fontSize: 15, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>{subtitle}</div>}
          {tip && <div style={{ marginTop: 14 }}>{tip}</div>}
        </div>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10 }}>
          <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 80, letterSpacing: '-0.03em', color: 'var(--ink)' }}>{val}<span style={{ display: 'inline-block', width: 2, height: '0.78em', background: FB, marginLeft: 5, borderRadius: 1, transform: 'translateY(0.04em)' }} /></span>
          <span style={{ fontFamily: 'var(--mono)', fontSize: 16, letterSpacing: '0.12em', color: 'var(--ink-3)', fontWeight: 600 }}>KG</span>
        </div>
        <div style={{ padding: '0 20px 12px' }}>
          <button style={{ width: '100%', background: 'var(--ink)', color: 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600 }}>{cta}</button>
        </div>
        <window.IOSNumberPad />
      </div>
      <HomeIndicator />
    </Phone>
  );
}
function OnbCurrentWeight() {
  return <WeightEntry flow="setup" label="Current Weight" step={5} total={7} title="What do you weigh now?" subtitle="Your fasted morning weight. That's your start line." val="78.4" />;
}

// Limit with a coach recommendation derived from sport + current weight.
// The athlete can type their own (most will), but they see the natural class
// and what cutting below it actually costs first.
function OnbGoalWeight({ sportKey = 'MMA', current = 78.4, val: val0 = '74.4', sex = 'men', weeks = 8 }) {
  // Live keypad so an unsafe target can gate to GoalOutOfRange (publishes __goalWeight).
  const [val, setVal] = React.useState(() => window.__goalWeight || val0);
  const dirty = React.useRef(!!window.__goalWeight);
  const onKey = (k) => setVal((v) => {
    if (k === 'del') { dirty.current = true; return v.length > 1 ? v.slice(0, -1) : '0'; }
    if (!dirty.current) { dirty.current = true; v = ''; }
    if (k === '.') return v.includes('.') ? v : (v === '' ? '0.' : v + '.');
    if (/\.\d$/.test(v)) return v;
    if (v === '0') return k;
    if (v.replace('.', '').length >= 4) return v;
    return v + k;
  });
  React.useEffect(() => { window.__goalWeight = val; }, [val]);
  const rec = (window.recommendDivision || (() => null))(sportKey, current, sex);
  const entered = parseFloat(val);
  const tipData = goalTip(sportKey, current, entered, rec, weeks);
  return (
    <Phone styleKey="balanced" label="Goal Weight">
      <StatusBar />
      <OnbHeader step={6} total={7} />
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        <div style={{ padding: '14px 20px 0' }}>
          <FlowTag flow="setup" />
          <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.15, color: 'var(--ink)', textWrap: 'balance', marginTop: 10 }}>Your limit</div>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 15, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>The weight you must hit on the scale. We add your gear and cycle allowances on top.</div>
        </div>

        <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'center', gap: 10, padding: '14px 0 2px' }}>
          <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 64, letterSpacing: '-0.03em', color: 'var(--ink)' }}>{val}<span style={{ display: 'inline-block', width: 2, height: '0.78em', background: FB, marginLeft: 5, borderRadius: 1, transform: 'translateY(0.04em)' }} /></span>
          <span style={{ fontFamily: 'var(--mono)', fontSize: 15, letterSpacing: '0.12em', color: 'var(--ink-3)', fontWeight: 600 }}>KG</span>
        </div>

        {rec ? (
          <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
            <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', padding: '10px 20px 0' }}>
              <span style={{ fontFamily: 'var(--display)', fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 700 }}>Tap your class</span>
              <span style={{ fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--ink-3)' }}>or type a custom limit</span>
            </div>
            <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', WebkitOverflowScrolling: 'touch', padding: '8px 20px 0' }}>
              <GroupedList>
                {(() => {
                  const S = (window.FC_SPORTS || {})[sportKey] || {};
                  const ladder = (S.divisions && S.divisions[sex === 'women' ? 'women' : 'men'] || []).filter((d) => d[1] < 900);
                  const eq = S.equipmentKg || 0;
                  return ladder.map((d, i) => {
                    const body = +(d[1] - eq).toFixed(1);
                    const on = Math.abs((parseFloat(val) || 0) - body) < 0.05;
                    const isRec = d[0] === rec.name;
                    const cut = +(current - body).toFixed(1);
                    return (
                      <div key={d[0]}>
                        <button type="button" onClick={() => { dirty.current = true; setVal(String(body)); }} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '14px 18px', width: '100%', textAlign: 'left', border: 'none', cursor: 'pointer', background: on ? 'var(--accent-soft)' : 'transparent' }}>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                              <span style={{ fontFamily: 'var(--sans)', fontSize: 15.5, fontWeight: 600, color: 'var(--ink)' }}>{d[0]}</span>
                              {isRec && <span style={{ fontFamily: 'var(--mono)', fontSize: 8.5, letterSpacing: '0.06em', textTransform: 'uppercase', fontWeight: 700, color: FB, background: on ? 'var(--paper)' : 'var(--accent-soft)', borderRadius: 100, padding: '2px 7px' }}>Recommended</span>}
                            </div>
                          </div>
                          <div style={{ flexShrink: 0, textAlign: 'right' }}>
                            <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 19, color: 'var(--ink)' }}>{body.toFixed(1)}</span>
                            <span style={{ fontFamily: 'var(--mono)', fontSize: 10, color: 'var(--ink-3)', fontWeight: 600, marginLeft: 2 }}>kg</span>
                          </div>
                          <span style={{ width: 22, height: 22, borderRadius: '50%', flexShrink: 0, border: on ? 'none' : '1.6px solid var(--rule-2)', background: on ? FB : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                            {on && <svg width="11" height="9" viewBox="0 0 11 9" fill="none"><path d="M1 4.5 L4 7.5 L10 1" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>}
                          </span>
                        </button>
                        {i < ladder.length - 1 && <div style={{ height: 1, background: 'var(--rule)', marginLeft: 18 }} />}
                      </div>
                    );
                  });
                })()}
              </GroupedList>
              {tipData && (
                <div style={{ margin: '12px 2px 2px' }}>
                  <CoachTip coach={false} tone={tipData.tone} surface={tipData.surface} chip={tipData.chip} headline={tipData.headline} bullets={tipData.bullets} note={tipData.note} />
                </div>
              )}
            </div>
          </div>
        ) : (
          <div style={{ flex: 1, minHeight: 0 }}>
            <div style={{ padding: '0 20px' }}>
              <div style={{ background: 'var(--accent-soft)', borderRadius: 'var(--radius)', padding: '14px 16px' }}>
                <div style={{ fontFamily: 'var(--display)', fontSize: 10, letterSpacing: '0.14em', textTransform: 'uppercase', color: FB, fontWeight: 700 }}>No fixed classes</div>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-2)', lineHeight: 1.45, marginTop: 7, textWrap: 'pretty' }}>Set the limit your promotion gave you.</div>
              </div>
            </div>
            {tipData && (
              <div style={{ padding: '12px 20px 0' }}>
                <CoachTip coach={false} tone={tipData.tone} surface={tipData.surface} chip={tipData.chip} headline={tipData.headline} bullets={tipData.bullets} note={tipData.note} />
              </div>
            )}
            <div style={{ flex: 1, minHeight: 8 }} />
          </div>
        )}
        <div style={{ padding: '0 20px 12px' }}>
          <button style={{ width: '100%', background: 'var(--ink)', color: 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600 }}>Continue</button>
        </div>
        <window.IOSNumberPad onKey={onKey} />
      </div>
      <HomeIndicator />
    </Phone>
  );
}

// ── 7 · CUT RATE ─────────────────────────────────────────────────────────────
function OnbCutRate() {
  const rates = [
    ['0.25', 'Conservative', 'Slow and steady. Needs a longer runway.', false],
    ['0.5', 'Optimal', 'Preserves muscle and power. The sweet spot.', true],
    ['0.75', 'Aggressive', 'Effective, but can bite into performance.', false],
    ['1.0', 'Maximum', 'The safety limit. Faster is over-cutting.', false],
  ];
  const [sel, setSel] = React.useState(1);
  return (
    <OnbStep flow="setup" label="Cut Rate" step={7} total={7} title="How fast do you cut?" subtitle="No fight booked, so you set the pace. It's a share of bodyweight per week; we project your weigh-in date."
      footer={<div style={{ marginTop: 16 }}><CoachTip sport={window.__sport || (window.FC && window.FC.sport) || 'MMA'} chip="0.5% / wk" headline="Make weight light and strong — that’s how you win." bullets={['Past 1% a week, you lose muscle, not fat', 'At 0.5% you make weight and still hit hard in the late rounds']} /></div>}>
      <GroupedList>
        {rates.map((r, i) => {
          const on = i === sel;
          return (
          <div key={r[1]}>
            <button type="button" onClick={() => setSel(i)} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '15px 18px', width: '100%', textAlign: 'left', border: 'none', cursor: 'pointer', background: on ? 'var(--accent-soft)' : 'transparent' }}>
              <div style={{ width: 64, flexShrink: 0 }}>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 2 }}>
                  <span style={{ fontFamily: 'var(--num)', fontWeight: 300, fontSize: 27, letterSpacing: '-0.02em', color: 'var(--ink)' }}>{r[0]}</span>
                  <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--ink-3)', fontWeight: 600 }}>%</span>
                </div>
                <div style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.1em', color: 'var(--ink-3)', fontWeight: 600, marginTop: 3 }}>PER WEEK</div>
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 15.5, fontWeight: 600, color: 'var(--ink)' }}>{r[1]}</div>
                <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.4, marginTop: 2, textWrap: 'pretty' }}>{r[2]}</div>
              </div>
              <span style={{ width: 22, height: 22, borderRadius: '50%', flexShrink: 0, border: on ? 'none' : '1.6px solid var(--rule-2)', background: on ? FB : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                {on && <svg width="11" height="9" viewBox="0 0 11 9" fill="none"><path d="M1 4.5 L4 7.5 L10 1" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>}
              </span>
            </button>
            {i < rates.length - 1 && <div style={{ height: 1, background: 'var(--rule)', marginLeft: 18 }} />}
          </div>
          );
        })}
      </GroupedList>
    </OnbStep>
  );
}

// ── CUT RATE (DERIVED) — folded into the Goal Weight tip ─────────────────────
// When there's a fight date, the rate isn't a choice — it's gap ÷ weeks. We no
// longer ask for it on its own screen; goalTip()/paceVerdict() above compute it
// and state the coach's verdict the instant the athlete picks their limit. The
// no-date pace picker (OnbCutRate) still exists for the no-fight-booked case.

// ── 8 · CUT STRATEGIES ───────────────────────────────────────────────────────
function StrategyCard({ name, desc, warn, on, muted, onClick }) {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-start', gap: 13, padding: '14px 16px', background: 'var(--paper)', border: `1px solid ${on ? FB : 'var(--rule)'}`, borderRadius: 'var(--radius-ctl)', opacity: muted ? 0.62 : 1 }}>
      <button type="button" onClick={onClick} style={{ display: 'flex', alignItems: 'flex-start', gap: 13, flex: 1, minWidth: 0, background: 'none', border: 'none', padding: 0, textAlign: 'left', font: 'inherit', color: 'inherit', cursor: onClick ? 'pointer' : 'default' }}>
        <span style={{ width: 22, height: 22, borderRadius: '50%', flexShrink: 0, marginTop: 1, border: on ? 'none' : '1.6px solid var(--rule-2)', background: on ? FB : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          {on && <svg width="11" height="9" viewBox="0 0 11 9" fill="none"><path d="M1 4.5 L4 7.5 L10 1" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>}
        </span>
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 15.5, fontWeight: on ? 600 : 500, color: 'var(--ink)' }}>{name}</span>
          <span style={{ display: 'block', fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.45, marginTop: 4, textWrap: 'pretty' }}>
            {warn && <span style={{ color: FB, fontWeight: 600 }}>{warn} </span>}{desc}
          </span>
        </span>
      </button>
      <button type="button" aria-label={`About ${name}`} style={{ flexShrink: 0, width: 20, height: 20, borderRadius: '50%', border: '1.4px solid var(--rule-2)', background: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--serif)', fontSize: 11, fontStyle: 'italic', color: 'var(--ink-3)', marginTop: 1, cursor: 'pointer', padding: 0 }}>i</button>
    </div>
  );
}
function GroupLabel({ children }) {
  return <div style={{ fontFamily: 'var(--display)', fontSize: 10.5, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600, margin: '4px 2px 10px' }}>{children}</div>;
}
function OnbCutStrategy() {
  const [sel, setSel] = React.useState(() => new Set(['Low Fibre Diet']));
  const toggle = (name) => setSel(s => { const n = new Set(s); n.has(name) ? n.delete(name) : n.add(name); return n; });
  return (
    <OnbStep flow="setup" label="Cut Strategies" step={8} total={8} title="How will you cut fight week?" subtitle="Fight-week tactics, on top of your daily camp cut. Pick what you'll actually use." cta="Build my plan" skip="Skip, decide later">
      <GroupLabel>Recommended for you</GroupLabel>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        <StrategyCard name="Low Fibre Diet" desc="Flush your gut to drop ~1% of scale weight." on={sel.has('Low Fibre Diet')} onClick={() => toggle('Low Fibre Diet')} />
        <StrategyCard name="Low Sodium Diet" desc="Cut sodium 2–3 days for ~0.5–1% water weight." on={sel.has('Low Sodium Diet')} onClick={() => toggle('Low Sodium Diet')} />
        <StrategyCard name="Sweat Out" warn="Never more than 5% of bodyweight." desc="Sauna or sweat session, fight week only." on={sel.has('Sweat Out')} onClick={() => toggle('Sweat Out')} />
      </div>
      <div style={{ marginTop: 18 }}><GroupLabel>Not recommended for your sport</GroupLabel></div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        <StrategyCard name="Low Carb" warn="Takes 24h to reload." desc="Glycogen fuels performance, risky this close in." on={sel.has('Low Carb')} muted onClick={() => toggle('Low Carb')} />
        <StrategyCard name="Water Load" warn="Not for female athletes." desc="Manipulates water for a temporary ~0.5% drop." on={sel.has('Water Load')} muted onClick={() => toggle('Water Load')} />
      </div>
    </OnbStep>
  );
}

// ── COACH'S READ · the proof moment before the plan builds ───────────────────
// The payoff of everything they entered: a named coach interprets their sport,
// timeline and numbers in plain language. This is where onboarding earns trust,
// the app demonstrably reasoned about THEM, not a template.
// CoachReadBody = just the read (header + safe-line scale + insights), reusable
// inside the standalone Coach's Read screen and the merged Plan reveal.
function CoachReadBody({ sportKey = 'MMA', sex = (window.__sex || 'M'), variant }) {
  const FC = window.FC, S = (window.FC_SPORTS || {})[sportKey] || {}, P = (window.FC_SPORT_PROTECT || {})[sportKey] || {};
  const v = variant || window.__coachVariant || 'insight';
  const isFemale = sex === 'F';
  const startW = FC.planStart, goal = FC.goalWeight, drop = startW - goal, weeks = 8;
  const pct = (drop / weeks / startW) * 100;            // weekly %
  const cutPct = (drop / startW) * 100;                 // total cut as % of bodyweight
  const weekly = drop / weeks;                          // kg per week
  const division = (FC && FC.division) || null;
  const campName = division ? ('Your ' + division + ' camp') : ('Your ' + sportKey + ' camp');
  const planType = window.planTypeFor ? window.planTypeFor(sportKey, true) : 'camp';
  const waterKg = planType === 'walk' ? 0 : planType === 'minimal' ? +(drop * 0.10).toFixed(1) : +(drop * 0.28).toFixed(1);
  const realKg = +(drop - waterKg).toFixed(1);
  const whenPhrase = { 'day-before': 'the day before', 'same-day': 'the morning you fight', 'within-2h': 'in your gi, minutes before you roll', 'same-or-day-before': 'same-day as an amateur, the day before as a pro' }[S.weighIn] || 'on your promotion’s schedule';

  // VALUE-FIRST insights — true, specific, useful even if they never pay. About THE FIGHTER.
  const insights = [];
  insights.push({ dot: 'var(--t-green)', label: 'YOUR PACE', head: 'Aim for about ' + weekly.toFixed(1) + ' kg a week.',
    body: 'Quick self-check: if you’re not down roughly ' + weekly.toFixed(1) + ' kg after seven days, tighten your food before you ever touch water.' });
  if (waterKg > 0) insights.push({ dot: 'var(--accent)', label: 'REAL VS WATER', head: 'Only ~' + waterKg.toFixed(1) + ' kg of this should be water.',
    body: 'You weigh in ' + whenPhrase + ', so about ' + waterKg.toFixed(1) + ' kg can come off as fight-week water and go back on after. The other ' + realKg.toFixed(1) + ' kg has to be real weight, off across camp — so start now.' });
  else insights.push({ dot: 'var(--accent)', label: 'REAL VS WATER', head: 'All of it has to be real weight.',
    body: 'You weigh in ' + whenPhrase + ' — no time to rehydrate. Any weight you pull with water, you carry into the fight as weakness. Walk it down in camp instead.' });
  if (isFemale) insights.push({ dot: 'var(--mustard-deep)', label: 'YOUR CYCLE', head: 'Expect ~0.5 kg of water in your back half.',
    body: 'If the scale jumps in the luteal phase, that’s water, not fat — don’t panic-cut it. Hold your pace and it comes off on its own.' });
  else if (P.protect) insights.push({ dot: 'var(--ink)', label: 'PROTECT', head: 'Guard what wins ' + sportKey + ' fights.', body: P.protect });

  const leadHead = cutPct <= 5 ? (drop.toFixed(1) + ' kg is ' + cutPct.toFixed(1) + '% of your bodyweight.') : (drop.toFixed(1) + ' kg is ' + cutPct.toFixed(1) + '% — a big cut, but it maps out.');
  const leadSub = cutPct <= 5
    ? 'That’s in the range you can make on real weight — if you start now and keep it gradual. Here’s what that means for you.'
    : 'A cut this size needs a long, clean runway — gradual every week, no crash. Here’s what that means for you.';

  const Header = (
    <React.Fragment>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.18em', textTransform: 'uppercase', color: FB, fontWeight: 600 }}>The corner’s read</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, letterSpacing: '-0.02em', lineHeight: 1.14, color: 'var(--ink)', marginTop: 16, textWrap: 'balance' }}>{leadHead}</div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 14.5, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>{leadSub}</div>
    </React.Fragment>
  );

  // a slim scientific anchor: where their cut sits vs the safe lines
  const Scale = (
    <div style={{ marginTop: 20 }}>
      <div style={{ position: 'relative', height: 8, borderRadius: 4, background: 'linear-gradient(90deg, var(--t-green) 0%, var(--t-green) 55%, #c9a23a 74%, var(--accent) 100%)' }}>
        <div style={{ position: 'absolute', top: -5, left: Math.min(98, (cutPct / 10) * 100) + '%', transform: 'translateX(-50%)', width: 3, height: 18, background: 'var(--ink)', borderRadius: 2, border: '1.5px solid var(--paper)' }} />
      </div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 8 }}>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.04em', color: 'var(--ink-3)', fontWeight: 600 }}>5% · SAFE OVER A CAMP</span>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '0.04em', color: 'var(--ink-3)', fontWeight: 600 }}>10%+ · DANGER</span>
      </div>
    </div>
  );

  const List = (compact) => (
    <div style={{ marginTop: 22 }}>
      <div style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.16em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600 }}>What this means for you</div>
      {insights.map((f, i) => (
        <div key={i} style={{ display: 'flex', gap: 13, padding: (compact ? '13px' : '16px') + ' 0', borderBottom: i < insights.length - 1 ? '1px solid var(--rule)' : 'none' }}>
          <span style={{ flexShrink: 0, marginTop: 6, width: 7, height: 7, borderRadius: '50%', background: f.dot }} />
          <div style={{ flex: 1 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
              <span style={{ fontFamily: 'var(--sans)', fontSize: compact ? 14.5 : 15.5, fontWeight: 600, color: 'var(--ink)', lineHeight: 1.3 }}>{f.head}</span>
              <span style={{ flexShrink: 0, fontFamily: 'var(--mono)', fontSize: 8.5, letterSpacing: '0.08em', color: 'var(--rule-2)', fontWeight: 600 }}>{f.label}</span>
            </div>
            {!compact && <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-2)', lineHeight: 1.5, marginTop: 4, textWrap: 'pretty' }}>{f.body}</div>}
          </div>
        </div>
      ))}
    </div>
  );

  const Narrative = (
    <div style={{ marginTop: 20, paddingTop: 18, borderTop: '1.5px solid var(--ink)' }}>
      <div style={{ fontFamily: 'var(--serif)', fontStyle: 'italic', fontSize: 17, lineHeight: 1.6, color: 'var(--ink)', textWrap: 'pretty' }}>
        “Aim for about {weekly.toFixed(1)} kilos a week — if you’re not down that after seven days, tighten the food before you touch water. {waterKg > 0 ? 'You weigh in ' + whenPhrase + ', so maybe ' + waterKg.toFixed(1) + ' kilos can be fight-week water; the other ' + realKg.toFixed(1) + ' has to be real, off in camp.' : 'You weigh in ' + whenPhrase + ' — no rehydration window, so every kilo has to be real weight.'} {isFemale ? 'And when the scale jumps in your back half, that’s water — hold your pace.' : (P.protect || '')}”
      </div>
      <div style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600, marginTop: 14 }}>Your corner · {sportKey}</div>
    </div>
  );

  const body = v === 'narrative' ? Narrative : List(v === 'brief');

  return <React.Fragment>{Header}{Scale}{body}</React.Fragment>;
}

// Your first 7 days — turns the read into the immediate next move. The bridge
// from "here's the science" to "here's what you do tomorrow morning." Reused on
// the standalone Coach's Read and on the merged Plan reveal.
function FirstWeekCard({ startW = 78.4, weekly = 0.5, weighInTime = '06:30' }) {
  const firstWeekTarget = +(startW - weekly).toFixed(1);
  return (
    <div style={{ marginTop: 22, border: '1.5px solid var(--ink)', borderRadius: 'var(--radius)', overflow: 'hidden' }}>
      <div style={{ background: 'var(--ink)', padding: '12px 16px', display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
        <span style={{ fontFamily: 'var(--display)', fontSize: 13, letterSpacing: '0.12em', fontWeight: 600, color: 'var(--paper)' }}>YOUR FIRST 7 DAYS</span>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 9.5, letterSpacing: '0.04em', color: 'rgba(246,244,239,0.6)', fontWeight: 600 }}>STARTS TOMORROW AM</span>
      </div>
      <div style={{ padding: '4px 16px 6px' }}>
        {[
          { k: 'TARGET', head: 'Reach ' + firstWeekTarget.toFixed(1) + ' kg by next week', sub: 'About ' + weekly.toFixed(1) + ' kg off — all from food, not water.' },
          { k: 'HABIT', head: 'Weigh in every morning, fasted', sub: 'Same time daily at ' + weighInTime + '. Consistency beats the exact hour.' },
          { k: 'TODAY', head: 'Log your starting weight', sub: 'One number sets your baseline — the daily verdict starts from there.' },
        ].map((s, i, a) => (
          <div key={s.k} style={{ display: 'flex', gap: 13, padding: '13px 0', borderBottom: i < a.length - 1 ? '1px solid var(--rule)' : 'none' }}>
            <span style={{ flexShrink: 0, width: 50, fontFamily: 'var(--mono)', fontSize: 8.5, letterSpacing: '0.08em', color: 'var(--accent)', fontWeight: 700, marginTop: 3 }}>{s.k}</span>
            <div style={{ flex: 1 }}>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 600, color: 'var(--ink)', lineHeight: 1.3 }}>{s.head}</div>
              <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-2)', lineHeight: 1.45, marginTop: 3, textWrap: 'pretty' }}>{s.sub}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// Standalone Coach's Read — kept for the design showcase (insight / narrative /
// brief variants, per-sport). In the live flow the read is folded into the Plan
// reveal (PlanPreview variant="reveal"), so loading → one combined screen.
function OnbCoachsRead({ sportKey = 'MMA', sex = (window.__sex || 'M'), variant }) {
  const FC = window.FC;
  const startW = FC.planStart, weekly = (FC.planStart - FC.goalWeight) / 8;
  return (
    <Phone styleKey="balanced" label="Coach's Read">
      <StatusBar />
      <div style={{ padding: '4px 20px 0', display: 'flex', alignItems: 'center' }}>
        <button aria-label="Back" style={{ width: 34, height: 34, marginLeft: -6, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <svg width="11" height="18" viewBox="0 0 11 18" fill="none"><path d="M9 1 L2 9 L9 17" stroke="var(--ink)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </button>
      </div>
      <div className="scroll" style={{ flex: 1, padding: '10px 20px 18px', display: 'flex', flexDirection: 'column' }}>
        <CoachReadBody sportKey={sportKey} sex={sex} variant={variant} />
        <FirstWeekCard startW={startW} weekly={weekly} weighInTime={FC.weighInTime} />
        <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 18, textWrap: 'pretty' }}>That’s your read — yours to keep. Inside, it becomes daily targets that adjust as your body responds.</div>
        <div style={{ marginTop: 16 }}>{window.ProofBand ? <window.ProofBand sport={sportKey} compact /> : null}</div>
        <div style={{ flex: 1, minHeight: 14 }} />
        <button style={{ width: '100%', background: 'var(--ink)', color: 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, marginTop: 16 }}>BUILD MY PLAN</button>
      </div>
      <HomeIndicator />
    </Phone>
  );
}

// ── LOADING (shared by both flows) ───────────────────────────────────────────
// Inject the loading-screen keyframes once.
if (typeof document !== 'undefined' && !document.getElementById('ow-build-kf')) {
  const st = document.createElement('style');
  st.id = 'ow-build-kf';
  st.textContent =
    '@keyframes owSpin{to{transform:rotate(360deg)}}' +
    '@keyframes owRowIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}' +
    '@keyframes owPop{0%{transform:scale(.4)}60%{transform:scale(1.12)}100%{transform:scale(1)}}';
  document.head.appendChild(st);
}

// "Building your camp" — the anticipation beat between the last input and the
// Coach's Read. Instead of a bare spinner, it runs the build as a sequence of
// steps that each double as a UVP, written from the athlete's REAL numbers
// (cut size, pace, fight-week water, sport, cycle). Plays once on mount; the
// end state is all-complete so print/export shows a finished list, not mid-run.
function OnbLoading({ refine = false }) {
  const FC = window.FC || {};
  const sport = FC.sport || 'MMA';
  const water = (window.planTypeFor ? window.planTypeFor(sport, true) : 'camp') !== 'walk';

  // A few short build lines, value-tinted but terse — no numbers, no paragraphs.
  const steps = [
    'Pacing your cut to protect your power',
    water ? 'Building your fight-week taper' : 'Setting a real-weight cut',
    `Tuning to ${sport} rules`,
    'Arming your daily verdict',
  ];

  const PER = 600;
  const reduce = typeof matchMedia !== 'undefined' && matchMedia('(prefers-reduced-motion: reduce)').matches;
  const [done, setDone] = React.useState(reduce ? steps.length : 0);
  React.useEffect(() => {
    if (reduce) return;
    let n = 0;
    const id = setInterval(() => { n += 1; setDone(n); if (n >= steps.length) clearInterval(id); }, PER);
    return () => clearInterval(id);
  }, []);
  const allDone = done >= steps.length;

  const DARK = { '--paper': '#15140f', '--paper-2': '#211f18', '--ink': '#faf7f1', '--ink-2': 'rgba(250,247,241,0.80)', '--ink-3': 'rgba(250,247,241,0.52)', '--rule': 'rgba(250,247,241,0.14)' };
  const ACC = '#e0563d';

  return (
    <Phone styleKey="balanced" label={refine ? 'Refining Plan' : 'Building Plan'}>
      <div style={{ ...DARK, flex: 1, display: 'flex', flexDirection: 'column', background: '#15140f', color: 'var(--ink)' }}>
        <StatusBar />
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '40px 34px', textAlign: 'center' }}>
          {window.OWMark
            ? <window.OWMark sport={sport} size={120} dark={true} accent={ACC} />
            : <svg width="44" height="44" viewBox="0 0 46 46" fill="none" style={{ animation: allDone ? 'none' : 'owSpin 1s linear infinite' }}><circle cx="23" cy="23" r="20" stroke="rgba(250,247,241,0.2)" strokeWidth="3"/><path d="M23 3 a20 20 0 0 1 20 20" stroke={ACC} strokeWidth="3" strokeLinecap="round"/></svg>}
          <div style={{ fontFamily: 'var(--sans)', fontSize: 22, fontWeight: 700, letterSpacing: '-0.01em', color: 'var(--ink)', marginTop: 24, textWrap: 'balance' }}>{refine ? 'Refining your plan' : 'Building your camp'}</div>

          {/* short build lines, one active at a time */}
          <div style={{ marginTop: 22, display: 'flex', flexDirection: 'column', gap: 10, minHeight: 132 }}>
            {steps.map((label, i) => {
              const state = i < done ? 'done' : i === done && !allDone ? 'active' : 'pending';
              return (
                <div key={label} style={{ display: 'flex', alignItems: 'center', gap: 11, opacity: state === 'pending' ? 0.32 : 1, transition: 'opacity .4s' }}>
                  <span style={{ flexShrink: 0, width: 18, height: 18, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    {state === 'done'
                      ? <span style={{ width: 18, height: 18, borderRadius: '50%', background: ACC, display: 'flex', alignItems: 'center', justifyContent: 'center', animation: reduce ? 'none' : 'owPop .3s ease' }}><svg width="9" height="7" viewBox="0 0 11 9" fill="none"><path d="M1 4.5 L4 7.5 L10 1" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg></span>
                      : state === 'active'
                        ? <svg width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ animation: reduce ? 'none' : 'owSpin .9s linear infinite' }}><circle cx="10" cy="10" r="8" stroke="rgba(250,247,241,0.18)" strokeWidth="2.4"/><path d="M10 2 a8 8 0 0 1 8 8" stroke={ACC} strokeWidth="2.4" strokeLinecap="round"/></svg>
                        : <span style={{ width: 7, height: 7, borderRadius: '50%', border: '1.5px solid rgba(250,247,241,0.3)' }} />}
                  </span>
                  <span style={{ fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 500, color: state === 'pending' ? 'var(--ink-3)' : 'var(--ink-2)', transition: 'color .3s' }}>{label}</span>
                </div>
              );
            })}
          </div>
        </div>
        <HomeIndicator />
      </div>
    </Phone>
  );
}

// ══════════════════════════════════════════════════════════════════════════════
//  FLOW 2 · REFINE PLAN, post-paywall refinement
// ══════════════════════════════════════════════════════════════════════════════

// ── INTRO · launches the post-paywall plan-refinement flow ───────────────────
function GoalSettingIntro() {
  const items = [
    ['Weigh-in timing', 'When your daily reminder fires.'],
    ['Cycle adjustments', 'So a normal hormonal swing never reads as behind.'],
    ['Competition rules', 'Equipment, re-weigh and hydration for your promotion.'],
  ];
  return (
    <Phone styleKey="balanced" label="Refine Plan · Intro">
      <StatusBar />
      <div style={{ flex: 1, padding: '24px 24px 22px', display: 'flex', flexDirection: 'column' }}>
        <FlowTag flow="goal" />
        <div style={{ fontFamily: 'var(--sans)', fontSize: 30, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.12, color: 'var(--ink)', marginTop: 12, textWrap: 'balance' }}>Dial in your plan.</div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 15.5, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 12, textWrap: 'pretty' }}>Your plan is live. Three quick steps make every target precise to your body and your promotion.</div>
        <div style={{ marginTop: 24, display: 'flex', flexDirection: 'column', gap: 0, flex: 1 }}>
          <GroupedList>
            {items.map((it, i) => (
              <div key={it[0]}>
                <div style={{ display: 'flex', alignItems: 'flex-start', gap: 14, padding: '15px 18px' }}>
                  <span style={{ width: 26, height: 26, flexShrink: 0, borderRadius: 8, background: 'var(--accent-soft)', color: FB, fontFamily: 'var(--mono)', fontSize: 12, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>{i + 1}</span>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, color: 'var(--ink)' }}>{it[0]}</div>
                    <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-3)', marginTop: 2, lineHeight: 1.45 }}>{it[1]}</div>
                  </div>
                </div>
                {i < items.length - 1 && <div style={{ height: 1, background: 'var(--rule)', marginLeft: 58 }} />}
              </div>
            ))}
          </GroupedList>
          <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 16, textWrap: 'pretty', padding: '0 2px' }}>Takes about two minutes. You can change any of it later from your plan.</div>
        </div>
        <button style={{ width: '100%', background: 'var(--ink)', color: 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600 }}>BEGIN SETUP</button>
        <button style={{ width: '100%', background: 'transparent', color: 'var(--ink-3)', border: 'none', minHeight: 44, fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 500, marginTop: 4 }}>Not now</button>
      </div>
      <HomeIndicator />
    </Phone>
  );
}

// ── 1 · WEIGH-IN TIME ────────────────────────────────────────────────────────
function OnbWeighInTime() {
  const times = [['Morning', '06:00 – 08:00', true], ['Midday', '11:00 – 13:00', false], ['Afternoon', '15:00 – 17:00', false], ['Evening', '19:00 – 21:00', false]];
  return (
    <OnbStep flow="goal" label="Weigh-in Time" step={1} total={9} title="When do you weigh in?" subtitle="One fasted weigh-in a day, same time each morning, gives the truest trend.">
      <GroupedList>
        {times.map((t, i) => <SelRow key={t[0]} label={t[0]} sub={t[1]} on={t[2]} last={i === times.length - 1} />)}
      </GroupedList>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.5, padding: '12px 4px 0', textWrap: 'pretty' }}>First thing: after the bathroom, before food or water.</div>
    </OnbStep>
  );
}

// ── 2 · MORNING REMINDER ─────────────────────────────────────────────────────
function OnbReminders() {
  return (
    <OnbStep flow="goal" label="Morning Reminder" step={2} total={9} title="Set your reminder" subtitle="A nudge at the same time each day keeps your trend honest and complete.">
      <GroupedList>
        <Row label="Morning reminder" chevron={false} control={<Toggle on={true} />} />
        <Row label="Reminder time" value="06:30" last />
      </GroupedList>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.5, padding: '12px 4px 0', textWrap: 'pretty' }}>Miss a day and we hold the trend rather than guess. The line only moves on real data.</div>
    </OnbStep>
  );
}

// ── 3 · CYCLE TRACKING (female branch) ───────────────────────────────────────
function OnbCycle() {
  return (
    <OnbStep flow="goal" label="Cycle Tracking" step={3} total={9} title="Track your cycle?" subtitle="Track it and we plan around water shifts near weigh-in, and learn your pattern over time." skip="Not now">
      <GroupedList>
        <Row label="Track my cycle" chevron={false} control={<Toggle on={true} />} last />
      </GroupedList>
      <div style={{ marginTop: 14 }}>
        <CoachTip sport={window.__sport || (window.FC && window.FC.sport) || 'MMA'} chip="Cycle-aware" headline="We adjust what we expect on the scale — never your target." bullets={['Before your period you may hold up to about half a kilo of water, sometimes none', 'We fold that into your expected weight, so a normal swing never reads as off-pace', 'Your limit and your training don’t change — we refine as we learn your pattern']} note="On contraception or an irregular cycle we watch your symptoms instead of a calendar." />
      </div>
    </OnbStep>
  );
}

// ── 4 · PERIOD DATE ──────────────────────────────────────────────────────────
function OnbPeriod() {
  return (
    <OnbStep flow="goal" label="Period Date" step={5} total={9} title="When did your last period start?" subtitle="Anchors the phase model so we can predict your water shifts.">
      <Calendar month="December 2025" start={1} days={31} sel={14} />
    </OnbStep>
  );
}

// ── 5 · CYCLE LENGTH ─────────────────────────────────────────────────────────
function OnbCycleLength() {
  const stepper = (v, unit) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: 0, background: 'var(--paper)', border: '1px solid var(--rule)', borderRadius: 'var(--radius-ctl)', overflow: 'hidden' }}>
      <button style={{ width: 54, minHeight: 52, fontSize: 24, color: 'var(--ink-3)', fontFamily: 'var(--num)' }}>{'−'}</button>
      <div style={{ flex: 1, textAlign: 'center', display: 'flex', alignItems: 'baseline', justifyContent: 'center', gap: 5 }}>
        <span style={{ fontFamily: 'var(--num)', fontSize: 28, fontWeight: 300, color: 'var(--ink)' }}>{v}</span>
        <span style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--ink-3)', fontWeight: 600 }}>{unit}</span>
      </div>
      <button style={{ width: 54, minHeight: 52, fontSize: 24, color: FB, fontFamily: 'var(--num)' }}>+</button>
    </div>
  );
  return (
    <OnbStep flow="goal" label="Cycle Length" step={6} total={9} title="How long is your cycle?" subtitle="An average is fine. We refine it from your own logged data over time.">
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <div><div style={{ fontFamily: 'var(--display)', fontSize: 10.5, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600, marginBottom: 8 }}>Cycle length</div>{stepper(28, 'days')}</div>
        <div><div style={{ fontFamily: 'var(--display)', fontSize: 10.5, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink-3)', fontWeight: 600, marginBottom: 8 }}>Period length</div>{stepper(5, 'days')}</div>
      </div>
    </OnbStep>
  );
}

// ── 6 · PHASE WEIGHT CHANGES ─────────────────────────────────────────────────
function PhaseField({ phase, when, est, val, note, last }) {
  return (
    <div style={{ padding: '14px 0', borderTop: '1px solid var(--rule)' }}>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, color: 'var(--ink)' }}>{phase} <span style={{ fontWeight: 400, color: 'var(--ink-3)' }}>· {when}</span></div>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginTop: 10 }}>
        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: 'var(--paper)', border: '1px solid var(--rule)', borderRadius: 'var(--radius-ctl)', padding: '0 14px', minHeight: 46 }}>
          <span style={{ fontFamily: 'var(--sans)', fontSize: 15, color: 'var(--ink-2)' }}>{est}</span>
          <svg width="11" height="7" viewBox="0 0 11 7" fill="none"><path d="M1 1 L5.5 5.5 L10 1" stroke="var(--rule-2)" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </div>
        <div style={{ width: 96, display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: 'var(--paper)', border: '1px solid var(--rule)', borderRadius: 'var(--radius-ctl)', padding: '0 14px', minHeight: 46 }}>
          <span style={{ fontFamily: 'var(--num)', fontSize: 17, color: 'var(--ink)' }}>{val}</span>
          <span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--ink-3)', fontWeight: 600 }}>kg</span>
        </div>
      </div>
      {note && <div style={{ fontFamily: 'var(--sans)', fontSize: 12, color: 'var(--ink-3)', lineHeight: 1.45, marginTop: 8, textWrap: 'pretty' }}>{note}</div>}
    </div>
  );
}
function OnbPhaseChanges() {
  return (
    <OnbStep flow="goal" label="Phase Weight Changes" step={7} total={9} title="Weight changes by phase" subtitle="Typical estimates, small, water-only, and different for everyone. Edit any number, or set them all to zero." skip="Use defaults">
      <CoachTip coach={false} chip="Fluid, not fat" headline="Starting estimates, not your numbers yet." bullets={['Cycle swings are water, usually under a kilo', 'Many athletes see almost none', 'Used so a normal swing never reads as ‘behind’', 'Then learned from your weigh-ins and refined']} />
      <div style={{ marginTop: 8 }}>
        <PhaseField phase="Luteal" when="1–2 wks before" est="Gain" val="+0.5" note="Water builds through the luteal week, about half a kilo is typical, but it varies widely." />
        <PhaseField phase="Menstruation" when="first days" est="Gain" val="+0.3" note="Retention often peaks the first day or two of bleeding, then drops away." />
        <PhaseField phase="Follicular" when="after period" est="None" val="0" note="Your lightest, baseline week, usually no change." />
        <PhaseField phase="Ovulation" when="mid-cycle" est="None" val="0" last />
      </div>
    </OnbStep>
  );
}

// ── 7 · COMPETITION RULES ────────────────────────────────────────────────────
function OnbCompRules({ sportKey = 'MMA' }) {
  const S = (window.FC_SPORTS || {})[sportKey] || (window.FC_SPORTS || {}).MMA || {};
  const weighVal = S.weighIn === 'within-2h' ? 'Same day, in the gi' : S.weighIn === 'same-or-day-before' ? 'Same day / day before' : S.weighIn === 'same-day' ? 'Same day' : 'Day before';
  const weighShort = S.weighIn === 'same-day' ? 'the morning you compete' : S.weighIn === 'within-2h' ? 'in the gi, minutes before you roll' : S.weighIn === 'same-or-day-before' ? 'the same day (amateur)' : 'the day before';
  const ruleBullets = [
    'Weighs in ' + weighShort,
    S.equipmentReq ? ('Counts your ' + S.equipmentKg + ' kg of gear in the target') : 'No gear — your body target is the class limit',
    S.reweigh ? ('Leaves room for the +' + S.reweighPct + '% re-weigh control') : 'Single weigh-in, so we keep the cut conservative',
  ];
  return (
    <OnbStep flow="goal" label="Competition Rules" step={8} total={9} title="Confirm your competition rules" subtitle={`Pre-filled for ${sportKey}. Double-check in case your promotion runs different rules.`} footer={
      <>
        <div style={{ marginTop: 14 }}><CoachTip sport={sportKey} chip={sportKey} headline="Your scale target is built from your rulebook." bullets={ruleBullets} note="Fight-week water is capped at what your weigh-in timing safely allows." /></div>
        <button style={{ width: '100%', background: 'var(--paper)', color: 'var(--ink)', border: '1px solid var(--rule-2)', borderRadius: 'var(--radius-ctl)', minHeight: 50, fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, marginTop: 14 }}>Enter details manually</button>
      </>
    }>
      <GroupedList>
        <Row label="Sport" value={sportKey} />
        <Row label="Weigh-in" value={weighVal} note={S.note} />
        <Row label="Equipment requirement" value={S.equipmentReq ? 'Yes' : 'No'} />
        {S.equipmentReq
          ? <Row label="Equipment weight" value={`+${S.equipmentKg} kg`} note={`You weigh in dressed, so your body target is ${S.equipmentKg} kg under the class limit.`} />
          : <Row label="Weigh-in attire" value="Underwear / shorts" note="Your body target is the class limit." />}
        {S.reweigh
          ? <Row label="Random re-weigh" value={`≤ +${S.reweighPct}% of class`} note="A random control on competition day caps how much you can rebound, so you can't over-dehydrate." last />
          : S.freeEntry
            ? <Row label="Weight class" value="Free entry" note="No universal class table for this sport, so you set your limit." last />
            : <Row label="Re-weigh / hydration" value="None" note="Single weigh-in, no post-weigh-in cap." last />}
      </GroupedList>
    </OnbStep>
  );
}

// ── 8 · CHECK DETAILS ────────────────────────────────────────────────────────
function OnbCheck() {
  return (
    <OnbStep flow="goal" label="Check Details" step={9} total={9} title="Does this look right?" subtitle="One last look before we refine your camp." cta="Refine my plan">
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div>
          <GroupLabel>Your details</GroupLabel>
          <GroupedList>
            <Row label="Sport" value="MMA · Lightweight" />
            <Row label="Weigh-in day" value="Sat 11 Jan" />
            <Row label="Start weight" value="78.4 kg" />
            <Row label="Division limit" value="74.4 kg" />
            <Row label="Cut rate" value="0.5% / wk" last />
          </GroupedList>
        </div>
        <div>
          <GroupLabel>Refinements</GroupLabel>
          <GroupedList>
            <Row label="Weigh-in reminder" value="06:30" />
            <Row label="Cycle tracking" value="On · luteal +0.5 kg" />
            <Row label="Equipment allowance" value="+0.5 kg" />
            <Row label="Re-weigh buffer" value="1%" last />
          </GroupedList>
        </div>
      </div>
    </OnbStep>
  );
}

// ── 9 · CHOOSE PLAN (Tapered / Linear) ───────────────────────────────────────
function PlanSpark({ tapered }) {
  // 309-wide mini chart. Tapered = steep then flatten; linear = straight.
  const W = 285, H = 96, padL = 6, padR = 6, padT = 10, padB = 8;
  const x = (t) => padL + t * (W - padL - padR);
  const y = (v) => padT + (1 - v) * (H - padT - padB);
  const N = 24;
  const pts = Array.from({ length: N + 1 }, (_, i) => {
    const t = i / N;
    const v = tapered ? 1 - (1 - Math.pow(1 - t, 2.2)) : 1 - t; // tapered eases out, linear straight
    return [x(t), y(v)];
  });
  const line = pts.map((p, i) => (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' ');
  const area = `${line} L ${x(1).toFixed(1)} ${y(0).toFixed(1)} L ${x(0).toFixed(1)} ${y(0).toFixed(1)} Z`;
  return (
    <svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{ display: 'block' }}>
      <defs><linearGradient id={tapered ? 'spkT' : 'spkL'} x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor={FB} stopOpacity="0.12" /><stop offset="1" stopColor={FB} stopOpacity="0" /></linearGradient></defs>
      {[0.25, 0.5, 0.75].map(g => <line key={g} x1={padL} x2={W - padR} y1={y(g)} y2={y(g)} stroke="var(--rule)" strokeWidth="0.7" strokeDasharray="2 3" />)}
      <path d={area} fill={`url(#${tapered ? 'spkT' : 'spkL'})`} />
      <path d={line} fill="none" stroke={FB} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
      <circle cx={x(1)} cy={y(0)} r="3.5" fill={FB} />
    </svg>
  );
}
function PlanCard({ name, desc, tapered, on }) {
  return (
    <div style={{ background: 'var(--paper)', border: `1px solid ${on ? FB : 'var(--rule)'}`, borderRadius: 'var(--radius)', padding: 16, boxShadow: on ? 'var(--card-shadow)' : 'none' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <span style={{ fontFamily: 'var(--sans)', fontSize: 18, fontWeight: 600, color: 'var(--ink)' }}>{name}</span>
        {on && <span style={{ fontFamily: 'var(--display)', fontSize: 9, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--t-green)', fontWeight: 700, border: '1px solid var(--t-green)', borderRadius: 100, padding: '1px 7px' }}>Recommended</span>}
        <span style={{ marginLeft: 'auto', width: 22, height: 22, borderRadius: '50%', flexShrink: 0, border: on ? 'none' : '1.6px solid var(--rule-2)', background: on ? FB : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          {on && <svg width="11" height="9" viewBox="0 0 11 9" fill="none"><path d="M1 4.5 L4 7.5 L10 1" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/></svg>}
        </span>
      </div>
      <div style={{ fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 8, textWrap: 'pretty' }}>{desc}</div>
      <div style={{ marginTop: 14 }}><PlanSpark tapered={tapered} /></div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 6 }}>
        {['Wk 1', 'Wk 4', 'Fight'].map(l => <span key={l} style={{ fontFamily: 'var(--mono)', fontSize: 9.5, letterSpacing: '0.04em', color: 'var(--ink-3)', fontWeight: 600 }}>{l}</span>)}
      </div>
    </div>
  );
}
function OnbPlanOptions() {
  return (
    <Phone styleKey="balanced" label="Choose Plan">
      <StatusBar />
      <div style={{ flex: 1, padding: '20px 20px 18px', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        <FlowTag flow="goal" />
        <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.15, color: 'var(--ink)', marginTop: 10, textWrap: 'balance' }}>Choose your plan shape</div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 15, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>Same fight-day target, two ways to get there. You can switch any time.</div>
        <div style={{ marginTop: 20, flex: 1, display: 'flex', flexDirection: 'column', gap: 14 }}>
          <PlanCard name="Tapered" desc="Move early, then ease into a controlled glide into fight week. Lightest final week." tapered={true} on={true} />
          <PlanCard name="Linear" desc="A steady, identical rate every week of camp. Predictable and simple to follow." tapered={false} on={false} />
        </div>
        <button style={{ width: '100%', background: 'var(--ink)', color: 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, marginTop: 14 }}>START MY CAMP</button>
      </div>
      <HomeIndicator />
    </Phone>
  );
}

// ── CYCLE REGULARITY (female branch, step 4) ─────────────────────────────────
// Captures whether the athlete is cycling normally. Hard camps and aggressive cuts
// frequently disrupt or stop periods, and a lost period can flag low energy
// availability (RED-S). When irregular/absent we lean on logged data or switch the
// cycle math off entirely, and surface a gentle, non-diagnostic safety note.
function OnbCycleRegularity() {
  return (
    <OnbStep flow="goal" label="Cycle Regularity" step={4} total={9} title="Are your periods regular right now?" subtitle="Hard camps and big cuts can shift or pause your cycle. This sets how much we lean on cycle timing versus your logged data."
      footer={<div style={{ marginTop: 16 }}><CoachTip coach={false} tone="amber" chip="Worth watching" headline="Take a missing period seriously." bullets={['It can signal low energy availability (RED-S)', 'That costs bone, recovery and power', 'We keep cycle adjustments off and suggest a check-in with your coach or doctor']} note="Not medical advice." /></div>}>
      <GroupedList>
        <SelRow label="Regular" sub="Fairly predictable, month to month" on={true} />
        <SelRow label="Irregular" sub="Varies a lot, we’ll lean on your logged data" on={false} />
        <SelRow label="Not currently" sub="No periods right now, or on a long break" on={false} last />
      </GroupedList>
    </OnbStep>
  );
}

// ── CYCLE CHECK-IN (ongoing), "where am I, and is it right?" with help ───────
function CycleTimeline({ today = 24, length = 28 }) {
  const PH = [
    { label: 'Menses', days: 5, color: '#b0432f' },
    { label: 'Follicular', days: 8, color: '#6b8f5a' },
    { label: 'Ovulation', days: 3, color: '#c08a2e' },
    { label: 'Luteal', days: 12, color: '#8a6d9b' },
  ];
  const pct = (today / length) * 100;
  return (
    <div>
      <div style={{ position: 'relative', display: 'flex', gap: 3, height: 14 }}>
        {PH.map((p) => <div key={p.label} style={{ flexGrow: p.days, background: p.color, opacity: 0.9, borderRadius: 4 }} />)}
        <div style={{ position: 'absolute', top: -6, bottom: -6, left: `calc(${pct}% - 1px)`, width: 2, background: 'var(--ink)', borderRadius: 1 }} />
        <div style={{ position: 'absolute', top: -11, left: `calc(${pct}% - 6px)`, width: 12, height: 12, borderRadius: '50%', background: 'var(--ink)', border: '2.5px solid var(--paper-2)' }} />
      </div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '7px 16px', marginTop: 13 }}>
        {PH.map(p => (
          <div key={p.label} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <span style={{ width: 8, height: 8, borderRadius: 2, background: p.color, flexShrink: 0 }} />
            <span style={{ fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '0.02em', color: 'var(--ink-3)', fontWeight: 600 }}>{p.label}</span>
          </div>
        ))}
      </div>
    </div>
  );
}
function CycleCheckIn() {
  return (
    <Phone styleKey="balanced" label="Cycle Check-in">
      <StatusBar />
      <div style={{ padding: '4px 20px 0', display: 'flex', alignItems: 'center', gap: 12 }}>
        <button aria-label="Back" style={{ width: 34, height: 34, marginLeft: -6, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <svg width="11" height="18" viewBox="0 0 11 18" fill="none"><path d="M9 1 L2 9 L9 17" stroke="var(--ink)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>
        </button>
        <span style={{ fontFamily: 'var(--display)', fontSize: 13, letterSpacing: '0.14em', textTransform: 'uppercase', fontWeight: 600, color: 'var(--ink)' }}>Your cycle</span>
      </div>
      <div className="scroll" style={{ flex: 1, padding: '14px 20px 18px', display: 'flex', flexDirection: 'column', overflow: 'auto' }}>
        <div style={{ fontFamily: 'var(--display)', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', color: FB, fontWeight: 600 }}>Day 24 of 28</div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.12, color: 'var(--ink)', marginTop: 8, textWrap: 'balance' }}>Late luteal, your heaviest week.</div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 15, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>Expect to be holding around <b>+0.5 kg</b> of water. Read the trend line, not today’s number.</div>
        <div style={{ marginTop: 24 }}><CycleTimeline /></div>
        <div style={{ marginTop: 22 }}>
          <CoachTip chip="+0.5 kg hold" headline="This is water, and it’s already in your plan." bullets={['Your weekly target was lowered to absorb this swing', 'A higher scale reading right now doesn’t mean you’re behind', 'It clears in the first days of your period']} />
        </div>
        <div style={{ marginTop: 16 }}>
          <GroupedList>
            <Row label="Log period start" value="14 Dec" />
            <Row label="Cycle length" value="28 days" />
            <Row label="Phase adjustments" value="On · luteal +0.5 kg" last />
          </GroupedList>
        </div>
        <button style={{ width: '100%', textAlign: 'left', background: 'var(--paper)', border: '1px solid var(--rule)', borderRadius: 'var(--radius-ctl)', padding: '14px 16px', marginTop: 14, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
          <div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, color: 'var(--ink)' }}>This doesn’t match how I feel</div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 12.5, color: 'var(--ink-3)', marginTop: 2 }}>See the signs of each phase, or re-anchor your dates.</div>
          </div>
          <span style={{ fontFamily: 'var(--serif)', fontSize: 12, fontStyle: 'italic', color: 'var(--ink-3)', width: 22, height: 22, borderRadius: '50%', border: '1.4px solid var(--rule-2)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>?</span>
        </button>
      </div>
      <HomeIndicator />
    </Phone>
  );
}

// ── CYCLE LEARNING (ongoing), data-driven recommendation, always overridable ─
function MiniCompare({ label, val, color, w }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
      <span style={{ width: 86, flexShrink: 0, fontFamily: 'var(--sans)', fontSize: 13, color: 'var(--ink-2)' }}>{label}</span>
      <div style={{ flex: 1, height: 26, background: 'var(--rule)', borderRadius: 6, overflow: 'hidden' }}>
        <div style={{ width: `${w}%`, height: '100%', background: color, borderRadius: 6 }} />
      </div>
      <span style={{ width: 50, textAlign: 'right', flexShrink: 0, fontFamily: 'var(--num)', fontSize: 17, color: 'var(--ink)' }}>{val}</span>
    </div>
  );
}
function CycleLearning() {
  return (
    <Phone styleKey="balanced" label="Cycle Learning">
      <StatusBar />
      <div style={{ padding: '4px 20px 0', display: 'flex', alignItems: 'center' }}>
        <button aria-label="Close" style={{ width: 34, height: 34, marginLeft: -6, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <svg width="15" height="15" viewBox="0 0 15 15" fill="none"><path d="M2 2 L13 13 M13 2 L2 13" stroke="var(--ink)" strokeWidth="2" strokeLinecap="round"/></svg>
        </button>
      </div>
      <div className="scroll" style={{ flex: 1, padding: '10px 20px 18px', display: 'flex', flexDirection: 'column', overflow: 'auto' }}>
        <div style={{ fontFamily: 'var(--display)', fontSize: 11, letterSpacing: '0.16em', textTransform: 'uppercase', color: FB, fontWeight: 600 }}>Learned from your weigh-ins</div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 27, fontWeight: 700, letterSpacing: '-0.02em', lineHeight: 1.13, color: 'var(--ink)', marginTop: 8, textWrap: 'balance' }}>Your luteal week runs a little heavier.</div>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 15, lineHeight: 1.5, color: 'var(--ink-2)', marginTop: 10, textWrap: 'pretty' }}>Across your last three cycles, the scale rose about <b>+0.8 kg</b> in your luteal week, more than the <b>+0.5 kg</b> default we started with. Want to use your own number?</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 24, background: 'var(--paper)', border: 'var(--card-border)', borderRadius: 'var(--radius)', boxShadow: 'var(--card-shadow)', padding: '18px' }}>
          <MiniCompare label="Default" val="+0.5" color="var(--rule-2)" w={50} />
          <MiniCompare label="Your data" val="+0.8" color={FB} w={80} />
        </div>
        <div style={{ marginTop: 18 }}>
          <CoachTip coach={false} tone="green" chip="3 cycles logged" headline="I’ll update your targets to match your body." bullets={['Targets near your period pre-cut a touch more, so weigh-in still lands on the limit', 'Refined every cycle. Change it any time']} />
        </div>
        <div style={{ flex: 1, minHeight: 12 }} />
        <button style={{ width: '100%', background: 'var(--ink)', color: 'var(--paper)', border: 'none', borderRadius: 'var(--radius-ctl)', minHeight: 54, fontFamily: 'var(--sans)', fontSize: 16, fontWeight: 600, marginTop: 18 }}>USE MY NUMBER · +0.8 KG</button>
        <button style={{ width: '100%', background: 'transparent', color: 'var(--ink-3)', border: 'none', minHeight: 44, fontFamily: 'var(--sans)', fontSize: 14.5, fontWeight: 500, marginTop: 4 }}>Keep the +0.5 kg default</button>
        <div style={{ fontFamily: 'var(--sans)', fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.5, marginTop: 12, textWrap: 'pretty' }}>Based on cycles logged during an active cut, so we round conservatively. Not medical advice.</div>
      </div>
      <HomeIndicator />
    </Phone>
  );
}

Object.assign(window, {
  // shared step chrome (reused by the comp-rules manual sub-flow)
  OnbStep, OnbHeader, FlowTag, SelRow,
  // Flow 1 · Onboarding
  OnbLaunch, OnbAccount, OnbSport, OnbFightDate, OnbSex, OnbWeighInWindow, OnbCurrentWeight, OnbGoalWeight,
  OnbCutRate, OnbCutStrategy, OnbCoachsRead, CoachReadBody, FirstWeekCard, OnbLoading,
  // Flow 2 · Refine Plan
  GoalSettingIntro, OnbWeighInTime, OnbReminders, OnbCycle, OnbCycleRegularity, OnbPeriod, OnbCycleLength,
  OnbPhaseChanges, OnbCompRules, OnbCheck, OnbPlanOptions,
  // Cycle · adaptive & ongoing
  CycleCheckIn, CycleLearning,
});
