// 743 Days — SMS fiction prototype
// Views: lock → chat → paywall

const { useState, useEffect, useRef, useCallback } = React;

// ---------- Routing helpers (testing) ----------
function readHashView() {
  const h = (typeof window !== 'undefined' && window.location.hash || '').replace('#', '');
  if (h === 'chat' || h === 'chat1') return { view: 'chat', chapter: 1 };
  if (h === 'chat2') return { view: 'chat', chapter: 2 };
  if (h === 'chat3') return { view: 'chat', chapter: 3 };
  if (h === 'chat4') return { view: 'chat', chapter: 4 };
  if (h === 'chat5') return { view: 'chat', chapter: 5 };
  if (h === 'chat6') return { view: 'chat', chapter: 6 };
  if (h === 'chat7') return { view: 'chat', chapter: 7 };
  if (h === 'chat8') return { view: 'chat', chapter: 8 };
  if (h === 'paywall') return { view: 'paywall', chapter: 1 };
  if (h === 'end2') return { view: 'end2', chapter: 2 };
  if (h === 'end3') return { view: 'end3', chapter: 3 };
  if (h === 'end4') return { view: 'end4', chapter: 4 };
  if (h === 'end5') return { view: 'end5', chapter: 5 };
  if (h === 'end6') return { view: 'end6', chapter: 6 };
  if (h === 'end7') return { view: 'end7', chapter: 7 };
  if (h === 'end8') return { view: 'end8', chapter: 8 };
  return { view: 'lock', chapter: 1 };
}

// ---------- Chapter configuration ----------
const CHAPTERS = {
  1: {
    script: () => window.CONVERSATION,
    storageKey: '743:chat:1:v3',
    contactName: 'Unknown',
    endEvent: 'end-chapter1',
  },
  2: {
    script: () => window.CHAPTER2,
    storageKey: '743:chat:2:v3',
    contactName: 'Katie',
    photo: 'assets/katie.png',
    endEvent: 'end-chapter2',
  },
  3: {
    script: () => window.CHAPTER3,
    storageKey: '743:chat:3:v3',
    contactName: 'Mia',
    endEvent: 'end-chapter3',
  },
  4: {
    script: () => window.CHAPTER4,
    storageKey: '743:chat:4:v3',
    contactName: 'Katie',
    photo: 'assets/katie.png',
    endEvent: 'end-chapter4',
  },
  5: {
    script: () => window.CHAPTER5,
    storageKey: '743:chat:5:v3',
    contactName: 'Mia',
    endEvent: 'end-chapter5',
  },
  6: {
    script: () => window.CHAPTER6,
    storageKey: '743:chat:6:v3',
    contactName: 'Katie',
    photo: 'assets/katie.png',
    endEvent: 'end-chapter6',
  },
  7: {
    script: () => window.CHAPTER7,
    storageKey: '743:chat:7:v3',
    contactName: 'Katie',
    photo: 'assets/katie.png',
    endEvent: 'end-chapter7',
  },
  8: {
    script: () => window.CHAPTER8,
    storageKey: '743:chat:8:v3',
    contactName: 'Mia',
    endEvent: 'end-chapter8',
  },
};

// ---------- Auto-typer hook ----------
function useAutoType(target, active, onDone, nonce) {
  const [value, setValue] = useState('');
  useEffect(() => {
    if (!active || !target) { setValue(''); return; }
    setValue('');
    let i = 0;
    let cancelled = false;
    let pending = null;
    const tick = () => {
      if (cancelled) return;
      i += 1;
      setValue(target.slice(0, i));
      if (i >= target.length) {
        onDone && onDone();
        return;
      }
      const ch = target[i - 1];
      let delay = 38 + Math.random() * 50;
      if (ch === ' ') delay += 30;
      if (',;:'.includes(ch)) delay += 140;
      if ('.?!'.includes(ch)) delay += 220;
      pending = setTimeout(tick, delay);
    };
    const start = setTimeout(tick, 250);
    return () => { cancelled = true; clearTimeout(start); if (pending) clearTimeout(pending); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [target, active, nonce]);
  return value;
}

// Draft animator: types each draft, holds, erases, pauses, then next.
function useMeDraft(drafts, active, onDone) {
  const [value, setValue] = useState('');
  useEffect(() => {
    if (!active || !drafts || drafts.length === 0) { setValue(''); return; }
    let cancelled = false;
    let timers = [];
    const wait = (ms) => new Promise((resolve) => {
      const id = setTimeout(resolve, ms);
      timers.push(id);
    });
    (async () => {
      for (let d = 0; d < drafts.length; d++) {
        if (cancelled) return;
        const text = drafts[d];
        // type in
        for (let i = 1; i <= text.length; i++) {
          if (cancelled) return;
          setValue(text.slice(0, i));
          await wait(45 + Math.random() * 60);
        }
        await wait(500 + Math.random() * 350);
        // erase
        for (let i = text.length - 1; i >= 0; i--) {
          if (cancelled) return;
          setValue(text.slice(0, i));
          await wait(25 + Math.random() * 30);
        }
        await wait(400 + Math.random() * 350);
      }
      if (cancelled) return;
      setValue('');
      onDone && onDone();
    })();
    return () => { cancelled = true; timers.forEach(clearTimeout); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [drafts, active]);
  return value;
}

// ---------- Status bar ----------
function StatusBar({ tone, showTime = true }) {
  return (
    <div className={"statusbar statusbar--" + tone}>
      {showTime
        ? <div className="statusbar__time">23:47</div>
        : <div className="statusbar__time statusbar__time--hidden" />}
      <div className="statusbar__notch" />
      <div className="statusbar__right">
        <svg viewBox="0 0 18 12" width="18" height="12" aria-hidden="true">
          <circle cx="2" cy="10" r="1.4" fill="currentColor"/>
          <circle cx="6" cy="10" r="1.4" fill="currentColor"/>
          <circle cx="10" cy="10" r="1.4" fill="currentColor"/>
          <circle cx="14" cy="10" r="1.4" fill="currentColor" opacity=".4"/>
        </svg>
        <svg viewBox="0 0 16 12" width="16" height="12" aria-hidden="true">
          <path d="M8 10.5l-2-2a2.8 2.8 0 014 0l-2 2zm-4-4l-2-2a8.5 8.5 0 0112 0l-2 2a5.6 5.6 0 00-8 0z" fill="currentColor"/>
        </svg>
        <div className="statusbar__battery">
          <div className="statusbar__battery-fill" />
        </div>
      </div>
    </div>
  );
}

// ---------- Login flow ----------
function LoginFlow({ onComplete, onClose, initialEmail }) {
  const [stage, setStage] = useState('email'); // 'email' | 'sent' | 'resuming'
  const [email, setEmail] = useState(initialEmail || '');
  const [touched, setTouched] = useState(false);
  const [sending, setSending] = useState(false);
  const [error, setError] = useState('');
  const isLive = !!(window.Backend && window.Backend.live);
  const inputRef = useRef(null);

  useEffect(() => {
    if (stage === 'email' && inputRef.current) {
      const t = setTimeout(() => inputRef.current && inputRef.current.focus(), 300);
      return () => clearTimeout(t);
    }
  }, [stage]);

  async function submit(e) {
    if (e) e.preventDefault();
    setTouched(true);
    const ok = /\S+@\S+\.\S+/.test(email);
    if (!ok) return;
    setError('');
    setSending(true);
    const result = await window.Backend.signIn(email);
    setSending(false);
    if (result && result.error) { setError(result.error); return; }
    setStage('sent');
  }

  function openLink() {
    setStage('resuming');
    setTimeout(() => onComplete && onComplete(email), 1100);
  }

  return (
    <div className="login" role="dialog" aria-modal="true" aria-label="Log in">
      <div className="login__scrim" onClick={onClose} />
      <div className="login__sheet">
        <button className="login__close" onClick={onClose} aria-label="Close">
          <svg viewBox="0 0 24 24" width="16" height="16"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
        </button>

        {stage === 'email' && (
          <form onSubmit={submit}>
            <div className="login__eyebrow">Welcome back</div>
            <div className="login__title">Pick up where you left off.</div>
            <p className="login__sub">Enter the email you used to save your place. We'll send you a one‑time link.</p>

            <label className="login__label" htmlFor="login-email">Email</label>
            <input
              ref={inputRef}
              id="login-email"
              className={"login__input" + (touched && !/\S+@\S+\.\S+/.test(email) ? ' is-invalid' : '')}
              type="email"
              autoComplete="email"
              placeholder="your@email.com"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
            />
            <button className="login__primary" type="submit" disabled={sending}>
              {sending ? 'Sending…' : 'Send me a link'}
            </button>
            {error && <div className="login__error">{error}</div>}
            <div className="login__fine">No password. We'll email a secure link that signs you in.</div>
          </form>
        )}

        {stage === 'sent' && (
          <div className="login__sent">
            <div className="login__icon" aria-hidden="true">
              <svg viewBox="0 0 36 28" width="40" height="32">
                <rect x="1" y="1" width="34" height="26" rx="3" fill="none" stroke="currentColor" strokeWidth="1.4"/>
                <path d="M2 3l16 14L34 3" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            </div>
            <div className="login__title">Check your email.</div>
            <p className="login__sub">We sent a link to <strong>{email}</strong>. Tap it on this device to come back to the story.</p>
            {!isLive && (
              <button className="login__primary login__primary--mute" onClick={openLink} type="button">
                Open the magic link
                <span className="login__demo">demo</span>
              </button>
            )}
            <button className="login__textlink" onClick={() => setStage('email')} type="button">
              Use a different email
            </button>
          </div>
        )}

        {stage === 'resuming' && (
          <div className="login__resume">
            <div className="login__spinner" aria-hidden="true" />
            <div className="login__title">Welcome back.</div>
            <p className="login__sub">Picking up where you left off…</p>
          </div>
        )}
      </div>
    </div>
  );
}

// ---------- Mobile menu (hamburger + popdown) ----------
function MobileMenu({ onOpenLogin, onBuy, loggedInEmail, hasAccess, onLogout }) {
  const [open, setOpen] = useState(false);
  const isLoggedIn = !!loggedInEmail;
  return (
    <React.Fragment>
      <button
        className="phone-menu-trigger"
        onClick={() => setOpen((o) => !o)}
        aria-label="Menu"
        aria-expanded={open}
      >
        <span /><span /><span />
      </button>
      {open && (
        <React.Fragment>
          <div className="phone-menu-scrim" onClick={() => setOpen(false)} />
          <div className="phone-menu-pop" role="menu">
            {isLoggedIn && (
              <div className="phone-menu-email" title={loggedInEmail}>
                <div className="phone-menu-email__label">Signed in as</div>
                <div className="phone-menu-email__addr">{loggedInEmail}</div>
              </div>
            )}
            <button
              className="phone-menu-item"
              onClick={() => {
                setOpen(false);
                if (isLoggedIn) { onLogout && onLogout(); }
                else { onOpenLogin && onOpenLogin(); }
              }}
            >
              {isLoggedIn ? (
                <svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
                  <path d="M14 17l5-5-5-5M19 12H7M10 4H5a1 1 0 00-1 1v14a1 1 0 001 1h5" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              ) : (
                <svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
                  <path d="M10 17l5-5-5-5M15 12H3M14 4h5a1 1 0 011 1v14a1 1 0 01-1 1h-5" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
              )}
              <span>{isLoggedIn ? 'Log out' : 'Log in'}</span>
            </button>
            {!hasAccess && onBuy && (
              <button
                className="phone-menu-item phone-menu-item--buy"
                onClick={() => { setOpen(false); onBuy(); }}
              >
                <svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
                  <path d="M5 8h14l-1 12H6L5 8zm3 0V6a4 4 0 018 0v2" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
                <span>Buy the full story</span>
              </button>
            )}
          </div>
        </React.Fragment>
      )}
    </React.Fragment>
  );
}

// ---------- Lock screen ----------
function LockScreen({ onOpenNotification, onBuy, onOpenLogin, loggedInEmail, hasAccess, onLogout }) {
  const [pressed, setPressed] = useState(false);
  return (
    <div className="lock" data-screen-label="01 Lock screen">
      <div className="lock__wallpaper" />
      <div className="lock__vignette" />

      <StatusBar tone="light" />

      <MobileMenu onOpenLogin={onOpenLogin} onBuy={onBuy} loggedInEmail={loggedInEmail} hasAccess={hasAccess} onLogout={onLogout} />

      <div className="lock__time-stack">
        <div className="lock__date">Tuesday, March 12</div>
        <div className="lock__time">23:47</div>
      </div>

      <div className="lock__notif-area">
        <div
          className={"notif" + (pressed ? ' notif--pressed' : '')}
          onPointerDown={() => setPressed(true)}
          onPointerUp={() => { setPressed(false); onOpenNotification(); }}
          onPointerLeave={() => setPressed(false)}
          role="button"
          aria-label="Open message from Unknown"
        >
          <div className="notif__icon" aria-hidden="true">
            <svg viewBox="0 0 24 24" width="22" height="22">
              <path d="M12 3c-5 0-9 3.4-9 7.6 0 2.4 1.3 4.5 3.4 5.9-.1 1-.6 2.3-1.5 3.3-.2.2 0 .5.3.5 1.7-.2 3.4-.8 4.6-1.6.7.1 1.4.2 2.2.2 5 0 9-3.4 9-7.6S17 3 12 3z" fill="currentColor"/>
            </svg>
          </div>
          <div className="notif__body">
            <div className="notif__head">
              <div className="notif__title">Messages</div>
              <div className="notif__time">now</div>
            </div>
            <div className="notif__sender">Unknown</div>
            <div className="notif__text">dad</div>
          </div>
        </div>

      </div>

      <div className="lock__footer">
        <div className="lock__home-indicator" />
      </div>
    </div>
  );
}

// ---------- Bubble + typing ----------
function Bubble({ from, text, photo, alt, tail, grouped }) {
  const cls = [
    'bubble',
    photo ? 'bubble--photo' : '',
    from === 'me' ? 'bubble--me' : 'bubble--them',
    tail ? 'bubble--tail' : '',
    grouped ? 'bubble--grouped' : '',
  ].join(' ');
  return (
    <div className={cls}>
      <div className="bubble__inner">{photo ? <img src={photo} alt={alt || ''} /> : text}</div>
    </div>
  );
}

function TypingBubble() {
  return (
    <div className="bubble bubble--them bubble--tail bubble--typing">
      <div className="bubble__inner">
        <span className="dot" />
        <span className="dot" />
        <span className="dot" />
      </div>
    </div>
  );
}

// ---------- Chat ----------
try { localStorage.removeItem('743:chat:1'); } catch (e) {}
try { localStorage.removeItem('743:chat:2'); } catch (e) {}
try { localStorage.removeItem('743:chat:3'); } catch (e) {}
try { localStorage.removeItem('743:chat:4'); } catch (e) {}
try { localStorage.removeItem('743:chat:5'); } catch (e) {}
try { localStorage.removeItem('743:chat:6'); } catch (e) {}
try { localStorage.removeItem('743:chat:7'); } catch (e) {}
for (let i = 1; i <= 7; i++) { try { localStorage.removeItem('743:chat:' + i + ':v2'); } catch (e) {} }

function loadSaved(storageKey) {
  try {
    const raw = localStorage.getItem(storageKey);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (parsed && Array.isArray(parsed.thread) && Array.isArray(parsed.queue)) return parsed;
  } catch (e) {}
  return null;
}
function clearAllSaved() {
  try { localStorage.removeItem('743:chat:1:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:2:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:3:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:4:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:5:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:6:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:7:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:8:v3'); } catch (e) {}
  try { localStorage.removeItem('743:chat:1'); } catch (e) {}
  try { localStorage.removeItem('743:email'); } catch (e) {}
}

function Chat({ chapter, onEndChapter, onBuy, onOpenLogin, loggedInEmail, hasAccess, onLogout }) {
  const cfg = CHAPTERS[chapter];
  // Restore prior session if present, scoped to this chapter.
  const saved = React.useMemo(() => loadSaved(cfg.storageKey), [cfg.storageKey]);
  const [queue, setQueue] = useState(() => (saved ? saved.queue : cfg.script()));
  const [thread, setThread] = useState(() => (saved ? saved.thread : []));
  // 'idle' | 'their-typing' | 'auto-typing' | 'awaiting-send' | 'choosing' | 'me-drafting' | 'end'
  const [phase, setPhase] = useState('idle');
  const [autoTargetText, setAutoTargetText] = useState('');
  const [autoNonce, setAutoNonce] = useState(0);
  const [activeDrafts, setActiveDrafts] = useState(null);
  const scrollRef = useRef(null);

  const step = queue[0];

  // Consume one step
  const advance = useCallback(() => {
    setQueue((q) => q.slice(1));
  }, []);

  useEffect(() => {
    if (!step) { setPhase('end'); return; }

    if (step.kind === 'timestamp') {
      setThread((t) => [...t, { kind: 'timestamp', label: step.label }]);
      advance(); return;
    }
    if (step.kind === 'pause') {
      const id = setTimeout(advance, step.ms || 1000);
      return () => clearTimeout(id);
    }
    if (step.kind === 'them-draft') {
      setPhase('their-typing');
      const dwell = step.ms || (1300 + Math.random() * 900);
      const id = setTimeout(() => { setPhase('idle'); advance(); }, dwell);
      return () => clearTimeout(id);
    }
    if (step.kind === 'me-draft') {
      setPhase('me-drafting');
      setActiveDrafts(step.drafts);
      return;
    }
    if (step.kind === 'system') {
      setThread((t) => [...t, { kind: 'system', text: step.text }]);
      advance(); return;
    }
    if (step.kind === 'photo') {
      const from = step.from || 'them';
      if (from === 'them') setPhase('their-typing');
      const id = setTimeout(() => {
        setThread((tt) => [...tt, { from, photo: step.src, alt: step.alt }]);
        setPhase('idle');
        advance();
      }, from === 'them' ? 2200 : 900);
      return () => clearTimeout(id);
    }
    if (step.kind === 'crack') {
      setThread((t) => [...t, { kind: 'crack', text: step.text }]);
      advance(); return;
    }
    if (step.kind === 'end-chapter1' || step.kind === 'end-chapter2' || step.kind === 'end-chapter3' || step.kind === 'end-chapter4' || step.kind === 'end-chapter5' || step.kind === 'end-chapter6' || step.kind === 'end-chapter7' || step.kind === 'end-chapter8') {
      setPhase('end');
      const id = setTimeout(() => onEndChapter(), step.delay || 1200);
      return () => clearTimeout(id);
    }

    if (step.from === 'them') {
      setPhase('their-typing');
      const dwell = 1200 + Math.min(2800, (step.text || '').length * 62) + Math.random() * 500 + (step.extraDwell || 0);
      const id = setTimeout(() => {
        setThread((tt) => [...tt, { from: 'them', text: step.text }]);
        setPhase('idle');
        advance();
      }, dwell);
      return () => clearTimeout(id);
    }

    if (step.from === 'me') {
      if (step.choices) {
        setPhase('choosing');
      } else {
        setPhase('auto-typing');
        setAutoTargetText(step.text);
        setAutoNonce((n) => n + 1);
      }
      return;
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [step]);

  // Pick a choice → send immediately, splice `then` into queue.
  function handlePickChoice(opt) {
    setThread((t) => [...t, { from: 'me', text: opt.text }]);
    // remove the choice step (queue[0]) and prepend the `then` beats
    setQueue((q) => [...(opt.then || []), ...q.slice(1)]);
    setPhase('idle');
  }

  const onAutoTypeDone = useCallback(() => setPhase('awaiting-send'), []);
  const onDraftDone = useCallback(() => {
    setActiveDrafts(null);
    setPhase('idle');
    advance();
  }, [advance]);

  const inputValueAuto = useAutoType(autoTargetText, phase === 'auto-typing', onAutoTypeDone, autoNonce);
  const inputValueDraft = useMeDraft(activeDrafts, phase === 'me-drafting', onDraftDone);

  function handleSend() {
    if (phase !== 'awaiting-send') return;
    const sent = autoTargetText;
    setThread((t) => [...t, { from: 'me', text: sent }]);
    setAutoTargetText('');
    setPhase('idle');
    advance();
  }

  const savedKeyRef = useRef(cfg.storageKey);
  useEffect(() => {
    // Persist progress so a return visitor can resume.
    if (savedKeyRef.current !== cfg.storageKey) { savedKeyRef.current = cfg.storageKey; return; }
    try {
      const fresh = cfg.script();
      if (thread.length === 0 && queue === fresh) return;
      localStorage.setItem(cfg.storageKey, JSON.stringify({ thread, queue }));
    } catch (e) {}
  }, [thread, queue, cfg]);

  useEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [thread, phase]);

  const displayInputValue =
    phase === 'auto-typing' ? inputValueAuto :
    phase === 'awaiting-send' ? autoTargetText :
    phase === 'me-drafting' ? inputValueDraft :
    '';

  const showCaret = phase === 'auto-typing' || phase === 'me-drafting';
  const sendEnabled = phase === 'awaiting-send';
  const isChoosing = phase === 'choosing' && step && step.choices;

  return (
    <div className="chat" data-screen-label="02 Chat thread">
      <StatusBar tone="dark" showTime={false} />

      <MobileMenu onOpenLogin={onOpenLogin} onBuy={onBuy} loggedInEmail={loggedInEmail} hasAccess={hasAccess} onLogout={onLogout} />

      <div className="chat__header">
        <div className="chat__contact">
          <div className="chat__contact-name">{cfg.contactName}</div>
        </div>
      </div>

      <div className="chat__scroll" ref={scrollRef}>
        <div className="chat__contact-card">
          <div className="chat__contact-card-avatar">
            {cfg.photo ? (
              <img src={cfg.photo} alt="" className="chat__contact-card-photo" />
            ) : (
              <svg viewBox="0 0 64 64" width="68" height="68">
                <circle cx="32" cy="32" r="32" fill="#3a3a3c"/>
                <path d="M32 34a9 9 0 100-18 9 9 0 000 18zm-17 21c0-9 8-15 17-15s17 6 17 15" fill="#8e8e93"/>
              </svg>
            )}
          </div>
          <div className="chat__contact-card-name">{cfg.contactName}</div>
        </div>

        <div className="bubbles">
          {thread.map((m, i) => {
            if (m.kind === 'timestamp') {
              const parts = m.label.split(' ');
              return (
                <div className="ts" key={i}>
                  <span className="ts__day">{parts[0]}</span>
                  <span className="ts__time"> {parts.slice(1).join(' ')}</span>
                </div>
              );
            }
            if (m.kind === 'system') {
              return <div className="sysmsg" key={i}>{m.text}</div>;
            }
            if (m.kind === 'crack') {
              return <div className="crackmsg" key={i}>{m.text}</div>;
            }
            const prev = thread[i - 1];
            const next = thread[i + 1];
            const samePrev = prev && prev.from === m.from;
            const sameNext = next && next.from === m.from;
            return (
              <Bubble
                key={i}
                from={m.from}
                photo={m.photo}
                alt={m.alt}
                text={m.text}
                tail={!sameNext}
                grouped={!!samePrev}
              />
            );
          })}
          {phase === 'their-typing' && <TypingBubble />}
        </div>
      </div>

      {isChoosing && (
        <div className="choices" role="group" aria-label="Choose a message">
          <div className="choices__hint">Choose a message</div>
          {step.choices.map((c, i) => (
            <button
              key={i}
              className="choices__option"
              onClick={() => handlePickChoice(c)}
            >
              {c.label || c.text}
            </button>
          ))}
        </div>
      )}

      <div className="composer">
        <div className="composer__field">
          <div className="composer__text">
            {displayInputValue || <span className="composer__placeholder">Message</span>}
            {showCaret && <span className="composer__caret" />}
          </div>
          <button
            className={"composer__send" + (sendEnabled ? ' is-enabled' : '')}
            onClick={handleSend}
            aria-label="Send"
            disabled={!sendEnabled}
          >
            <svg viewBox="0 0 24 24" width="20" height="20">
              <path d="M12 5l0 14M6 11l6-6 6 6" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </button>
        </div>
      </div>

      <div className="chat__home-indicator" />
    </div>
  );
}

// ---------- Paywall (end of Chapter 1) ----------
const OFFER_MS = 10 * 60 * 1000;

function useOfferCountdown(active) {
  const [left, setLeft] = useState(OFFER_MS);
  useEffect(() => {
    if (!active) return;
    let start;
    try {
      start = Number(localStorage.getItem('743:offer:start'));
      if (!start || Number.isNaN(start) || Date.now() - start >= OFFER_MS) {
        start = Date.now();
        localStorage.setItem('743:offer:start', String(start));
      }
    } catch (e) { start = Date.now(); }
    const tick = () => setLeft(Math.max(0, OFFER_MS - (Date.now() - start)));
    tick();
    const id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, [active]);
  const total = Math.ceil(left / 1000);
  const mm = Math.floor(total / 60);
  const ss = total % 60;
  return mm + ':' + String(ss).padStart(2, '0');
}

function Paywall({ onContinue, direct, onDismiss, signedIn }) {
  const [email, setEmail] = useState('');
  const [busy, setBusy] = useState(false);
  const [sentTo, setSentTo] = useState('');
  const [error, setError] = useState('');
  const [stage, setStage] = useState(direct ? 'paywall' : 'chapter-end'); // 'chapter-end' | 'paywall'
  const offerClock = useOfferCountdown(stage === 'paywall');

  // After the end-card text fully shows, wait a beat, then slide the paywall up
  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 6500);
    return () => clearTimeout(id);
  }, [stage]);

  async function go() {
    setError('');
    setBusy(true);
    const result = (await onContinue(email)) || {};
    setBusy(false);
    if (result.error) { setError(result.error); return; }
    if (result.sent) setSentTo(email);
  }

  return (
    <div className="paywall" data-screen-label="03 Paywall">
      {/* End-card narration sits behind the sheet */}
      {!direct && (
        <div className="endcard">
          <div className="endcard__inner">
            <p>743 days of silence.</p>
            <p>One conversation from a number you don't know.</p>
            <p>She knew the cosmonaut. The lake. The lunchbox notes.</p>
            <p className="endcard__beat">And she remembers you standing beside her on the night she disappeared.</p>
            <p className="endcard__break">You remember being somewhere else.</p>
          </div>
        </div>
      )}

      {direct && <div className="paysheet__scrim" onClick={onDismiss} aria-hidden="true" />}

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '') + (direct ? ' paysheet--full' : '')}>
        <div className="paysheet__grab" />

        {direct && (
          <button className="paysheet__close" type="button" onClick={onDismiss} aria-label="Close">
            <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
              <path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"/>
            </svg>
          </button>
        )}

        <div className="paysheet__timer" role="timer" aria-live="off">
          <span className="paysheet__timer-label">Limited time offer</span>
          <span className="paysheet__timer-clock">{offerClock}</span>
        </div>

        {direct ? (
          <React.Fragment>
            <div className="paysheet__eyebrow">743 Days</div>
            <div className="paysheet__title">Unlock All 8 Chapters</div>
            <p className="paysheet__teaser">
              Your daughter has been missing 743 days.<br/>
              Tonight she texts you from a number you don't know,<br/>
              and she remembers you being there.
            </p>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <div className="paysheet__eyebrow">End of Chapter 1</div>
            <div className="paysheet__chap">Chapter 2</div>
            <div className="paysheet__title">The Jacket</div>
            <p className="paysheet__teaser">
              If she is really Mia,<br/>
              one of you remembers that night wrong.
            </p>
          </React.Fragment>
        )}

        <div className="paysheet__divider" />

        <div className="paysheet__unlock">
          <ul className="paysheet__unlock-list">
            <li>{direct ? 'Chapters 1 through 8, start to finish' : 'Chapters 2 through 8, start to finish'}</li>
            <li>Pay once. Nothing else to buy later</li>
            <li>Your place is saved on every device</li>
          </ul>
        </div>

        {sentTo ? (
          <div className="paysheet__sent">
            <div className="paysheet__sent-title">Check your email.</div>
            <p className="paysheet__sent-sub">
              We sent a sign-in link to <strong>{sentTo}</strong>. Tap it on this device and you'll come
              straight back here to pay.
            </p>
            <button className="paysheet__textlink" type="button" onClick={() => { setSentTo(''); setError(''); }}>
              Use a different email
            </button>
          </div>
        ) : (
          <React.Fragment>
            {!signedIn && (
              <React.Fragment>
                <label className="paysheet__label" htmlFor="pw-email">Your email</label>
                <input
                  id="pw-email"
                  className="paysheet__email"
                  type="email"
                  placeholder="your@email.com"
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  autoComplete="email"
                />
              </React.Fragment>
            )}

            <button className="paysheet__cta" type="button" disabled={busy} onClick={go}>
              <span className="paysheet__cta-label">{busy ? 'One moment…' : 'Unlock the full story'}</span>
              <span className="paysheet__cta-price"><s>$12.99</s> $7.99</span>
            </button>
            {error && <div className="paysheet__error">{error}</div>}
          </React.Fragment>
        )}

        <div className="paysheet__assure">
          <svg viewBox="0 0 16 16" width="11" height="11" aria-hidden="true">
            <path d="M5 7V5a3 3 0 016 0v2m-7 0h8v7H4V7z" fill="none" stroke="currentColor" strokeWidth="1.2"/>
          </svg>
          <span>Secure checkout · Powered by Stripe</span>
        </div>

        <div className="paysheet__fine">
          One payment for the complete story. No subscription, no per-chapter charges.
        </div>
      </div>
    </div>
  );
}

// ---------- Chapter 2 end ----------
function Chapter2End({ onContinue }) {
  const [stage, setStage] = useState('chapter-end');

  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 7000);
    return () => clearTimeout(id);
  }, [stage]);

  return (
    <div className="paywall" data-screen-label="04 Chapter 2 End">
      <div className="endcard">
        <div className="endcard__inner">
          <p>Mia remembers you giving her the blue jacket.</p>
          <p>Katie remembers Mia without it.</p>
          <p>You remember the jacket too.</p>
          <p className="endcard__break">You just don't remember why.</p>
        </div>
      </div>

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '')}>
        <div className="paysheet__grab" />

        <div className="paysheet__eyebrow">End of Chapter 2</div>
        <div className="paysheet__chap">Chapter 3</div>
        <div className="paysheet__title">What Do You Remember?</div>
        <p className="paysheet__teaser">
          The unknown number hasn't texted again.<br/>
          Yet.
        </p>

        <div className="paysheet__divider" />

        <button className="paysheet__cta paysheet__cta--continue" type="button" onClick={onContinue}>
          <span className="paysheet__cta-label">Continue reading</span>
        </button>
      </div>
    </div>
  );
}

// ---------- Chapter 3 end ----------
function Chapter3End({ onContinue }) {
  const [stage, setStage] = useState('chapter-end');

  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 7000);
    return () => clearTimeout(id);
  }, [stage]);

  return (
    <div className="paywall" data-screen-label="06 Chapter 3 End">
      <div className="endcard">
        <div className="endcard__inner">
          <p>Mia says she can leave.</p>
          <p>She says she wants to come home.</p>
          <p>But something about the night she disappeared is keeping her where she is.</p>
          <p className="endcard__beat">And in her version of that night...</p>
          <p className="endcard__break">you were already there.</p>
        </div>
      </div>

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '')}>
        <div className="paysheet__grab" />

        <div className="paysheet__eyebrow">End of Chapter 3</div>
        <div className="paysheet__chap">Chapter 4</div>
        <div className="paysheet__title">Level 3</div>
        <p className="paysheet__teaser">
          There is one place you can actually go.<br/>
          The mall parking garage.
        </p>

        <div className="paysheet__divider" />

        <button className="paysheet__cta paysheet__cta--continue" type="button" onClick={onContinue}>
          <span className="paysheet__cta-label">Continue reading</span>
        </button>
      </div>
    </div>
  );
}

// ---------- Chapter 4 end ----------
function Chapter4End({ onContinue }) {
  const [stage, setStage] = useState('chapter-end');

  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 7000);
    return () => clearTimeout(id);
  }, [stage]);

  return (
    <div className="paywall" data-screen-label="08 Chapter 4 End">
      <div className="endcard">
        <div className="endcard__inner">
          <p>Eleven minutes from photo to call.</p>
          <p>Six or seven of them spent searching.</p>
          <p>Gray hoodie found on Level 3.</p>
          <p>Blue jacket never recovered.</p>
          <p className="endcard__beat">Mia wanted you at the mall that day.</p>
          <p className="endcard__break">She remembers you showing up anyway.</p>
        </div>
      </div>

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '')}>
        <div className="paysheet__grab" />

        <div className="paysheet__eyebrow">End of Chapter 4</div>
        <div className="paysheet__chap">Chapter 5</div>
        <div className="paysheet__title">Before Blue</div>
        <p className="paysheet__teaser">
          The unknown number lights up again.<br/>
          This time, you have one question ready.
        </p>

        <div className="paysheet__divider" />

        <button className="paysheet__cta paysheet__cta--continue" type="button" onClick={onContinue}>
          <span className="paysheet__cta-label">Continue reading</span>
        </button>
      </div>
    </div>
  );
}

// ---------- Chapter 5 end ----------
function Chapter5End({ onContinue }) {
  const [stage, setStage] = useState('chapter-end');

  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 7000);
    return () => clearTimeout(id);
  }, [stage]);

  return (
    <div className="paywall" data-screen-label="10 Chapter 5 End">
      <div className="endcard">
        <div className="endcard__inner">
          <p>The room is not locked. No one has told Mia she cannot leave.</p>
          <p>The phone can reach only you.</p>
          <p>She remembers gray before blue. She remembers you throwing the gray hoodie down.</p>
          <p className="endcard__beat">She remembers running from you. She remembers your hand on the blue jacket.</p>
          <p>Then the messages stop.</p>
          <p className="endcard__break">And your hand remembers something your mind does not.</p>
        </div>
      </div>

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '')}>
        <div className="paysheet__grab" />

        <div className="paysheet__eyebrow">End of Chapter 5</div>
        <div className="paysheet__chap">Chapter 6</div>
        <div className="paysheet__title">The Stairs</div>
        <p className="paysheet__teaser">
          For the first time,<br/>
          Mia's story continues past the jacket.
        </p>

        <div className="paysheet__divider" />

        <button className="paysheet__cta paysheet__cta--continue" type="button" onClick={onContinue}>
          <span className="paysheet__cta-label">Continue reading</span>
        </button>
      </div>
    </div>
  );
}

// ---------- Chapter 6 end ----------
function Chapter6End({ onContinue }) {
  const [stage, setStage] = useState('chapter-end');

  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 7000);
    return () => clearTimeout(id);
  }, [stage]);

  return (
    <div className="paywall" data-screen-label="12 Chapter 6 End">
      <div className="endcard">
        <div className="endcard__inner">
          <p>Mia says she ran from you.</p>
          <p>Katie remembers you once asking why she ran from you.</p>
          <p>You arrived at the mall wet, with a hoodie that smelled like dirty water.</p>
          <p>You were already angry before Katie and Mia left home. The gray hoodie belonged to Katie first.</p>
          <p className="endcard__beat">For 743 days, you remembered being home.</p>
          <p className="endcard__break">Now you are standing in a place your body seems to remember first.</p>
        </div>
      </div>

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '')}>
        <div className="paysheet__grab" />

        <div className="paysheet__eyebrow">End of Chapter 6</div>
        <div className="paysheet__chap">Chapter 7</div>
        <div className="paysheet__title">The Morning</div>
        <p className="paysheet__teaser">
          Before Level 3, before the blue jacket,<br/>
          there was a reason you could not look at Katie that morning.
        </p>

        <div className="paysheet__divider" />

        <button className="paysheet__cta paysheet__cta--continue" type="button" onClick={onContinue}>
          <span className="paysheet__cta-label">Continue reading</span>
        </button>
      </div>
    </div>
  );
}

// ---------- Chapter 7 end ----------
function Chapter7End({ onContinue }) {
  const [stage, setStage] = useState('chapter-end');

  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 7000);
    return () => clearTimeout(id);
  }, [stage]);

  return (
    <div className="paywall" data-screen-label="14 Chapter 7 End">
      <div className="endcard">
        <div className="endcard__inner">
          <p>Katie was having an affair before Mia disappeared. You appear to have discovered it that morning.</p>
          <p>You secretly followed Katie and Mia to the mall, and waited until Katie went downstairs because you wanted to see Mia alone.</p>
          <p>The gray hoodie belonged to Katie. Seeing Mia wear it triggered your anger.</p>
          <p>You made Mia take it off, gave her the blue jacket, and frightened her. Mia ran into the east stairwell.</p>
          <p className="endcard__break">What happened after you grabbed the blue jacket is still missing.</p>
        </div>
      </div>

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '')}>
        <div className="paysheet__grab" />

        <div className="paysheet__eyebrow">End of Chapter 7</div>
        <div className="paysheet__chap">Chapter 8</div>
        <div className="paysheet__title">Five Minutes</div>
        <p className="paysheet__teaser">
          Mia comes back. This time, you know exactly<br/>
          which part of the story you need her to finish.
        </p>

        <div className="paysheet__divider" />

        <button className="paysheet__cta paysheet__cta--continue" type="button" onClick={onContinue}>
          <span className="paysheet__cta-label">Continue reading</span>
        </button>
      </div>
    </div>
  );
}

// ---------- Chapter 8 end ----------
function Chapter8End() {
  const [stage, setStage] = useState('chapter-end');
  const [name, setName] = useState('');
  const [rating, setRating] = useState(0);
  const [hover, setHover] = useState(0);
  const [message, setMessage] = useState('');
  const [sent, setSent] = useState(false);
  const [open, setOpen] = useState(false);
  const [sending, setSending] = useState(false);
  const [error, setError] = useState('');

  async function submit(e) {
    e.preventDefault();
    setError('');
    setSending(true);
    const result = await window.Backend.sendFeedback({ name, rating, message, chapter: '8' });
    setSending(false);
    if (!result || !result.ok) { setError('That did not send. Try again in a moment.'); return; }
    setSent(true);
  }

  useEffect(() => {
    if (stage !== 'chapter-end') return;
    const id = setTimeout(() => setStage('paywall'), 7000);
    return () => clearTimeout(id);
  }, [stage]);

  return (
    <div className="paywall" data-screen-label="16 Chapter 8 End">
      <div className="endcard endcard--finale">
        <div className="endcard__inner">
          <p>You spent 743 days waiting for Mia to come home.</p>
          <p className="endcard__break">Tonight, she brought you back instead.</p>
        </div>
      </div>

      <div className={"paysheet" + (stage === 'paywall' ? ' is-up' : '')}>
        <div className="paysheet__grab" />

        {sent ? (
          <div className="feedback__done">
            <div className="feedback__note">Thank you. Your note came through.</div>
          </div>
        ) : !open ? (
          <div className="feedback__intro">
            <div className="feedback__note">Thank you for reading 743 Days.</div>
            <button className="feedback__open" type="button" onClick={() => setOpen(true)}>
              Share feedback
            </button>
          </div>
        ) : (
          <React.Fragment>
            <div className="paysheet__eyebrow">The End</div>
            <div className="paysheet__title">Share your experience</div>

            <div className="paysheet__divider" />

            <form className="feedback" onSubmit={submit}>
              <label className="paysheet__label" htmlFor="fb-name">Your name</label>
              <input
                id="fb-name"
                className="paysheet__input"
                type="text"
                placeholder="Name or initials"
                value={name}
                onChange={(e) => setName(e.target.value)}
              />

              <div className="paysheet__label">Your rating</div>
              <div className="feedback__stars" role="radiogroup" aria-label="Your rating">
                {[1, 2, 3, 4, 5].map((n) => (
                  <button
                    key={n}
                    type="button"
                    className={"feedback__star" + (n <= (hover || rating) ? ' is-on' : '')}
                    onClick={() => setRating(n)}
                    onMouseEnter={() => setHover(n)}
                    onMouseLeave={() => setHover(0)}
                    role="radio"
                    aria-checked={rating === n}
                    aria-label={n + (n === 1 ? ' star' : ' stars')}
                  >
                    <svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
                      <path d="M12 3.6l2.6 5.5 6 .8-4.4 4.2 1.1 6-5.3-2.9-5.3 2.9 1.1-6L3.4 9.9l6-.8L12 3.6z" fill={n <= (hover || rating) ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="1.3" strokeLinejoin="round"/>
                    </svg>
                  </button>
                ))}
              </div>

              <label className="paysheet__label" htmlFor="fb-message">Your message</label>
              <textarea
                id="fb-message"
                className="paysheet__input feedback__message"
                rows="3"
                placeholder="What stayed with you?"
                value={message}
                onChange={(e) => setMessage(e.target.value)}
              ></textarea>

              <button className="paysheet__cta paysheet__cta--continue" type="submit" disabled={sending}>
                <span className="paysheet__cta-label">{sending ? 'Sending…' : 'Send feedback'}</span>
              </button>
              {error && <div className="feedback__error">{error}</div>}
            </form>
          </React.Fragment>
        )}
      </div>
    </div>
  );
}

// ---------- Dev shortcuts ----------
function DevShortcuts({ view, chapter, goLock, goChat, goEnd, resetAll }) {
  const [open, setOpen] = useState(null); // null | 'chapter' | 'end'

  function toggle(name) {
    setOpen((cur) => (cur === name ? null : name));
  }
  function close() { setOpen(null); }

  const chapters = [1, 2, 3, 4, 5, 6, 7, 8];
  const endLabels = { 1: 'End 1 (Paywall)', 2: 'End 2', 3: 'End 3', 4: 'End 4', 5: 'End 5', 6: 'End 6', 7: 'End 7', 8: 'End 8 (Finale)' };
  const endViewMap = { 1: 'paywall', 2: 'end2', 3: 'end3', 4: 'end4', 5: 'end5', 6: 'end6', 7: 'end7', 8: 'end8' };

  const inChapter = view === 'chat';
  const inEnd = view === 'paywall' || view === 'end2' || view === 'end3' || view === 'end4' || view === 'end5' || view === 'end6' || view === 'end7' || view === 'end8';

  return (
    <React.Fragment>
      {open && <div className="dev-shortcuts__scrim" onClick={close} />}
      <div className="dev-shortcuts" aria-label="Dev shortcuts">
        <button onClick={goLock} className={view === 'lock' ? 'is-active' : ''}>Lock</button>

        <div className="dev-shortcuts__group">
          <button
            onClick={() => toggle('chapter')}
            className={inChapter ? 'is-active' : (open === 'chapter' ? 'is-open' : '')}
            aria-expanded={open === 'chapter'}
          >
            Chapter
            <svg viewBox="0 0 10 6" width="8" height="6" aria-hidden="true" className="dev-shortcuts__caret">
              <path d="M1 5l4-4 4 4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </button>
          {open === 'chapter' && (
            <div className="dev-shortcuts__menu" role="menu">
              {chapters.map((n) => (
                <button
                  key={n}
                  className={inChapter && chapter === n ? 'is-active' : ''}
                  onClick={() => { close(); goChat(n); }}
                >
                  Ch {n}
                </button>
              ))}
            </div>
          )}
        </div>

        <div className="dev-shortcuts__group">
          <button
            onClick={() => toggle('end')}
            className={inEnd ? 'is-active' : (open === 'end' ? 'is-open' : '')}
            aria-expanded={open === 'end'}
          >
            End Card
            <svg viewBox="0 0 10 6" width="8" height="6" aria-hidden="true" className="dev-shortcuts__caret">
              <path d="M1 5l4-4 4 4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </button>
          {open === 'end' && (
            <div className="dev-shortcuts__menu" role="menu">
              {chapters.map((n) => (
                <button
                  key={n}
                  className={view === endViewMap[n] ? 'is-active' : ''}
                  onClick={() => { close(); goEnd(n); }}
                >
                  {endLabels[n]}
                </button>
              ))}
            </div>
          )}
        </div>

        <button onClick={resetAll} title="Clear saved progress and email">Reset</button>
      </div>
    </React.Fragment>
  );
}

// ---------- Brand (top-left of desktop page) ----------
function Brand({ view, chapter }) {
  const isCh2 = (view === 'chat' && chapter === 2) || view === 'end2';
  const isCh3 = (view === 'chat' && chapter === 3) || view === 'end3';
  const isCh4 = (view === 'chat' && chapter === 4) || view === 'end4';
  const isCh5 = (view === 'chat' && chapter === 5) || view === 'end5';
  const isCh6 = (view === 'chat' && chapter === 6) || view === 'end6';
  const isCh7 = (view === 'chat' && chapter === 7) || view === 'end7';
  const isCh8 = (view === 'chat' && chapter === 8) || view === 'end8';
  return (
    <div className="brand" aria-hidden={false}>
      <span className="brand__num">743</span>
      <span className="brand__word">Days</span>
      <div className="brand__rule"></div>
      {isCh8 ? (
        <div className="brand__sub">
          <span className="brand__chap">Ch 8</span>
          <span className="brand__sub-text">The five minutes you have never been able to finish.</span>
        </div>
      ) : isCh7 ? (
        <div className="brand__sub">
          <span className="brand__chap">Ch 7</span>
          <span className="brand__sub-text">Why you couldn't look at her that morning.</span>
        </div>
      ) : isCh6 ? (
        <div className="brand__sub">
          <span className="brand__chap">Ch 6</span>
          <span className="brand__sub-text">Your body remembers the stairwell before you do.</span>
        </div>
      ) : isCh5 ? (
        <div className="brand__sub">
          <span className="brand__chap">Ch 5</span>
          <span className="brand__sub-text">The phone isn't hers. Your chat was already on it.</span>
        </div>
      ) : isCh4 ? (
        <div className="brand__sub">
          <span className="brand__chap">Ch 4</span>
          <span className="brand__sub-text">Back at the garage. A few minutes nobody can account for.</span>
        </div>
      ) : isCh3 ? (
        <div className="brand__sub">
          <span className="brand__chap">Ch 3</span>
          <span className="brand__sub-text">She can leave. She won't say why she doesn't.</span>
        </div>
      ) : isCh2 ? (
        <div className="brand__sub">
          <span className="brand__chap">Ch 2</span>
          <span className="brand__sub-text">Someone else was there that night. She remembers it differently.</span>
        </div>
      ) : (
        <div className="brand__sub">Your phone just lit up. Unknown number. It's been 743 days.</div>
      )}
    </div>
  );
}

// ---------- App shell + dev shortcut ----------
function App() {
  const initial = readHashView();
  const [view, setView] = useState(initial.view);
  const [chapter, setChapter] = useState(initial.chapter);
  const [transitioning, setTransitioning] = useState(false);
  const [loginOpen, setLoginOpen] = useState(false);
  const [buyDirect, setBuyDirect] = useState(false);
  const [buyReturn, setBuyReturn] = useState(null);
  const [loggedInEmail, setLoggedInEmail] = useState(() => {
    try { return localStorage.getItem('743:email') || ''; } catch (e) { return ''; }
  });
  const [pillMenuOpen, setPillMenuOpen] = useState(false);
  const [hasAccess, setHasAccess] = useState(false);
  const [loadingChapter, setLoadingChapter] = useState(0);
  const [notice, setNotice] = useState('');

  // Mirror whatever the backend says about this reader: signed in, paid up.
  useEffect(() => {
    let alive = true;
    async function sync() {
      const s = await window.Backend.session();
      if (!alive) return;
      setLoggedInEmail(s.email || '');
      setHasAccess(!!s.hasFullAccess);
      if (s.email) { try { localStorage.setItem('743:email', s.email); } catch (e) {} }

      // Signed in from a "finish my purchase" link: put them back on the sheet.
      let intent = '';
      try { intent = localStorage.getItem('743:intent') || ''; } catch (e) {}
      if (intent === 'buy' && s.email && !s.hasFullAccess) {
        try { localStorage.removeItem('743:intent'); } catch (e) {}
        setBuyDirect(false);
        setChapter(1);
        setView('paywall');
      } else if (intent === 'buy' && s.hasFullAccess) {
        try { localStorage.removeItem('743:intent'); } catch (e) {}
      }
    }
    sync();
    const off = window.Backend.onAuthChange(sync);
    return () => { alive = false; if (off) off(); };
  }, []);

  // Coming back from Stripe. The webhook may land a moment after the reader
  // does, so poll briefly before giving up.
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    if (params.get('purchase') !== 'success') return;
    window.history.replaceState({}, '', window.location.pathname + window.location.hash);
    let alive = true;
    (async () => {
      for (let i = 0; i < 8; i++) {
        const s = await window.Backend.session();
        if (!alive) return;
        if (s.hasFullAccess) {
          setHasAccess(true);
          setLoggedInEmail(s.email || '');
          goChat(2);
          return;
        }
        await new Promise((r) => setTimeout(r, 1200));
      }
      if (alive) setNotice('Payment received. Access is still syncing — refresh in a moment.');
    })();
    return () => { alive = false; };
  }, []);

  // Keep view in sync with URL hash for quick testing.
  useEffect(() => {
    const onHash = () => {
      const next = readHashView();
      setView(next.view);
      setChapter(next.chapter);
    };
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);

  function goLock() { window.location.hash = ''; setView('lock'); setChapter(1); }
  async function goChat(ch) {
    const n = ch || 1;
    if (n !== 1) {
      setLoadingChapter(n);
      const result = await window.Backend.loadChapter(n);
      setLoadingChapter(0);
      if (!result.ok) {
        if (result.error === 'login') { setLoginOpen(true); return; }
        if (result.error === 'locked') { goPaywall(); return; }
        setNotice('That chapter would not load. Check your connection and try again.');
        return;
      }
    }
    setChapter(n);
    setTransitioning(true);
    setTimeout(() => { setView('chat'); setTransitioning(false); }, 380);
  }

  // The paywall CTA. Signed-out readers use the one email field on the sheet to
  // get a sign-in link; signed-in readers go straight to Stripe.
  async function unlock(email) {
    if (hasAccess || !window.Backend.live) { goChat(2); return {}; }

    if (!loggedInEmail) {
      if (!/\S+@\S+\.\S+/.test(email || '')) {
        return { error: 'Enter your email so the purchase can be saved to your account.' };
      }
      try { localStorage.setItem('743:intent', 'buy'); } catch (e) {}
      const signIn = await window.Backend.signIn(email);
      if (signIn.error) return { error: signIn.error };
      return { sent: true };
    }

    const result = await window.Backend.startCheckout();
    if (result.needsLogin) { setLoginOpen(true); return {}; }
    if (result.alreadyOwned) { setHasAccess(true); goChat(2); return {}; }
    if (result.error) return { error: result.error };
    return {};
  }
  function goPaywall() {
    try { localStorage.setItem('743:reached:paywall', '1'); } catch (e) {}
    setBuyDirect(false);
    setView('paywall'); setChapter(1);
  }
  function openBuy() {
    setBuyReturn({ view, chapter });
    setBuyDirect(true);
    setView('paywall'); setChapter(1);
  }
  function dismissBuy() {
    const back = buyReturn || { view: 'lock', chapter: 1 };
    setBuyDirect(false);
    setChapter(back.chapter || 1);
    setView(back.view === 'paywall' ? 'lock' : back.view);
  }
  function goEnd2() { setView('end2'); setChapter(2); }
  function goEnd3() { setView('end3'); setChapter(3); }
  function goEnd4() { setView('end4'); setChapter(4); }
  function goEnd5() { setView('end5'); setChapter(5); }
  function goEnd6() { setView('end6'); setChapter(6); }
  function goEnd7() { setView('end7'); setChapter(7); }
  function goEnd8() { setView('end8'); setChapter(8); }

  function handleChatEnd() {
    if (chapter === 2) goEnd2();
    else if (chapter === 3) goEnd3();
    else if (chapter === 4) goEnd4();
    else if (chapter === 5) goEnd5();
    else if (chapter === 6) goEnd6();
    else if (chapter === 7) goEnd7();
    else if (chapter === 8) goEnd8();
    else goPaywall();
  }

  function openLogin() { setLoginOpen(true); }
  function closeLogin() { setLoginOpen(false); }
  function handleLoginComplete(email) {
    try { localStorage.setItem('743:email', email); } catch (e) {}
    setLoggedInEmail(email);
    setLoginOpen(false);
    setView('chat');
  }

  function logout() {
    window.Backend.signOut();
    setLoggedInEmail('');
    setHasAccess(false);
    setPillMenuOpen(false);
  }

  function resetAll() {
    clearAllSaved();
    setLoggedInEmail('');
    setPillMenuOpen(false);
    window.location.hash = '';
    window.location.reload();
  }

  return (
    <React.Fragment>
      <Brand view={view} chapter={chapter} />
      <div className="device">
        <div className={"device__inner" + (transitioning ? ' is-transitioning' : '')}>
          {view === 'lock' && <LockScreen onOpenNotification={() => goChat(1)} onBuy={openBuy} onOpenLogin={openLogin} loggedInEmail={loggedInEmail} hasAccess={hasAccess} onLogout={logout} />}
          {view === 'chat' && <Chat key={chapter} chapter={chapter} onEndChapter={handleChatEnd} onBuy={openBuy} onOpenLogin={openLogin} loggedInEmail={loggedInEmail} hasAccess={hasAccess} onLogout={logout} />}
          {view === 'paywall' && <Paywall direct={buyDirect} onDismiss={dismissBuy} onContinue={unlock} signedIn={!!loggedInEmail} />}
          {view === 'end2' && <Chapter2End onContinue={() => goChat(3)} />}
          {view === 'end3' && <Chapter3End onContinue={() => goChat(4)} />}
          {view === 'end4' && <Chapter4End onContinue={() => goChat(5)} />}
          {view === 'end5' && <Chapter5End onContinue={() => goChat(6)} />}
          {view === 'end6' && <Chapter6End onContinue={() => goChat(7)} />}
          {view === 'end7' && <Chapter7End onContinue={() => goChat(8)} />}
          {view === 'end8' && <Chapter8End />}
        </div>
        {loadingChapter > 0 && (
          <div className="device__loading" role="status">
            <div className="device__spinner" aria-hidden="true" />
            <div className="device__loading-text">Loading Chapter {loadingChapter}…</div>
          </div>
        )}
      </div>

      {notice && (
        <div className="app-notice" role="status">
          <span>{notice}</span>
          <button type="button" onClick={() => setNotice('')} aria-label="Dismiss">
            <svg viewBox="0 0 24 24" width="13" height="13"><path d="M6 6l12 12M18 6L6 18" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round"/></svg>
          </button>
        </div>
      )}

      {/* Desktop "Log in" pill — hidden on mobile via CSS. When logged in,
          clicking opens a small dropdown with the email and a Log out action. */}
      <div className="login-pill-wrap">
        {!hasAccess && (
          <button className="buy-pill" type="button" onClick={openBuy} aria-label="Buy the full story" title="Buy the full story">
            <svg viewBox="0 0 24 24" width="17" height="17" aria-hidden="true">
              <path d="M3 4h2.2l2 11.2a1.6 1.6 0 001.6 1.3h8.4a1.6 1.6 0 001.6-1.2L20.5 8H6.2" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
              <circle cx="10" cy="20" r="1.3" fill="currentColor"/>
              <circle cx="17" cy="20" r="1.3" fill="currentColor"/>
            </svg>
          </button>
        )}
        <button
          className={"login-pill" + (loggedInEmail ? ' is-loggedin' : '') + (pillMenuOpen ? ' is-open' : '')}
          onClick={() => loggedInEmail ? setPillMenuOpen((o) => !o) : openLogin()}
          aria-label={loggedInEmail ? "Account menu" : "Log in"}
          aria-expanded={loggedInEmail ? pillMenuOpen : undefined}
        >
          {loggedInEmail ? (
            <React.Fragment>
              <span className="login-pill__dot" aria-hidden="true" />
              <span className="login-pill__label">{loggedInEmail}</span>
              <svg className="login-pill__caret" viewBox="0 0 10 6" width="9" height="6" aria-hidden="true">
                <path d="M1 1l4 4 4-4" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            </React.Fragment>
          ) : (
            <span className="login-pill__label">Log in</span>
          )}
        </button>

        {pillMenuOpen && loggedInEmail && (
          <React.Fragment>
            <div className="login-pill-scrim" onClick={() => setPillMenuOpen(false)} />
            <div className="login-pill-menu" role="menu">
              <div className="login-pill-menu__email">
                <div className="login-pill-menu__label">Signed in as</div>
                <div className="login-pill-menu__addr">{loggedInEmail}</div>
              </div>
              <div className="login-pill-menu__divider" />
              <button
                className="login-pill-menu__item"
                onClick={() => { setPillMenuOpen(false); logout(); }}
              >
                <svg viewBox="0 0 24 24" width="15" height="15" aria-hidden="true">
                  <path d="M14 17l5-5-5-5M19 12H7M10 4H5a1 1 0 00-1 1v14a1 1 0 001 1h5" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>
                <span>Log out</span>
              </button>
            </div>
          </React.Fragment>
        )}
      </div>

      {/* Dev shortcuts — visible only on desktop, fixed to the page. */}
      <DevShortcuts
        view={view}
        chapter={chapter}
        goLock={goLock}
        goChat={goChat}
        goEnd={(n) => { if (n === 2) goEnd2(); else if (n === 3) goEnd3(); else if (n === 4) goEnd4(); else if (n === 5) goEnd5(); else if (n === 6) goEnd6(); else if (n === 7) goEnd7(); else if (n === 8) goEnd8(); else goPaywall(); }}
        resetAll={resetAll}
      />

      {loginOpen && (
        <LoginFlow
          initialEmail={loggedInEmail}
          onComplete={handleLoginComplete}
          onClose={closeLogin}
        />
      )}
    </React.Fragment>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
