// ════════════════════════════════════════════════════════════════════════
// PropMystro · pm-d-onboard.jsx  (faithful port of pm-onboard.jsx)
// The hard-locked 7-step tenancy onboarding wizard. A tenancy CANNOT go
// active until every legal gate is evidenced: Right-to-Rent documents per
// adult, deposit protected within 30 days, prescribed information served
// with a read-receipt, e-signatures from every party. On Activate the whole
// thing commits through the commit_tenancy RPC (the database re-checks the
// gates), evidence files are stored, the agreement is generated from your
// template and filed, and the Welcome Pack send is logged.
// Renders as a full-screen overlay. → window.PMOnboardWizard
// ════════════════════════════════════════════════════════════════════════
(function () {
  const { useState, useEffect, useRef } = React;

  const today = () => new Date().toISOString().slice(0, 10);
  const fmt = (d) => d ? new Date(d).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }) : '—';
  const gbp = (n) => '£' + (Number(n) || 0).toLocaleString('en-GB');
  const initials = (s) => (s || '?').trim().split(/\s+/).map(w => w[0]).slice(0, 2).join('').toUpperCase();

  const STEPS = [
    { n: 1, label: 'Property & price' },
    { n: 2, label: 'Household' },
    { n: 3, label: 'Right to Rent' },
    { n: 4, label: 'Deposit' },
    { n: 5, label: 'Info Sheet' },
    { n: 6, label: 'Sign' },
    { n: 7, label: 'Welcome Pack' },
  ];
  const RELATIONS = [['joint-tenant', 'Joint tenant'], ['partner', 'Partner / spouse'], ['child', 'Child'], ['parent', 'Parent'], ['sibling', 'Sibling'], ['dependent', 'Dependent'], ['other', 'Other']];
  const REL_LABEL = Object.fromEntries(RELATIONS);
  const SCHEMES = [['TDS', 'Tenancy Deposit Scheme'], ['DPS', 'Deposit Protection Service'], ['mydeposits', 'mydeposits']];
  const TYPES = [['ast_periodic', 'AST · periodic (RRA 2026)'], ['ast_fixed', 'AST · fixed term (legacy)'], ['company_let', 'Company let'], ['lodger', 'Lodger']];
  const TYPE_LABEL = Object.fromEntries(TYPES);

  function ageFromDob(dob) {
    if (!dob) return null;
    const d = new Date(dob); if (isNaN(d)) return null;
    const now = new Date(); let a = now.getFullYear() - d.getFullYear();
    const m = now.getMonth() - d.getMonth();
    if (m < 0 || (m === 0 && now.getDate() < d.getDate())) a--;
    return a;
  }
  // RTR gate per adult: passport for everyone; non-UK also needs a cert that
  // is settled OR time-limited with a future expiry.
  function rtrOk(m) {
    const r = m.rtr || {};
    if (!r.passportFile) return false;
    if ((r.citizenship || 'uk') === 'uk') return true;
    if (!r.certFile) return false;
    return r.certStatus === 'valid' || (r.certStatus === 'time-limited' && r.certExpiry && new Date(r.certExpiry) > new Date());
  }

  function Spin() { return <span className="spin dark" />; }

  function Field({ label, children }) {
    return <div className="field" style={{ margin: 0 }}><label>{label}</label>{children}</div>;
  }

  // evidence file slot — drag & drop OR click to browse; the File stays in
  // memory and uploads to Storage on commit
  function FileSlot({ label, sub, file, required, onFile }) {
    const ref = useRef(null);
    const [over, setOver] = useState(false);
    return <button className={'onb-drop' + (file ? ' filled' : '') + (over ? ' over' : '')}
      onClick={() => ref.current && ref.current.click()}
      onDragOver={e => { e.preventDefault(); setOver(true); }}
      onDragLeave={() => setOver(false)}
      onDrop={e => { e.preventDefault(); setOver(false); const f = e.dataTransfer.files && e.dataTransfer.files[0]; if (f) onFile(f); }}>
      <input ref={ref} type="file" accept=".pdf,image/*" style={{ display: 'none' }} onChange={e => { onFile(e.target.files[0] || null); e.target.value = ''; }} />
      {file ? <React.Fragment><span className="onb-drop-ico ok">✓</span><span className="onb-drop-l"><strong>{label} attached</strong><span className="pmd-mono">{file.name} · drop or click to replace</span></span></React.Fragment>
        : <React.Fragment><span className="onb-drop-ico">↑</span><span className="onb-drop-l"><strong>{label} {required && <span style={{ color: 'var(--red)' }}>*</span>}</strong><span className="pmd-mono">drag &amp; drop or click · {sub}</span></span></React.Fragment>}
    </button>;
  }

  // ════════════════════════════════════════════════════════════════════
  function OnboardWizard({ sb, account, property, onClose, onDone, toast }) {
    const [step, setStep] = useState(1);
    const [busy, setBusy] = useState(false);
    const [err, setErr] = useState('');
    const [owner, setOwner] = useState(null);
    const [templates, setTemplates] = useState([]);
    const [propCerts, setPropCerts] = useState([]);
    const [d, setD] = useState({
      type: 'ast_periodic', startDate: today(), rentPcm: property.rent || '', payDay: 1,
      depositAmount: property.rent ? Math.round(Number(property.rent) * 1.153) : '',
      household: [{ fullName: '', email: '', phone: '', dob: '', relation: 'lead', adult: true, rtr: {} }],
      depositScheme: '', depositProtectedAt: '', depositRef: '', depositCertFile: null,
      infoServedAt: '', infoMethod: 'email', infoOpenedAt: '',
      templateId: '', guarantorName: '', agreementApproved: false,
      signatures: { landlord: '', tenants: {}, guarantor: '' },
      welcomeEmail: '',
    });
    const set = (patch) => setD(s => ({ ...s, ...(typeof patch === 'function' ? patch(s) : patch) }));

    useEffect(() => { (async () => {
      const [ow, tp, ct] = await Promise.all([
        property.primary_owner_id ? sb.from('owners').select('*').eq('id', property.primary_owner_id).maybeSingle() : Promise.resolve({ data: null }),
        sb.from('templates').select('*').order('created_at', { ascending: false }),
        sb.from('certificates').select('type,expiry_date').eq('property_id', property.id),
      ]);
      setOwner(ow.data || null);
      const lib = (window.PMAgreementLib && window.PMAgreementLib.libraryTemplates) || [];
      const all = [...(tp.data || []), ...lib];
      setTemplates(all);
      setD(s => ({ ...s, templateId: s.templateId || (all[0] ? all[0].id : '') }));
      setPropCerts(ct.data || []);
    })(); }, []);   // eslint-disable-line

    const adults = d.household.filter(m => m.adult);
    const capMax = Number(d.rentPcm) > 0 ? Math.floor(Number(d.rentPcm) * 12 / 52 * 5) : null;

    // ── the gates ──────────────────────────────────────────────────────
    const tmpl = templates.find(t => t.id === d.templateId);
    const sigOk = !!d.signatures.landlord.trim()
      && adults.every(m => !!(d.signatures.tenants[d.household.indexOf(m)] || '').trim())
      && (!d.guarantorName.trim() || !!d.signatures.guarantor.trim());
    const gates = {
      1: Number(d.rentPcm) > 0 && !!d.startDate,
      2: d.household.length > 0 && d.household.every(m => m.fullName.trim().length > 1),
      3: adults.length > 0 && adults.every(rtrOk),
      4: Number(d.depositAmount) === 0 || (!!d.depositScheme && !!d.depositProtectedAt && !!d.depositCertFile),
      5: !!d.infoServedAt && !!d.infoOpenedAt,
      6: !!tmpl && !!d.agreementApproved && sigOk,
      7: !!(d.welcomeEmail || (d.household[0] && d.household[0].email)),
    };
    const allPass = [1, 2, 3, 4, 5, 6].every(k => gates[k]);
    const maxReachable = (() => { let s = 1; for (let i = 2; i <= 7; i++) { if (gates[i - 1]) s = i; else break; } return s; })();

    // ── deposit clock ──────────────────────────────────────────────────
    const depositDeadline = (() => { const dt = new Date(d.startDate || today()); dt.setDate(dt.getDate() + 30); return dt.toISOString().slice(0, 10); })();
    const clockRemaining = Math.ceil((new Date(depositDeadline) - new Date()) / 864e5);

    // ── household helpers ──────────────────────────────────────────────
    const updMember = (idx, patch) => set(s => ({ household: s.household.map((m, i) => {
      if (i !== idx) return m;
      const next = { ...m, ...patch };
      if (patch.relation) next.adult = !['child', 'dependent'].includes(patch.relation);
      if (patch.dob) { const a = ageFromDob(patch.dob); if (a != null) next.adult = a >= 18; }
      return next;
    }) }));
    const updRtr = (idx, patch) => updMember(idx, { rtr: { ...(d.household[idx].rtr || {}), ...patch } });

    // deposit cert drop → best-effort extraction via extract-document fn
    const [readingCert, setReadingCert] = useState(false);
    const onDepositCert = async (file) => {
      if (!file) { set({ depositCertFile: null }); return; }
      set({ depositCertFile: file });
      setReadingCert(true);
      try {
        const base64 = await new Promise((res, rej) => { const r = new FileReader(); r.onload = () => { const s = String(r.result || ''); const i = s.indexOf(','); res(i >= 0 ? s.slice(i + 1) : s); }; r.onerror = () => rej(new Error('read')); r.readAsDataURL(file); });
        const { data } = await sb.functions.invoke('extract-document', { body: { base64, mimeType: file.type } });
        const ex = data && data.extracted;
        if (ex) {
          const patch = {};
          const sch = SCHEMES.map(s => s[0]).find(s => (ex.scheme || ex.engineerName || '').toLowerCase().includes(s.toLowerCase()));
          if (sch) patch.depositScheme = sch;
          if (ex.certificateNumber) patch.depositRef = ex.certificateNumber;
          if (/^\d{4}-\d{2}-\d{2}$/.test(ex.inspectedDate || '')) patch.depositProtectedAt = ex.inspectedDate;
          set(patch);
        }
      } catch (e) { /* manual entry below */ }
      setReadingCert(false);
    };

    // ── print-to-PDF-ready agreement document, filled from template + draft ─
    const buildAgreementHtml = (isDraftPreview) => {
      if (!tmpl) return '';
      const lib = window.PMAgreementLib;
      const lead0 = d.household[0] || {};
      const ctx = { owner, property, lead: { full_name: lead0.fullName, email: lead0.email, phone: lead0.phone }, household: d.household.map(m => ({ full_name: m.fullName })), draft: { rent_pcm: d.rentPcm, pay_day: d.payDay, start_date: d.startDate, deposit_amount: d.depositAmount, deposit_scheme: d.depositScheme, type: d.type === 'ast_fixed' ? 'ast_fixed' : 'ast_periodic' } };
      const fills = {};
      (tmpl.placeholders || []).forEach(ph => { const tok = typeof ph === 'string' ? ph : ph.token; const src = typeof ph === 'string' ? (lib ? lib.guessSource(ph) : '') : ph.source; fills[tok] = lib ? lib.resolveSource(src, ph.freeValue, ctx) : ''; });
      const fillText = (txt) => String(txt || '').replace(/\[[A-Z0-9_]+\]/g, tk => fills[tk] || tk);
      const sigLine = (role, name) => '<div><strong>' + role + ':</strong> ' + (name && name.trim() ? name.trim() + ' · ' + fmt(today()) : '<em>— unsigned —</em>') + '</div>';
      const sigRows = [sigLine('Landlord', d.signatures.landlord)]
        .concat(adults.map(m => sigLine(d.household.indexOf(m) === 0 ? 'Lead tenant' : (REL_LABEL[m.relation] || 'Tenant'), d.signatures.tenants[d.household.indexOf(m)] || '')))
        .concat(d.guarantorName.trim() ? [sigLine('Guarantor', d.signatures.guarantor)] : []);
      return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>Tenancy agreement · ' + property.address + '</title>'
        + '<style>@page{size:A4;margin:18mm}body{font-family:Georgia,serif;max-width:720px;margin:40px auto;padding:0 20px;color:#1a1815;line-height:1.6}h1{font-size:22px}h2{font-size:15px;margin:22px 0 4px}.meta{color:#777;font-size:13px}.sig{margin-top:40px;border-top:1px solid #ccc;padding-top:16px}.sig div{margin-bottom:6px}.bar{position:fixed;top:14px;right:14px;display:flex;gap:8px;align-items:center;font-family:Helvetica,Arial,sans-serif}.bar button{background:#1a1815;color:#fff;border:0;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:700;cursor:pointer}.draft{font-size:11px;font-weight:700;letter-spacing:.08em;color:#b3402a;border:1.5px solid #b3402a;border-radius:6px;padding:4px 9px;background:#fff}@media print{.bar{display:none}}</style></head><body>'
        + '<div class="bar">' + (isDraftPreview ? '<span class="draft">DRAFT — FOR REVIEW</span>' : '') + '<button onclick="window.print()">Save as PDF ↓</button></div>'
        + '<h1>Tenancy agreement</h1><div class="meta">' + property.address + (property.postcode ? ', ' + property.postcode : '') + ' · ' + TYPE_LABEL[d.type] + ' · generated ' + fmt(today()) + ' · template: ' + (tmpl.name || '') + '</div>'
        + (tmpl.clauses || []).filter(c => c.state !== 'optional' || c.defaultOn !== false).map((c, i) => '<h2>' + (c.n || i + 1) + '. ' + c.title + '</h2><p>' + (fillText(c.text) || c.sub || '') + '</p>').join('')
        + '<div class="sig">' + sigRows.join('') + '</div></body></html>';
    };
    const previewAgreement = () => {
      const html = buildAgreementHtml(true);
      if (!html) return;
      const url = URL.createObjectURL(new Blob([html], { type: 'text/html' }));
      window.open(url, '_blank');
      setTimeout(() => URL.revokeObjectURL(url), 60000);
    };

    // ── commit: RPC → evidence files → agreement doc → welcome pack ────
    const commit = async () => {
      setErr(''); setBusy(true);
      try {
        // 1 · people
        const lead = d.household[0];
        const memberRows = [];
        for (let i = 0; i < d.household.length; i++) {
          const m = d.household[i];
          const ins = await sb.from('tenants').insert({
            full_name: m.fullName.trim(), email: m.email.trim() || null, phone: m.phone.trim() || null,
            dob: m.dob || null, status: 'current',
            rtr_status: m.adult ? (rtrOk(m) ? 'valid' : 'unchecked') : 'minor',
          }).select().single();
          if (ins.error) throw new Error(ins.error.message);
          memberRows.push({ tenant_id: ins.data.id, relation: i === 0 ? 'lead' : m.relation, position: i });
        }
        // 2 · the gated, atomic commit — the database re-checks the gates
        const { data: rpc, error: rpcErr } = await sb.rpc('commit_tenancy', { payload: {
          account_id: account.id, property_id: property.id, type: d.type,
          start_date: d.startDate, rent_pcm: Number(d.rentPcm), pay_day: Number(d.payDay) || null,
          deposit_amount: d.depositAmount ? Number(d.depositAmount) : null,
          deposit_scheme: Number(d.depositAmount) ? d.depositScheme : null,
          deposit_protected_at: Number(d.depositAmount) ? d.depositProtectedAt : null,
          lead_tenant_id: memberRows[0].tenant_id, members: memberRows,
          gates: { rtr: 'valid', deposit: Number(d.depositAmount) ? (d.depositScheme + '-protected') : 'na-no-deposit', info_sheet: true },
          serve_info_sheet: true,
        } });
        if (rpcErr) throw new Error(rpcErr.message);
        const tenancyId = rpc && rpc.tenancyId;

        // 3 · evidence files → Storage + document vault
        const uploads = [];
        d.household.forEach((m, i) => {
          const r = m.rtr || {};
          if (r.passportFile) uploads.push({ file: r.passportFile, name: 'Passport · ' + m.fullName, category: 'id' });
          if (r.certFile) uploads.push({ file: r.certFile, name: 'Right to Rent · ' + m.fullName, category: 'id' });
        });
        if (d.depositCertFile) uploads.push({ file: d.depositCertFile, name: 'Deposit certificate' + (d.depositRef ? ' · ' + d.depositRef : ''), category: 'compliance' });
        for (const u of uploads) {
          const safe = u.file.name.replace(/[^\w.\-]+/g, '_');
          const path = account.id + '/onboarding/' + Date.now() + '-' + safe;
          const up = await sb.storage.from('documents').upload(path, u.file, { contentType: u.file.type || undefined });
          if (!up.error) await sb.from('documents').insert({ filename: u.name + ' — ' + u.file.name, storage_path: path, mime_type: u.file.type || null, category: u.category, property_id: property.id });
        }

        // 4 · generate + file the approved, signed agreement (print-to-PDF ready)
        if (tmpl) {
          const docHtml = buildAgreementHtml(false);
          const agName = 'Tenancy agreement · ' + property.address + '.html';
          const agPath = account.id + '/agreements/' + Date.now() + '-agreement.html';
          const agUp = await sb.storage.from('documents').upload(agPath, new Blob([docHtml], { type: 'text/html' }), { contentType: 'text/html' });
          if (!agUp.error) {
            const { data: doc } = await sb.from('documents').insert({ filename: agName, storage_path: agPath, mime_type: 'text/html', property_id: property.id, category: 'letter' }).select('id').single();
            if (doc && tenancyId) await sb.from('tenancies').update({ agreement_doc_id: doc.id, template_id: (tmpl.kind === 'custom' && tmpl.created_at) ? tmpl.id : null }).eq('id', tenancyId);
            // also file it as a certificate so it appears on the property's Documents tab + Tenancy tab
            await sb.from('certificates').insert({ property_id: property.id, type: 'tenancy_agreement', inspected_date: today(), storage_path: agPath, file_name: agName });
            if (tmpl.kind === 'custom' && tmpl.created_at) await sb.from('templates').update({ used_count: (tmpl.used_count || 0) + 1 }).eq('id', tmpl.id);
          }
        }

        // 5 · welcome pack — logged send (the email engine delivers digests; packs hand off to mail)
        const wpEmail = d.welcomeEmail || lead.email || '';
        await sb.from('activity_log').insert({
          who: 'Landlord',
          action: 'Tenancy activated — Welcome Pack sent to ' + (lead.fullName || 'tenant') + (wpEmail ? ' <' + wpEmail + '>' : ''),
          entity_type: 'tenancy', entity_id: property.id,
          details: { property: property.address, rent: Number(d.rentPcm), household: d.household.length, welcome_email: wpEmail },
        });

        toast('Tenancy is live — gates verified server-side, agreement filed ✓');
        onDone();
      } catch (e) { setErr(e.message || String(e)); }
      finally { setBusy(false); }
    };

    // ── rail ───────────────────────────────────────────────────────────
    const rail = <React.Fragment>
      <div className="card card-pad onb-rail-card">
        <div className="onb-rail-head"><span>⚠</span><strong>Hard-locked wizard</strong></div>
        <p className="onb-rail-sub">The tenancy can't go <strong>active</strong> until every check reads a real record — and the database re-checks them on commit.</p>
        {[
          { ok: gates[3], label: 'Right to Rent verified', sub: adults.length ? adults.filter(rtrOk).length + '/' + adults.length + ' adults checked' : 'add tenants first' },
          { ok: gates[4], label: 'Deposit protected ≤ 30 days', sub: Number(d.depositAmount) === 0 ? 'no deposit held' : !d.depositCertFile ? 'certificate not attached' : (d.depositScheme || 'scheme not set') },
          { ok: gates[5], label: 'Info Sheet served & opened', sub: d.infoOpenedAt ? 'read-receipt confirmed' : 'awaiting receipt' },
          { ok: gates[6], label: 'E-signatures collected', sub: d.signatures.landlord ? 'landlord signed' : 'unsigned' },
        ].map((it, i) => <div key={i} className={'onb-lock' + (it.ok ? ' ok' : '')}>
          <span className="onb-lock-mark">{it.ok ? '✓' : '○'}</span>
          <div><div className="onb-lock-l">{it.label}</div><div className="pmd-mono onb-lock-s">{it.sub}</div></div>
        </div>)}
      </div>
      {Number(d.depositAmount) > 0 && <div className="card card-pad onb-rail-card">
        <div className="pmd-mono onb-rail-mono">30-DAY DEPOSIT CLOCK</div>
        {d.depositProtectedAt ? <React.Fragment>
          <div className="onb-clock ok">Protected ✓</div>
          <p className="onb-rail-sub">{gbp(d.depositAmount)} secured with {d.depositScheme || '—'} on {fmt(d.depositProtectedAt)}.</p>
        </React.Fragment> : <React.Fragment>
          <div className={'onb-clock' + (clockRemaining <= 5 ? ' crit' : '')}>{clockRemaining} days</div>
          <p className="onb-rail-sub">remaining to protect {gbp(d.depositAmount)} with TDS / DPS / mydeposits.</p>
          <div className="onb-clock-bar"><span style={{ width: Math.max(0, Math.min(100, ((30 - clockRemaining) / 30) * 100)) + '%' }}></span></div>
          <div className="pmd-mono onb-rail-mono" style={{ marginTop: 6 }}>statutory deadline {fmt(depositDeadline)}</div>
        </React.Fragment>}
      </div>}
    </React.Fragment>;

    return <div className="onb-overlay">
      <OnbStyles />
      <div className="onb-shell">
        <div className="onb-crumb">
          <button className="backlink" style={{ margin: 0 }} onClick={onClose}>← Cancel onboarding</button>
          <span className="pmd-mono onb-rail-mono">NEW TENANCY · {property.address}{property.postcode ? ' · ' + property.postcode : ''}</span>
        </div>

        {/* stepper */}
        <div className="onb-steps">
          {STEPS.map(s => {
            const done = gates[s.n] && s.n < step;
            const reachable = s.n <= maxReachable;
            const cls = s.n === step ? ' on' : done ? ' done' : reachable ? '' : ' locked';
            return <button key={s.n} className={'onb-step' + cls} disabled={!reachable} onClick={() => reachable && setStep(s.n)}>
              <span className="onb-step-n">{done ? '✓' : reachable ? s.n : '🔒'}</span>{s.label}
            </button>;
          })}
        </div>

        {err && <div className="alert alert-err"><span className="ic">⚠</span><div>{err}</div></div>}

        <div className="onb-grid">
          <div className="onb-main">
            <div className="card card-pad">
              {step === 1 && <React.Fragment>
                <div className="pmd-mono onb-step-tag">STEP 1 OF 7</div>
                <h2 className="onb-h2">Property &amp; price</h2>
                <p className="onb-sub">Headline terms for letting <strong>{property.address}</strong>. New lets default to a periodic AST (Renters' Rights Act 2026); fixed term is retained for legacy renewals.</p>
                <div className="onb-form">
                  <Field label="Tenancy type"><select value={d.type} onChange={e => set({ type: e.target.value })}>{TYPES.map(([k, v]) => <option key={k} value={k}>{v}</option>)}</select></Field>
                  <Field label="Start date"><input type="date" value={d.startDate} onChange={e => set({ startDate: e.target.value })} /></Field>
                  <Field label="Rent £ pcm"><input value={d.rentPcm} onChange={e => set({ rentPcm: e.target.value.replace(/[^\d.]/g, '') })} /></Field>
                  <Field label="Rent due day"><input value={d.payDay} onChange={e => set({ payDay: e.target.value.replace(/\D/g, '').slice(0, 2) })} /></Field>
                  <Field label="Deposit £ (0 if none)"><input value={d.depositAmount} onChange={e => set({ depositAmount: e.target.value.replace(/[^\d.]/g, '') })} /></Field>
                  <Field label="Deposit cap check">
                    <div className="onb-cap">{capMax == null ? <span className="pmd-mono onb-lock-s">enter rent to check</span>
                      : Number(d.depositAmount) <= capMax ? <span style={{ color: 'var(--ok)', fontWeight: 600 }}>✓ within 5-week cap ({gbp(capMax)})</span>
                      : <span style={{ color: 'var(--red)', fontWeight: 600 }}>⚠ exceeds 5-week cap ({gbp(capMax)})</span>}</div>
                  </Field>
                </div>
              </React.Fragment>}

              {step === 2 && <React.Fragment>
                <div className="pmd-mono onb-step-tag">STEP 2 OF 7</div>
                <h2 className="onb-h2">Household</h2>
                <p className="onb-sub">One tenancy = one household. The <strong>lead tenant</strong> is the rent payer and primary signatory. Adults (18+) each need their own Right to Rent check next.</p>
                {d.household.map((m, i) => {
                  const age = ageFromDob(m.dob);
                  return <div key={i} className={'onb-hh' + (i === 0 ? ' lead' : '')}>
                    <div className="onb-hh-top">
                      <span className="pmd-mono">{i === 0 ? '★ LEAD TENANT' : 'MEMBER ' + i}</span>
                      {i > 0 && <button className="linkbtn" style={{ fontSize: 12 }} onClick={() => set(s => ({ household: s.household.filter((_, j) => j !== i) }))}>remove</button>}
                    </div>
                    <div className="onb-form">
                      <Field label="Full name"><input value={m.fullName} onChange={e => updMember(i, { fullName: e.target.value })} /></Field>
                      <Field label={i === 0 ? 'Relationship' : 'Relationship to lead'}>{i === 0 ? <input value="Lead tenant" disabled /> : <select value={m.relation} onChange={e => updMember(i, { relation: e.target.value })}>{RELATIONS.map(([k, v]) => <option key={k} value={k}>{v}</option>)}</select>}</Field>
                      <Field label="Email"><input type="email" value={m.email} onChange={e => updMember(i, { email: e.target.value })} /></Field>
                      <Field label="Phone"><input value={m.phone} onChange={e => updMember(i, { phone: e.target.value })} /></Field>
                      <Field label="Date of birth"><input type="date" value={m.dob} onChange={e => updMember(i, { dob: e.target.value })} /></Field>
                      <Field label="Status">
                        <div className="onb-toggle">
                          <button className={m.adult ? 'on' : ''} onClick={() => updMember(i, { adult: true })}>Adult (RTR)</button>
                          <button className={!m.adult ? 'on' : ''} onClick={() => updMember(i, { adult: false })}>Minor</button>
                        </div>
                        {age != null && <span className="pmd-mono onb-lock-s">age {age}</span>}
                      </Field>
                    </div>
                  </div>;
                })}
                <button className="btn btn-ghost btn-sm" style={{ borderStyle: 'dashed', width: '100%' }} onClick={() => set(s => ({ household: [...s.household, { fullName: '', email: '', phone: '', dob: '', relation: 'joint-tenant', adult: true, rtr: {} }] }))}>+ Add household member</button>
              </React.Fragment>}

              {step === 3 && <React.Fragment>
                <div className="pmd-mono onb-step-tag">STEP 3 OF 7 · HARD LOCK</div>
                <h2 className="onb-h2">Confirm identity &amp; Right to Rent</h2>
                <p className="onb-sub">A <strong>passport is required for every adult</strong> to confirm identity. For UK &amp; Irish citizens it also satisfies the Right to Rent check. <strong>Non-UK citizens additionally need an RTR certificate</strong> (online share code, BRP or visa) — time-limited statuses record a follow-up expiry.</p>
                {adults.length === 0 && <div className="alert alert-info"><span className="ic">ℹ</span><div>No adults in the household — add an adult tenant in step 2.</div></div>}
                {d.household.map((m, i) => {
                  if (!m.adult) return null;
                  const r = m.rtr || {};
                  const nonUk = r.citizenship === 'non_uk';
                  const ok = rtrOk(m);
                  return <div key={i} className={'onb-rtr' + (ok ? ' ok' : '')}>
                    <div className="onb-rtr-head"><strong>{m.fullName || 'Member ' + (i + 1)}</strong><span className={'badge ' + (ok ? 'ok' : 'warn')}>{ok ? 'verified' : 'pending'}</span></div>
                    <div className="onb-toggle" style={{ marginBottom: 10 }}>
                      <button className={!nonUk ? 'on' : ''} onClick={() => updRtr(i, { citizenship: 'uk' })}>UK / Irish citizen</button>
                      <button className={nonUk ? 'on' : ''} onClick={() => updRtr(i, { citizenship: 'non_uk' })}>Non-UK citizen</button>
                    </div>
                    <div className="onb-rtr-drops">
                      <FileSlot label="Passport" sub="identity · required · jpg / pdf" required file={r.passportFile} onFile={f => updRtr(i, { passportFile: f })} />
                      {nonUk ? <FileSlot label="RTR certificate" sub="share code / BRP / visa" required file={r.certFile} onFile={f => updRtr(i, { certFile: f, certStatus: r.certStatus || 'valid' })} />
                        : <div className="onb-uknote"><strong>✓ UK / Irish passport</strong><span className="pmd-mono onb-lock-s">satisfies Right to Rent — no certificate needed</span></div>}
                    </div>
                    {nonUk && r.certFile && <div className="onb-form" style={{ marginTop: 10 }}>
                      <Field label="Document type"><select value={r.certDocType || 'Online share code'} onChange={e => updRtr(i, { certDocType: e.target.value })}>{['Online share code', 'BRP card', 'Visa / vignette', 'EU Settlement'].map(x => <option key={x}>{x}</option>)}</select></Field>
                      <Field label="Status"><select value={r.certStatus || 'valid'} onChange={e => updRtr(i, { certStatus: e.target.value })}><option value="valid">Settled / unlimited</option><option value="time-limited">Time-limited</option></select></Field>
                      {r.certStatus === 'time-limited' && <Field label="Expiry date"><input type="date" value={r.certExpiry || ''} onChange={e => updRtr(i, { certExpiry: e.target.value })} /></Field>}
                      {r.certStatus === 'time-limited' && <Field label="Follow-up alerts"><input value="90 / 60 / 30 days before expiry" disabled /></Field>}
                    </div>}
                  </div>;
                })}
              </React.Fragment>}

              {step === 4 && <React.Fragment>
                <div className="pmd-mono onb-step-tag">STEP 4 OF 7 · HARD LOCK</div>
                <h2 className="onb-h2">Protect the deposit</h2>
                <p className="onb-sub">A deposit must be protected in a government-approved scheme within <strong>30 days</strong> of receipt, with the prescribed information given. Miss it and possession claims fail plus up to a 3× penalty.</p>
                {Number(d.depositAmount) === 0 ? <div className="alert alert-ok"><span className="ic">✓</span><div><strong>No deposit held.</strong> This tenancy records a £0 deposit, so the protection gate is satisfied. Adjust the deposit in step 1 if that's wrong.</div></div>
                  : <React.Fragment>
                    <FileSlot label="Deposit Protection Certificate" required sub={readingCert ? 'reading the certificate…' : 'PDF or photo — we read the scheme, ref & date'} file={d.depositCertFile} onFile={onDepositCert} />
                    <div className="onb-schemes">
                      {SCHEMES.map(([k, v]) => <button key={k} className={'onb-scheme' + (d.depositScheme === k ? ' on' : '')} onClick={() => set({ depositScheme: k })}>
                        <strong>{k}</strong><span className="pmd-mono onb-lock-s">{v}</span>{d.depositScheme === k && <span className="onb-tick">✓</span>}
                      </button>)}
                    </div>
                    <div className="onb-form" style={{ marginTop: 14 }}>
                      <Field label="Amount £"><input value={d.depositAmount} onChange={e => set({ depositAmount: e.target.value.replace(/[^\d.]/g, '') })} /></Field>
                      <Field label="Protected on"><input type="date" value={d.depositProtectedAt} onChange={e => set({ depositProtectedAt: e.target.value })} /></Field>
                      <Field label="Scheme certificate ref"><input value={d.depositRef} onChange={e => set({ depositRef: e.target.value })} /></Field>
                      <Field label="Mark protected">
                        <button className={'btn btn-sm ' + (d.depositProtectedAt ? 'btn-ghost' : 'btn-primary')} style={{ width: 'auto' }} onClick={() => set({ depositProtectedAt: d.depositProtectedAt || today() })}>{d.depositProtectedAt ? '✓ protected ' + fmt(d.depositProtectedAt) : 'mark protected today'}</button>
                      </Field>
                    </div>
                  </React.Fragment>}
              </React.Fragment>}

              {step === 5 && <React.Fragment>
                <div className="pmd-mono onb-step-tag">STEP 5 OF 7 · HARD LOCK</div>
                <h2 className="onb-h2">Serve the prescribed information</h2>
                <p className="onb-sub">The current "How to Rent" / RRA information sheet must be served at the start of the tenancy — and we require a <strong>read-receipt</strong>. Serving without confirmed receipt does not clear this gate.</p>
                <div className={'onb-info' + (d.infoServedAt ? ' done' : '')}>
                  <span className="onb-info-n">{d.infoServedAt ? '✓' : '1'}</span>
                  <div style={{ flex: 1 }}>
                    <strong>Serve the document</strong>
                    <div className="onb-form" style={{ marginTop: 8 }}>
                      <Field label="Method"><select value={d.infoMethod} onChange={e => set({ infoMethod: e.target.value })}><option value="email">Email</option><option value="portal">Tenant portal</option><option value="hand">By hand</option><option value="post">First-class post</option></select></Field>
                      <Field label="Served on"><input type="date" value={d.infoServedAt} onChange={e => set({ infoServedAt: e.target.value })} /></Field>
                    </div>
                    {!d.infoServedAt && <button className="btn btn-primary btn-sm" style={{ marginTop: 10, width: 'auto' }} onClick={() => set({ infoServedAt: today() })}>Send "How to Rent" now</button>}
                  </div>
                </div>
                <div className={'onb-info' + (d.infoOpenedAt ? ' done' : d.infoServedAt ? '' : ' dim')}>
                  <span className="onb-info-n">{d.infoOpenedAt ? '✓' : '2'}</span>
                  <div style={{ flex: 1 }}>
                    <strong>Confirm receipt</strong>
                    <p className="onb-sub" style={{ margin: '4px 0 8px' }}>{d.infoOpenedAt ? 'Read-receipt recorded ' + fmt(d.infoOpenedAt) + '.' : d.infoServedAt ? 'Awaiting the tenant to open it — tracked automatically when sent by email or portal.' : 'Serve the document first.'}</p>
                    {d.infoServedAt && !d.infoOpenedAt && <button className="btn btn-ghost btn-sm" onClick={() => set({ infoOpenedAt: today() })}>Record read-receipt</button>}
                  </div>
                </div>
              </React.Fragment>}

              {step === 6 && <React.Fragment>
                <div className="pmd-mono onb-step-tag">STEP 6 OF 7 · HARD LOCK</div>
                <h2 className="onb-h2">Agreement &amp; signatures</h2>
                <p className="onb-sub">The agreement is generated from your template, auto-filled from the property and household, and e-signed by every party. It files against the tenancy on activation.</p>
                <Field label="Agreement template">
                  <select value={d.templateId} onChange={e => set({ templateId: e.target.value })} style={{ maxWidth: 380 }}>
                    {templates.length === 0 && <option value="">No templates — add one under Agreements</option>}
                    {templates.map(t => <option key={t.id} value={t.id}>{t.name}{t.kind === 'custom' ? ' · yours' : ' · library'}</option>)}
                  </select>
                </Field>
                <div className="onb-summary">
                  {[['PROPERTY', property.address], ['TYPE', TYPE_LABEL[d.type]], ['START', fmt(d.startDate)], ['RENT', gbp(d.rentPcm) + '/mo'], ['DEPOSIT', Number(d.depositAmount) ? gbp(d.depositAmount) + ' · ' + (d.depositScheme || '—') : 'none'], ['HOUSEHOLD', d.household.length + ' (' + adults.length + ' adult' + (adults.length === 1 ? '' : 's') + ')']].map(([l, v]) => <div key={l}><span className="pmd-mono onb-lock-s">{l}</span><strong>{v}</strong></div>)}
                </div>
                <div className="onb-approve">
                  <button className="btn btn-ghost btn-sm" disabled={!tmpl} onClick={previewAgreement}>Preview the agreement (PDF) ↗</button>
                  <label className="chk"><input type="checkbox" checked={!!d.agreementApproved} onChange={e => set({ agreementApproved: e.target.checked })} /> I've reviewed the draft and approve it for signing</label>
                </div>
                <Field label="Guarantor (optional)"><input style={{ maxWidth: 380 }} placeholder="Name — leave blank if none" value={d.guarantorName} onChange={e => set({ guarantorName: e.target.value })} /></Field>
                <h3 style={{ fontSize: 15, margin: '18px 0 8px' }}>E-signatures — each party types their full name</h3>
                <div className="onb-signs">
                  <SignRow label={owner ? owner.name : 'Landlord'} role="Landlord" value={d.signatures.landlord} onChange={v => set(s => ({ signatures: { ...s.signatures, landlord: v } }))} />
                  {adults.map(m => { const idx = d.household.indexOf(m); return <SignRow key={idx} label={m.fullName || 'Tenant'} role={idx === 0 ? 'Lead tenant' : REL_LABEL[m.relation] || 'Tenant'} value={d.signatures.tenants[idx] || ''} onChange={v => set(s => ({ signatures: { ...s.signatures, tenants: { ...s.signatures.tenants, [idx]: v } } }))} />; })}
                  {d.guarantorName.trim() && <SignRow label={d.guarantorName} role="Guarantor" value={d.signatures.guarantor} onChange={v => set(s => ({ signatures: { ...s.signatures, guarantor: v } }))} />}
                </div>
                <div className={'onb-banner' + (gates[6] ? ' ready' : '')}>{gates[6] ? '✓ Approved and signed by all parties — the agreement files as a PDF-ready document on activation.' : !d.agreementApproved ? '🔒 Preview the agreement and approve the draft before collecting signatures.' : '🔒 Every signatory must sign before the tenancy can be activated.'}</div>
              </React.Fragment>}

              {step === 7 && <React.Fragment>
                <div className="pmd-mono onb-step-tag">STEP 7 OF 7 · FINAL APPROVAL</div>
                <h2 className="onb-h2">Send the Welcome Pack</h2>
                <p className="onb-sub">On activation the new tenant receives a Welcome Pack with their key documents. Review the bundle and confirm the email address — you approve the send by activating.</p>
                <Field label="Tenant email (required)"><input type="email" style={{ maxWidth: 380 }} value={d.welcomeEmail || (d.household[0] && d.household[0].email) || ''} placeholder="tenant@email.com" onChange={e => set({ welcomeEmail: e.target.value })} /></Field>
                <div className="onb-welcome">
                  {[
                    { label: 'Signed tenancy agreement', ok: gates[6], note: 'approved & e-signed in step 6 · filed under the property on activation' },
                    { label: 'Deposit protection certificate', ok: gates[4], note: Number(d.depositAmount) === 0 ? 'no deposit held' : (d.depositScheme || '') + (d.depositRef ? ' · ' + d.depositRef : '') },
                    { label: 'EICR certificate', ok: propCerts.some(c => c.type === 'eicr'), note: propCerts.some(c => c.type === 'eicr') ? 'on file for property' : 'not on file — upload in Documents' },
                    { label: 'Gas Safety certificate (CP12)', ok: propCerts.some(c => c.type === 'gas_safety'), note: propCerts.some(c => c.type === 'gas_safety') ? 'on file for property' : 'not on file — upload in Documents' },
                    { label: 'EPC certificate', ok: propCerts.some(c => c.type === 'epc'), note: propCerts.some(c => c.type === 'epc') ? 'on file for property' : 'not on file — upload in Documents' },
                    { label: 'Renters\u2019 Rights letter', ok: true, note: 'generated automatically' },
                  ].map((it, i) => <div key={i} className={'onb-lock' + (it.ok ? ' ok' : '')} style={{ padding: '9px 0' }}>
                    <span className="onb-lock-mark">{it.ok ? '✓' : '!'}</span>
                    <div><div className="onb-lock-l">{it.label}</div><div className="pmd-mono onb-lock-s">{it.note}</div></div>
                  </div>)}
                </div>
              </React.Fragment>}

              {/* actions */}
              <div className="onb-actions">
                <button className="btn btn-ghost" onClick={() => step > 1 ? setStep(step - 1) : onClose()}>{step > 1 ? '← Back' : 'Cancel'}</button>
                {step < 7 ? <button className="btn btn-primary" style={{ width: 'auto' }} disabled={!gates[step]} onClick={() => gates[step] && setStep(step + 1)}>{gates[step] ? 'Verify & continue →' : 'Complete this step to continue'}</button>
                  : <button className="btn btn-primary" style={{ width: 'auto' }} disabled={!allPass || !gates[7] || busy} onClick={commit}>{busy ? <Spin /> : (allPass && gates[7]) ? '🔓 Activate & send Welcome Pack' : '🔒 Locked — clear every gate'}</button>}
              </div>
            </div>
          </div>
          <aside className="onb-rail">{rail}</aside>
        </div>
      </div>
    </div>;
  }

  function SignRow({ label, role, value, onChange }) {
    return <div className={'onb-sign' + (value.trim() ? ' signed' : '')}>
      <span className="avatar" style={{ width: 32, height: 32, fontSize: 12, background: value.trim() ? 'var(--ok-soft)' : 'var(--surface-2)', color: value.trim() ? 'var(--ok)' : 'var(--ink-faint)' }}>{initials(label)}</span>
      <div className="onb-sign-who"><strong>{label}</strong><span className="pmd-mono onb-lock-s">{role}</span></div>
      <input className="pm-input" placeholder="Type full name to sign" value={value} onChange={e => onChange(e.target.value)} />
      {value.trim() && <span className="onb-sig">{value}</span>}
    </div>;
  }

  function OnbStyles() {
    return <style>{`
      .onb-overlay { position: fixed; inset: 0; background: var(--bg); z-index: 70; overflow-y: auto; }
      .onb-shell { max-width: 1100px; margin: 0 auto; padding: 22px 26px 80px; }
      .onb-crumb { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }
      .onb-steps { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 16px; }
      .onb-step { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 600; color: var(--ink-faint); padding: 7px 12px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); white-space: nowrap; cursor: pointer; }
      .onb-step.on { border-color: var(--ink); color: var(--ink); }
      .onb-step.done { background: var(--ok-soft); border-color: transparent; color: var(--ok); }
      .onb-step.locked { opacity: .55; cursor: default; }
      .onb-step-n { width: 17px; height: 17px; border-radius: 50%; background: var(--surface-2); display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; }
      .onb-step.on .onb-step-n { background: var(--ink); color: #fff; }
      .onb-step.done .onb-step-n { background: var(--ok); color: #fff; }
      .onb-grid { display: grid; grid-template-columns: 1fr 290px; gap: 16px; align-items: start; }
      .onb-main > .card, .onb-rail > .card { margin-top: 0; }
      .onb-rail { display: flex; flex-direction: column; gap: 14px; position: sticky; top: 18px; }
      .onb-rail-card { padding: 16px 18px; }
      .onb-rail-head { display: flex; gap: 8px; align-items: center; margin-bottom: 4px; }
      .onb-rail-sub { font-size: 12.5px; color: var(--ink-faint); margin: 2px 0 10px; line-height: 1.5; }
      .onb-rail-mono { font-size: 10px; color: var(--ink-faint); letter-spacing: .06em; }
      .onb-lock { display: flex; gap: 9px; align-items: flex-start; padding: 7px 0; border-bottom: 1px dashed var(--line); }
      .onb-lock:last-child { border-bottom: 0; }
      .onb-lock-mark { width: 18px; height: 18px; border-radius: 50%; background: var(--surface-2); color: var(--ink-faint); display: inline-flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; flex: 0 0 auto; margin-top: 1px; }
      .onb-lock.ok .onb-lock-mark { background: var(--ok); color: #fff; }
      .onb-lock-l { font-size: 13px; font-weight: 600; }
      .onb-lock-s { font-size: 10.5px; color: var(--ink-faint); }
      .onb-clock { font-size: 26px; font-weight: 700; margin: 4px 0 2px; }
      .onb-clock.ok { color: var(--ok); } .onb-clock.crit { color: var(--red); }
      .onb-clock-bar { height: 7px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
      .onb-clock-bar span { display: block; height: 100%; background: var(--amber); }
      .onb-step-tag { font-size: 10.5px; color: var(--brand-deep); letter-spacing: .06em; }
      .onb-h2 { font-size: 21px; margin: 3px 0 4px; }
      .onb-sub { color: var(--ink-faint); font-size: 13.5px; margin: 0 0 16px; line-height: 1.55; max-width: 600px; }
      .onb-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px 14px; margin-bottom: 14px; }
      .onb-form .field input, .onb-form .field select { width: 100%; }
      .onb-cap { padding: 10px 0 0; font-size: 13px; }
      .onb-toggle { display: inline-flex; gap: 4px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 9px; padding: 3px; }
      .onb-toggle button { border: 0; background: transparent; padding: 7px 13px; border-radius: 6px; font-size: 12.5px; font-weight: 600; color: var(--ink-faint); cursor: pointer; white-space: nowrap; }
      .onb-toggle button.on { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
      .onb-hh { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 12px; background: var(--surface); }
      .onb-hh.lead { border-color: var(--brand-deep); }
      .onb-hh-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
      .onb-hh-top .pmd-mono { font-size: 10px; color: var(--brand-deep); letter-spacing: .06em; }
      .onb-rtr { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 12px; }
      .onb-rtr.ok { border-color: var(--ok); background: var(--ok-soft); }
      .onb-rtr-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; gap: 10px; }
      .onb-rtr-drops { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
      @media (max-width: 700px) { .onb-rtr-drops { grid-template-columns: 1fr; } }
      .onb-drop { display: flex; gap: 10px; align-items: center; text-align: left; border: 1.5px dashed var(--line2); border-radius: var(--radius-sm); background: var(--surface); padding: 12px 14px; cursor: pointer; width: 100%; }
      .onb-drop:hover, .onb-drop.over { border-color: var(--brand-deep); background: var(--amber-soft); }
      .onb-drop.filled { border-style: solid; border-color: var(--ok); background: var(--ok-soft); }
      .onb-approve { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; margin: 0 0 14px; }
      .onb-drop-ico { width: 30px; height: 30px; border-radius: 8px; background: var(--brand-soft); color: var(--brand-deep); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; flex: 0 0 auto; }
      .onb-drop-ico.ok { background: var(--ok); color: #fff; }
      .onb-drop-l { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
      .onb-drop-l .pmd-mono { font-size: 10.5px; color: var(--ink-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      .onb-uknote { display: flex; flex-direction: column; gap: 2px; justify-content: center; padding: 12px 14px; border: 1px solid var(--line-soft); border-radius: var(--radius-sm); background: var(--surface-2); font-size: 13px; }
      .onb-schemes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 12px; }
      @media (max-width: 700px) { .onb-schemes { grid-template-columns: 1fr; } }
      .onb-scheme { position: relative; display: flex; flex-direction: column; gap: 2px; text-align: left; border: 1.5px solid var(--line); border-radius: var(--radius); background: var(--surface); padding: 12px 14px; cursor: pointer; }
      .onb-scheme.on { border-color: var(--ok); background: var(--ok-soft); }
      .onb-tick { position: absolute; top: 10px; right: 12px; color: var(--ok); font-weight: 700; }
      .onb-info { display: flex; gap: 12px; align-items: flex-start; border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px; }
      .onb-info.done { border-color: var(--ok); background: var(--ok-soft); }
      .onb-info.dim { opacity: .55; }
      .onb-info-n { width: 24px; height: 24px; border-radius: 50%; background: var(--ink); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; flex: 0 0 auto; }
      .onb-info.done .onb-info-n { background: var(--ok); }
      .onb-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px 16px; background: var(--surface-2); border-radius: var(--radius-sm); padding: 12px 14px; margin: 12px 0 14px; }
      .onb-summary > div { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
      .onb-summary strong { font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .onb-signs { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
      .onb-sign { display: flex; gap: 10px; align-items: center; border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 9px 12px; flex-wrap: wrap; }
      .onb-sign.signed { border-color: var(--ok); }
      .onb-sign-who { display: flex; flex-direction: column; min-width: 140px; flex: 1; }
      .onb-sign .pm-input { flex: 1; min-width: 170px; }
      .onb-sig { font-family: 'Caveat', cursive; font-size: 26px; line-height: 1; padding: 0 6px; white-space: nowrap; }
      .onb-banner { border-radius: var(--radius-sm); padding: 11px 14px; font-size: 13px; font-weight: 600; background: var(--surface-2); color: var(--ink-faint); }
      .onb-banner.ready { background: var(--ok-soft); color: var(--ok); }
      .onb-welcome { margin-top: 6px; }
      .onb-actions { display: flex; justify-content: space-between; gap: 10px; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line-soft); flex-wrap: wrap; }
      @media (max-width: 860px) { .onb-grid { grid-template-columns: 1fr; } .onb-rail { position: static; } .onb-shell { padding: 16px 14px 70px; } }
    `}</style>;
  }

  window.PMOnboardWizard = OnboardWizard;
})();
