${numRow("Amount min ($)", "amount_min", 'type="number" step="0.01" min="0"')} ${numRow("Amount max ($)", "amount_max", 'type="number" step="0.01" min="0"')} ${numRow("Shipping min ($)", "shipping_min", 'type="number" step="0.01" min="0"')} ${numRow("Shipping max ($)", "shipping_max", 'type="number" step="0.01" min="0"')}
`; } else if (step === 7) { b.innerHTML = H("Step 7 — Gmail accounts") + hint(`Select up to ${OPT.limits.max_accounts} sending accounts. Only authorized, active accounts are shown.`) + (OPT.accounts.length ? pickList("accounts", C.account_ids, a => ({ v: a.id, main: a.email, sub: `${a.sends_today}/${a.daily_limit} today · ${a.sending_method}` }), "account_ids") : `
⚠
No authorized Gmail accounts. Connect one on the Gmail Accounts page first.
`); } else if (step === 8) { const strat = C.rotation_strategy; b.innerHTML = H("Step 8 — Account rotation") + hint("How senders rotate. Weighted lets you favor specific accounts; per-account caps and cooldowns are enforced regardless.") + `
`; bindRotExtra(); } else if (step === 9) { b.innerHTML = H("Step 9 — Limits & pacing") + hint("Conservative pacing protects your accounts. Sends are throttled and retried with exponential backoff.") + `
${numRow("Max sends per account", "max_per_account", 'type="number" min="1" max="2000"')} ${numRow("Interval between sends (s)", "send_interval_seconds", 'type="number" step="0.5" min="0.5" max="3600"')}
${numRow("Rate cap (per minute)", "rate_per_minute", 'type="number" min="1" max="600"')} ${numRow("Max retries", "max_retries", 'type="number" min="0" max="10"')} ${numRow("Retry delay (s)", "retry_delay_seconds", 'type="number" min="5" max="3600"')}
`; } else if (step === 10) { b.innerHTML = H("Step 10 — Company phone") + hint("Which active phone number appears as %CompanyPhone%.") + (OPT.phones.length ? pickList("phones", C.phone_selection === "fixed" ? (C.variable_config.phone_id ?? null) : null, p => ({ v: p.id, main: p.phone, sub: p.label || "" }), "phone_fixed", true) : `
⚠
No active phone numbers. %CompanyPhone% will be blank unless numbers are added on the Company Phones page.
`) + `
${["round_robin", "random", "fixed"].map(m => ` `).join("")}
`; } else if (step === 11) { b.innerHTML = H("Step 11 — Unsubscribe handling") + hint("Permission-based sending requires working opt-out. Disabling is not permitted.") + `
ℹ
Suppression list is always checked before sending, regardless of this setting. Recipients who opt out are skipped automatically.
`; } else if (step === 12) { const vc = C.variable_config; const list = OPT.lists.find(l => l.id === C.recipient_list_id); const accs = (OPT.accounts || []).filter(a => C.account_ids.includes(a.id)); const tpls = (OPT.templates || []).filter(t => C.template_ids.includes(t.id)); const subCount = (OPT.subjects || []).filter(s => C.subject_set_ids.includes(s.id)).length; const total = list ? list.valid_count : 0; const cap = Math.max(...(accs.map(a => C.max_per_account).concat([0])), 1); const capacity = accs.length * cap; b.innerHTML = H("Step 12 — Confirm and start") + hint("Review the full configuration. You can still edit any step, then create the campaign.") + `
Campaign name
${esc(C.name) || "—"}
Recipient list
${list ? esc(list.name) + ` (${list.valid_count} valid of ${list.row_count})` : "—"}
Subjects
${subCount} selected · ${esc(C.subject_rotation)}
Templates
${tpls.length ? tpls.map(t => esc(t.name)).join(", ") : "—"} · ${esc(C.template_sequence_mode)}
Message type
${esc(C.message_type)}
Attachments
${C.attach_mode === "none" ? "none" : esc(C.attach_mode) + (C.attach_mode === "pdf" && C.attach_pdf_template_id ? ` — template #${C.attach_pdf_template_id}` : "") + (C.attach_mode !== "none" && C.attach_mode !== "pdf" && C.attachment_id ? ` — attachment #${C.attachment_id}` : "")}
Invoice format
${esc(vc.invoice_format || "{prefix}{R4}-{R5}-{R6}-{seq4}")} · prefix ${esc(vc.invoice_prefix || "INV-")}
Amount range
${esc(vc.amount_min ?? 300)} – ${esc(vc.amount_max ?? 1500)} · shipping ${esc(vc.shipping_min ?? 5)} – ${esc(vc.shipping_max ?? 50)}
Accounts
${accs.length ? accs.map(a => esc(a.email)).join(", ") : "—"} · ${esc(C.rotation_strategy)}${C.rotation_strategy === "weighted" ? ` (weights ${esc(JSON.stringify(C.rotation_weights))})` : ""}
Per-account cap
${C.max_per_account} → capacity ${capacity} of ${total} recipients
Pacing
${C.send_interval_seconds}s interval · ${C.rate_per_minute}/min · ${C.max_retries} retries @ ${C.retry_delay_seconds}s
Phone
${esc(C.phone_selection)}
Unsubscribe
${C.unsubscribe_enabled ? "enabled" : "disabled"}
${capacity < total ? `
⚠
Account capacity (${capacity}) is below the recipient count (${total}). The campaign will stop when capacity is exhausted.
` : ""}
ℹ
After creating, the campaign opens in the live monitor where you press Start. No email leaves until you do.
`; } b.scrollTop = 0; bindStep(); } function pickList(kind, current, mapper, stateKey, radio = false) { const items = OPT[kind] || []; if (!items.length) return `
◱

Nothing available yet.

`; const cur = Array.isArray(current) ? current : (current ? [current] : []); return `
` + items.map(it => { const m = mapper(it);