/* global React, Icon, Row, Field, Spinner, CourseCard,
   ONBOARDING_PHASES, onboardingProgress, useOnboarding, beginOnboarding,
   CourseEditor, CourseStatusCard, LocationSection, RcEditor, CreateManagerForm,
   savePartnership, PARTNER_TIERS, useCourses, useRcCourses, useRcTeeCourseIds, teesFor,
   setCourseStatus, useStaff, SandboxNineCard, QuadrantEditor, StickySave, useDirty */
// Onboarding a course, start to ready.
//
// Two pieces: a picker that starts one, and a panel that carries it through.
// Neither holds any progress state of its own — the panel reads the same
// derived checklist the cards do, so "where was I" is answered by the database
// rather than by anything this screen remembers. Leaving half way and coming
// back a week later on another machine lands in the same place.
//
// Sections are jumpable on purpose. Onboarding does not arrive in order: the
// contract may be weeks behind the scorecard, and a manager account may exist
// long before anyone has measured a quadrant.

// ─── Progress bar ────────────────────────────────────────────────────────────
function ProgressBar({ pct, height = 6, showLabel = false }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0, flex: 1 }}>
      <div style={{
        flex: 1, minWidth: 0, height, borderRadius: 999, overflow: 'hidden',
        background: 'rgba(14,28,19,0.16)',
      }}>
        <div style={{
          width: `${Math.max(0, Math.min(100, pct || 0))}%`, height: '100%',
          background: pct >= 100 ? 'var(--forest)' : 'var(--cream)',
          transition: 'width var(--dur-slow, 400ms) var(--ease, ease)',
        }}/>
      </div>
      {showLabel && (
        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-muted)', flexShrink: 0 }}>
          {pct}%
        </span>
      )}
    </div>
  );
}

// ─── The phase bar ───────────────────────────────────────────────────────────
// One segment per phase, and the segment is also the tab. Two things were on
// screen before — a percentage card and a tab bar whose labels each carried a
// fraction — and between them they said the same thing three times over.
//
// Width is proportional to the number of items in the phase, so a segment that
// looks twice as long really is twice as much work; a bar that gave equal
// thirds to a seven-item phase and a three-item one would move in lies. Fill
// is that phase's own progress. Read left to right it is the whole job.
function PhaseBar({ sections, phases, value, onChange }) {
  return (
    <div role="tablist" className="no-scrollbar" style={{
      display: 'flex', gap: 3, padding: 3, borderRadius: 'var(--r-sm)',
      background: 'var(--surface-sunken)', border: '1px solid var(--line)',
      overflowX: 'auto', maxWidth: '100%',
    }}>
      {sections.map(s => {
        const p = phases.find(x => x.id === s.id) || null;
        // Go live carries no checklist of its own — it is the gate, not a
        // phase — so it gets a fixed share rather than a zero-width sliver.
        const weight = p ? Math.max(2, p.total) : 3;
        const fill = p ? (p.total ? Math.round((p.done / p.total) * 100) : 100) : 0;
        const on = s.id === value;
        const done = p ? p.done === p.total : false;
        return (
          <button key={s.id} role="tab" aria-selected={on} onClick={() => onChange(s.id)}
            title={p ? `${s.label} — ${p.done} of ${p.total} done` : s.hint}
            style={{
              flex: `${weight} 1 0`, minWidth: 88, textAlign: 'left',
              padding: '8px 11px 9px', borderRadius: 'calc(var(--r-sm) - 2px)',
              border: 'none', cursor: 'pointer', font: 'inherit',
              background: on ? 'var(--cream)' : 'transparent',
              color: on ? 'var(--forest)' : 'var(--ink-muted)',
              transition: 'background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease)',
            }}>
            {/* Go live has no checklist to fill, so it gets a blank spacer
                rather than an empty track — an empty track next to four real
                ones reads as "nothing done here yet", which is a claim about
                progress the gate is not making. The spacer keeps the labels on
                one baseline. */}
            <div style={{
              height: 5, borderRadius: 999, overflow: 'hidden', marginBottom: 7,
              background: !p ? 'transparent'
                : on ? 'rgba(28,73,42,0.18)' : 'var(--line-strong)',
            }}>
              {p && (
                <div style={{
                  width: `${fill}%`, height: '100%',
                  background: on ? 'var(--forest)' : 'var(--cream)',
                  transition: 'width var(--dur-slow, 400ms) var(--ease, ease)',
                }}/>
              )}
            </div>
            <div style={{
              fontSize: 12.5, fontWeight: on ? 700 : 600,
              whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
            }}>
              {done ? '✓ ' : ''}{s.short}
            </div>
          </button>
        );
      })}
    </div>
  );
}

// ─── One checklist row ───────────────────────────────────────────────────────
// Deliberately the same shape as the readiness rows on CourseStatusCard: a
// filled disc when it is done, a red-edged ring when it is not. Two different
// checklists on one screen should at least look like the same idea.
function CheckRow({ item }) {
  return (
    <div style={{ display: 'flex', gap: 10, alignItems: 'flex-start', fontSize: 12.5, padding: '3px 0' }}>
      <span style={{
        flexShrink: 0, width: 16, height: 16, borderRadius: 999, marginTop: 1,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        background: item.done ? 'var(--forest)' : 'transparent',
        border: item.done ? 'none' : `1.5px solid ${item.blocks ? 'rgba(155,58,46,0.6)' : 'rgba(14,28,19,0.25)'}`,
        color: '#fff', fontSize: 10, fontWeight: 800,
      }}>{item.done ? '✓' : ''}</span>
      <span style={{ flex: 1, minWidth: 0, opacity: item.done ? 0.65 : 1 }}>
        <strong style={{ color: 'var(--ink)', fontWeight: item.done ? 500 : 700 }}>{item.label}</strong>
        {item.hint ? <span style={{ opacity: 0.65 }}> — {item.hint}</span> : null}
        {!item.blocks && <span style={{ opacity: 0.5, fontStyle: 'italic' }}> (doesn’t block going live)</span>}
      </span>
    </div>
  );
}

// Where a step is done elsewhere in the portal — the manager's tee sheet, or
// Stripe — and there is nothing to embed here. Saying so beats a dead button.
function ElsewhereNote({ children }) {
  return (
    <div className="card" style={{ padding: '14px 16px', background: 'rgba(28,73,42,0.05)', fontSize: 12.5, lineHeight: 1.6 }}>
      {children}
    </div>
  );
}

// ─── Partnership terms ───────────────────────────────────────────────────────
// The only genuinely new editor here. Everything else on this screen is an
// existing one mounted with `embedded`; partnership had no write path at all.
function PartnershipCard({ course, onSaved }) {
  const [form, setForm] = React.useState({
    partnership_tier: course.partnership_tier || 'pilot',
    contract_start:   course.contract_start || '',
    contract_end:     course.contract_end || '',
    founding_partner: !!course.founding_partner,
  });
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [msg, setMsg] = React.useState('');
  const pristine = React.useRef(form);
  const dirty = useDirty(form, pristine);
  const set = (k, v) => { setForm(f => ({ ...f, [k]: v })); setMsg(''); };

  React.useEffect(() => {
    const next = {
      partnership_tier: course.partnership_tier || 'pilot',
      contract_start:   course.contract_start || '',
      contract_end:     course.contract_end || '',
      founding_partner: !!course.founding_partner,
    };
    setForm(next);
    pristine.current = next;
    setErr(''); setMsg('');
  }, [course.id]); // eslint-disable-line react-hooks/exhaustive-deps

  async function save() {
    setBusy(true); setErr(''); setMsg('');
    try {
      await savePartnership(course.id, form);
      pristine.current = form;
      setMsg('Terms saved.');
      onSaved && onSaved();
    } catch (e) { setErr(e.message || 'Could not save.'); }
    setBusy(false);
  }

  const tiers = Object.keys(PARTNER_TIERS || { pilot: 1, growth: 1, embed: 1 });
  return (<>
    <StickySave dirty={dirty} saving={busy} error={err} message={!dirty && msg ? msg : ''}
      label="Save terms" onSave={save}
      onDiscard={() => { setForm(pristine.current); setErr(''); }}/>
    <div className="card" style={{ padding: 22 }}>
      <div className="eyebrow" style={{ marginBottom: 14 }}>Partnership terms</div>
      <Row>
        <Field label="Tier">
          <select className="input" value={form.partnership_tier} onChange={e => set('partnership_tier', e.target.value)}>
            {tiers.map(t => (
              <option key={t} value={t}>{(PARTNER_TIERS[t] || {}).label || t}</option>
            ))}
          </select>
        </Field>
        <Field label="Founding partner">
          <select className="input" value={form.founding_partner ? 'yes' : 'no'}
            onChange={e => set('founding_partner', e.target.value === 'yes')}>
            <option value="no">No</option>
            <option value="yes">Yes — rate locked</option>
          </select>
        </Field>
      </Row>
      <Row>
        <Field label="Contract start">
          <input className="input" type="date" value={form.contract_start} onChange={e => set('contract_start', e.target.value)}/>
        </Field>
        <Field label="Contract end">
          <input className="input" type="date" value={form.contract_end} onChange={e => set('contract_end', e.target.value)}/>
        </Field>
      </Row>
    </div>
  </>);
}

// ─── Who runs it ─────────────────────────────────────────────────────────────
// A course that already has a manager was still being shown the create-account
// form — with a "select a course" dropdown, inside the course it was standing
// in. Two mistakes: prompting for something already done, and asking a question
// with one right answer.
//
// Names come from useStaff, which the courses grid already loads and which has
// resolved them once; course_managers has two foreign keys into profiles, so an
// unqualified join is ambiguous and this is the read that already deals with it.
function ManagerSection({ course, adminId, onChanged }) {
  const [staff] = useStaff();
  const [adding, setAdding] = React.useState(false);

  if (!staff) return <Spinner/>;
  const entry = (staff.byCourse || {})[course.id];
  const managers = (entry ? entry.managers : []) || [];

  const nameOf = (m) => {
    const u = m.user;
    if (!u) return 'Unknown account';
    const full = [u.first_name, u.last_name].filter(Boolean).join(' ');
    return full || (u.handle ? `@${String(u.handle).replace(/^@/, '')}` : 'Unknown account');
  };

  if (managers.length && !adding) {
    return (
      <div className="card" style={{ padding: 22 }}>
        <div className="eyebrow" style={{ marginBottom: 12 }}>Course manager</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {managers.map(m => (
            <div key={m.id} style={{ display: 'flex', alignItems: 'baseline', gap: 8, fontSize: 13.5 }}>
              <span style={{ color: 'var(--forest)', fontWeight: 800 }}>✓</span>
              <strong style={{ color: 'var(--ink)' }}>{nameOf(m)}</strong>
              {m.user && m.user.handle && (
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11.5, color: 'var(--ink-muted)' }}>
                  @{String(m.user.handle).replace(/^@/, '')}
                </span>
              )}
              {m.role && m.role !== 'manager' && (
                <span className="pill-mono" style={{ fontSize: 9, padding: '2px 7px' }}>{m.role}</span>
              )}
            </div>
          ))}
        </div>
        <div style={{ fontSize: 12.5, color: 'var(--ink-muted)', marginTop: 14, lineHeight: 1.6 }}>
          {managers.length === 1 ? 'This account has' : 'These accounts have'} partner-portal
          access to this course. Accounts are managed on the Course Managers screen.
        </div>
        <button className="btn btn-ghost" style={{ marginTop: 14 }} onClick={() => setAdding(true)}>
          + Add another manager
        </button>
      </div>
    );
  }

  return (
    <>
      {adding && (
        <button className="btn btn-ghost" style={{ marginBottom: 12 }} onClick={() => setAdding(false)}>
          ← Back to the manager on file
        </button>
      )}
      <CreateManagerForm embedded lockCourseId={course.id} adminId={adminId}
        onCreated={() => { setAdding(false); onChanged && onChanged(); }}
        onClose={() => setAdding(false)}/>
    </>
  );
}

// ─── Publishing as Coming Soon ───────────────────────────────────────────────
// The end of the identity phase, and the point a course first becomes visible
// to golfers. Deliberately a button rather than something that fires on its own
// when the last field saves: putting a course in front of people is not a side
// effect of filling in a form.
//
// It says what it will do before it can do it, so the button is never a
// surprise appearing from nowhere, and it only ever moves a draft — a course
// paused as inactive stays paused, and one already live is left alone.
function ComingSoonCard({ course, phase, status, onChanged }) {
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const ready = phase && phase.done === phase.total;
  const published = status && status !== 'draft';

  async function publish() {
    setBusy(true); setErr('');
    try {
      await setCourseStatus(course.id, 'coming_soon');
      onChanged && onChanged('coming_soon');
    } catch (e) { setErr(e.message || 'Could not publish.'); }
    setBusy(false);
  }

  if (published) {
    return (
      <ElsewhereNote>
        <strong>This course is visible to golfers</strong>, badged Coming Soon — they can
        see it and can’t book it. Going fully bookable is the Go live step at the end.
      </ElsewhereNote>
    );
  }

  const left = phase ? phase.items.filter(i => !i.done) : [];
  return (
    <div className="card" style={{ padding: 22 }}>
      <div className="eyebrow" style={{ marginBottom: 6 }}>Visibility</div>
      <div style={{ fontSize: 13, color: 'var(--ink-muted)', lineHeight: 1.6, marginBottom: ready ? 16 : 0 }}>
        The course is a <strong>draft</strong> — hidden from golfers entirely. Finishing
        this section publishes it as <strong>Coming Soon</strong>: visible in the app,
        badged, not yet bookable.
        {!ready && left.length > 0 && (
          <> Still to fill in: {left.map(i => i.label.toLowerCase()).join(', ')}.</>
        )}
      </div>
      {err && <div role="alert" style={{ marginBottom: 12, fontSize: 13, color: 'var(--loss)' }}>{err}</div>}
      {ready && (
        <button className="btn btn-forest" onClick={publish} disabled={busy}>
          {busy ? 'Publishing…' : 'Publish as Coming Soon'}
        </button>
      )}
    </div>
  );
}

// ─── The panel ───────────────────────────────────────────────────────────────
// `short` is what the phase bar shows. The full label still heads the section
// below it, where there is room for it — a segment sized to three checklist
// items cannot hold the words "Access & operations" without ellipsing them
// into nonsense.
const ONBOARD_SHORT = {
  identity: 'Identity', layout: 'Layout', commercials: 'Commercials', access: 'Access',
};
const ONBOARD_SECTIONS = [
  ...ONBOARDING_PHASES.map(p => ({ id: p.id, label: p.label, short: ONBOARD_SHORT[p.id] || p.label, hint: p.hint })),
  { id: 'golive', label: 'Go live', short: 'Go live', hint: 'What the database requires before it will let this course be booked' },
];

function OnboardingPanel({ course, rc, adminId, onChanged }) {
  const [loaded, reloadLoaded] = useOnboarding(course.id);
  const [rcTeeIds] = useRcTeeCourseIds();
  const [section, setSection] = React.useState(null);
  const [status, setStatus] = React.useState(course.status);

  const progress = React.useMemo(() => onboardingProgress({
    course, rc, rcTees: teesFor(rc, rcTeeIds),
    holes: loaded ? loaded.holes : [],
    managers: loaded ? loaded.managers : [],
    futureSlots: loaded ? loaded.futureSlots : 0,
  }), [course, rc, rcTeeIds, loaded]);

  // Land on the first unfinished section rather than always on the first one —
  // resuming should put you where the work is. Only until the first manual
  // choice, after which it stays put.
  const landing = progress.nextIncomplete ? progress.nextIncomplete.phase : 'golive';
  const active = section || landing;

  const refresh = React.useCallback(() => { reloadLoaded(); onChanged && onChanged(); }, [reloadLoaded, onChanged]);
  const phase = progress.phases.find(p => p.id === active) || null;

  if (loaded === null) return <Spinner/>;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
      <div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginBottom: 8 }}>
          <div className="eyebrow" style={{ flex: 1, minWidth: 0 }}>
            Onboarding · {progress.done} of {progress.total} done
            {progress.blockingLeft > 0
              ? ` · ${progress.blockingLeft} before it can go live`
              : ' · nothing blocking'}
          </div>
          <div style={{ fontFamily: 'var(--font-display)', fontSize: 22, color: 'var(--paper)', lineHeight: 1 }}>
            {progress.pct}%
          </div>
        </div>
        <PhaseBar sections={ONBOARD_SECTIONS} phases={progress.phases}
          value={active} onChange={setSection}/>
      </div>

      <div key={active} className="rise-in" style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        {phase && (
          <div className="card" style={{ padding: 20 }}>
            <div className="eyebrow" style={{ marginBottom: 4 }}>
              {phase.label} · {phase.done}/{phase.total}
            </div>
            <div style={{ fontSize: 12.5, opacity: 0.65, marginBottom: 12 }}>{phase.hint}</div>
            {phase.items.map(it => <CheckRow key={it.id} item={it}/>)}
          </div>
        )}

        {/* Each step mounts only the fields its own checklist asks for. The
            course form used to arrive whole wherever it was used, which is how
            Identity came to carry a pricing card and a nine-hole pin-quadrant
            grid — and how the two notes that used to sit here came to exist,
            pointing at fields on a different step. The fields moved instead. */}
        {active === 'identity' && (<>
          <ComingSoonCard course={course} phase={phase} status={status}
            onChanged={(s) => { setStatus(s); refresh(); }}/>
          <CourseEditor embedded hideStatus fields={['course']}
            course={course} onSaved={refresh} onClose={() => {}}/>
          <LocationSection course={course} onSaved={refresh}/>
        </>)}

        {active === 'layout' && (<>
          <RcEditor embedded course={rc || null}
            prefill={rc ? null : { name: course.name, city: course.city, state: course.state }}
            onSaved={refresh} onClose={() => {}}/>
          <SandboxNineCard courseId={course.id} onSaved={refresh}/>
          <QuadrantEditor courseId={course.id}/>
        </>)}

        {active === 'commercials' && (<>
          <CourseEditor embedded hideStatus fields={['pricing']}
            course={course} onSaved={refresh} onClose={() => {}}/>
          <PartnershipCard course={course} onSaved={refresh}/>
        </>)}

        {active === 'access' && (<>
          <ManagerSection course={course} adminId={adminId} onChanged={refresh}/>
          <ElsewhereNote>
            <strong>Tee times</strong> are published from the course’s own portal, under
            Tee Sheet — set an interval, a price and a window, then apply it across as
            many days as you want a horizon for.
            <br/>
            <strong>Stripe</strong> is connected out of band; the account id lands on the
            course record. It is the one item that does not stand between this course
            and going live, but it does stand between the course and being paid.
          </ElsewhereNote>
        </>)}

        {active === 'golive' && (
          <CourseStatusCard courseId={course.id} status={status}
            onChanged={(s) => { setStatus(s); refresh(); }}/>
        )}
      </div>
    </div>
  );
}

// ─── Starting one ────────────────────────────────────────────────────────────
// Either a course already in the system with no Sandbox record, or a name typed
// from scratch. Both end the same way: a `courses` row and the panel open on it.
function OnboardPicker({ onStarted, onClose }) {
  const [sbxCourses] = useCourses();
  const [rcCourses] = useRcCourses();
  const [query, setQuery] = React.useState('');
  const [busy, setBusy] = React.useState('');
  const [err, setErr] = React.useState('');
  const [fresh, setFresh] = React.useState({ name: '', city: '', state: 'FL' });

  const key = (n) => String(n || '').trim().toLowerCase();
  const candidates = React.useMemo(() => {
    if (!sbxCourses || !rcCourses) return null;
    const taken = new Set(sbxCourses.map(c => key(c.name)));
    const term = query.trim().toLowerCase();
    return rcCourses
      .filter(r => !taken.has(key(r.name)))
      .filter(r => !term || [r.name, r.city, r.state].some(v => String(v || '').toLowerCase().includes(term)))
      .sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
  }, [sbxCourses, rcCourses, query]);

  async function start(seed, tag) {
    setBusy(tag); setErr('');
    try {
      const id = await beginOnboarding(seed);
      // The seed goes with the id. The course page opens before the network
      // dataset has reloaded, and with only an id it would render a header
      // with no name on it for a beat.
      onStarted({ id, ...seed, status: 'draft', hasSbx: true, onboarding: true });
    } catch (e) { setErr(e.message || 'Could not start onboarding.'); setBusy(''); }
  }

  return (
    <div style={{ maxWidth: 780, margin: '0 auto' }}>
      <button className="btn btn-ghost" onClick={onClose} style={{ marginBottom: 18 }}>← Back to courses</button>
      <div style={{ fontFamily: 'var(--font-display)', fontSize: 26, color: 'var(--forest)', marginBottom: 4 }}>
        Onboard a course
      </div>
      <div style={{ fontSize: 13, opacity: 0.65, marginBottom: 18, lineHeight: 1.6 }}>
        This creates the Sandbox record and walks the whole checklist — identity,
        layout, terms, access — resuming wherever you leave it. The course stays a
        <strong> draft</strong>, hidden from golfers, until you finish the first
        section and publish it as Coming Soon; it only becomes bookable when you
        pull the Go Live trigger at the end.
      </div>

      {err && <div role="alert" style={{ marginBottom: 14, fontSize: 13, color: 'var(--loss)', background: 'rgba(155,58,46,0.08)', padding: '10px 14px', borderRadius: 10 }}>{err}</div>}

      <div className="card" style={{ padding: 22, marginBottom: 16 }}>
        <div className="eyebrow" style={{ marginBottom: 12 }}>A course already in the system</div>
        <input className="input" value={query} onChange={e => setQuery(e.target.value)}
          placeholder="Search the courses we hold a scorecard for" aria-label="Search courses"
          style={{ marginBottom: 12 }}/>
        {candidates === null ? <Spinner/> : candidates.length === 0 ? (
          <div style={{ fontSize: 13, opacity: 0.65 }}>
            {query ? `Nothing matches “${query}”.` : 'Every course on file already has a Sandbox record.'}
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6, maxHeight: 320, overflowY: 'auto' }}>
            {candidates.map(r => (
              <button key={r.id} className="btn btn-ghost" disabled={!!busy}
                onClick={() => start({ name: r.name, city: r.city, state: r.state }, r.id)}
                style={{ justifyContent: 'space-between', display: 'flex', textAlign: 'left', width: '100%' }}>
                <span>
                  <strong>{r.name}</strong>
                  <span style={{ opacity: 0.6, fontSize: 12 }}>
                    {[r.city, r.state].filter(Boolean).length ? ` — ${[r.city, r.state].filter(Boolean).join(', ')}` : ''}
                    {r.holes ? ` · ${r.holes} holes` : ''}
                  </span>
                </span>
                <span style={{ opacity: 0.7, fontSize: 12 }}>{busy === r.id ? 'Starting…' : 'Onboard →'}</span>
              </button>
            ))}
          </div>
        )}
      </div>

      <div className="card" style={{ padding: 22 }}>
        <div className="eyebrow" style={{ marginBottom: 4 }}>Or a course we don’t have yet</div>
        <div style={{ fontSize: 12.5, opacity: 0.65, marginBottom: 14 }}>
          Its scorecard can come later — the layout step will take it.
        </div>
        <Row>
          <Field label="Course name" full>
            <input className="input" value={fresh.name} onChange={e => setFresh(f => ({ ...f, name: e.target.value }))}
              placeholder="International Links Melreese"/>
          </Field>
        </Row>
        <Row>
          <Field label="City"><input className="input" value={fresh.city} onChange={e => setFresh(f => ({ ...f, city: e.target.value }))} placeholder="Miami"/></Field>
          <Field label="State"><input className="input" value={fresh.state} onChange={e => setFresh(f => ({ ...f, state: e.target.value }))} placeholder="FL"/></Field>
        </Row>
        <button className="btn btn-forest" style={{ marginTop: 14 }}
          disabled={!fresh.name.trim() || !!busy}
          onClick={() => start(fresh, 'new')}>
          {busy === 'new' ? 'Starting…' : 'Start onboarding'}
        </button>
      </div>
    </div>
  );
}

Object.assign(window, { OnboardingPanel, OnboardPicker, ProgressBar, PhaseBar, PartnershipCard, ONBOARD_SECTIONS });
