// Dateora — mobile version. Single-column layouts inside an IOSDevice frame. // Reuses Btn / Field / AvatarPortrait / Bubble / Icon / Display / Eyebrow // from the web version. The IOSDevice wrapper is added by Dateora.html — this // component just renders the content (which fills the full device, status bar // hover-overlays at the top). const M_TOP_INSET = 56; // leave room for status bar / dynamic island // ─── Mobile shell ───────────────────────────────────────────── function MobileDateoraApp({ themeId = 'mentor' }) { const t = window.DateoraThemes[themeId]; const [screen, setScreen] = React.useState('welcome'); const [user, setUser] = React.useState(null); const [tab, setTab] = React.useState('conversation'); const [messages, setMessages] = React.useState(window.__DateoraSeedMessages || []); const [sessions, setSessions] = React.useState(window.__DateoraSeedHistory || []); const [sending, setSending] = React.useState(false); const reset = () => { setScreen('welcome'); setUser(null); setTab('conversation'); }; const handleSend = async (text) => { const next = [...messages, { role: 'user', text }]; setMessages(next); setSending(true); try { const transcript = next.map((m) => (m.role === 'coach' ? 'Adam: ' : 'User: ') + m.text).join('\n\n'); const reply = await window.claude.complete(window.__DateoraCoachSystem + '\n\nConversation so far:\n' + transcript + '\n\nAdam:'); setMessages((m) => [...m, { role: 'coach', text: (reply || '').trim() || "Tell me more — what happened next?" }]); } catch { const fb = ["Stay with that. Smallest version of that you could try this week?", "You went abstract. Bring it back to the actual person. What was she wearing?", "Good. Rep that line out loud. Three times. How does it land in your mouth?"]; setMessages((m) => [...m, { role: 'coach', text: fb[Math.floor(Math.random() * fb.length)] }]); } finally { setSending(false); } }; const containerStyle = { width: '100%', height: '100%', background: t.bg, color: t.ink, fontFamily: t.fontBody, overflow: 'hidden', position: 'relative', backgroundImage: t.bgGradient, }; let body; if (screen === 'welcome') body = setScreen('signup')} onSignIn={() => setScreen('signin')} />; else if (screen === 'signup' || screen === 'signin') body = { setUser({ email }); setScreen(screen === 'signup' ? 'plan' : 'app'); }} onBack={() => setScreen(screen === 'signup' ? 'welcome' : 'signup')} />; else if (screen === 'plan') body = setScreen('payment')} onBack={() => setScreen('signup')} />; else if (screen === 'payment') body = setScreen('app')} onBack={() => setScreen('plan')} />; else if (screen === 'app') body = ( {tab === 'conversation' && } {tab === 'history' && setSessions((s) => s.filter((x) => x.id !== id))} onOpen={() => setTab('conversation')} />} {tab === 'account' && } ); return (
{body} s === 'welcome' ? reset() : setScreen(s)} />
); } // ─── Welcome ───────────────────────────────────────────────── const MWelcome = ({ t, onStart, onSignIn }) => (
For men who freeze {t.welcomeHeadline}

{t.welcomeSub}

{t.welcomeCTA}
); // ─── Auth ───────────────────────────────────────────────── const MAuth = ({ t, mode, onSubmit, onBack }) => { const [email, setEmail] = React.useState(mode === 'signin' ? 'you@example.com' : ''); const [pw, setPw] = React.useState(mode === 'signin' ? '••••••••' : ''); const isSignup = mode === 'signup'; return (
{isSignup ? 'Step 1 of 3' : 'Sign in'}
{isSignup ? 'Create your account' : 'Welcome back'}

{isSignup ? 'Email and a password. Nothing else right now.' : 'Sign in to continue with Adam.'}

{ e.preventDefault(); onSubmit({ email }); }} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}> onSubmit({ email })}> {isSignup ? 'Continue' : 'Sign in'}
or
onSubmit({ email: 'google@example.com' })}> Continue with Google
{isSignup ? 'Have an account? ' : 'New here? '} {isSignup ? 'Sign in' : 'Create one'}
); }; // ─── Plan ───────────────────────────────────────────────── const MPlan = ({ t, onStart, onBack }) => { const features = ['Unlimited sessions with Adam', 'Voice and text conversations', 'Saved history — review your reps', 'Cancel two clicks, any page']; return (
Step 2 of 3
{t.planEyebrow} {t.planTitle}

{t.planBody}

{t.id === 'studio' && (
FREE 14 DAYS
)}
Membership
Dateora · Unlimited
{t.planPrice}
{t.planPeriod}
{features.map((f) => (
{f}
))}
{t.planFinePrint}
{t.planCTA}
); }; // ─── Payment ───────────────────────────────────────────────── const MPayment = ({ t, onPaid, onBack }) => { const [card, setCard] = React.useState('4242 4242 4242 4242'); const [exp, setExp] = React.useState('12 / 28'); const [cvc, setCvc] = React.useState('123'); const [processing, setProcessing] = React.useState(false); const submit = () => { setProcessing(true); setTimeout(onPaid, 1100); }; return (
Step 3 of 3
Card to start your trial

No charge today. Email 3 days before trial ends.

or pay with card
Due today $0.00
{processing ? Starting… : 'Start 14-day trial'}
Secured by Stripe · No charge today
); }; // ─── App shell with bottom tab nav ───────────────────────────── const MAppShell = ({ t, tab, onTab, onLogout, children }) => { const items = [ { id: 'conversation', label: 'Session', icon: }, { id: 'history', label: 'History', icon: }, { id: 'account', label: 'Account', icon: }, ]; return (
{children}
{items.map((it) => { const active = it.id === tab; return ( ); })}
); }; // ─── Conversation Mobile ───────────────────────────────────── const MConversation = ({ t, messages, onSend, sending }) => { const [draft, setDraft] = React.useState(''); const ref = React.useRef(null); React.useEffect(() => { if (ref.current) ref.current.scrollTop = ref.current.scrollHeight; }, [messages, sending]); const send = () => { const v = draft.trim(); if (!v || sending) return; onSend(v); setDraft(''); }; const lastIsCoach = messages.length > 0 && messages[messages.length - 1].role === 'coach'; return (
{/* compact header */}
Adam · {t.coachRole}
Live · session 04 · 06:18
{/* avatar card */}
LIVE
{[3, 7, 11, 14, 9, 5, 11, 14, 8].map((h, i) => (
))}
{/* chat scroll */}
{messages.map((m, i) => )} {sending && }
{/* quick reply chips */} {lastIsCoach && !draft && !sending && (
{["I went blank again", "Give me a real opener", "What should I have said?"].map((s) => ( ))}
)} {/* input */}
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); send(); } }} placeholder={sending ? 'Adam is thinking…' : 'Message Adam…'} disabled={sending} style={{ flex: 1, border: 'none', outline: 'none', background: 'transparent', color: t.ink, fontSize: 14, fontFamily: t.fontBody, padding: '8px 0', minWidth: 0 }} />
); }; // ─── History Mobile ────────────────────────────────────────── const MHistory = ({ t, sessions, onDelete, onOpen }) => (
Your reps History
{sessions.map((s) => (
onOpen(s.id)} style={{ padding: '12px 10px', display: 'flex', alignItems: 'center', gap: 12, borderBottom: '1px solid ' + t.border, cursor: 'pointer', }}>
{String(s.idx).padStart(2, '0')}
{s.title}
{s.when} · {s.minutes} min · {s.reps} reps
))}
); // ─── Account Mobile ────────────────────────────────────────── const MAccount = ({ t, email }) => (
Settings Account
12 days remaining} /> } /> } /> Erase everything} actionable />
); const MSection = ({ t, title, children }) => (
{title}
{children}
); const MRow = ({ t, label, value, actionable }) => (
{label}
{value} {actionable && }
); const MToggle = ({ t, on }) => { const [v, setV] = React.useState(on); return ( ); }; // Tiny floating jump-screens pill, sized for mobile function MScreenNav({ t, screen, onJump }) { const [open, setOpen] = React.useState(false); const steps = [ { id: 'welcome', label: '01' }, { id: 'signup', label: '02' }, { id: 'plan', label: '03' }, { id: 'payment', label: '04' }, { id: 'app', label: '05' }, ]; return (
setOpen(true)} onMouseLeave={() => setOpen(false)} style={{ position: 'absolute', top: 16, left: '50%', transform: 'translateX(-50%)', zIndex: 100, padding: 3, borderRadius: 100, background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(20px)', border: '1px solid rgba(255,255,255,0.12)', display: 'flex', gap: 2, fontSize: 10, opacity: open ? 1 : 0.3, transition: 'opacity .2s', }}> {steps.map((s) => ( ))}
); } Object.assign(window, { MobileDateoraApp });