// Luke phone — the Reader. Parchment/Exposit register, function from the reader Bill's dad uses.
// Tap a word → glass hovers → tap again returns to place. Gear = display; book = reading position.
// All overlays bloom up from the bottom as Liquid-Glass sheets.
function LukeReader({ onBack, hideStatus }) {
  const { Ic, pi } = window.LukePhoneKit;
  const D = window.LukeReaderData;
  const [word, setWord] = React.useState(null);   // active tapped word { verse, position, token } or null
  const [wordData, setWordData] = React.useState(null); // fetched word-study card, "loading", or null
  const [chapters, setChapters] = React.useState({});   // live chapter cache: "book|ch" -> { verses:[{n,t}], tagged:{} }
  const [sheet, setSheet] = React.useState(null);
  const [listen, setListen] = React.useState(null);
  const [listenMin, setListenMin] = React.useState(false);
  const [notes, setNotes] = React.useState(() => LS.get("luke_reader_notes", []));
  const [highlights, setHighlights] = React.useState(() => LS.get("luke_reader_highlights", {}));
  const [hlMenu, setHlMenu] = React.useState(null);
  const [noteDraft, setNoteDraft] = React.useState(null);
  const rootRef = React.useRef(null);
  const lp = React.useRef({ timer: null, x: 0, y: 0, fired: false });
  const wordSeq = React.useRef(0); // monotonic tap counter — only the latest tap's word response is applied
  const [browseVer, setBrowseVer] = React.useState(false);
  const [intro, setIntro] = React.useState(() => !LS.get("luke_reader_seen_intro", false));
  React.useEffect(() => { LS.set("luke_reader_notes", notes); }, [notes]);
  React.useEffect(() => { LS.set("luke_reader_highlights", highlights); }, [highlights]);
  const onListen = (book, chapter, label) => { setSheet(null); setListenMin(false); setListen({ book, chapter, label, url: bgAudioUrl(book, chapter) }); };
  const resetAll = () => { ["luke_reader_plan", "luke_reading_position", "luke_reader_display"].forEach((k) => { try { localStorage.removeItem(k); } catch (e) {} }); location.reload(); };
  const disp0 = LS.get("luke_reader_display", {});
  const [size, setSize] = React.useState(disp0.size != null ? disp0.size : 21);
  const [justify, setJustify] = React.useState(disp0.justify != null ? disp0.justify : false);
  const [nums, setNums] = React.useState(disp0.nums != null ? disp0.nums : true);
  const [vbv, setVbv] = React.useState(disp0.vbv != null ? disp0.vbv : false);
  const [font, setFont] = React.useState(disp0.font || "newsreader");
  const [red, setRed] = React.useState(disp0.red != null ? disp0.red : false);
  const [dark, setDark] = React.useState(() => LS.get("luke_reader_theme", "light") === "dark");
  React.useEffect(() => { LS.set("luke_reader_theme", dark ? "dark" : "light"); }, [dark]);
  const [pos, setPos] = React.useState(() => LS.get("luke_reading_position", { book: "Genesis", chapter: 1, supp: "psalms", psalm: 1, prov: 1 }));
  // Bill's plan: Genesis→Revelation, four chapters a day, plus one Psalm or Proverb (supplement cycles Proverbs→Psalms→Proverbs).
  const [plan, setPlan] = React.useState(() => ({ type: "book-by-book", bbBook: "Esther", bbChapter: 5, choice: "psalms", psalmCh: 63, provCh: 21, yearDay: 1, ninetyDay: 1, mcheyneDay: 1, history: [], ...LS.get("luke_reader_plan", {}) }));
  React.useEffect(() => { LS.set("luke_reader_display", { size, justify, nums, vbv, font, red }); }, [size, justify, nums, vbv, font, red]);
  React.useEffect(() => { LS.set("luke_reading_position", pos); }, [pos]);
  React.useEffect(() => { LS.set("luke_reader_plan", plan); }, [plan]);
  const ALL = D.BOOKS_OT.concat(D.BOOKS_NT);
  const hlList = Object.keys(highlights).map((ref) => ({ ref, color: highlights[ref] })).sort((a, b) => { const pa = (r) => { const m = r.match(/^(.+)\s+(\d+):(\d+)$/); return m ? [ALL.findIndex((x) => x[0] === m[1]), +m[2], +m[3]] : [999, 0, 0]; }; const A = pa(a.ref), B = pa(b.ref); return (A[0] - B[0]) || (A[1] - B[1]) || (A[2] - B[2]); });
  const neighbor = (delta) => {
    const idx = ALL.findIndex((b) => b[0] === pos.book); if (idx < 0) return null;
    let bi = idx, ch = pos.chapter + delta;
    if (ch < 1) { if (bi === 0) return null; bi--; ch = ALL[bi][1]; }
    else if (ch > ALL[bi][1]) { if (bi === ALL.length - 1) return null; bi++; ch = 1; }
    return { book: ALL[bi][0], chapter: ch };
  };
  const prevRef = neighbor(-1), nextRef = neighbor(1);
  // Live chapter loading — the reader used to read a baked window.LukeKJV; now it fetches the current
  // chapter plus its two neighbours from /api/read/chapter (a SWITCH, no model) so the finger-tracked
  // three-pane slide always has the adjacent chapters ready. Cached by "book|ch"; each is fetched once.
  const chapKey = (r) => r.book + "|" + r.chapter;
  React.useEffect(() => {
    let live = true;
    const want = [pos, prevRef, nextRef].filter(Boolean);
    want.forEach((r) => {
      const k = chapKey(r);
      if (chapters[k]) return;
      window.LukeRead.chapter(r.book, r.chapter)
        .then((data) => { if (live) setChapters((c) => (c[k] ? c : { ...c, [k]: data })); })
        .catch(() => { if (live) setChapters((c) => (c[k] ? c : { ...c, [k]: { verses: [], tagged: {}, error: true } })); });
    });
    return () => { live = false; };
    // eslint-disable-next-line
  }, [pos.book, pos.chapter]);
  const vpRef = React.useRef(null);
  const drag = React.useRef({ active: false, decided: false, x0: 0, y0: 0, w: 402, samples: [], busy: false });
  const reduced = React.useRef(typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches);
  const [dx, setDx] = React.useState(0);
  const [anim, setAnim] = React.useState(false);
  // Resume-proofing for the installed PWA: iOS suspends JS timers while the app is backgrounded, so a
  // page-turn (animateTo) or long-press timer that was mid-flight when the app went to the background
  // may never fire — leaving drag.busy stuck true (paging dead) or a highlight menu half-armed. On
  // every resume we hard-reset those transient bits so the reader is fully live again with no restart.
  React.useEffect(() => {
    const revive = () => {
      drag.current.active = false; drag.current.decided = false; drag.current.busy = false;
      if (lp.current.timer) { clearTimeout(lp.current.timer); lp.current.timer = null; }
      setAnim(false); setDx(0);
    };
    const onVis = () => { if (document.visibilityState === "visible") revive(); };
    // visibilitychange is dispatched on document; pageshow also fires on a bfcache restore (the page
    // comes back from cache, not a fresh load).
    document.addEventListener("visibilitychange", onVis);
    window.addEventListener("pageshow", revive);
    return () => { document.removeEventListener("visibilitychange", onVis); window.removeEventListener("pageshow", revive); };
    // eslint-disable-next-line
  }, []);
  // The buttery slide: the page tracks the thumb 1:1, then settles or completes on release.
  const commit = (delta) => { const t = delta > 0 ? nextRef : prevRef; if (t) setPos((p) => ({ ...p, book: t.book, chapter: t.chapter })); };
  const animateTo = (target, delta) => {
    if (reduced.current) { if (delta) commit(delta); setDx(0); setAnim(false); return; }
    drag.current.busy = true; setAnim(true); setDx(target);
    window.setTimeout(() => { if (delta) commit(delta); setAnim(false); setDx(0); drag.current.busy = false; }, 360);
  };
  const page = (delta) => { if (drag.current.busy) return; const t = delta > 0 ? nextRef : prevRef; if (!t) return; const w = vpRef.current ? vpRef.current.clientWidth : 402; animateTo(delta > 0 ? -w : w, delta); };
  const onDown = (e) => { if (drag.current.busy) return; const w = vpRef.current ? vpRef.current.clientWidth : 402; drag.current = { active: true, decided: false, x0: e.clientX, y0: e.clientY, w, samples: [{ x: e.clientX, t: performance.now() }], busy: false }; };
  const onMove = (e) => {
    const d = drag.current; if (!d.active) return;
    const rx = e.clientX - d.x0, ry = e.clientY - d.y0;
    if (!d.decided) {
      if (Math.abs(rx) < 8 && Math.abs(ry) < 8) return;
      if (Math.abs(rx) <= Math.abs(ry)) { d.active = false; return; } // vertical wins → let the page scroll
      d.decided = true; try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {} setAnim(false);
    }
    let v = rx; if ((v > 0 && !prevRef) || (v < 0 && !nextRef)) v *= 0.32; // rubber-band at the ends
    d.samples.push({ x: e.clientX, t: performance.now() }); if (d.samples.length > 6) d.samples.shift();
    setDx(v);
  };
  const onUp = (e) => {
    const d = drag.current; if (!d.active) return; d.active = false;
    if (!d.decided) return;
    const w = d.w, rx = e.clientX - d.x0, s = d.samples;
    let vel = 0; if (s.length >= 2) { const a = s[0], b = s[s.length - 1], dt = b.t - a.t; if (dt > 0) vel = (b.x - a.x) / dt; }
    if ((rx < -w * 0.28 || vel < -0.5) && nextRef) animateTo(-w, 1);
    else if ((rx > w * 0.28 || vel > 0.5) && prevRef) animateTo(w, -1);
    else animateTo(0, 0);
  };
  // Tap a word -> live word study (/api/read/word), served verbatim, no model. `position` comes from
  // the tag's own index so the original language resolves to the RIGHT occurrence (the same English
  // word can carry a different original twice in one verse). Tapping the active word again closes it.
  const onTapWord = (verseNum, position, token) => {
    // Tapping the active word again closes it. Bump the sequence so any in-flight fetch is ignored.
    if (word && word.verse === verseNum && word.position === position) { wordSeq.current++; setWord(null); setWordData(null); return; }
    const clean = String(token).replace(/[^A-Za-z'’-]/g, "");
    // Each tap gets its own sequence number. Only the response for the LATEST tap is applied, so a
    // slower earlier request (word A) can never overwrite a later word's data (word B) if it resolves
    // out of order. Combined with the loading reset below, the card only ever shows the tapped word.
    const seq = ++wordSeq.current;
    setWord({ verse: verseNum, position, token: clean });
    setWordData("loading");
    window.LukeRead.word({ word: clean, book: pos.book, chapter: pos.chapter, verse: verseNum, position })
      .then((d) => { if (seq === wordSeq.current) setWordData(d); })
      .catch(() => { if (seq === wordSeq.current) setWordData({ head: clean, webster: null, strong: null, orig: null, tr: null, xrefs: [], dictionary: [], naves: [] }); });
  };
  const clearLP = () => { if (lp.current.timer) { clearTimeout(lp.current.timer); lp.current.timer = null; } };
  const onVerseDown = (e, ref, text) => {
    lp.current.fired = false; lp.current.x = e.clientX; lp.current.y = e.clientY; clearLP();
    lp.current.timer = setTimeout(() => {
      lp.current.fired = true; lp.current.timer = null;
      const rect = rootRef.current ? rootRef.current.getBoundingClientRect() : { left: 0, top: 0, width: 402, height: 820 };
      setHlMenu({ ref, text, x: lp.current.x - rect.left, y: lp.current.y - rect.top, W: rect.width, H: rect.height });
      try { navigator.vibrate && navigator.vibrate(8); } catch (_) {}
    }, 470);
  };
  const onVerseMove = (e) => { if (!lp.current.timer) return; if (Math.abs(e.clientX - lp.current.x) > 10 || Math.abs(e.clientY - lp.current.y) > 10) clearLP(); };
  const onVerseUp = () => clearLP();
  const setHighlight = (ref, bg) => { setHighlights((h) => ({ ...h, [ref]: bg })); };
  const removeHighlight = (ref) => { setHighlights((h) => { const n = { ...h }; delete n[ref]; return n; }); };
  const addNoteFromVerse = (ref, text) => { setNoteDraft({ text, ref }); setSheet("notes"); setHlMenu(null); };
  const [jumpBack, setJumpBack] = React.useState(null);
  const [flashVref, setFlashVref] = React.useState(null); // verse to briefly highlight after a jump
  const scrollWant = React.useRef(null);                  // verse we still need to scroll into view
  const onJump = (r) => {
    const sameChapter = r.book === pos.book && r.chapter === pos.chapter;
    // Only touch pos when the chapter actually changes — a same-chapter jump must NOT reload the
    // chapter (that was the bug: setPos with identical book/chapter did nothing, so the verse was
    // never brought into view). We scroll to it directly instead, below.
    if (!sameChapter) { setJumpBack({ book: pos.book, chapter: pos.chapter }); setPos((p) => ({ ...p, book: r.book, chapter: r.chapter })); }
    wordSeq.current++; // ignore any in-flight word fetch — the card is closing as we navigate
    setSheet(null); setWord(null); setWordData(null);
    // Bring the exact verse into view for BOTH cases: same chapter (scroll now) or a new chapter
    // (scroll once its verses have rendered). The effect below watches scrollWant + the loaded-chapter
    // cache and fires as soon as the verse element exists.
    if (r.verse) { const vref = r.book + " " + r.chapter + ":" + r.verse; scrollWant.current = vref; setFlashVref(vref); }
  };
  const parseRef = (s) => { const m = String(s).match(/^\s*(.+?)\s+(\d+)(?::(\d+))?/); return m ? { book: m[1].trim(), chapter: +m[2], verse: m[3] ? +m[3] : null } : null; };
  const onXref = (s) => { const r = parseRef(s); if (r) onJump(r); };
  // Scroll the wanted verse into view once it is on screen — same chapter = immediately; new chapter =
  // after its verses load (a later `chapters` update re-runs this effect). Clears the want after
  // scrolling so it fires exactly once. Same walk-up-to-the-scroll-container technique the book picker
  // uses to reveal the open book.
  React.useEffect(() => {
    const vref = scrollWant.current;
    if (!vref) return;
    let raf2; const raf1 = requestAnimationFrame(() => { raf2 = requestAnimationFrame(() => {
      const root = vpRef.current; if (!root) return;
      const el = root.querySelector('[data-vref="' + vref + '"]');
      if (!el) return; // target chapter not rendered yet — a later chapters update re-runs this effect
      let sc = el.parentElement;
      while (sc && !(/auto|scroll/.test(getComputedStyle(sc).overflowY) && sc.scrollHeight > sc.clientHeight + 4)) sc = sc.parentElement;
      if (!sc) return;
      sc.scrollTop += el.getBoundingClientRect().top - sc.getBoundingClientRect().top - 40;
      scrollWant.current = null;
    }); });
    return () => { cancelAnimationFrame(raf1); cancelAnimationFrame(raf2); };
  }, [pos.book, pos.chapter, chapters, flashVref]);
  // Let the highlight linger briefly, then fade (the verse background carries a .2s transition).
  React.useEffect(() => {
    if (!flashVref) return;
    const t = setTimeout(() => setFlashVref(null), 1600);
    return () => clearTimeout(t);
  }, [flashVref]);
  const STANDING = { "1 Kings": "The First Book of the Kings" };
  // Everything the (module-scope) ChapterPane needs, bundled once per render. ChapterPane itself lives
  // at module scope so opening the word card RE-RENDERS the pane instead of REMOUNTING it — a remount
  // would build a fresh scroll container and snap the reader back to the top of the chapter. Passing a
  // fresh ctx object each render is fine: it drives a re-render (which keeps the scroll position),
  // never a remount (which would lose it).
  const paneCtx = {
    chapters, STANDING, font, size, justify, nums, vbv, highlights, word, flash: flashVref,
    onVerseDown, onVerseMove, onVerseUp, onTapWord, lp,
  };

  return (
    <div ref={rootRef} className={"skin-exposit" + (dark ? " theme-dark" : "")} data-skin="exposit" style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", background: "var(--parchment)" }}>
      {hideStatus ? <div style={{ flex: "none", height: "calc(env(safe-area-inset-top, 0px) + 18px)" }} /> : <window.LukePhoneKit.StatusBar time="6:14" />}
      {/* top bar */}
      <div style={{ flex: "none", display: "flex", alignItems: "center", padding: "2px 10px 12px", borderBottom: "1px solid var(--parchment-edge)" }}>
        <div style={{ width: 104, flexShrink: 0, display: "flex", alignItems: "center", gap: 2 }}>
          {onBack
            ? <button onClick={onBack} aria-label="Back" style={{ ...btn, width: 34, height: 34 }}><Ic d={pi.chevL} size={22} sw={2} /></button>
            : <button onClick={() => setSheet("appsettings")} aria-label="Settings" style={{ ...btn, width: 34, height: 34 }}><Ic d={GEAR} size={20} sw={1.7} /></button>}
          <button onClick={() => setSheet("search")} aria-label="Search" style={{ ...btn, width: 34, height: 34 }}><Ic d={pi.search} size={20} sw={1.9} /></button>
          <button onClick={() => setSheet("notes")} aria-label="Notes" style={{ ...btn, width: 34, height: 34 }}><Ic d={pi.edit} size={19} sw={1.8} /></button>
        </div>
        <button onClick={() => setSheet("browse")} style={{ flex: 1, minWidth: 0, overflow: "hidden", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 6, background: "transparent", border: "none", cursor: "pointer", padding: "6px 2px", whiteSpace: "nowrap" }}>
          <span style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 18, color: "var(--text-strong)", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{pos.book} {pos.chapter}</span>
          <span style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-muted)", letterSpacing: ".04em", flexShrink: 0 }}>KJV</span>
          <span style={{ color: "var(--text-muted)", display: "inline-flex", flexShrink: 0 }}><Ic d={pi.chevDown} size={16} /></span>
        </button>
        <div style={{ width: 104, flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 2 }}>
          <button onClick={() => onListen(pos.book, pos.chapter, pos.book + " " + pos.chapter)} aria-label="Listen" style={{ ...btn, width: 34, height: 34 }}><Ic d={pi.headphones} size={19} sw={1.8} /></button>
          <button onClick={() => setSheet("settings")} aria-label="Display" style={{ ...btn, width: 34, height: 34 }}><span style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 17 }}>Aa</span></button>
          <button onClick={() => setSheet("plan")} aria-label="Reading plan" style={{ ...btn, width: 34, height: 34 }}><Ic d={pi.menu} size={21} sw={2} /></button>
        </div>
      </div>
      {/* scripture — finger-tracked chapter slide */}
      <div ref={vpRef} onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp} style={{ flex: 1, minHeight: 0, position: "relative", overflow: "hidden", touchAction: "pan-y" }}>
        <div style={{ position: "absolute", inset: 0, display: "flex", width: "300%", transform: "translateX(calc(-33.3333% + " + dx + "px))", transition: anim ? "transform .34s cubic-bezier(.2,0,0,1)" : "none", willChange: "transform" }}>
          <ChapterPane r={prevRef} ctx={paneCtx} />
          <ChapterPane r={pos} ctx={paneCtx} />
          <ChapterPane r={nextRef} ctx={paneCtx} />
        </div>
      </div>

      <button onClick={() => page(-1)} aria-label="Previous chapter" style={edgeBtn("left")}><Ic d={pi.chevL} size={22} sw={2} /></button>
      <button onClick={() => page(1)} aria-label="Next chapter" style={edgeBtn("right")}><Ic d={pi.chevR} size={22} sw={2} /></button>
      {jumpBack && (<div style={{ position: "absolute", left: 12, right: 12, bottom: 14, zIndex: 60, display: "flex", alignItems: "center", gap: 8, background: "var(--glass-bg)", backdropFilter: "blur(var(--glass-blur))", WebkitBackdropFilter: "blur(var(--glass-blur))", border: "1px solid var(--glass-border)", borderRadius: 14, boxShadow: "var(--glass-shadow)", padding: "8px 10px 8px 12px" }}>
        <button onClick={() => { setPos((p) => ({ ...p, book: jumpBack.book, chapter: jumpBack.chapter })); setJumpBack(null); }} style={{ flex: 1, display: "flex", alignItems: "center", gap: 10, background: "transparent", border: "none", cursor: "pointer", textAlign: "left" }}>
          <span style={{ color: "var(--olive-700)", display: "inline-flex" }}><Ic d={pi.chevL} size={20} sw={2} /></span>
          <span style={{ fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-body)" }}>Back to <b style={{ fontWeight: 600 }}>{jumpBack.book} {jumpBack.chapter}</b></span>
        </button>
        <button onClick={() => setJumpBack(null)} aria-label="Dismiss" style={{ width: 30, height: 30, borderRadius: "50%", border: "none", background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "grid", placeItems: "center", flexShrink: 0 }}><Ic d={pi.close} size={18} sw={2} /></button>
      </div>)}
      {intro && (
        <div style={{ position: "absolute", inset: 0, zIndex: 130, display: "grid", placeItems: "center", padding: 22, background: "rgba(38,44,23,.20)" }}>
          <div style={{ width: "100%", maxWidth: 322, background: "var(--exp-pop)", backdropFilter: "blur(26px) saturate(1.4)", WebkitBackdropFilter: "blur(26px) saturate(1.4)", border: "1px solid var(--exp-hair)", borderRadius: 24, boxShadow: "0 24px 64px rgba(30,28,20,.34)", padding: "26px 22px 20px" }}>
            <div style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 27, color: "var(--text-strong)", textAlign: "center" }}>Welcome</div>
            <div style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-muted)", textAlign: "center", marginTop: 3, marginBottom: 20 }}>Three things to know</div>
            <div style={{ display: "flex", flexDirection: "column", gap: 15, marginBottom: 22 }}>
              {[
                [<Ic d={pi.book} size={19} sw={1.7} />, <span>Tap an <b style={{ fontWeight: 600, color: "var(--text-strong)" }}>underlined word</b> to see what it means.</span>],
                [<Ic d={pi.chevR} size={19} sw={2.2} />, <span><b style={{ fontWeight: 600, color: "var(--text-strong)" }}>Swipe</b> left or right to turn the page.</span>],
                [<span style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 15 }}>Aa</span>, <span>Tap <b style={{ fontWeight: 600, color: "var(--text-strong)" }}>Aa</b> to make the text larger.</span>],
              ].map((r, i) => (
                <div key={i} style={{ display: "flex", alignItems: "center", gap: 13 }}>
                  <span style={{ width: 38, height: 38, borderRadius: 11, background: "var(--olive-50)", color: "var(--olive-700)", display: "grid", placeItems: "center", flexShrink: 0 }}>{r[0]}</span>
                  <span style={{ fontFamily: "var(--font-serif)", fontSize: 16, lineHeight: 1.4, color: "var(--text-body)" }}>{r[1]}</span>
                </div>
              ))}
            </div>
            <button onClick={() => { LS.set("luke_reader_seen_intro", true); setIntro(false); }} style={{ width: "100%", background: "var(--brand)", color: "#fff", border: "none", borderRadius: 999, padding: 14, fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15, cursor: "pointer" }}>Got it</button>
          </div>
        </div>
      )}
      {hlMenu && (<React.Fragment>
        <div onPointerDown={() => setHlMenu(null)} style={{ position: "absolute", inset: 0, zIndex: 105 }} />
        <HighlightMenu key={hlMenu.ref + ":" + Math.round(hlMenu.y)} menu={hlMenu} colors={HL_COLORS} current={highlights[hlMenu.ref]} onPick={(bg) => setHighlight(hlMenu.ref, bg)} onRemove={() => removeHighlight(hlMenu.ref)} onAddNote={() => addNoteFromVerse(hlMenu.ref, hlMenu.text)} onClose={() => setHlMenu(null)} />
      </React.Fragment>)}
      <WordGlass open={!!word} head={word ? word.token : null} data={wordData} onClose={() => { wordSeq.current++; setWord(null); setWordData(null); }} onXref={onXref} />
      <ListenDock item={listen} min={listenMin} onMin={() => setListenMin(true)} onExpand={() => setListenMin(false)} onClose={() => setListen(null)} />      <BottomSheet open={sheet === "plan"} onClose={() => setSheet(null)} full swipeClose>
        <PlanSheet D={D} plan={plan} setPlan={setPlan} onJump={onJump} onListen={onListen} />
      </BottomSheet>
      <BottomSheet open={sheet === "browse"} onClose={() => setSheet(null)} full swipeClose>
        <div style={{ flex: "none", padding: "8px 20px 8px", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
          <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)" }}>Go to</div>
          <button onClick={() => setBrowseVer((v) => !v)} style={{ display: "inline-flex", alignItems: "center", gap: 5, background: "transparent", border: "none", borderRadius: 999, padding: "7px 4px", cursor: "pointer", fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 13, color: "var(--olive-700)" }}>{D.ref.version}<Ic d={pi.chevDown} size={14} /></button>
        </div>
        <Collapse open={browseVer}><div style={{ padding: "0 20px 10px" }}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: "transparent", border: "1px solid var(--exp-ink-16)", borderRadius: 10, padding: "12px" }}>
            <span style={{ fontFamily: "var(--font-sans)", fontSize: 16, color: "var(--olive-700)", fontWeight: 600 }}>King James Version</span>
            <span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Ic d={pi.check} size={16} sw={2.2} /></span>
          </div>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)", padding: "10px 12px 4px" }}>More translations coming soon.</div>
        </div></Collapse>
        <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "0 16px 16px" }}><BookAccordion D={D} book={pos.book} chapter={pos.chapter} onPick={(b, c) => { setPos((p) => ({ ...p, book: b, chapter: c })); setSheet(null); }} /></div>
      </BottomSheet>
      <BottomSheet open={sheet === "settings"} onClose={() => setSheet(null)} full swipeClose>
        <SettingsSheet size={size} setSize={setSize} justify={justify} setJustify={setJustify} nums={nums} setNums={setNums} vbv={vbv} setVbv={setVbv} font={font} setFont={setFont} red={red} setRed={setRed} dark={dark} setDark={setDark} version={D.ref.version} />
      </BottomSheet>
      <BottomSheet open={sheet === "search"} onClose={() => setSheet(null)} full swipeClose>
        <SearchSheet D={D} onJump={onJump} />
      </BottomSheet>
      <BottomSheet open={sheet === "appsettings"} onClose={() => setSheet(null)} full swipeClose>
        <AppSettingsSheet version={D.ref.version} onReset={resetAll} />
      </BottomSheet>
      <BottomSheet open={sheet === "notes"} onClose={() => setSheet(null)} full swipeClose>
        <NotesSheet notes={notes} setNotes={setNotes} whereRef={pos.book + " " + pos.chapter} draft={noteDraft} onConsumeDraft={() => setNoteDraft(null)} highlightList={hlList} onJumpRef={onXref} onRemoveHighlight={removeHighlight} />
      </BottomSheet>
    </div>
  );
}

// One chapter pane in the three-pane finger-tracked slide.
// ⭑ MODULE SCOPE ON PURPOSE — DO NOT move this back inside LukeReader. A component defined inside the
// render body gets a NEW function identity on every render, so React unmounts and REMOUNTS it whenever
// LukeReader re-renders (e.g. when a tapped word opens the word card). Remounting rebuilds this pane's
// scrollable div from scratch, snapping the reader back to the top of the chapter — the exact bug this
// fixes. With a stable module-scope identity, a state change RE-RENDERS the pane (keeping its scroll
// position) instead of remounting it. Everything it needs arrives in `ctx`.
function ChapterPane({ r, ctx }) {
  if (!r) return <div style={{ flex: "0 0 33.3333%", height: "100%" }} />;
  const cached = ctx.chapters[r.book + "|" + r.chapter];
  const bodyFont = ctx.font === "garamond" ? "var(--font-serif-display)" : "var(--font-serif)";
  const vs = cached ? cached.verses : null;
  const tg = (cached && cached.tagged) || {};
  if (!vs) return (
    <div style={{ flex: "0 0 33.3333%", height: "100%", display: "grid", placeItems: "center", padding: "26px" }}>
      <span style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 16, color: "var(--olive-600)" }}>{cached && cached.error ? "Could not load this chapter." : "…"}</span>
    </div>
  );
  return (
    <div style={{ flex: "0 0 33.3333%", height: "100%", overflowY: "auto", padding: "26px 26px 40px" }}>
      <div style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 15, color: "var(--olive-600)", textAlign: "center", marginBottom: 22 }}>{ctx.STANDING[r.book] || r.book}</div>
      <div style={{ fontFamily: bodyFont, fontSize: ctx.size, lineHeight: 1.72, color: "var(--text-body)", textAlign: ctx.justify ? "justify" : "left", hyphens: ctx.justify ? "auto" : "none", WebkitUserSelect: "none", userSelect: "none", WebkitTouchCallout: "none" }}>
        <span style={{ float: "left", fontFamily: bodyFont, fontWeight: 500, fontSize: ctx.size * 3.1, lineHeight: .82, color: "var(--olive-600)", margin: "6px 12px 0 0" }}>{r.chapter}</span>
        {vs.map((v) => {
          const vref = r.book + " " + r.chapter + ":" + v.n;
          const hlc = ctx.highlights[vref];
          // A jump target flashes with the olive highlight tint (unless the reader already highlighted
          // it, which wins). It fades on its own via the .2s background transition.
          const flashBg = (!hlc && ctx.flash === vref) ? "rgba(181,199,138,0.34)" : null;
          const bg = hlc || flashBg;
          const hp = { onPointerDown: (e) => ctx.onVerseDown(e, vref, v.t), onPointerMove: ctx.onVerseMove, onPointerUp: ctx.onVerseUp, onPointerCancel: ctx.onVerseUp };
          return ctx.vbv ? (
            <div key={v.n} data-vref={vref} {...hp} style={{ marginBottom: "0.5em", background: bg || "transparent", borderRadius: bg ? 6 : 0, padding: bg ? "3px 8px" : 0, transition: "background .2s" }}>
              {ctx.nums && <sup style={{ fontFamily: "var(--font-sans)", fontSize: ".58em", fontWeight: 700, color: "var(--olive-600)", verticalAlign: "super", marginRight: 4 }}>{v.n}</sup>}
              {renderVerseTokens(v, tg[v.n] || [], ctx)}
            </div>
          ) : (
            <React.Fragment key={v.n}>
              <span data-vref={vref} {...hp} style={{ background: bg || "transparent", borderRadius: bg ? 5 : 0, padding: bg ? "2px 4px" : 0, boxDecorationBreak: "clone", WebkitBoxDecorationBreak: "clone", transition: "background .2s" }}>
                {ctx.nums && <sup style={{ fontFamily: "var(--font-sans)", fontSize: ".58em", fontWeight: 700, color: "var(--olive-600)", verticalAlign: "super", marginRight: 4, marginLeft: v.n === 1 ? 0 : 3 }}>{v.n}</sup>}
                {renderVerseTokens(v, tg[v.n] || [], ctx)}
              </span>{" "}
            </React.Fragment>
          );
        })}
      </div>
    </div>
  );
}

// A word is underlined/tappable when the live chapter tagging carries a Strong's number for it
// (a content word with an original behind it). Alignment walks the tagged list in step with the
// rendered text tokens; the position sent on tap is the tag's OWN `p`, so the original language
// resolves to the right occurrence even when a word repeats in the verse. Module scope alongside
// ChapterPane (see the note above) — takes the render context it needs via `ctx`.
function renderVerseTokens(v, taggedList, ctx) {
  const out = [];
  const norm = (s) => String(s).toLowerCase().replace(/[^a-z'’]/g, "");
  const list = taggedList || [];
  let ti = 0;
  v.t.split(/(\s+)/).forEach((tok, i) => {
    if (/^\s+$/.test(tok)) { out.push(tok); return; }
    const key = norm(tok);
    if (!key) { out.push(tok); return; }
    // Align this text word to the next tagged entry; resync within a small look-ahead so a
    // MetaV-only extra word (e.g. a Psalm superscription) can't shift the whole verse.
    let tag = null;
    if (ti < list.length && norm(list[ti].w) === key) { tag = list[ti]; ti++; }
    else {
      for (let k = ti + 1; k <= ti + 3 && k < list.length; k++) {
        if (norm(list[k].w) === key) { tag = list[k]; ti = k + 1; break; }
      }
    }
    const tappable = tag && tag.s && tag.s.length;
    if (tappable) {
      const active = ctx.word && ctx.word.verse === v.n && ctx.word.position === tag.p;
      out.push(
        <span key={i} onClick={() => { if (ctx.lp.current.fired) { ctx.lp.current.fired = false; return; } ctx.onTapWord(v.n, tag.p, tok); }}
          style={{ cursor: "pointer", borderBottom: "1.5px dotted var(--olive-300)", color: active ? "var(--olive-700)" : "inherit", background: active ? "var(--parchment-deep)" : "transparent", borderRadius: 3 }}>{tok}</span>
      );
    } else out.push(tok);
  });
  return out;
}
const btn = { width: 40, height: 40, borderRadius: "50%", border: "none", background: "transparent", color: "var(--text-body)", cursor: "pointer", display: "grid", placeItems: "center", flexShrink: 0 };
const edgeBtn = (side) => ({ position: "absolute", top: "50%", transform: "translateY(-50%)", [side]: 8, width: 38, height: 38, borderRadius: "50%", border: "1px solid var(--exp-hair)", background: "var(--exp-edge-fill)", backdropFilter: "blur(14px) saturate(1.4)", WebkitBackdropFilter: "blur(14px) saturate(1.4)", color: "var(--olive-700)", cursor: "pointer", display: "grid", placeItems: "center", opacity: .92, zIndex: 40, boxShadow: "0 4px 16px rgba(30,28,20,.16), inset 0 1px 0 var(--exp-top)" });
const dockBtn = { width: 36, height: 36, borderRadius: "50%", border: "none", background: "var(--exp-ink-06)", color: "var(--text-muted)", cursor: "pointer", display: "grid", placeItems: "center", flexShrink: 0 };
const HL_COLORS = [{ id: "olive", name: "Olive", bg: "rgba(181,199,138,0.34)" }, { id: "gold", name: "Gold", bg: "rgba(224,196,132,0.36)" }, { id: "clay", name: "Clay", bg: "rgba(210,183,143,0.36)" }, { id: "teal", name: "Teal", bg: "rgba(158,190,188,0.34)" }];

const SEG = { display: "flex", gap: 4, background: "var(--exp-ink-06)", borderRadius: 12, padding: 4 };
const LS = {
  get(k, fb) { try { const s = localStorage.getItem(k); return s ? JSON.parse(s) : fb; } catch (e) { return fb; } },
  set(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} },
};
const BG_AUDIO = { "Genesis": "Gen", "Exodus": "Exod", "Leviticus": "Lev", "Numbers": "Num", "Deuteronomy": "Deut", "Joshua": "Josh", "Judges": "Judg", "Ruth": "Ruth", "1 Samuel": "1Sam", "2 Samuel": "2Sam", "1 Kings": "1Kgs", "2 Kings": "2Kgs", "1 Chronicles": "1Chr", "2 Chronicles": "2Chr", "Ezra": "Ezra", "Nehemiah": "Neh", "Esther": "Esth", "Job": "Job", "Psalms": "Ps", "Proverbs": "Prov", "Ecclesiastes": "Eccl", "Song of Solomon": "Song", "Isaiah": "Isa", "Jeremiah": "Jer", "Lamentations": "Lam", "Ezekiel": "Ezek", "Daniel": "Dan", "Hosea": "Hos", "Joel": "Joel", "Amos": "Amos", "Obadiah": "Obad", "Jonah": "Jonah", "Micah": "Mic", "Nahum": "Nah", "Habakkuk": "Hab", "Zephaniah": "Zeph", "Haggai": "Hag", "Zechariah": "Zech", "Malachi": "Mal", "Matthew": "Matt", "Mark": "Mark", "Luke": "Luke", "John": "John", "Acts": "Acts", "Romans": "Rom", "1 Corinthians": "1Cor", "2 Corinthians": "2Cor", "Galatians": "Gal", "Ephesians": "Eph", "Philippians": "Phil", "Colossians": "Col", "1 Thessalonians": "1Thess", "2 Thessalonians": "2Thess", "1 Timothy": "1Tim", "2 Timothy": "2Tim", "Titus": "Titus", "Philemon": "Phlm", "Hebrews": "Heb", "James": "Jas", "1 Peter": "1Pet", "2 Peter": "2Pet", "1 John": "1John", "2 John": "2John", "3 John": "3John", "Jude": "Jude", "Revelation": "Rev" };
function bgAudioUrl(book, ch) { return "https://www.biblegateway.com/audio/mclean/kjv/" + (BG_AUDIO[book] || book.replace(/\s+/g, "")) + "." + ch; }

// Listen — a persistent Bible Gateway (McLean KJV) player. Minimize it to a dock and keep reading while it plays.
function ListenDock({ item, min, onMin, onExpand, onClose }) {
  const { Ic, pi } = window.LukePhoneKit;
  const last = React.useRef(null);
  if (item) last.current = item;
  const it = item || last.current;
  const [mounted, setMounted] = React.useState(!!item);
  const [show, setShow] = React.useState(false);
  React.useEffect(() => {
    if (item) { setMounted(true); return; }
    setShow(false);
    const t = setTimeout(() => setMounted(false), 460);
    return () => clearTimeout(t);
  }, [item]);
  React.useEffect(() => {
    if (!mounted || !item) return;
    let id2; const id1 = requestAnimationFrame(() => { id2 = requestAnimationFrame(() => setShow(true)); });
    return () => { cancelAnimationFrame(id1); cancelAnimationFrame(id2); };
  }, [mounted, item]);
  const [playing, setPlaying] = React.useState(false);
  React.useEffect(() => { setPlaying(false); }, [item && item.url]);
  if (!mounted || !it) return null;
  const expanded = show && !min;
  return (
    <React.Fragment>
      <div onClick={onMin} style={{ position: "absolute", inset: 0, background: "rgba(38,44,23,.10)", opacity: expanded ? 1 : 0, pointerEvents: expanded ? "auto" : "none", transition: "opacity .3s ease", zIndex: 90 }} />
      <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 95, height: min ? 76 : "66%", display: "flex", flexDirection: "column", overflow: "hidden",
        transform: show ? "translateY(0)" : "translateY(103%)", transition: "transform .44s cubic-bezier(.32,.72,0,1), height .4s cubic-bezier(.32,.72,0,1)",
        background: "var(--exp-sheet)", backdropFilter: "blur(26px) saturate(1.4)", WebkitBackdropFilter: "blur(26px) saturate(1.4)",
        borderTop: "1px solid var(--exp-hair-soft)", borderRadius: "28px 28px 49px 49px", boxShadow: "0 -14px 50px rgba(30,28,20,.20), inset 0 1px 0 var(--exp-top)" }}>
        {!min && <div onClick={onMin} style={{ width: 44, height: 5, borderRadius: 3, background: "var(--exp-ink-16)", margin: "10px auto 2px", flexShrink: 0, cursor: "pointer" }} />}
        <div style={{ flex: min ? 1 : "none", display: "flex", alignItems: "center", gap: 8, padding: min ? "0 12px 0 18px" : "6px 18px 10px" }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 11, color: "var(--brand-text)" }}>Listen &middot; KJV</div>
            <div style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: min ? 17 : 22, color: "var(--text-strong)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{it.label}</div>
          </div>
          {min
            ? <button onClick={onExpand} aria-label="Expand player" style={dockBtn}><Ic d={pi.chevUp} size={20} sw={2} /></button>
            : <button onClick={onMin} aria-label="Minimize player" style={dockBtn}><Ic d={pi.chevDown} size={20} sw={2} /></button>}
          <button onClick={onClose} aria-label="Close player" style={dockBtn}><Ic d={pi.close} size={18} sw={2} /></button>
        </div>
        <div style={{ flex: min ? "none" : 1, height: min ? 0 : undefined, minHeight: 0, opacity: min ? 0 : 1, pointerEvents: min ? "none" : "auto", padding: min ? "0 18px" : "0 18px 6px", transition: "opacity .25s ease" }}>
          <div style={{ height: "100%", borderRadius: 16, overflow: "hidden", border: "1px solid var(--parchment-edge)", background: "var(--exp-paper)", position: "relative" }}>
            {playing
              ? <iframe src={it.url} title={"Listen to " + it.label} allow="autoplay" style={{ width: "100%", height: "100%", border: "none" }} />
              : <button onClick={() => setPlaying(true)} aria-label={"Play " + it.label} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: "none", background: "var(--parchment)", cursor: "pointer", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12 }}>
                  <span style={{ width: 66, height: 66, borderRadius: "50%", background: "var(--brand)", color: "#fff", display: "grid", placeItems: "center", boxShadow: "0 8px 22px rgba(68,78,39,.28)" }}><svg width="27" height="27" viewBox="0 0 24 24" fill="currentColor" style={{ marginLeft: 3 }}><path d="M8 5v14l11-7z" /></svg></span>
                  <span style={{ fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-muted)" }}>Tap to play &middot; {it.label}</span>
                </button>}
          </div>
        </div>
        {!min && <div style={{ flex: "none", padding: "10px 18px 18px" }}><button onClick={() => window.open(it.url, "_blank", "noopener")} style={{ width: "100%", background: "transparent", border: "1.5px solid var(--brand)", borderRadius: 999, padding: "12px", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 600, color: "var(--olive-700)", cursor: "pointer" }}>Open on Bible Gateway</button></div>}
      </div>
    </React.Fragment>
  );
}

// Liquid-Glass bottom sheet — floating chrome over the reading surface.
const GEAR = '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>';

// Search — live search across the whole KJV via /api/read/search (a substring switch over the verse
// table, no model). Debounced; tap a hit to jump there.
function SearchSheet({ D, onJump }) {
  const { Ic, pi } = window.LukePhoneKit;
  const [q, setQ] = React.useState("");
  const [results, setResults] = React.useState([]);
  const [busy, setBusy] = React.useState(false);
  const inputRef = React.useRef(null);
  const seq = React.useRef(0);
  React.useEffect(() => { const t = setTimeout(() => { inputRef.current && inputRef.current.focus({ preventScroll: true }); }, 500); return () => clearTimeout(t); }, []);
  const term = q.trim();
  React.useEffect(() => {
    if (term.length < 3) { setResults([]); setBusy(false); return; }
    setBusy(true);
    const my = ++seq.current;
    const t = setTimeout(() => {
      window.LukeRead.search(term)
        .then((rows) => { if (my === seq.current) { setResults(rows || []); setBusy(false); } })
        .catch(() => { if (my === seq.current) { setResults([]); setBusy(false); } });
    }, 260);
    return () => clearTimeout(t);
  }, [term]);
  return (
    <React.Fragment>
      <div style={{ flex: "none", padding: "8px 20px 12px" }}>
        <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)", marginBottom: 12 }}>Search</div>
        <div style={{ display: "flex", alignItems: "center", gap: 8, background: "var(--exp-ink-06)", borderRadius: 12, padding: "10px 14px" }}>
          <span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Ic d={pi.search} size={18} sw={1.9} /></span>
          <input ref={inputRef} value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search the King James text" style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", fontFamily: "var(--font-serif)", fontSize: 16, color: "var(--text-strong)" }} />
          {q && <button onClick={() => setQ("")} aria-label="Clear" style={{ border: "none", background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "inline-flex", flexShrink: 0 }}><Ic d={pi.close} size={16} sw={2} /></button>}
        </div>
        {term.length >= 3 && <div style={{ fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--text-muted)", marginTop: 10 }}>{busy ? "Searching…" : (results.length + (results.length >= 100 ? "+" : "") + " " + (results.length === 1 ? "result" : "results"))}</div>}
      </div>
      <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "0 20px 24px" }}>
        {term.length < 3
          ? <div style={{ fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-muted)", textAlign: "center", marginTop: 30 }}>Type a few letters to search all 66 books.</div>
          : results.map((r, i) => (
              <button key={i} onClick={() => onJump({ book: r.book, chapter: r.chapter })} style={{ display: "block", width: "100%", textAlign: "left", background: "transparent", border: "none", borderTop: i === 0 ? "none" : "1px solid var(--parchment-edge)", padding: "13px 0", cursor: "pointer" }}>
                <div style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 14, color: "var(--olive-700)", marginBottom: 3 }}>{r.book} {r.chapter}:{r.verse}</div>
                <div style={{ fontFamily: "var(--font-serif)", fontSize: 15, lineHeight: 1.5, color: "var(--text-body)" }}>{r.text}</div>
              </button>))}
      </div>
    </React.Fragment>
  );
}

// App settings — the gear (top-left). App-level, not type: version, sync, sources, reset-all.
function AppSettings({ version, onReset, onAbout, onPrivacy }) {
  const { Ic, pi } = window.LukePhoneKit;
  const row = { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: "transparent", border: "none", borderTop: "1px solid var(--parchment-edge)", padding: "16px 0", cursor: "pointer", textAlign: "left" };
  const lbl = { fontFamily: "var(--font-serif)", fontSize: 17, color: "var(--text-strong)" };
  const val = { fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--olive-700)", fontWeight: 600 };
  const chev = { color: "var(--text-muted)", display: "inline-flex" };
  return (
    <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "6px 22px 26px" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "8px 0 20px" }}>
        <img src="/assets/logos/exposit_reader_glyph_olive.svg" alt="Exposit Reader" width="54" height="54" style={{ display: "block", borderRadius: 14, flexShrink: 0 }} />
        <div>
          <div style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 24, color: "var(--text-strong)", lineHeight: 1.1 }}>Exposit Bible Reader</div>
        </div>
      </div>
      <button style={{ ...row, borderTop: "none", cursor: "default" }}><span style={lbl}>Sync &amp; backup</span><span style={{ ...val, color: "var(--text-faint)" }}>This device</span></button>
      <button style={row} onClick={onAbout}><span style={lbl}>About &amp; sources</span><span style={chev}><Ic d={pi.chevR} size={18} /></span></button>
      <button style={row} onClick={onPrivacy}><span style={lbl}>Privacy policy</span><span style={chev}><Ic d={pi.chevR} size={18} /></span></button>
    </div>
  );
}

// About & sources — its own pushed view, so it slides in the same way Privacy does.
function AboutSheet({ onBack }) {
  const { Ic, pi } = window.LukePhoneKit;
  return (
    <React.Fragment>
      <div style={{ flex: "none", display: "flex", alignItems: "center", gap: 6, padding: "8px 20px 10px" }}>
        <button onClick={onBack} aria-label="Back" style={{ width: 34, height: 34, borderRadius: "50%", border: "none", background: "transparent", color: "var(--text-body)", cursor: "pointer", display: "grid", placeItems: "center", marginLeft: -6 }}><Ic d={pi.chevL} size={22} sw={2} /></button>
        <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)" }}>About &amp; sources</div>
      </div>
      <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "4px 22px 26px" }}>
        <div style={{ fontFamily: "var(--font-serif)", fontSize: 18, lineHeight: 1.6, color: "var(--text-body)" }}>Biblically grounded technology, made by people in ministry, for the local church.</div>
        <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 12, color: "var(--brand-text)", margin: "22px 0 6px" }}>The Reader</div>
        <div style={{ fontFamily: "var(--font-serif)", fontSize: 15.5, lineHeight: 1.6, color: "var(--text-body)" }}>A Bible reader for your phone, with Strong's word study built in. Part of Luke, and free to carry on its own.</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 10, margin: "20px 0 4px" }}>
          <button onClick={() => window.open("https://theocore.co", "_blank", "noopener")} style={{ width: "100%", background: "transparent", border: "1.5px solid var(--brand)", borderRadius: 999, padding: "12px", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 600, color: "var(--olive-700)", cursor: "pointer" }}>Visit theocore.co</button>
          <button onClick={() => window.open("mailto:info@theocore.co", "_blank", "noopener")} style={{ width: "100%", background: "transparent", border: "1.5px solid var(--brand)", borderRadius: 999, padding: "12px", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 600, color: "var(--olive-700)", cursor: "pointer" }}>Email info@theocore.co</button>
        </div>
        <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 12, color: "var(--brand-text)", margin: "24px 0 6px" }}>Sources</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8, fontFamily: "var(--font-serif)", fontSize: 15.5, lineHeight: 1.5, color: "var(--text-body)" }}>
          <div>Scripture: King James Version, public domain.</div>
          <div>Word study: Webster's 1828 Dictionary and Strong's Exhaustive Concordance, public domain.</div>
          <div>Audio: Bible Gateway, McLean KJV.</div>
        </div>
        <div style={{ borderTop: "1px solid var(--parchment-edge)", marginTop: 22, paddingTop: 16 }}>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 13, lineHeight: 1.6, color: "var(--text-muted)" }}>TheoCore Limited is based in Hastings, New Zealand.</div>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-faint)", marginTop: 4 }}>Company number 9447818 &middot; NZBN 9429053848611</div>
        </div>
      </div>
    </React.Fragment>
  );
}

// App settings + privacy as a horizontal slider — privacy slides in from the side, card stays put.
function AppSettingsSheet({ version, onReset }) {
  const [view, setView] = React.useState(null); // 'about' | 'privacy' | null
  const last = React.useRef("about");
  if (view) last.current = view;
  const detail = last.current;
  const panel = { width: "50%", height: "100%", display: "flex", flexDirection: "column", minHeight: 0, flex: "none" };
  return (
    <div style={{ flex: 1, minHeight: 0, display: "flex", overflow: "hidden" }}>
      <div style={{ display: "flex", width: "200%", height: "100%", flexShrink: 0, transform: view ? "translateX(-50%)" : "translateX(0)", transition: "transform .5s cubic-bezier(.32,.72,0,1)" }}>
        <div style={panel}><AppSettings version={version} onReset={onReset} onAbout={() => setView("about")} onPrivacy={() => setView("privacy")} /></div>
        <div style={panel}>{detail === "privacy" ? <PrivacySheet onBack={() => setView(null)} /> : <AboutSheet onBack={() => setView(null)} />}</div>
      </div>
    </div>
  );
}

// Highlight card — long-press a verse and this blooms just below the finger: soft colors + add-to-notes.
function HighlightMenu({ menu, colors, current, onPick, onRemove, onAddNote, onClose }) {
  const { Ic, pi } = window.LukePhoneKit;
  const [show, setShow] = React.useState(false);
  React.useEffect(() => { const id = requestAnimationFrame(() => setShow(true)); return () => cancelAnimationFrame(id); }, []);
  const W = 258, PH = 150;
  const left = Math.max(8, Math.min(menu.x - W / 2, menu.W - W - 8));
  const below = menu.y + 14 + PH < menu.H - 8;
  const top = below ? menu.y + 14 : Math.max(8, menu.y - 14 - PH);
  return (
    <div style={{ position: "absolute", left, top, width: W, zIndex: 110,
      transform: show ? "scale(1)" : "scale(.92)", opacity: show ? 1 : 0, transformOrigin: below ? "top center" : "bottom center",
      transition: "transform .26s cubic-bezier(.32,.72,0,1), opacity .2s ease",
      background: "var(--exp-pop)", backdropFilter: "blur(24px) saturate(1.4)", WebkitBackdropFilter: "blur(24px) saturate(1.4)",
      border: "1px solid var(--exp-hair)", borderRadius: 18, boxShadow: "0 16px 44px rgba(30,28,20,.26), inset 0 1px 0 var(--exp-top)", padding: "12px 16px 14px" }}>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
        <span style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 14, color: "var(--olive-700)" }}>{menu.ref}</span>
        <button onClick={onClose} aria-label="Close" style={{ width: 28, height: 28, borderRadius: "50%", border: "none", background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "grid", placeItems: "center", marginRight: -6 }}><Ic d={pi.close} size={16} sw={2} /></button>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 11 }}>
        {colors.map((c) => (
          <button key={c.id} onClick={() => onPick(c.bg)} aria-label={c.name} style={{ width: 34, height: 34, borderRadius: "50%", cursor: "pointer", background: c.bg, border: current === c.bg ? "2px solid var(--olive-400)" : "1px solid var(--exp-ink-10)", display: "grid", placeItems: "center", color: "var(--olive-700)" }}>{current === c.bg && <Ic d={pi.check} size={15} sw={2.2} />}</button>
        ))}
        {current && <button onClick={onRemove} aria-label="Remove highlight" style={{ width: 34, height: 34, borderRadius: "50%", cursor: "pointer", background: "transparent", border: "1px solid var(--exp-ink-12)", color: "var(--text-muted)", display: "grid", placeItems: "center", marginLeft: "auto" }}><Ic d={pi.close} size={16} sw={2} /></button>}
      </div>
      <button onClick={onAddNote} style={{ marginTop: 14, width: "100%", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8, background: "transparent", border: "1.5px solid var(--brand)", borderRadius: 999, padding: "10px", fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 600, color: "var(--olive-700)", cursor: "pointer" }}><Ic d={pi.edit} size={16} sw={1.8} />Add to notes</button>
    </div>
  );
}

// Notes — its own place (not under settings). Write, keep, delete; saved on the device.
function NotesSheet({ notes, setNotes, whereRef, draft, onConsumeDraft, highlightList, onJumpRef, onRemoveHighlight }) {
  const { Ic, pi } = window.LukePhoneKit;
  const [text, setText] = React.useState("");
  const [composing, setComposing] = React.useState(false);
  const [attach, setAttach] = React.useState(true);
  const [refOverride, setRefOverride] = React.useState(null);
  const [editId, setEditId] = React.useState(null);
  const [editText, setEditText] = React.useState("");
  const [q, setQ] = React.useState("");
  const [notesOpen, setNotesOpen] = React.useState(true);
  const [hlOpen, setHlOpen] = React.useState(false);
  const taRef = React.useRef(null);
  const editRef = React.useRef(null);
  React.useEffect(() => { if (composing) { const t = setTimeout(() => { if (taRef.current) taRef.current.focus({ preventScroll: true }); }, 360); return () => clearTimeout(t); } }, [composing]);
  React.useEffect(() => { if (editId != null) { const t = setTimeout(() => { if (editRef.current) editRef.current.focus({ preventScroll: true }); }, 60); return () => clearTimeout(t); } }, [editId]);
  const startEdit = (n) => { setComposing(false); setEditText(n.text); setEditId(n.id); };
  const saveEdit = () => { const t = editText.trim(); setNotes(notes.map((x) => x.id === editId ? { ...x, text: t || x.text } : x)); setEditId(null); };
  React.useEffect(() => { if (draft) { setEditId(null); setComposing(true); setText(draft.text || ""); setAttach(true); setRefOverride(draft.ref || null); onConsumeDraft && onConsumeDraft(); } }, [draft]);
  const add = () => { const t = text.trim(); if (!t) return; setNotes([{ id: Date.now(), text: t, ref: attach ? (refOverride || whereRef) : null, when: new Date().toLocaleDateString("en-NZ", { day: "numeric", month: "short" }) }, ...notes]); setText(""); setComposing(false); setAttach(true); setRefOverride(null); };
  const term = q.trim().toLowerCase();
  const fnotes = notes.filter((n) => !term || ((n.text || "") + " " + (n.ref || "")).toLowerCase().indexOf(term) !== -1);
  const fhl = (highlightList || []).filter((h) => !term || h.ref.toLowerCase().indexOf(term) !== -1);
  const accBtn = { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: "transparent", border: "none", padding: "14px 0", cursor: "pointer" };
  return (
    <React.Fragment>
      <div style={{ flex: "none", display: "flex", alignItems: "center", justifyContent: "space-between", padding: "8px 20px 12px" }}>
        <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)" }}>Notes</div>
        <button onClick={() => { setEditId(null); setText(""); setRefOverride(null); setAttach(true); setComposing((c) => !c); }} style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "var(--brand)", color: "#fff", border: "none", borderRadius: 999, padding: "8px 14px", fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13, cursor: "pointer" }}><Ic d={pi.plus} size={16} sw={2.2} />New</button>
      </div>
      <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "0 20px 24px" }}>
        {composing && <div style={{ marginBottom: 16 }}>
          <button onClick={() => setAttach((a) => !a)} style={{ display: "inline-flex", alignItems: "center", gap: 9, marginBottom: 12, background: "transparent", border: "none", cursor: "pointer", padding: 0 }}>
            <span style={{ width: 22, height: 22, borderRadius: "50%", border: "1.5px solid " + (attach ? "var(--brand)" : "var(--olive-300)"), background: attach ? "var(--brand)" : "transparent", color: "#fff", display: "grid", placeItems: "center", flexShrink: 0 }}>{attach && <Ic d={pi.check} size={13} sw={2.4} />}</span>
            <span style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-muted)" }}>Attach reference &middot; <b style={{ color: "var(--olive-700)", fontWeight: 600 }}>{refOverride || whereRef}</b></span>
          </button>
          <textarea ref={taRef} value={text} onChange={(e) => setText(e.target.value)} placeholder="Write a note…" style={{ width: "100%", minHeight: 240, resize: "none", border: "1px solid var(--parchment-edge)", borderRadius: 12, background: "var(--exp-ink-05)", padding: 12, fontFamily: "var(--font-serif)", fontSize: 16, lineHeight: 1.5, color: "var(--text-strong)", outline: "none", boxSizing: "border-box" }} />
          <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
            <button onClick={() => { setComposing(false); setText(""); setRefOverride(null); setEditId(null); }} style={{ flex: 1, background: "transparent", border: "1px solid var(--exp-ink-14)", borderRadius: 999, padding: 11, fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 14, color: "var(--text-muted)", cursor: "pointer" }}>Cancel</button>
            <button onClick={add} style={{ flex: 1, background: "var(--brand)", color: "#fff", border: "none", borderRadius: 999, padding: 11, fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 14, cursor: "pointer" }}>Save</button>
          </div>
        </div>}
        <div style={{ display: "flex", alignItems: "center", gap: 8, background: "var(--exp-ink-06)", borderRadius: 12, padding: "10px 14px", marginBottom: 10 }}>
          <span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Ic d={pi.search} size={17} sw={1.9} /></span>
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search notes and highlights" style={{ flex: 1, minWidth: 0, border: "none", background: "transparent", outline: "none", fontFamily: "var(--font-serif)", fontSize: 15, color: "var(--text-strong)" }} />
          {q && <button onClick={() => setQ("")} aria-label="Clear" style={{ border: "none", background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "inline-flex", flexShrink: 0 }}><Ic d={pi.close} size={15} sw={2} /></button>}
        </div>
        <div>
          <button onClick={() => setNotesOpen((o) => !o)} style={accBtn}><span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13, color: "var(--brand-text)" }}>Notes <span style={{ color: "var(--text-muted)", fontWeight: 600 }}>{fnotes.length}</span></span><span style={{ color: "var(--text-muted)", transform: notesOpen ? "rotate(180deg)" : "none", transition: "transform .2s", display: "inline-flex" }}><Ic d={pi.chevDown} size={18} /></span></button>
          <Collapse open={notesOpen}><div style={{ padding: "0 0 6px" }}>
            {fnotes.length ? fnotes.map((n) => (
              editId === n.id ? (
                <div key={n.id} style={{ borderTop: "1px solid var(--parchment-edge)", padding: "14px 0" }}>
                  {n.ref && <div style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 13, color: "var(--olive-700)", marginBottom: 6 }}>{n.ref}</div>}
                  <textarea ref={editRef} value={editText} onChange={(e) => setEditText(e.target.value)} style={{ width: "100%", minHeight: 210, resize: "none", border: "1px solid var(--parchment-edge)", borderRadius: 12, background: "var(--exp-ink-05)", padding: 12, fontFamily: "var(--font-serif)", fontSize: 16, lineHeight: 1.5, color: "var(--text-strong)", outline: "none", boxSizing: "border-box" }} />
                  <div style={{ display: "flex", gap: 8, marginTop: 10 }}>
                    <button onClick={() => setEditId(null)} style={{ flex: 1, background: "transparent", border: "1px solid var(--exp-ink-14)", borderRadius: 999, padding: 10, fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 14, color: "var(--text-muted)", cursor: "pointer" }}>Cancel</button>
                    <button onClick={saveEdit} style={{ flex: 1, background: "var(--brand)", color: "#fff", border: "none", borderRadius: 999, padding: 10, fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 14, cursor: "pointer" }}>Save</button>
                  </div>
                </div>
              ) : (
                <div key={n.id} style={{ borderTop: "1px solid var(--parchment-edge)", padding: "14px 0", display: "flex", gap: 12, alignItems: "flex-start" }}>
                  <div style={{ flex: 1 }}>
                    {n.ref && <div style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 13, color: "var(--olive-700)", marginBottom: 3 }}>{n.ref}</div>}
                    <div style={{ fontFamily: "var(--font-serif)", fontSize: 16, lineHeight: 1.5, color: "var(--text-body)", whiteSpace: "pre-wrap" }}>{n.text}</div>
                    <div style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-muted)", marginTop: 4 }}>{n.when}</div>
                  </div>
                  <div style={{ display: "flex", alignItems: "center", gap: 2, flexShrink: 0 }}>
                    <button onClick={() => startEdit(n)} aria-label="Edit note" style={{ border: "none", background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "inline-flex", padding: 4 }}><Ic d={pi.edit} size={15} sw={1.9} /></button>
                    <button onClick={() => setNotes(notes.filter((x) => x.id !== n.id))} aria-label="Delete note" style={{ border: "none", background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "inline-flex", padding: 4 }}><Ic d={pi.close} size={16} sw={2} /></button>
                  </div>
                </div>
              )
            )) : <div style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-muted)", padding: "6px 0 10px" }}>{term ? "No notes match your search." : "No notes yet. Tap New to write one."}</div>}
          </div></Collapse>
        </div>
        <div style={{ borderTop: "1px solid var(--parchment-edge)" }}>
          <button onClick={() => setHlOpen((o) => !o)} style={accBtn}><span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13, color: "var(--brand-text)" }}>Highlights <span style={{ color: "var(--text-muted)", fontWeight: 600 }}>{fhl.length}</span></span><span style={{ color: "var(--text-muted)", transform: hlOpen ? "rotate(180deg)" : "none", transition: "transform .2s", display: "inline-flex" }}><Ic d={pi.chevDown} size={18} /></span></button>
          <Collapse open={hlOpen}><div style={{ padding: "0 0 6px" }}>
            {fhl.length ? fhl.map((h) => (
              <div key={h.ref} style={{ display: "flex", alignItems: "center", gap: 12, padding: "11px 0", borderTop: "1px solid var(--parchment-edge)" }}>
                <span style={{ width: 22, height: 22, borderRadius: "50%", background: h.color, border: "1px solid var(--exp-ink-10)", flexShrink: 0 }} />
                <button onClick={() => onJumpRef(h.ref)} style={{ flex: 1, textAlign: "left", background: "transparent", border: "none", cursor: "pointer", fontFamily: "var(--font-serif)", fontSize: 16, color: "var(--text-body)", padding: 0 }}>{h.ref}</button>
                <button onClick={() => onRemoveHighlight(h.ref)} aria-label="Remove highlight" style={{ border: "none", background: "transparent", color: "var(--text-muted)", cursor: "pointer", display: "inline-flex", flexShrink: 0, padding: 4 }}><Ic d={pi.close} size={15} sw={2} /></button>
              </div>
            )) : <div style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-muted)", padding: "6px 0 10px" }}>{term ? "No highlights match your search." : "No highlights yet. Long-press a verse to highlight it."}</div>}
          </div></Collapse>
        </div>
      </div>
    </React.Fragment>
  );
}

// Privacy — the TheoCore privacy policy (verbatim), adapted to parchment.
function PrivacySheet({ onBack }) {
  const { Ic, pi } = window.LukePhoneKit;
  const P = window.LukePrivacy || { updated: "", html: "" };
  return (
    <React.Fragment>
      <div style={{ flex: "none", display: "flex", alignItems: "center", gap: 6, padding: "8px 20px 10px" }}>
        <button onClick={onBack} aria-label="Back" style={{ width: 34, height: 34, borderRadius: "50%", border: "none", background: "transparent", color: "var(--text-body)", cursor: "pointer", display: "grid", placeItems: "center", marginLeft: -6 }}><Ic d={pi.chevL} size={22} sw={2} /></button>
        <div><div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)" }}>Privacy policy</div><div style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-muted)" }}>{P.updated}</div></div>
      </div>
      <div className="lw-privacy" style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "4px 22px 26px" }}>
        <style>{".lw-privacy h2{font-family:var(--font-display);font-weight:700;font-size:16px;color:var(--text-strong);margin:22px 0 8px;line-height:1.3}.lw-privacy .num{color:var(--olive-600)}.lw-privacy p{font-family:var(--font-serif);font-size:15px;line-height:1.6;color:var(--text-body);margin:0 0 12px}.lw-privacy ul{margin:0 0 12px;padding-left:20px}.lw-privacy li{font-family:var(--font-serif);font-size:15px;line-height:1.55;color:var(--text-body);margin:0 0 6px}.lw-privacy a{color:var(--olive-700)}.lw-privacy strong{font-weight:600}"}</style>
        <div dangerouslySetInnerHTML={{ __html: P.html }} />
      </div>
    </React.Fragment>
  );
}

// Smooth reveal — pushes content below down/up with buttery easing; card never jumps.
function Collapse({ open, children }) {
  const [render, setRender] = React.useState(open);
  const [show, setShow] = React.useState(open);
  React.useEffect(() => {
    let r1, r2, t;
    if (open) { setRender(true); r1 = requestAnimationFrame(() => { r2 = requestAnimationFrame(() => setShow(true)); }); }
    else { setShow(false); t = setTimeout(() => setRender(false), 480); }
    return () => { cancelAnimationFrame(r1); cancelAnimationFrame(r2); clearTimeout(t); };
  }, [open]);
  if (!render) return null;
  return (
    <div style={{ display: "grid", gridTemplateRows: show ? "1fr" : "0fr", opacity: show ? 1 : 0, transition: "grid-template-rows .48s cubic-bezier(.32,.72,0,1), opacity .36s ease" }}>
      <div style={{ minHeight: 0, overflow: "hidden" }}>{children}</div>
    </div>
  );
}

function BottomSheet({ open, onClose, children, tall, full, mid, swipeClose }) {
  const [mounted, setMounted] = React.useState(open);
  const [show, setShow] = React.useState(false);
  // Drag-to-dismiss (opt-in via swipeClose): a short downward finger-drag on the grip closes the sheet,
  // in addition to tap-away and tap-again. Dragging only from the grip keeps it clear of the scroll area.
  const [dragY, setDragY] = React.useState(0);
  const [dragging, setDragging] = React.useState(false);
  const dg = React.useRef({ active: false, y0: 0 });
  React.useEffect(() => {
    if (open) { setMounted(true); return; }
    setShow(false);
    const t = setTimeout(() => setMounted(false), 460);
    return () => clearTimeout(t);
  }, [open]);
  React.useEffect(() => {
    if (!mounted || !open) return;
    // Two frames: let the off-screen (103%) frame paint first, THEN slide up — keeps the entrance buttery.
    let id2; const id1 = requestAnimationFrame(() => { id2 = requestAnimationFrame(() => setShow(true)); });
    return () => { cancelAnimationFrame(id1); cancelAnimationFrame(id2); };
  }, [mounted, open]);
  React.useEffect(() => { if (open) { setDragY(0); setDragging(false); } }, [open]);
  if (!mounted) return null;
  const gripDown = (e) => { if (!swipeClose) return; dg.current = { active: true, y0: e.clientY }; setDragging(true); try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {} };
  const gripMove = (e) => { if (!dg.current.active) return; setDragY(Math.max(0, e.clientY - dg.current.y0)); };
  const gripUp = (e) => { if (!dg.current.active) return; dg.current.active = false; setDragging(false); const dy = e.clientY - (dg.current.y0 || e.clientY); if (dy > 90) onClose(); else setDragY(0); };
  const baseTransform = show ? ("translateY(" + dragY + "px)") : "translateY(103%)";
  const grip = swipeClose
    ? { onPointerDown: gripDown, onPointerMove: gripMove, onPointerUp: gripUp, onPointerCancel: gripUp, style: { padding: "8px 0 4px", margin: "0", flexShrink: 0, cursor: "grab", touchAction: "none" } }
    : { style: { flexShrink: 0 } };
  return (
    <React.Fragment>
      {/* pointerEvents follows `show`: the moment the sheet begins closing (or is caught mid-close when
          an installed PWA is backgrounded and its unmount timer is suspended) this backdrop stops
          intercepting taps, so it can never sit invisibly over the top bar and swallow every tap. */}
      <div onClick={onClose} style={{ position: "absolute", inset: 0, background: "rgba(38,44,23,.10)", opacity: show ? 1 : 0, transition: "opacity .3s ease", zIndex: 90, pointerEvents: show ? "auto" : "none" }} />
      <div style={{ position: "absolute", left: 0, right: 0, bottom: 0, zIndex: 95, height: full ? "88%" : mid ? "66%" : undefined, maxHeight: full ? "88%" : mid ? "66%" : tall ? "86%" : "auto", display: "flex", flexDirection: "column", pointerEvents: show ? "auto" : "none",
        transform: baseTransform, transition: dragging ? "none" : "transform .44s cubic-bezier(.32,.72,0,1)",
        background: "var(--exp-sheet)", backdropFilter: "blur(26px) saturate(1.4)", WebkitBackdropFilter: "blur(26px) saturate(1.4)",
        borderTop: "1px solid var(--exp-hair-soft)", borderRadius: "28px 28px 49px 49px", boxShadow: "0 -14px 50px rgba(30,28,20,.20), inset 0 1px 0 var(--exp-top)" }}>
        <div {...grip}><div style={{ width: 44, height: 5, borderRadius: 3, background: "var(--exp-ink-16)", margin: "10px auto 2px" }} /></div>
        {children}
      </div>
    </React.Fragment>
  );
}

// Deterministic display trim — NOT a summary. Returns the first line/sentence of a verbatim entry as
// the meaning cap, plus the exact remainder for "view more". No model touches this; it is pure string
// slicing over the stored Webster's text, so what the pastor reads is always Webster's own words.
function websterCap(text) {
  if (!text) return { cap: "", rest: "" };
  const t = String(text).trim();
  const nl = t.indexOf("\n");
  let cut = nl >= 0 ? nl : t.length;
  // If there's no newline, end the cap at the first sentence boundary that lands past the head-word
  // and part-of-speech (so "GRACE, n." isn't the whole meaning line).
  if (nl < 0) {
    const m = t.slice(30).search(/\.\s/);
    if (m >= 0) cut = Math.min(cut, 30 + m + 1);
  }
  return { cap: t.slice(0, cut).trim(), rest: t.slice(cut).trim() };
}

// Word glass — Webster's 1828 first (meaning cap + view more), Strong's original + cross-references,
// then Bible dictionaries (Easton's/Smith's) and Nave's one tap deeper. Every line is served verbatim
// from /api/read/word; nothing is generated. Tap the word again, tap away, or swipe the card down to close.
function WordGlass({ open, head, data, onClose, onXref }) {
  const { Ic, pi } = window.LukePhoneKit;
  const last = React.useRef(null);
  const loaded = (data && data !== "loading") ? data : null;
  if (loaded) last.current = loaded;
  const loading = data === "loading";
  // While a NEW word is loading, show THAT word's header + a loading state — NEVER the previous word's
  // data. last.current is reserved for the close animation only (data === null while the sheet slides
  // out), so the card fades out on its last content instead of blanking abruptly.
  const w = loaded || (loading ? null : last.current);
  const [tab, setTab] = React.useState(null); // 'orig' | 'xref' | 'dict' | null
  const [more, setMore] = React.useState(false); // Webster's "view more"
  // Reset the accordions/"view more" whenever a new word is tapped (head changes), so a new word never
  // opens with the previous word's expanded sections.
  React.useEffect(() => { if (open) { setTab(null); setMore(false); } }, [open, head]);
  const eyebrow = { fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 12, color: "var(--brand-text)", marginBottom: 6 };
  const row = { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: "transparent", border: "none", borderTop: "1px solid var(--parchment-edge)", padding: "14px 0 13px", cursor: "pointer", textAlign: "left" };
  const wc = websterCap(w && w.webster);
  const xrefs = (w && w.xrefs) || [];
  const dict = (w && w.dictionary) || [];
  const naves = (w && w.naves) || [];
  const deepCount = dict.length + naves.length;
  return (
    <BottomSheet open={open} onClose={onClose} swipeClose>
      <div style={{ padding: "6px 24px 30px", maxHeight: "72vh", overflowY: "auto" }}>
        {loading ? (
          <React.Fragment>
            <div style={{ display: "flex", alignItems: "flex-end", gap: 12, marginBottom: 4 }}>
              <span style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 30, color: "var(--text-strong)", letterSpacing: "-.01em" }}>{head}</span>
            </div>
            <div style={{ padding: "22px 0 8px", fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 17, color: "var(--text-muted)" }}>Looking that up&hellip;</div>
          </React.Fragment>
        ) : !w ? null : (<React.Fragment>
        <div style={{ display: "flex", alignItems: "flex-end", gap: 12, marginBottom: 4 }}>
          <span style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 30, color: "var(--text-strong)", letterSpacing: "-.01em" }}>{w.head}</span>
          {w.orig && <span style={{ fontFamily: "var(--font-serif-display)", fontStyle: "italic", fontSize: 19, color: "var(--olive-700)", paddingBottom: 3 }}>{w.orig}</span>}
          {w.tr && <span style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)", paddingBottom: 5 }}>{w.tr}</span>}
        </div>
        <div style={{ marginTop: 16 }}>
          <div style={eyebrow}>Webster's 1828</div>
          <div style={{ fontFamily: "var(--font-serif)", fontSize: 18, lineHeight: 1.55, color: w.webster ? "var(--text-body)" : "var(--text-muted)", whiteSpace: "pre-wrap" }}>{w.webster ? (more ? w.webster : wc.cap) : "No entry in Webster's 1828 for this word."}</div>
          {w.webster && wc.rest && <button onClick={() => setMore((m) => !m)} style={{ marginTop: 8, background: "transparent", border: "none", padding: 0, cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: 600, color: "var(--olive-700)" }}>{more ? "View less" : "View more"}</button>}
        </div>
        <div style={{ marginTop: 18 }}>
          {w.strong && (<React.Fragment>
          <button style={row} onClick={() => setTab(tab === "orig" ? null : "orig")}>
            <span><span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 14, color: "var(--text-strong)" }}>Original language</span>
              <span style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--olive-700)", marginLeft: 8 }}>Strong's {w.strong}</span></span>
            <span style={{ color: "var(--text-muted)", transform: tab === "orig" ? "rotate(180deg)" : "none", transition: "transform .2s" }}><Ic d={pi.chevDown} size={18} /></span>
          </button>
          {tab === "orig" && <div style={{ padding: "0 0 14px" }}>
            <div style={{ fontFamily: "var(--font-serif)", fontSize: 16, lineHeight: 1.5, color: "var(--text-body)" }}>{w.lang} <b style={{ fontWeight: 600 }}>{w.orig}</b>{w.tr ? " (" + w.tr + ")" : ""}, Strong's {w.strong}.{w.ambiguous ? " More than one original stands behind this word here." : ""}</div>
            {w.strongsDef && <div style={{ marginTop: 12 }}><div style={eyebrow}>Strong's definition</div><div style={{ fontFamily: "var(--font-serif)", fontSize: 16.5, lineHeight: 1.55, color: "var(--text-body)" }}>{w.strongsDef}.</div></div>}
          </div>}
          </React.Fragment>)}

          {xrefs.length > 0 && (<React.Fragment>
          <button style={row} onClick={() => setTab(tab === "xref" ? null : "xref")}>
            <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 14, color: "var(--text-strong)" }}>Cross-references <span style={{ color: "var(--text-muted)", fontWeight: 600 }}>{xrefs.length}</span></span>
            <span style={{ color: "var(--text-muted)", transform: tab === "xref" ? "rotate(180deg)" : "none", transition: "transform .2s" }}><Ic d={pi.chevDown} size={18} /></span>
          </button>
          {tab === "xref" && <div style={{ display: "flex", flexWrap: "wrap", gap: 8, padding: "0 0 14px" }}>
            {xrefs.map((x, i) => <button key={i} onClick={() => onXref && onXref(x)} style={{ fontFamily: "var(--font-sans)", fontSize: 14, fontWeight: 600, color: "var(--olive-700)", background: "transparent", border: "1px solid var(--exp-ink-16)", borderRadius: 999, padding: "6px 14px", cursor: "pointer" }}>{x}</button>)}</div>}
          </React.Fragment>)}

          {deepCount > 0 && (<React.Fragment>
          <button style={row} onClick={() => setTab(tab === "dict" ? null : "dict")}>
            <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 14, color: "var(--text-strong)" }}>Bible dictionary &amp; topics <span style={{ color: "var(--text-muted)", fontWeight: 600 }}>{deepCount}</span></span>
            <span style={{ color: "var(--text-muted)", transform: tab === "dict" ? "rotate(180deg)" : "none", transition: "transform .2s" }}><Ic d={pi.chevDown} size={18} /></span>
          </button>
          {tab === "dict" && <div style={{ padding: "2px 0 14px" }}>
            {dict.map((e, i) => (
              <div key={"d" + i} style={{ marginBottom: 14 }}>
                <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 12, color: "var(--brand-text)", marginBottom: 4 }}>{(e.source || "Dictionary") + (e.term ? " · " + e.term : "")}</div>
                <div style={{ fontFamily: "var(--font-serif)", fontSize: 15.5, lineHeight: 1.55, color: "var(--text-body)", whiteSpace: "pre-wrap" }}>{e.text}</div>
              </div>
            ))}
            {naves.map((e, i) => (
              <div key={"n" + i} style={{ marginBottom: 14 }}>
                <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 12, color: "var(--brand-text)", marginBottom: 4 }}>{"Nave's Topical" + (e.subject ? " · " + e.subject : "")}</div>
                <div style={{ fontFamily: "var(--font-serif)", fontSize: 15.5, lineHeight: 1.55, color: "var(--text-body)", whiteSpace: "pre-wrap" }}>{e.entry}</div>
              </div>
            ))}
          </div>}
          </React.Fragment>)}
        </div>
        <div style={{ fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--text-muted)", textAlign: "center", marginTop: 18 }}>Tap the word again, tap away, or swipe down to close.</div>
        </React.Fragment>)}
      </div>
    </BottomSheet>
  );
}

// Book + chapter picker — smooth book scroll, then a tap-a-number chapter grid (no more +50 taps).
function ChapterGrid({ max, value, onSelect }) {
  const chips = []; for (let i = 1; i <= max; i++) chips.push(i);
  return (
    <div style={{ display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 7 }}>
      {chips.map((i) => {
        const on = i === value;
        return (
          <button key={i} onClick={() => onSelect(i)} style={{ aspectRatio: "1 / 1", borderRadius: 9, border: "none", background: on ? "var(--brand)" : "var(--exp-ink-06)", color: on ? "#fff" : "var(--text-body)", fontFamily: "var(--font-serif)", fontSize: 14, cursor: "pointer" }}>{i}</button>
        );
      })}
    </div>
  );
}

// Book accordion — smooth book scroll; tap a book and its chapter grid drops down inline (no back button).
function BookAccordion({ D, book, chapter, onPick }) {
  const { Ic, pi } = window.LukePhoneKit;
  const inNT = !!D.BOOKS_NT.find((b) => b[0] === book);
  const [test, setTest] = React.useState(inNT ? "nt" : "ot");
  const [open, setOpen] = React.useState(book);
  const books = test === "ot" ? D.BOOKS_OT : D.BOOKS_NT;
  const openRef = React.useRef(null);
  React.useEffect(() => {
    let raf2; const raf1 = requestAnimationFrame(() => { raf2 = requestAnimationFrame(() => {
      const el = openRef.current; if (!el) return;
      let sc = el.parentElement;
      while (sc && !(/auto|scroll/.test(getComputedStyle(sc).overflowY) && sc.scrollHeight > sc.clientHeight + 4)) sc = sc.parentElement;
      if (!sc) return;
      sc.scrollTop += el.getBoundingClientRect().top - sc.getBoundingClientRect().top - 52;
    }); });
    return () => { cancelAnimationFrame(raf1); cancelAnimationFrame(raf2); };
  }, []);
  const seg = SEG;
  const segBtn = (on) => ({ flex: 1, border: "none", cursor: "pointer", borderRadius: 9, padding: "9px 8px", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13, background: on ? "var(--brand)" : "transparent", color: on ? "#fff" : "var(--text-muted)" });
  return (
    <div>
      <div style={{ position: "sticky", top: 0, zIndex: 2, background: "var(--exp-frost)", backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)", paddingTop: 4, paddingBottom: 10 }}><div style={seg}><button style={segBtn(test === "ot")} onClick={() => setTest("ot")}>Old Testament</button><button style={segBtn(test === "nt")} onClick={() => setTest("nt")}>New Testament</button></div></div>
      {books.map(([name, c], bi) => {
        const isOpen = open === name;
        return (
          <div key={name} ref={isOpen ? openRef : undefined} style={{ borderTop: bi === 0 ? "none" : "1px solid var(--parchment-edge)" }}>
            <button onClick={() => setOpen(isOpen ? null : name)} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", border: "none", background: "transparent", padding: "15px 2px", cursor: "pointer", textAlign: "left" }}>
              <span style={{ fontFamily: "var(--font-serif)", fontSize: 17, color: isOpen ? "var(--olive-700)" : "var(--text-body)", fontWeight: isOpen ? 600 : 400 }}>{name}</span>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 10 }}>
                <span style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-muted)" }}>{c} ch</span>
                <span style={{ color: "var(--text-muted)", transform: isOpen ? "rotate(180deg)" : "none", transition: "transform .2s", display: "inline-flex" }}><Ic d={pi.chevDown} size={16} /></span>
              </span>
            </button>
            <Collapse open={isOpen}><div style={{ padding: "0 2px 16px" }}><ChapterGrid max={c} value={name === book ? chapter : 0} onSelect={(ch) => onPick(name, ch)} /></div></Collapse>
          </div>
        );
      })}
    </div>
  );
}

// Reset — a two-tap confirm. Sends a plan back to day 1 (or wipes everything, per label).
function ResetRow({ onReset, label, confirmLabel }) {
  const [c, setC] = React.useState(false);
  return (
    <button onClick={() => { if (c) { onReset(); setC(false); } else setC(true); }} style={{ width: "100%", background: c ? "var(--danger)" : "transparent", border: "none", borderRadius: c ? 999 : 0, padding: "12px", fontFamily: "var(--font-sans)", fontSize: 13.5, fontWeight: 600, color: c ? "#fff" : "var(--text-muted)", cursor: "pointer", transition: "background .15s" }}>{c ? (confirmLabel || "Tap again to reset to day 1") : (label || "Reset plan to the beginning")}</button>
  );
}

// Day picker — the dated plans (year / 90-day): a calm stepper.
function DayPicker({ D, plan, setPlan, onDone }) {
  const TOTAL = D.BOOKS_OT.concat(D.BOOKS_NT).reduce((s, b) => s + b[1], 0);
  const total = plan.type === "ninety" ? Math.ceil(TOTAL / 13) : 365;
  const field = plan.type === "year" ? "yearDay" : plan.type === "mcheyne" ? "mcheyneDay" : "ninetyDay";
  const [d, setD] = React.useState(plan[field] || 1);
  const sBtn = { width: 46, height: 46, borderRadius: "50%", border: "none", background: "var(--exp-ink-08)", color: "var(--olive-700)", cursor: "pointer", fontFamily: "var(--font-serif)", fontSize: 26, lineHeight: 1 };
  return (
    <div style={{ padding: "6px 22px 26px" }}>
      <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)", marginBottom: 4 }}>Starting day</div>
      <div style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-muted)", marginBottom: 24 }}>Choose which day of the plan to begin on.</div>
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 26, marginBottom: 26 }}>
        <button style={sBtn} onClick={() => setD(Math.max(1, d - 1))}>&minus;</button>
        <div style={{ textAlign: "center", minWidth: 96 }}><div style={{ fontFamily: "var(--font-serif)", fontWeight: 600, fontSize: 32, color: "var(--olive-700)" }}>Day {d}</div><div style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)" }}>of {total}</div></div>
        <button style={sBtn} onClick={() => setD(Math.min(total, d + 1))}>+</button>
      </div>
      <button onClick={() => { setPlan({ ...plan, [field]: d }); onDone(); }} style={{ width: "100%", background: "var(--brand)", color: "#fff", border: "none", borderRadius: 999, padding: 15, fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 16, cursor: "pointer" }}>Set starting day</button>
      <div style={{ marginTop: 10 }}><ResetRow onReset={() => { setPlan({ ...plan, [field]: 1, history: [] }); onDone(); }} /></div>
    </div>
  );
}

// Start here — one screen. Book plans: supplement chooser + book accordion. Dated plans: the day stepper.
function StartHere({ D, plan, setPlan, onDone }) {
  const { Ic, pi } = window.LukePhoneKit;
  const [suppOpen, setSuppOpen] = React.useState(false);
  if (plan.type !== "book-by-book") return <DayPicker D={D} plan={plan} setPlan={setPlan} onDone={onDone} />;
  const seg = { ...SEG, padding: 3 };
  const segBtn = (on) => ({ border: "none", cursor: "pointer", borderRadius: 9, padding: "7px 14px", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 12, background: on ? "var(--brand)" : "transparent", color: on ? "#fff" : "var(--text-muted)" });
  const suppMax = plan.choice === "psalms" ? 150 : 31;
  const suppVal = plan.choice === "psalms" ? plan.psalmCh : plan.provCh;
  return (
    <React.Fragment>
      <div style={{ flex: "none", padding: "8px 20px 0" }}>
        <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)" }}>Where do you begin?</div>
        <div style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-muted)", marginTop: 3 }}>Tap a book, then a chapter.</div>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 14, marginBottom: 8 }}>
          <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 12, color: "var(--brand-text)" }}>Supplement</span>
          <div style={seg}>
            <button style={segBtn(plan.choice === "psalms")} onClick={() => setPlan({ ...plan, choice: "psalms" })}>Psalm</button>
            <button style={segBtn(plan.choice === "proverbs")} onClick={() => setPlan({ ...plan, choice: "proverbs" })}>Proverb</button>
          </div>
        </div>
        <div style={{ borderTop: "1px solid var(--parchment-edge)" }}>
          <button onClick={() => setSuppOpen((o) => !o)} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", border: "none", background: "transparent", padding: "15px 2px", cursor: "pointer", textAlign: "left" }}>
            <span style={{ fontFamily: "var(--font-serif)", fontSize: 17, color: suppOpen ? "var(--olive-700)" : "var(--text-body)", fontWeight: suppOpen ? 600 : 400 }}>{plan.choice === "psalms" ? "Psalm " : "Proverbs "}{suppVal}</span>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 10 }}>
              <span style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-muted)" }}>{suppMax} ch</span>
              <span style={{ color: "var(--text-muted)", transform: suppOpen ? "rotate(180deg)" : "none", transition: "transform .2s", display: "inline-flex" }}><Ic d={pi.chevDown} size={16} /></span>
            </span>
          </button>
          <Collapse open={suppOpen}><div style={{ maxHeight: 172, overflowY: "auto", padding: "0 2px 16px" }}><ChapterGrid max={suppMax} value={suppVal} onSelect={(c) => { setPlan(plan.choice === "psalms" ? { ...plan, psalmCh: c } : { ...plan, provCh: c }); setSuppOpen(false); }} /></div></Collapse>
        </div>
        <div style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 12, color: "var(--brand-text)", margin: "16px 0 0" }}>Main reading</div>
      </div>
      <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "0 20px 24px" }}>
        <BookAccordion D={D} book={plan.bbBook} chapter={plan.bbChapter} onPick={(b, c) => { setPlan({ ...plan, bbBook: b, bbChapter: c }); onDone(); }} />
      </div>
      <div style={{ flex: "none", borderTop: "1px solid var(--parchment-edge)", padding: "6px 12px 12px" }}><ResetRow onReset={() => { setPlan({ ...plan, bbBook: "Genesis", bbChapter: 1, psalmCh: 1, provCh: 1, history: [] }); onDone(); }} /></div>
    </React.Fragment>
  );
}

// Reading history — its own pushed view so the plan screen doesn't grow endlessly.
function HistoryView({ plan, onBack }) {
  const { Ic, pi } = window.LukePhoneKit;
  const list = plan.history || [];
  return (
    <React.Fragment>
      <div style={{ flex: "none", display: "flex", alignItems: "center", gap: 6, padding: "8px 20px 12px" }}>
        <button onClick={onBack} aria-label="Back" style={{ width: 34, height: 34, borderRadius: "50%", border: "none", background: "transparent", color: "var(--text-body)", cursor: "pointer", display: "grid", placeItems: "center", marginLeft: -6 }}><Ic d={pi.chevL} size={22} sw={2} /></button>
        <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)" }}>Reading history</div>
      </div>
      <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "0 20px 24px" }}>
        {list.length
          ? list.map((h, i) => (
              <div key={i} style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", padding: "13px 0", borderTop: i === 0 ? "none" : "1px solid var(--parchment-edge)" }}>
                <span style={{ fontFamily: "var(--font-serif)", fontSize: 16, color: "var(--text-body)" }}>{h.ref}</span>
                <span style={{ fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-muted)", flexShrink: 0, marginLeft: 12 }}>Day {h.day}</span>
              </div>))
          : <div style={{ fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-muted)", textAlign: "center", marginTop: 40 }}>Nothing marked complete yet. Finished readings will appear here.</div>}
      </div>
    </React.Fragment>
  );
}

// The reading-plan sheet — Plan / Start here tabs, plus a pushed history view.
function PlanSheet({ D, plan, setPlan, onJump, onListen }) {
  const [tab, setTab] = React.useState("plan");
  const [history, setHistory] = React.useState(false);
  const seg = SEG;
  const segBtn = (on) => ({ flex: 1, border: "none", cursor: "pointer", borderRadius: 9, padding: "10px 8px", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13, background: on ? "var(--brand)" : "transparent", color: on ? "#fff" : "var(--text-muted)" });
  if (history) return <HistoryView plan={plan} onBack={() => setHistory(false)} />;
  return (
    <React.Fragment>
      <div style={{ flex: "none", padding: "8px 20px 10px" }}>
        <div style={seg}>
          <button style={segBtn(tab === "plan")} onClick={() => setTab("plan")}>Plan</button>
          <button style={segBtn(tab === "start")} onClick={() => setTab("start")}>Start here</button>
        </div>
      </div>
      {tab === "plan"
        ? <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "4px 20px 26px" }}><PlanView D={D} plan={plan} setPlan={setPlan} onJump={onJump} onListen={onListen} onHistory={() => setHistory(true)} /></div>
        : <StartHere D={D} plan={plan} setPlan={setPlan} onDone={() => setTab("plan")} />}
    </React.Fragment>
  );
}

// Reader display — sized for 78-year-old eyes.
function SettingsSheet({ size, setSize, justify, setJustify, nums, setNums, vbv, setVbv, font, setFont, red, setRed, dark, setDark, version }) {
  const { Ic, pi } = window.LukePhoneKit;
  const [fontOpen, setFontOpen] = React.useState(false);
  const [verOpen, setVerOpen] = React.useState(false);
  const Toggle = ({ on, set }) => (
    <button onClick={() => set(!on)} style={{ width: 50, height: 30, borderRadius: 999, border: "none", cursor: "pointer", background: on ? "var(--brand)" : "var(--exp-ink-10)", border: on ? "1px solid transparent" : "1px solid var(--exp-hair-soft)", position: "relative", transition: "background .2s", flexShrink: 0 }}>
      <span style={{ position: "absolute", top: 3, left: on ? 23 : 3, width: 24, height: 24, borderRadius: "50%", background: "#fff", transition: "left .2s", boxShadow: "0 1px 3px rgba(0,0,0,.2)" }} />
    </button>
  );
  const rowS = { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "15px 0", borderTop: "1px solid var(--parchment-edge)" };
  const lbl = { fontFamily: "var(--font-serif)", fontSize: 17, color: "var(--text-strong)" };
  return (
    <div style={{ flex: 1, minHeight: 0, overflowY: "auto", padding: "6px 22px 26px" }}>
      <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)", marginBottom: 8 }}>Display</div>
      <div style={{ borderTop: "none" }}>
        <button style={{ ...rowS, border: "none", borderTop: "none", background: "transparent", width: "100%", cursor: "pointer" }} onClick={() => setVerOpen((o) => !o)}><span style={lbl}>Bible version</span><span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}><span style={{ fontFamily: "var(--font-sans)", fontSize: 15, color: "var(--olive-700)", fontWeight: 600 }}>{version}</span><span style={{ color: "var(--text-muted)", transform: verOpen ? "rotate(180deg)" : "none", transition: "transform .2s", display: "inline-flex" }}><Ic d={pi.chevDown} size={18} /></span></span></button>
        <Collapse open={verOpen}><div style={{ padding: "0 0 8px" }}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: "transparent", border: "1px solid var(--exp-ink-16)", borderRadius: 10, padding: "12px" }}>
            <span style={{ fontFamily: "var(--font-sans)", fontSize: 16, color: "var(--olive-700)", fontWeight: 600 }}>King James Version</span>
            <span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Ic d={pi.check} size={16} sw={2.2} /></span>
          </div>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)", padding: "10px 12px 4px" }}>More translations coming soon.</div>
        </div></Collapse>
      </div>
      <div style={rowS}><span style={lbl}>Dark theme</span><Toggle on={dark} set={setDark} /></div>
      <div style={{ padding: "15px 0", borderTop: "1px solid var(--parchment-edge)" }}>
        <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 10 }}><span style={lbl}>Text size</span><span style={{ fontFamily: "var(--font-sans)", fontSize: 14, color: "var(--text-muted)" }}>{size}px</span></div>
        <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <span style={{ fontFamily: "var(--font-serif)", fontSize: 15, color: "var(--text-muted)" }}>A</span>
          <input type="range" min="16" max="36" step="1" value={size} onChange={(e) => setSize(+e.target.value)} style={{ flex: 1, accentColor: "var(--brand)" }} />
          <span style={{ fontFamily: "var(--font-serif)", fontSize: 26, color: "var(--text-muted)" }}>A</span>
        </div>
      </div>
      <div style={rowS}><span style={lbl}>Justify text</span><Toggle on={justify} set={setJustify} /></div>
      <div style={rowS}><span style={lbl}>Verse numbers</span><Toggle on={nums} set={setNums} /></div>
      <div style={{ borderTop: "1px solid var(--parchment-edge)" }}>
        <button style={{ ...rowS, border: "none", borderTop: "none", background: "transparent", width: "100%", cursor: "pointer" }} onClick={() => setFontOpen((o) => !o)}><span style={lbl}>Reading font</span><span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}><span style={{ fontFamily: font === "garamond" ? "var(--font-serif-display)" : "var(--font-serif)", fontSize: 16, color: "var(--olive-700)" }}>{font === "garamond" ? "EB Garamond" : "Newsreader"}</span><span style={{ color: "var(--text-muted)", transform: fontOpen ? "rotate(180deg)" : "none", transition: "transform .2s", display: "inline-flex" }}><Ic d={pi.chevDown} size={18} /></span></span></button>
        <Collapse open={fontOpen}><div style={{ padding: "0 0 8px" }}>{[["newsreader", "Newsreader", "var(--font-serif)"], ["garamond", "EB Garamond", "var(--font-serif-display)"]].map(([id, name, ff]) => (
          <button key={id} onClick={() => { setFont(id); setFontOpen(false); }} style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: "transparent", border: "1px solid " + (font === id ? "var(--exp-ink-16)" : "transparent"), borderRadius: 10, padding: "12px", cursor: "pointer", textAlign: "left" }}>
            <span style={{ fontFamily: ff, fontSize: 18, color: font === id ? "var(--olive-700)" : "var(--text-body)" }}>{name}</span>
            {font === id && <span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Ic d={pi.check} size={16} sw={2.2} /></span>}
          </button>))}</div></Collapse>
      </div>
      <div style={rowS}><span style={lbl}>Verse by verse</span><Toggle on={vbv} set={setVbv} /></div>
      <div style={rowS}><span style={lbl}>Red letters</span><Toggle on={red} set={setRed} /></div>
      <div style={{ fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--text-faint)", marginTop: -8 }}>Words of Christ shown in red.</div>
    </div>
  );
}
// Adjust starting point removed — replaced by the Start here tab (BookChapterPicker / DayPicker).

// Today's plan — mirrors Bill's 3723 engine: a 4-chapter main range + one Psalm/Proverb, Book by book or Bible in a year.
function PlanView({ D, plan, setPlan, onJump, onListen, onHistory }) {
  const { Ic, pi } = window.LukePhoneKit;
  const ALL = D.BOOKS_OT.concat(D.BOOKS_NT);
  const NT = D.BOOKS_NT;
  const otBooks = D.BOOKS_OT.filter((b) => b[0] !== "Psalms" && b[0] !== "Proverbs");
  const TOTAL = ALL.reduce((s, b) => s + b[1], 0);
  const idxOf = (n) => ALL.findIndex((b) => b[0] === n);
  const pref = (s) => { const m = String(s).match(/^\s*(.+?)\s+(\d+)/); return m ? { book: m[1].trim(), chapter: +m[2] } : { book: String(s), chapter: 1 }; };
  const cidx = (book, ch) => { let i = 0; for (const [n, c] of ALL) { if (n === book) return i + ch; i += c; } return ch; };
  const fromIdx = (gi) => { let idx = gi; for (const [n, c] of ALL) { if (idx <= c) return { book: n, chapter: idx }; idx -= c; } const last = ALL[ALL.length - 1]; return { book: last[0], chapter: last[1] }; };
  const BG = { "Genesis": "Gen", "Exodus": "Exod", "Leviticus": "Lev", "Numbers": "Num", "Deuteronomy": "Deut", "Joshua": "Josh", "Judges": "Judg", "Ruth": "Ruth", "1 Samuel": "1Sam", "2 Samuel": "2Sam", "1 Kings": "1Kgs", "2 Kings": "2Kgs", "1 Chronicles": "1Chr", "2 Chronicles": "2Chr", "Ezra": "Ezra", "Nehemiah": "Neh", "Esther": "Esth", "Job": "Job", "Psalms": "Ps", "Proverbs": "Prov", "Ecclesiastes": "Eccl", "Song of Solomon": "Song", "Isaiah": "Isa", "Jeremiah": "Jer", "Lamentations": "Lam", "Ezekiel": "Ezek", "Daniel": "Dan", "Hosea": "Hos", "Joel": "Joel", "Amos": "Amos", "Obadiah": "Obad", "Jonah": "Jonah", "Micah": "Mic", "Nahum": "Nah", "Habakkuk": "Hab", "Zephaniah": "Zeph", "Haggai": "Hag", "Zechariah": "Zech", "Malachi": "Mal", "Matthew": "Matt", "Mark": "Mark", "Luke": "Luke", "John": "John", "Acts": "Acts", "Romans": "Rom", "1 Corinthians": "1Cor", "2 Corinthians": "2Cor", "Galatians": "Gal", "Ephesians": "Eph", "Philippians": "Phil", "Colossians": "Col", "1 Thessalonians": "1Thess", "2 Thessalonians": "2Thess", "1 Timothy": "1Tim", "2 Timothy": "2Tim", "Titus": "Titus", "Philemon": "Phlm", "Hebrews": "Heb", "James": "Jas", "1 Peter": "1Pet", "2 Peter": "2Pet", "1 John": "1John", "2 John": "2John", "3 John": "3John", "Jude": "Jude", "Revelation": "Rev" };
  const listen = (book, ch, label) => onListen(book, ch, label);
  const range = (book, startCh, count) => { let i = idxOf(book); if (i < 0) return book; let res = "", rem = count, ch = startCh; while (rem > 0 && i < ALL.length) { const [n, c] = ALL[i]; const end = Math.min(ch + rem - 1, c); if (ch === 1 && end === c) res += (res ? ", " : "") + n; else if (ch === end) res += (res ? ", " : "") + (n + " " + ch); else res += (res ? ", " : "") + (n + " " + ch + "-" + end); rem -= end - ch + 1; i++; ch = 1; } return res || book; };
  const advMain = (book, ch, count) => { let i = idxOf(book); if (i < 0) return { book: book, chapter: ch }; let c2 = ch + count; while (i < ALL.length && c2 > ALL[i][1]) { c2 -= ALL[i][1]; i++; } if (i >= ALL.length) return { book: "Genesis", chapter: 1 }; return { book: ALL[i][0], chapter: c2 }; };
  const yearOT = (day) => { let rem = (day - 1) * 3, bi = 0; while (bi < otBooks.length && rem >= otBooks[bi][1]) { rem -= otBooks[bi][1]; bi++; } if (bi >= otBooks.length) return { book: "Malachi", chapter: 4 }; return { book: otBooks[bi][0], chapter: rem + 1 }; };
  const yearNT = (day) => { let rem = day - 1, bi = 0; while (bi < NT.length && rem >= NT[bi][1]) { rem -= NT[bi][1]; bi++; } if (bi >= NT.length) { bi = 0; rem = 0; } return { book: NT[bi][0], chapter: rem + 1 }; };

  const bbk = plan.type === "book-by-book";
  const [done, setDone] = React.useState({});
  const [planOpen, setPlanOpen] = React.useState(false);
  const PLANS = [{ id: "book-by-book", name: "Book by book" }, { id: "mcheyne", name: "M'Cheyne (1 year)" }, { id: "year", name: "Bible in a year" }, { id: "ninety", name: "90-day whole Bible" }];
  let readings, dayNum, totalDays, frac, sub;
  if (bbk) {
    const suppBook = plan.choice === "psalms" ? "Psalms" : "Proverbs";
    const suppCh = plan.choice === "psalms" ? plan.psalmCh : plan.provCh;
    readings = [
      { key: "main", ref: range(plan.bbBook, plan.bbChapter, 4), start: { book: plan.bbBook, chapter: plan.bbChapter }, label: "4 chapters \u00b7 Main reading" },
      { key: "supp", ref: (plan.choice === "psalms" ? "Psalm " : "Proverbs ") + suppCh, start: { book: suppBook, chapter: suppCh }, label: "Daily supplement" },
    ];
    const gi = cidx(plan.bbBook, plan.bbChapter);
    dayNum = Math.floor((gi - 1) / 4) + 1; totalDays = Math.ceil(TOTAL / 4); frac = Math.min(1, (gi - 1) / TOTAL);
    sub = "Four chapters a day, plus a Psalm or a Proverb.";
  } else if (plan.type === "year") {
    const d = plan.yearDay, ps = ((d - 1) % 150) + 1, pv = ((d - 1) % 31) + 1;
    const ot = yearOT(d), nt = yearNT(d);
    readings = [
      { key: "ot", ref: range(ot.book, ot.chapter, 3), start: ot, label: "3 chapters \u00b7 Old Testament" },
      { key: "nt", ref: nt.book + " " + nt.chapter, start: nt, label: "1 chapter \u00b7 New Testament" },
      { key: "psalm", ref: "Psalm " + ps, start: { book: "Psalms", chapter: ps }, label: "Daily Psalm" },
      { key: "prov", ref: "Proverbs " + pv, start: { book: "Proverbs", chapter: pv }, label: "Daily Proverb" },
    ];
    dayNum = d; totalDays = 365; frac = Math.min(1, d / 365);
    sub = "The whole Bible in a year.";
  } else if (plan.type === "mcheyne") {
    const M = window.LukeMcheyne || [];
    const md = Math.min(plan.mcheyneDay || 1, M.length || 1);
    const subs = ["Family", "Family", "Secret", "Secret"];
    readings = (M[md - 1] || []).map((rf, i) => ({ key: "m" + i, ref: rf, start: pref(rf), label: subs[i] }));
    dayNum = md; totalDays = M.length || 365; frac = Math.min(1, md / (M.length || 365));
    sub = "Four readings a day \u00b7 family and secret.";
  } else {
    const d = plan.ninetyDay || 1, gi0 = (d - 1) * 13 + 1, st = fromIdx(gi0);
    readings = [{ key: "main", ref: range(st.book, st.chapter, 13), start: st, label: "13 chapters \u00b7 Whole Bible" }];
    dayNum = d; totalDays = Math.ceil(TOTAL / 13); frac = Math.min(1, (gi0 - 1) / TOTAL);
    sub = "The whole Bible in about 90 days.";
  }
  const doneCount = readings.filter((r) => done[r.key]).length;
  const allDone = doneCount === readings.length;
  const complete = () => {
    const hist = [{ ref: readings.map((r) => r.ref).join(" \u00b7 "), day: dayNum }, ...(plan.history || [])].slice(0, 60);
    let np;
    if (bbk) {
      const nm = advMain(plan.bbBook, plan.bbChapter, 4);
      let ps = plan.psalmCh, pv = plan.provCh;
      if (plan.choice === "psalms") ps = ps >= 150 ? 1 : ps + 1; else pv = pv >= 31 ? 1 : pv + 1;
      np = { ...plan, bbBook: nm.book, bbChapter: nm.chapter, psalmCh: ps, provCh: pv };
    } else if (plan.type === "year") {
      np = { ...plan, yearDay: plan.yearDay >= 365 ? 1 : plan.yearDay + 1 };
    } else if (plan.type === "mcheyne") {
      const mt = (window.LukeMcheyne || []).length || 365; const mc = plan.mcheyneDay || 1;
      np = { ...plan, mcheyneDay: mc >= mt ? 1 : mc + 1 };
    } else {
      const total = Math.ceil(TOTAL / 13); const cur = plan.ninetyDay || 1;
      np = { ...plan, ninetyDay: cur >= total ? 1 : cur + 1 };
    }
    setPlan({ ...np, history: hist });
    setDone({});
  };
  const resetPlan = () => {
    if (bbk) setPlan({ ...plan, bbBook: "Genesis", bbChapter: 1, psalmCh: 1, provCh: 1, history: [] });
    else if (plan.type === "year") setPlan({ ...plan, yearDay: 1, history: [] });
    else if (plan.type === "mcheyne") setPlan({ ...plan, mcheyneDay: 1, history: [] });
    else setPlan({ ...plan, ninetyDay: 1, history: [] });
    setDone({});
  };
  const setType = (t) => { setDone({}); setPlan({ ...plan, type: t }); };
  const seg = SEG;
  const segBtn = (on) => ({ flex: 1, border: "none", cursor: "pointer", borderRadius: 9, padding: "9px 8px", fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 13, background: on ? "var(--brand)" : "transparent", color: on ? "#fff" : "var(--text-muted)" });
  const Play = () => <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" style={{ display: "block" }}><path d="M8 5v14l11-7z" /></svg>;
  const Row = ({ r }) => {
    const on = !!done[r.key];
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 12, borderTop: "1px solid var(--parchment-edge)", padding: "12px 0" }}>
        <button onClick={() => setDone((d) => ({ ...d, [r.key]: !d[r.key] }))} aria-label={on ? "Mark unread" : "Mark read"} style={{ flexShrink: 0, width: 26, height: 26, borderRadius: "50%", border: "1.5px solid " + (on ? "var(--brand)" : "var(--olive-300)"), background: on ? "var(--brand)" : "transparent", color: "#fff", display: "grid", placeItems: "center", cursor: "pointer" }}>{on && <Ic d={pi.check} size={15} sw={2.4} />}</button>
        <button onClick={() => onJump(r.start)} style={{ flex: 1, background: "transparent", border: "none", cursor: "pointer", padding: 0, textAlign: "left" }}>
          <div style={{ fontFamily: "var(--font-serif)", fontSize: 18, color: on ? "var(--text-muted)" : "var(--text-strong)", textDecoration: on ? "line-through" : "none" }}>{r.ref}</div>
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--text-muted)", marginTop: 2 }}>{r.label}</div>
        </button>
        <button onClick={(e) => { e.stopPropagation(); listen(r.start.book, r.start.chapter, r.ref); }} aria-label={"Listen to " + r.ref} style={{ flexShrink: 0, display: "inline-flex", alignItems: "center", gap: 5, background: "transparent", color: "var(--olive-700)", border: "1.5px solid var(--brand)", borderRadius: 999, padding: "7px 12px", fontFamily: "var(--font-sans)", fontSize: 12.5, fontWeight: 600, cursor: "pointer" }}><Play />Listen</button>
      </div>
    );
  };
  return (
    <React.Fragment>
      <div style={{ fontFamily: "var(--font-display)", fontSize: 22, fontWeight: 700, color: "var(--text-strong)", marginTop: 2 }}>My reading plan</div>
      <div style={{ fontFamily: "var(--font-sans)", fontSize: 13.5, color: "var(--text-muted)", marginTop: 3, marginBottom: 14 }}>{sub}</div>
      <div style={{ borderTop: "1px solid var(--parchment-edge)", borderBottom: "1px solid var(--parchment-edge)", marginBottom: 14 }}>
        <button onClick={() => setPlanOpen((o) => !o)} style={{ width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between", background: "transparent", border: "none", padding: "15px 0", cursor: "pointer" }}>
          <span style={{ fontFamily: "var(--font-serif)", fontSize: 17, color: "var(--text-strong)" }}>Reading plan</span>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}><span style={{ fontFamily: "var(--font-sans)", fontSize: 15, color: "var(--olive-700)", fontWeight: 600 }}>{(PLANS.find((p) => p.id === plan.type) || PLANS[0]).name}</span><span style={{ color: "var(--text-muted)", transform: planOpen ? "rotate(180deg)" : "none", transition: "transform .2s", display: "inline-flex" }}><Ic d={pi.chevDown} size={18} /></span></span>
        </button>
        <Collapse open={planOpen}><div style={{ padding: "0 0 8px" }}>
          {PLANS.map((p) => (
            <button key={p.id} onClick={() => { setType(p.id); setPlanOpen(false); }} style={{ width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between", background: "transparent", border: "1px solid " + (p.id === plan.type ? "var(--exp-ink-16)" : "transparent"), borderRadius: 10, padding: "12px", cursor: "pointer", textAlign: "left" }}>
              <span style={{ fontFamily: "var(--font-serif)", fontSize: 16, color: p.id === plan.type ? "var(--olive-700)" : "var(--text-body)", fontWeight: p.id === plan.type ? 600 : 400 }}>{p.name}</span>
              {p.id === plan.type && <span style={{ color: "var(--text-muted)", display: "inline-flex" }}><Ic d={pi.check} size={16} sw={2.2} /></span>}
            </button>
          ))}
        </div></Collapse>
      </div>
      <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", marginBottom: 6 }}>
        <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13, color: "var(--brand-text)" }}>Day {dayNum} <span style={{ fontFamily: "var(--font-sans)", fontWeight: 400, color: "var(--text-muted)" }}>of {totalDays}</span></span>
        <span style={{ fontFamily: "var(--font-sans)", fontSize: 12.5, fontWeight: 600, color: allDone ? "var(--olive-700)" : "var(--text-muted)" }}>{doneCount} of {readings.length} read</span>
      </div>
      <div style={{ height: 8, borderRadius: 999, background: "var(--exp-ink-10)", border: "1px solid var(--exp-hair-soft)", boxShadow: "inset 0 1px 2px rgba(30,28,20,0.07)", overflow: "hidden", marginBottom: 8 }}><div style={{ width: (frac * 100) + "%", height: "100%", background: "var(--brand)", borderRadius: 999, transition: "width .3s" }} /></div>
      {readings.map((r) => <Row key={r.key} r={r} />)}
      {bbk && (
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 14 }}>
          <span style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)" }}>Supplement</span>
          <div style={{ ...seg, width: "auto", padding: 3 }}>
            <button style={{ ...segBtn(plan.choice === "psalms"), padding: "7px 14px", fontSize: 12 }} onClick={() => setPlan({ ...plan, choice: "psalms" })}>Psalm</button>
            <button style={{ ...segBtn(plan.choice === "proverbs"), padding: "7px 14px", fontSize: 12 }} onClick={() => setPlan({ ...plan, choice: "proverbs" })}>Proverb</button>
          </div>
        </div>
      )}
      <button onClick={complete} style={{ marginTop: 20, width: "100%", background: allDone ? "var(--brand)" : "transparent", color: allDone ? "#fff" : "var(--olive-700)", border: allDone ? "1px solid transparent" : "1.5px solid var(--brand)", borderRadius: 999, padding: 15, fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 16, cursor: "pointer", transition: "background .2s" }}>Mark today complete</button>
      <button onClick={onHistory} style={{ marginTop: 22, width: "100%", display: "flex", alignItems: "center", justifyContent: "space-between", background: "transparent", border: "1.5px solid var(--brand)", borderRadius: 12, padding: "14px 16px", cursor: "pointer" }}>
        <span style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 13, color: "var(--brand-text)" }}>Reading history</span>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 8, fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)" }}>{(plan.history || []).length ? plan.history.length + (plan.history.length === 1 ? " day" : " days") : "None yet"}<Ic d={pi.chevR} size={16} /></span>
      </button>
      <div style={{ marginTop: 8 }}><ResetRow onReset={resetPlan} /></div>
    </React.Fragment>
  );
}

window.LukeReader = LukeReader;
