/* ============================================================ BLUEPRINTSENSES — PRO UNLOCK One-time unlock per concept slot. Every lock in the app calls askUnlock(reason) and lands on the same offer; after payment the user stays on the step they were on, with every lock open. Payment is simulated in this prototype (no card fields, no charge). No student promotion for now. ============================================================ */ const { useState: useStateP } = React; const ProContext = React.createContext({ pro: false, askUnlock: () => {} }); const usePro = () => React.useContext(ProContext); /* small "Pro" pill */ function ProBadge({ dark }) { return ( Pro ); } const LockIcon = ({ size = 13, color = "currentColor" }) => ( ); /* a compact locked strip: "Pro · what it adds" — shown but not in the way */ function LockedStrip({ title, items, reason, compact }) { const { askUnlock } = usePro(); const { t } = useLang(); return ( ); } /* blur a Pro preview and put the offer on top */ function LockedPreview({ children, title, sub, reason, height = 260 }) { const { askUnlock } = usePro(); const { t } = useLang(); return (
{title}
{sub &&
{sub}
}
); } /* ---------- the offer ---------- */ function UnlockModal({ reason, plan, account, slot, onClose, onPaid }) { const { t, lang } = useLang(); const [stage, setStage] = useStateP("offer"); // offer → checkout → done const [name, setName] = useStateP((account && account.name) || ""); const [email, setEmail] = useStateP((account && account.email) || ""); const live = computeFin(plan.financials, t); const proj = computeProjection(plan.financials, t, "planned"); const conceptName = plan.idea.conceptName || t("pro.thisConcept"); const hasNumbers = (plan.financials.step || 0) > 0 && live.sales > 0 && live.investment > 0; // only real numbers, never untouched defaults const gains = [ ["pro.gainConcept", "pro.gainConceptSub"], ["pro.gainFinancial", "pro.gainFinancialSub"], ["pro.gainOwnership", "pro.gainOwnershipSub"], ["pro.gainOutputs", "pro.gainOutputsSub"], ]; const pay = () => { onPaid({ name: name.trim(), email: email.trim(), student: false, price: null }); setStage("done"); }; const overlay = { position: "fixed", inset: 0, background: "rgba(17,32,58,.55)", zIndex: 120, display: "grid", placeItems: "center", padding: 16, animation: "fadeIn .2s" }; const panel = { ...card, width: "min(600px, 100%)", maxHeight: "90vh", overflowY: "auto", padding: 0 }; return (
e.stopPropagation()} style={panel} role="dialog" aria-modal="true" aria-label={t("pro.unlockThis")}>
{slot ? t("pro.slotKicker") : t("pro.kicker")}
{slot ? t("pro.slotTitle") : t("pro.title", { name: conceptName })}
{reason && !slot &&
{t("pro.reason." + reason)}
} {slot &&
{t("pro.slotSub")}
}
{stage === "offer" && (
{!slot && hasNumbers && (
{t("pro.yourPreview")}
)}
{slot ? t("pro.slotGains") : t("pro.gains")}
{gains.map(([k, sub]) => (
✓ {t(k)} — {t(sub)}
))}
{t("pro.priceTerms")}
{t("pro.answersStay")}
)} {stage === "checkout" && (
{account ? t("pro.signedInAs", { email: account.email }) : t("pro.createAccount")}
{!account &&
{t("pro.accountWhy")}
} {!account && (
)}
{slot ? t("pro.slotLine") : t("pro.lineItem", { name: conceptName })}
{t("pro.priceTerms")}
{t("pro.demoNote")}
)} {stage === "done" && (
✓
{slot ? t("pro.slotDone") : t("pro.done")}
{slot ? t("pro.slotDoneSub") : t("pro.doneSub")}
)}
); } function PreviewTile({ label, value, locked }) { return (
{label}{locked && }
{value}
); } Object.assign(window, { ProContext, usePro, ProBadge, LockIcon, LockedStrip, LockedPreview, UnlockModal });