>
);
};
const GOALS = [
{ id: "muscle", icon: "◆", label: "Build muscle", sub: "Train harder. Recover faster." },
{ id: "weight", icon: "▼", label: "Lose weight", sub: "Quiet cravings. Move the scale." },
{ id: "skin", icon: "✿", label: "Fix your skin", sub: "Calm redness. Smooth texture." },
{ id: "hair", icon: "❋", label: "Grow your hair", sub: "Wake sleeping follicles." },
{ id: "energy", icon: "✦", label: "More energy", sub: "Stop needing the second coffee." },
{ id: "sleep", icon: "☾", label: "Sleep deeper", sub: "Wake up actually rested." },
{ id: "brain", icon: "▲", label: "Think smarter", sub: "Sharper focus. Calmer mood." },
{ id: "longevity", icon: "∞", label: "Age slower", sub: "Look 30 at 40." },
{ id: "libido", icon: "♥", label: "More drive", sub: "Reignite the spark." },
];
// Goals takes both: structured chip selection (max 3) + free-text "tell us
// in your own words" — the textarea becomes the highest-leverage signal
// Claude sees (verbatim patient language). Stored as { ids: [...], note: "..." }.
const StepGoals = ({ value, onChange }) => {
const v = Array.isArray(value) ? { ids: value, note: "" } : (value || { ids: [], note: "" });
const ids = v.ids || [];
const note = v.note || "";
const toggle = (id) => {
const next = ids.includes(id) ? ids.filter(x => x !== id) : [...ids, id].slice(-3);
onChange({ ids: next, note });
};
const setNote = (t) => onChange({ ids, note: t });
return (
<>
01 / 04 · Pick up to 3
What do you want different in 90 days?
No wrong answers — just your honest list.
{GOALS.map(g => (
toggle(g.id)}>
{g.icon}
{g.label}
{g.sub}
))}
In your own words (optional, but powerful)
>
);
};
// Step 2 of the funnel — capture lead info BEFORE the camera-permission ask
// so even bail-outs convert to leads. Required: name + email. Optional: phone
// (for SMS check-ins). Validation is light — we don't want to gate on it.
const StepContact = ({ value = {}, onChange }) => {
const v = { name: "", email: "", phone: "", ...value };
const set = (k, val) => onChange({ ...v, [k]: val });
const emailOk = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v.email || "");
const nameOk = (v.name || "").trim().length >= 2;
return (
<>
02 / 04 · Where to send your read
Who are we writing this for?
We email your analysis the moment it's ready and text check-ins as your protocol unfolds. Nothing else.
First name
set("name", e.target.value)}
placeholder="Alex"
autoComplete="given-name"
style={{ width: "100%", boxSizing: "border-box", padding: "14px 16px", background: "var(--paper-2)", border: "1.5px solid " + (nameOk || !v.name ? "var(--paper-3)" : "#e2c8c8"), borderRadius: "var(--r-md)", fontSize: 15, fontFamily: "inherit", color: "var(--ink)", outline: "none" }}/>
Email · required
set("email", e.target.value.trim())}
placeholder="you@where.com"
autoComplete="email"
style={{ width: "100%", boxSizing: "border-box", padding: "14px 16px", background: "var(--paper-2)", border: "1.5px solid " + (emailOk || !v.email ? "var(--paper-3)" : "#e2c8c8"), borderRadius: "var(--r-md)", fontSize: 15, fontFamily: "inherit", color: "var(--ink)", outline: "none" }}/>
Phone · optional, for check-ins
set("phone", e.target.value)}
placeholder="(555) 123-4567"
autoComplete="tel"
style={{ width: "100%", boxSizing: "border-box", padding: "14px 16px", background: "var(--paper-2)", border: "1.5px solid var(--paper-3)", borderRadius: "var(--r-md)", fontSize: 15, fontFamily: "inherit", color: "var(--ink)", outline: "none" }}/>
✓
BioReveal is a fit-finder, not medical advice. A licensed clinician reviews every protocol before fulfillment. No spam — unsubscribe anytime.
>
);
};
// NEW: compact safety screen — age, sex, pregnant, cancer flag, meds, allergies
// Replaces StepFeel + StepTraining + StepVices + StepBasics + StepMedical for
// the v2 funnel. Full clinical intake moves to the post-protocol consult form.
const StepSafety = ({ value = {}, onChange }) => {
const v = {
age: 32, sex: null, pregnant: false, cancer_history: false,
current_meds: "", allergies: "",
...value,
};
const set = (k, val) => onChange({ ...v, [k]: val });
return (
<>
04 / 04 · Safety screen · 30 seconds
A few safety basics.
Required for clinician review. Honest answers protect you.
Sex assigned at birth
{["female", "male", "intersex"].map(s => (
set("sex", s)}>
{s}
))}
Pregnant, breastfeeding, or trying to conceive?
set("pregnant", false)}>No
set("pregnant", true)}>Yes
Personal or 1st-degree-family cancer history?
set("cancer_history", false)}>No
set("cancer_history", true)}>Yes
Current meds & supplements (optional)
set("current_meds", e.target.value)}
placeholder="e.g. metformin, levothyroxine, creatine"
style={{ width: "100%", boxSizing: "border-box", padding: "12px 14px", background: "var(--paper-2)", border: "1.5px solid var(--paper-3)", borderRadius: "var(--r-md)", fontSize: 14, fontFamily: "inherit", color: "var(--ink)" }}/>
Drug or food allergies (optional)
set("allergies", e.target.value)}
placeholder="e.g. sulfa, penicillin, shellfish"
style={{ width: "100%", boxSizing: "border-box", padding: "12px 14px", background: "var(--paper-2)", border: "1.5px solid var(--paper-3)", borderRadius: "var(--r-md)", fontSize: 14, fontFamily: "inherit", color: "var(--ink)" }}/>
🔒 HIPAA-ALIGNED · Only the reviewing clinician sees the full record. Full intake on your consult.
>
);
};
const StepSelfie = ({ value, onChange }) => {
const [scanning, setScanning] = React.useState(false);
const [done, setDone] = React.useState(!!value);
const start = () => {
setScanning(true);
setTimeout(() => { setScanning(false); setDone(true); onChange({ captured: true }); }, 2400);
};
return (
<>
03 / 08 · Optional · Not stored
Snap a selfie.
A soft-light read of skin, eye area, and vitality cues. We use it as a fun visual signal — not a diagnostic. Skip if you'd rather not.
{scanning &&
}
RGB · 24-BIT
● REC
{done ? "✓ ANALYZED" : scanning ? "SCANNING…" : "ALIGN FACE"}
{done ? "✓ Captured" : scanning ? "Scanning…" : "Capture"}
{ setDone(true); onChange({ skipped: true }); }}>
Skip
>
);
};
const FEEL = [
{ id: "energy", label: "Energy day-to-day", low: "Drained", high: "Wired" },
{ id: "sleep", label: "Sleep quality", low: "Wrecked", high: "Restorative" },
{ id: "recovery", label: "Post-workout recovery", low: "Days", high: "Hours" },
{ id: "mood", label: "Mood & focus", low: "Foggy", high: "Locked-in" },
{ id: "drive", label: "Drive / libido", low: "Flat", high: "Roaring" },
];
const StepFeel = ({ value = {}, onChange }) => {
const get = (id) => value[id] ?? 5;
return (
<>
04 / 08 · Be honest
How do you feel right now?
1 = ground floor. 10 = peak. We calibrate from here.
{FEEL.map(f => {
const v = get(f.id);
return (
);
})}
>
);
};
const TRAIN = [
{ id: "elite", mark: "◆", label: "Elite", sub: "5+ days, programmed" },
{ id: "regular", mark: "▲", label: "Regular", sub: "3–4 days a week" },
{ id: "casual", mark: "●", label: "Casual", sub: "1–2 days, mixed" },
{ id: "none", mark: "○", label: "Just walking", sub: "Building back up" },
];
const StepTraining = ({ value, onChange }) => (
<>
05 / 08 · Movement profile
How are you training?
Stack intensity scales to your load.
{TRAIN.map(o => (
onChange(o.id)}>
{o.mark}
))}
>
);
const VICES = [
{ id: "drink", icon: "◇", label: "Alcohol", sub: "More than I'd like" },
{ id: "screen", icon: "▢", label: "Late screens", sub: "Scrolling past midnight" },
{ id: "stress", icon: "≈", label: "Chronic stress", sub: "Always wired" },
{ id: "sugar", icon: "✕", label: "Sugar / refined", sub: "Cravings rule" },
{ id: "smoke", icon: "~", label: "Nicotine", sub: "Vape, smoke, pouch" },
{ id: "sedentary", icon: "—", label: "Mostly sitting", sub: "Desk-bound days" },
];
const StepVices = ({ value = [], onChange }) => {
const toggle = (id) => onChange(value.includes(id) ? value.filter(v => v !== id) : [...value, id]);
return (
<>
06 / 08 · No judgment
What's blocking your peak?
Pick everything that applies. We adjust the protocol around it.
{VICES.map(v => (
toggle(v.id)}>
{v.icon}
{v.label}
{v.sub}
))}
>
);
};
const StepBasics = ({ value = {}, onChange }) => {
const sex = value.sex;
const age = value.age ?? 32;
return (
<>
07 / 08 · Calibration data
A few basics.
Sex and age shape baseline hormone curves.
Sex assigned at birth
{["female", "male", "intersex"].map(s => (
onChange({ ...value, sex: s })}>
{s}
))}
>
);
};
const StepEmail = ({ value = "", onChange }) => (
<>
08 / 08 · Where to send your blueprint
Reveal the protocol.
We'll email a copy of your stack + 12-week timeline. No spam, ever.
onChange(e.target.value)}
style={{
width: "100%", boxSizing: "border-box", padding: "18px 20px",
background: "var(--paper-2)", border: "1.5px solid var(--paper-3)",
borderRadius: "var(--r-md)", fontSize: 16, fontFamily: "inherit", color: "var(--ink)",
marginTop: 8,
}}
/>
✓
I understand BioReveal is a fit-finder, not medical advice. A licensed clinician reviews every order before fulfillment.
>
);
// StepSelfie is the legacy fake-scan; StepVitals (in bioreveal-vitals.jsx) is the real rPPG one.
window.BR_STEPS = window.BR_STEPS || {};
Object.assign(window.BR_STEPS, { StepIntro, StepGoals, StepContact, StepSelfie, StepFeel, StepTraining, StepVices, StepBasics, StepSafety, StepEmail });