// book-app.jsx — The Bound Book, rebuilt as an app. WIRED TO THE BACKEND.
// Horizontal swipeable card deck. One chapter per card. Sparse, native-feeling.
// Bilingual EN/AR via the V2.useTG() hook from v2-bridge.js — every visible
// string runs through t('english', 'العربية'); the active language is
// chosen by the floating LangToggle button.

const { useState, useRef, useEffect } = React;

/* ════════════════════════════════════════════════════════════
   Error boundary — catches React crashes so the screen never
   just goes black with no info. Invisible during normal use.
   ════════════════════════════════════════════════════════════ */

class CrashBoundary extends React.Component {
  constructor(p) { super(p); this.state = { err: null }; }
  static getDerivedStateFromError(err) { return { err }; }
  componentDidCatch(err, info) {
    console.error('[CrashBoundary] React crashed:', err?.message, info?.componentStack);
  }
  render() {
    if (this.state.err) {
      return (
        <div style={{ position: 'fixed', inset: 0, background: '#1a1410', color: '#ffb0a0',
                      padding: 24, overflow: 'auto', zIndex: 9999,
                      fontFamily: 'monospace', fontSize: 12 }}>
          <h3 style={{ color: '#ff8a8a' }}>The page hit an error</h3>
          <p>Take a screenshot of this and send it to Mo.</p>
          <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
            {String(this.state.err?.message || this.state.err)}
          </pre>
          {this.state.err?.stack && (
            <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontSize: 10, opacity: 0.7 }}>
              {this.state.err.stack}
            </pre>
          )}
          <button onClick={() => location.reload()}
                  style={{ marginTop: 16, padding: '10px 20px', background: '#6B4423',
                           color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>
            Reload
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

/* ════════════════════════════════════════════════════════════
   Fallback seed copy — only used when the server has 0 items.
   ════════════════════════════════════════════════════════════ */

const SEED_REASONS = [
  { en: 'The way your laugh fills a room before you do.',     ar: 'الطريقة التي تملأ بها ضحكتك الغرفة قبل أن تدخليها.' },
  { en: 'The way you say my name, twice, when you mean it.',   ar: 'الطريقة التي تنادينني بها باسمي، مرّتين، حين تعنين ذلك.' },
  { en: 'How safe the world becomes when you are next to me.', ar: 'كم يصبح العالم آمناً عندما تكونين بجانبي.' },
  { en: 'Your hand, in mine, in front of strangers.',          ar: 'يدُكِ، في يدي، أمام الغرباء.' },
  { en: 'The Tuesday on your balcony, repeated, forever.',     ar: 'يوم الثلاثاء على شُرفتكِ، يتكرّر، إلى الأبد.' },
  { en: 'That you let me finish my sentence, every time.',     ar: 'أنّكِ تتركينني أُتمّ جملتي، في كل مرة.' },
];

const SEED_PHOTOS = [
  { kind: 'sunset',   cap: 'Sunset at the Corniche.',  capAr: 'غروب على الكورنيش.' },
  { kind: 'city',     cap: 'Old city, the morning after.', capAr: 'البلدة القديمة، صباح اليوم التالي.' },
  { kind: 'garden',   cap: 'The garden in December.',  capAr: 'الحديقة في ديسمبر.' },
  { kind: 'sea',      cap: 'Sea, after the storm.',    capAr: 'البحر، بعد العاصفة.' },
  { kind: 'portrait', cap: 'You, reading.',            capAr: 'أنتِ، تقرئين.' },
];

const SEED_HOUSE = [
  { col: 'year',    num: 'I',   roof: 'gable', items: [
    { text_en: 'Spend a weekend in the old city.',   text_ar: 'نقضي عطلة في المدينة القديمة.' },
    { text_en: 'Start the Sunday breakfast ritual.', text_ar: 'نبدأ تقليد فطور الأحد.' },
    { text_en: 'Frame the first photograph.',        text_ar: 'نُؤطّر أول صورة.' },
  ]},
  { col: 'five',    num: 'II',  roof: 'hip', items: [
    { text_en: 'Two visas, one passport queue.',     text_ar: 'تأشيرتان، وطابور جوازات واحد.' },
    { text_en: 'A kitchen with morning light.',      text_ar: 'مطبخ بضوء الصباح.' },
    { text_en: 'Northern lights, together.',         text_ar: 'الشفق القطبي، معاً.' },
  ]},
  { col: 'forever', num: 'III', roof: 'dome', items: [
    { text_en: 'A door that always opens onto you.', text_ar: 'باب يُفتح دائماً عليكِ.' },
    { text_en: 'The same vows, at every age.',       text_ar: 'نفس الوعود، في كل عمر.' },
    { text_en: 'A long, ordinary life.',             text_ar: 'حياة طويلة، عادية.' },
  ]},
];

/* ════════════════════════════════════════════════════════════
   Floating UI overlays: language toggle + push toggle
   ════════════════════════════════════════════════════════════ */

function FloatingControls() {
  const [lang, setLang] = V2.useLang();
  const push = V2.usePushToggle();
  const t = V2.useTG();
  const { unlocked } = V2.useAuth();

  const onLogout = () => {
    if (confirm(t('Log out and re-enter the password?', 'تسجيل الخروج وإدخال الكلمة مرة أخرى؟'))) {
      V2.logout();
    }
  };

  return (
    <div style={{
      position: 'fixed', top: 14, right: 14, zIndex: 200,
      display: 'flex', gap: 8, alignItems: 'center'
    }}>
      {push.supported && (
        <button onClick={push.toggle}
                title={push.subscribed ? 'Notifications on' : 'Tap to enable notifications'}
                style={pillBtn}>
          {push.subscribed ? '🔔' : '🔕'}
        </button>
      )}
      <button onClick={() => setLang(lang === 'en' ? 'ar' : 'en')}
              title="Switch language"
              style={pillBtn}>
        {lang === 'en' ? 'ع' : 'EN'}
      </button>
      {unlocked && (
        <button onClick={onLogout}
                title={t('Log out', 'تسجيل الخروج')}
                style={pillBtn}>
          ⎋
        </button>
      )}
    </div>
  );
}

const pillBtn = {
  background: 'rgba(0,0,0,0.55)',
  border: '1px solid rgba(197,160,94,0.5)',
  color: '#C5A05E',
  width: 38, height: 38, borderRadius: '50%',
  fontSize: 14, fontFamily: "'EB Garamond', serif",
  cursor: 'pointer', backdropFilter: 'blur(6px)',
  WebkitBackdropFilter: 'blur(6px)',
  display: 'flex', alignItems: 'center', justifyContent: 'center',
  letterSpacing: '0.02em',
};

// Helper to apply RTL to text containers when Arabic.
const dirOf = (lang) => lang === 'ar' ? 'rtl' : 'ltr';

/* ════════════════════════════════════════════════════════════
   Device detection — show iPhone frame on desktop preview only
   ════════════════════════════════════════════════════════════ */

function useIsPhone() {
  const [phone, setPhone] = useState(() => window.matchMedia('(max-width: 700px)').matches);
  useEffect(() => {
    const mq = window.matchMedia('(max-width: 700px)');
    const handler = (e) => setPhone(e.matches);
    if (mq.addEventListener) mq.addEventListener('change', handler);
    else mq.addListener(handler);
    return () => {
      if (mq.removeEventListener) mq.removeEventListener('change', handler);
      else mq.removeListener(handler);
    };
  }, []);
  return phone;
}

function PhoneShell({ children }) {
  const isPhone = useIsPhone();
  const t = V2.useTG();
  if (isPhone) {
    return (
      <div style={{ position: 'fixed', inset: 0, background: '#0a0e1a', overflow: 'hidden' }}>
        <FloatingControls />
        {children}
      </div>
    );
  }
  return (
    <div className="phone-stage">
      <FloatingControls />
      <div className="phone-stage__header">
        <h1 className="phone-stage__title">M <em>&amp;</em> D · <em>{t('on phone', 'على الهاتف')}</em></h1>
        <p className="phone-stage__sub">{t('Swipe ➜ to move between chapters', 'اسحب ⬅ للتنقّل بين الفصول', 'اسحبي ⬅ للتنقّل بين الفصول')}</p>
      </div>
      <div className="phone-stage__label">{t('iPhone · swipe deck (desktop preview)', 'آيفون · معاينة')}</div>
      <IOSDevice width={402} height={874} dark={true}>
        {children}
      </IOSDevice>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Login gate
   ════════════════════════════════════════════════════════════ */

function LoginGate({ children }) {
  const { unlocked, login, pickIdentity, identity } = V2.useAuth();
  const t = V2.useTG();
  const [who, setWho] = useState(identity || null);
  const [password, setPassword] = useState('');
  const [error, setError] = useState(null);
  const [busy, setBusy] = useState(false);
  const inputRef = useRef(null);

  if (unlocked && who) return children;

  if (!who) {
    return (
      <PhoneShell>
        <div className="app__card app-c1" style={{ justifyContent: 'center', gap: 24 }}>
          <div className="app__stars" />
          <div className="app-c1__mono" style={{ fontSize: 80 }}>M <span className="app-c1__amp">&amp;</span> D</div>
          <div className="app-c1__rule" />
          <div className="app-c1__sub" dir={dirOf(t.lang)}>
            {t("who's visiting?", 'من الزائر؟')}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14, alignItems: 'center', marginTop: 20 }}>
            <button className="app-c7__send" style={{ width: 180 }} onClick={() => { setWho('mo');   pickIdentity('mo');   }}>
              {t('I am Mo', 'أنا محمد')}
            </button>
            <button className="app-c7__send" style={{ width: 180 }} onClick={() => { setWho('duha'); pickIdentity('duha'); }}>
              {t('I am Duha', 'أنا ضحى')}
            </button>
          </div>
        </div>
      </PhoneShell>
    );
  }

  const onSubmit = async (e) => {
    e?.preventDefault?.();
    setBusy(true); setError(null);
    const ok = await login(password, who);
    setBusy(false);
    if (!ok) {
      setError(t("That's not the word.", 'هذه ليست كلمتنا.'));
      setPassword('');
      setTimeout(() => inputRef.current?.focus(), 100);
    }
  };

  return (
    <PhoneShell>
      <div className="app__card app-c1" style={{ justifyContent: 'center', gap: 18 }}>
        <div className="app__stars" />
        <div className="app-c1__mono" style={{ fontSize: 64 }}>M <span className="app-c1__amp">&amp;</span> D</div>
        <div className="app-c1__rule" />
        <div className="app-c1__sub" dir={dirOf(t.lang)} style={{ marginBottom: 0 }}>
          {error || t("what's our word?", 'ما كلمتنا؟')}
        </div>
        <form onSubmit={onSubmit} style={{ display: 'flex', flexDirection: 'column', gap: 14, alignItems: 'center', width: '80%' }}>
          <input ref={inputRef} type="password"
                 value={password} onChange={(e) => setPassword(e.target.value)}
                 autoFocus className="app-c7__input"
                 placeholder="•••••"
                 style={{ textAlign: 'center', letterSpacing: '0.2em' }} />
          <button type="submit" className="app-c7__send" disabled={busy || !password}>
            {busy ? t('opening…', 'جارٍ الفتح…') : t('Enter', 'ادخل', 'ادخلي')}
          </button>
          <button type="button" onClick={() => setWho(null)}
                  style={{ background: 'none', border: 'none', color: '#C5A05E', opacity: 0.6,
                           fontSize: 11, letterSpacing: '0.1em', cursor: 'pointer', marginTop: 8 }}>
            {t('← change identity', '← تغيير الهوية')}
          </button>
        </form>
      </div>
    </PhoneShell>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 1 — Cover (static)
   ════════════════════════════════════════════════════════════ */

function Card1() {
  const t = V2.useTG();
  return (
    <div className="app__card app-c1">
      <div className="app__stars" />
      <div>
        <div className="app-c1__mono">M <span className="app-c1__amp">&amp;</span> D</div>
      </div>
      <div className="app-c1__rule" />
      <div dir={dirOf(t.lang)}>
        <div className="app-c1__sub">{t('a private universe', 'عالَم خاصّ')}</div>
      </div>
      <div className="app-c1__date">{t('since 5 · 10 · 2023', 'منذ ٥ · ١٠ · ٢٠٢٣')}</div>
      <div className="app-c1__swipe">{t('swipe  ➜', 'اسحب ⬅', 'اسحبي ⬅')}</div>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 2 — Days (live count, lang-aware)
   ════════════════════════════════════════════════════════════ */

function Card2() {
  const t = V2.useTG();
  const days = V2.useDays();
  const { hours, years } = V2.useHoursYears(days);
  return (
    <div className="app__card app-c2" dir={dirOf(t.lang)}>
      <div className="app__stars" />
      <div className="app-c2__kicker">{t('Together', 'معاً')}</div>
      <div className="app-c2__num">{days}</div>
      <div className="app-c2__lbl">{t('days, and counting.', 'يوم، والعدّ مستمرّ.')}</div>
      <div className="app-c2__ticker" dir="ltr">
        <div className="app-c2__ticker-cell">
          <div className="app-c2__ticker-val">{hours.toLocaleString()}</div>
          <div className="app-c2__ticker-lbl">{t('Hours', 'ساعة')}</div>
        </div>
        <div className="app-c2__ticker-cell">
          <div className="app-c2__ticker-val">{years.toFixed(2)}</div>
          <div className="app-c2__ticker-lbl">{t('Years', 'سنة')}</div>
        </div>
      </div>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 3 — A reason
   ════════════════════════════════════════════════════════════ */

function Card3() {
  const t = V2.useTG();
  const liveReasons = V2.useReasons();
  const list = (liveReasons || []).map(r => ({ en: r.text_en || '', ar: r.text_ar || '' }));
  const [i, setI] = useState(0);
  const [showAdd, setShowAdd] = useState(false);
  const [draftEn, setDraftEn] = useState('');
  const [draftAr, setDraftAr] = useState('');

  useEffect(() => { if (i >= list.length) setI(0); }, [list.length]);

  const r = list[i] || { en: '', ar: '' };

  // Empty state — no reasons yet. Show a gentle prompt with the "+ add" button.
  if (list.length === 0 && !showAdd) {
    return (
      <div className="app__card app-c3" dir={dirOf(t.lang)}>
        <div className="app__stars" />
        <div className="app-c3__kicker">{t('Why', 'لماذا')}</div>
        <div className="app-c3__mark">"</div>
        <div className="app-c3__quote" style={{ opacity: 0.7, fontStyle: 'italic' }}>
          {t('No reasons yet. Add the first one.', 'لا توجد أسباب بعد. أضف الأول.', 'لا توجد أسباب بعد. أضيفي الأول.')}
        </div>
        <button className="app-c3__next" onClick={() => setShowAdd(true)}>
          {t('+ Add a reason', '+ أضف سبباً', '+ أضيفي سبباً')}
        </button>
      </div>
    );
  }

  const onSave = async () => {
    if (!draftEn && !draftAr) { setShowAdd(false); return; }
    await V2.addReason(draftEn, draftAr);
    setDraftEn(''); setDraftAr(''); setShowAdd(false);
  };

  if (showAdd) {
    return (
      <div className="app__card app-c3" dir={dirOf(t.lang)}>
        <div className="app__stars" />
        <div className="app-c3__kicker">{t('A new reason', 'سبب جديد')}</div>
        <div className="app-c7__form" style={{ marginTop: 30 }}>
          <div className="app-c7__field">
            <div className="app-c7__lbl">{t('In English', 'بالإنجليزية')}</div>
            <input className="app-c7__input" value={draftEn} onChange={e => setDraftEn(e.target.value)}
                   placeholder="Because…" autoFocus />
          </div>
          <div className="app-c7__field">
            <div className="app-c7__lbl">{t('بالعربية', 'بالعربية')}</div>
            <input className="app-c7__input" value={draftAr} onChange={e => setDraftAr(e.target.value)}
                   placeholder="لأن…" dir="rtl" />
          </div>
          <button className="app-c7__send" onClick={onSave}>{t('Add ✦', 'إضافة ✦')}</button>
          <button onClick={() => setShowAdd(false)} className="app-c5__nav-btn" style={{ width: 'auto', marginTop: 8 }}>
            {t('cancel', 'إلغاء')}
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="app__card app-c3" dir={dirOf(t.lang)}>
      <div className="app__stars" />
      <div className="app-c3__kicker">{t('Why', 'لماذا')}</div>
      <div className="app-c3__mark">"</div>
      <div className="app-c3__quote">{t.lang === 'ar' ? `«${r.ar}»` : r.en}</div>
      <div className="app-c3__num">
        {t.lang === 'ar'
          ? `سبب ${i + 1} من ${list.length}`
          : `№ ${String(i + 1).padStart(2, '0')} of ${list.length}`}
      </div>
      <button className="app-c3__next" onClick={() => setI((i + 1) % list.length)}>
        {t('Tap for another', 'انقر لسبب آخر', 'انقري لسبب آخر')}
      </button>
      <button className="app-c5__nav-btn"
              style={{ width: 'auto', padding: '6px 18px', marginTop: 12, fontSize: 11 }}
              onClick={() => setShowAdd(true)}>
        {t('+ add a reason', '+ أضف سبباً', '+ أضيفي سبباً')}
      </button>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 4 — A letter
   ════════════════════════════════════════════════════════════ */

function Card4() {
  const t = V2.useTG();
  const letters = V2.useLetters();
  const sorted = [...letters].sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0));
  const latest = sorted[0] || null;
  const [writing, setWriting] = useState(false);
  const [draftTitle, setDraftTitle] = useState('');
  const [draftBody, setDraftBody] = useState('');

  const onSave = async () => {
    if (!draftBody.trim()) { setWriting(false); return; }
    await V2.addLetter(t.lang === 'ar'
      ? { title_ar: draftTitle, body_ar: draftBody }
      : { title_en: draftTitle, body_en: draftBody });
    setDraftTitle(''); setDraftBody(''); setWriting(false);
  };

  if (writing) {
    return (
      <div className="app__card app-c4" style={{ overflow: 'auto' }} dir={dirOf(t.lang)}>
        <div className="app-c4__head">
          <span>{t('New letter', 'رسالة جديدة')}</span>
          <span>{new Date().toLocaleDateString()}</span>
        </div>
        <h2 className="app-c4__title">{
          t.lang === 'ar'
            ? (t.gender === 'f'
                ? <>اكتبي له، <em>الآن.</em></>     /* Duha → writing to Mo */
                : <>اكتب لها، <em>الآن.</em></>)   /* Mo → writing to Duha */
            : <>Write to her, <em>now.</em></>}</h2>
        <div className="app-c4__rule" />
        <div className="app-c4__body">
          <input value={draftTitle} onChange={e => setDraftTitle(e.target.value)}
                 className="app-c7__input"
                 placeholder={t('Title — what is this letter for?', 'العنوان — لأجل ماذا هذه الرسالة؟')}
                 style={{ marginBottom: 12 }} />
          <textarea value={draftBody} onChange={e => setDraftBody(e.target.value)}
                    placeholder={t.gender === 'f'
                      ? t('My Mo, …', 'يا محمد، …')
                      : t('My Duha, …', 'يا ضحى، …')}
                    style={{ width: '100%', minHeight: 180, padding: 12,
                             background: '#fffdf6', border: '1px solid #d6c8a3', borderRadius: 4,
                             fontFamily: t.lang === 'ar' ? "'Amiri', serif" : "'EB Garamond', serif",
                             fontSize: 15, lineHeight: 1.7, color: '#3a2a18', resize: 'vertical' }} />
        </div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'center', marginTop: 16 }}>
          <button className="app-c5__nav-btn" style={{ width: 'auto', padding: '8px 20px' }} onClick={() => setWriting(false)}>
            {t('cancel', 'إلغاء')}
          </button>
          <button className="app-c7__send" onClick={onSave} style={{ width: 'auto', padding: '10px 24px' }}>
            {t('Seal ✦', 'اختم ✦', 'اختمي ✦')}
          </button>
        </div>
      </div>
    );
  }

  const title = (t.lang === 'ar' ? latest?.title_ar : latest?.title_en) || latest?.title_en || latest?.title_ar
                || t('For when you find this.', 'لحينما تجدين هذا.');
  // Empty-state placeholder body. Addresses the ACTIVE user (the one reading),
  // so the writer/sender flips: if Mo is reading, Duha is the future writer; vice versa.
  const emptyBodyEn = t.gender === 'f'
    ? 'My Duha,\n\nThis page is waiting for you. Whenever Mo writes you a letter through the site, it appears here.\n\n— Mo'
    : 'My Mo,\n\nThis page is waiting for you. Whenever Duha writes you a letter through the site, it appears here.\n\n— Duha';
  const emptyBodyAr = t.gender === 'f'
    ? 'يا ضحى،\n\nهذه الصفحة تنتظركِ. حين يكتب لكِ محمد رسالة من خلال الموقع، تظهر هنا.\n\n— محمد'
    : 'يا محمد،\n\nهذه الصفحة تنتظرك. حين تكتب لك ضحى رسالة من خلال الموقع، تظهر هنا.\n\n— ضحى';
  const body = (t.lang === 'ar' ? latest?.body_ar : latest?.body_en) || latest?.body_en || latest?.body_ar
                || (t.lang === 'ar' ? emptyBodyAr : emptyBodyEn);
  const dateLabel = latest?.addedAt
    ? new Date(latest.addedAt).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
    : t('unwritten', 'لم تُكتب بعد');

  return (
    <div className="app__card app-c4" style={{ overflow: 'auto' }} dir={dirOf(t.lang)}>
      <div className="app-c4__head">
        <span>{t.lang === 'ar' ? `رسالة ${sorted.length > 0 ? '١' : '—'} من ${sorted.length || 'كثيرة'}` :
                                   <>Letter <em>{sorted.length > 0 ? 'I' : '—'}</em>  ·  of {sorted.length || 'many'}</>}</span>
        <span>{dateLabel}</span>
      </div>
      <h2 className="app-c4__title">{title}</h2>
      <div className="app-c4__rule" />
      <div className="app-c4__body" style={{ fontFamily: t.lang === 'ar' ? "'Amiri', serif" : undefined }}>
        {body.split('\n').filter(Boolean).map((p, i) => <p key={i}>{p}</p>)}
      </div>
      <div className="app-c4__sig">
        <div className="app-c4__sig-name">{latest?.from === 'duha' ? t('Duha', 'ضحى') : t('Mo', 'محمد')}</div>
      </div>
      <div className="app-c4__seal">{latest?.from === 'duha' ? 'D' : 'M'}</div>

      <button className="app-c5__nav-btn"
              style={{ width: 'auto', padding: '8px 22px', marginTop: 18, fontSize: 12,
                       border: '1px solid #8b6f47', color: '#6b4423' }}
              onClick={() => setWriting(true)}>
        {t('+ write a new letter', '+ اكتب رسالة جديدة', '+ اكتبي رسالة جديدة')}
      </button>

      {sorted.length > 1 && (
        <div className="app-c4__list">
          <div className="app-c4__list-head">{t('More letters', 'رسائل أخرى')}</div>
          {sorted.slice(1, 5).map((l, i) => (
            <div key={l.id || i} className="app-c4__list-row">
              <div className="app-c4__list-row-top">
                <span><span className="app-c4__list-num">{['II','III','IV','V'][i] || (i+2)}.</span>
                      <span className="app-c4__list-title">
                        {(t.lang === 'ar' ? l.title_ar : l.title_en) || l.title_en || l.title_ar || t('untitled', 'بلا عنوان')}
                      </span></span>
                <span className="app-c4__list-date">{new Date(l.addedAt).toLocaleDateString()}</span>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 5 — A voice
   ════════════════════════════════════════════════════════════ */

function Card5() {
  const t = V2.useTG();
  const notes = V2.useVoiceNotes();
  const sorted = [...notes].sort((a, b) => (b.recordedAt || b.addedAt || 0) - (a.recordedAt || a.addedAt || 0));
  const [i, setI] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [stage, setStage] = useState('idle');
  const [recordedBlob, setRecordedBlob] = useState(null);
  const [titleDraft, setTitleDraft] = useState('');
  const audioRef = useRef(null);
  const rec = V2.useRecorder();

  useEffect(() => { if (i >= sorted.length) setI(0); }, [sorted.length]);

  const v = sorted[i];
  const url = V2.useImageUrl(v?.audioRef);

  useEffect(() => {
    if (audioRef.current) {
      audioRef.current.pause();
      audioRef.current.currentTime = 0;
    }
    setPlaying(false);
  }, [i, url]);

  const togglePlay = () => {
    if (!url) return;
    const el = audioRef.current;
    if (!el) return;
    if (playing) { el.pause(); setPlaying(false); }
    else { el.play().then(() => setPlaying(true)).catch(() => setPlaying(false)); }
  };

  const startRec = async () => {
    const ok = await rec.start();
    if (ok) setStage('recording');
  };
  const stopRec = async () => {
    const blob = await rec.stop();
    if (blob) { setRecordedBlob(blob); setStage('naming'); }
    else { setStage('idle'); }
  };
  const saveRec = async () => {
    await V2.saveVoiceNote(recordedBlob, titleDraft || t('untitled', 'بلا عنوان'));
    setRecordedBlob(null); setTitleDraft(''); setStage('idle');
  };

  if (stage === 'recording') {
    return (
      <div className="app__card app-c5" dir={dirOf(t.lang)}>
        <div className="app__stars" />
        <div className="app-c5__kicker">{t('Recording…', 'جارٍ التسجيل…')}</div>
        <div className="app-c5__disc app-c5__disc--spinning">
          <div className="app-c5__disc-center">●</div>
        </div>
        <div className="app-c5__title">{t('speak gently', 'تحدّث بهدوء', 'تحدّثي بهدوء')}</div>
        <div className="app-c5__meta">{t('tap to finish', 'انقر لإنهاء التسجيل', 'انقري لإنهاء التسجيل')}</div>
        <button className="app-c7__send" onClick={stopRec} style={{ marginTop: 20 }}>{t('Stop  ●', 'إيقاف ●')}</button>
      </div>
    );
  }

  if (stage === 'naming') {
    return (
      <div className="app__card app-c5" dir={dirOf(t.lang)}>
        <div className="app__stars" />
        <div className="app-c5__kicker">{t('Name it', 'سمّها', 'سمّيها')}</div>
        <div className="app-c5__disc">
          <div className="app-c5__disc-center">✓</div>
        </div>
        <input className="app-c7__input" autoFocus
               style={{ width: '76%', textAlign: 'center', marginTop: 18 }}
               value={titleDraft} onChange={e => setTitleDraft(e.target.value)}
               placeholder={t.gender === 'f'
                 ? t('e.g. "For your hardest days"', 'مثلاً: "لأصعب أيامكَ"')  /* Duha → addresses Mo (m) */
                 : t('e.g. "For your hardest days"', 'مثلاً: "لأصعب أيامكِ"')} /* Mo → addresses Duha (f) */
               />
        <button className="app-c7__send" onClick={saveRec} style={{ marginTop: 12 }}>{t('Save ✦', 'احفظ ✦', 'احفظي ✦')}</button>
        <button className="app-c5__nav-btn" style={{ width: 'auto', marginTop: 10 }}
                onClick={() => { setRecordedBlob(null); setStage('idle'); }}>
          {t('discard', 'تجاهل', 'تجاهلي')}
        </button>
      </div>
    );
  }

  return (
    <div className="app__card app-c5" dir={dirOf(t.lang)}>
      <div className="app__stars" />
      <div className="app-c5__kicker">{t('Voices', 'أصوات')}</div>
      <div className={`app-c5__disc ${playing ? 'app-c5__disc--spinning' : ''}`} onClick={togglePlay}>
        <div className="app-c5__disc-center">{playing ? '❚❚' : '▶'}</div>
      </div>
      {v ? (
        <>
          <div className="app-c5__title">"{(t.lang === 'ar' ? v.title_ar : v.title_en) || v.title_en || v.title_ar || t('untitled', 'بلا عنوان')}"</div>
          <div className="app-c5__meta" dir="ltr">№ {String(i + 1).padStart(2, '0')}  ·  {new Date(v.recordedAt || v.addedAt).toLocaleDateString()}</div>
        </>
      ) : (
        <>
          <div className="app-c5__title">{t('no voices yet', 'لا توجد أصوات بعد')}</div>
          <div className="app-c5__meta">{t('tap below to record the first', 'انقر أدناه لتسجيل الأول', 'انقري أدناه لتسجيل الأول')}</div>
        </>
      )}

      {url && (
        <audio ref={audioRef} src={url}
               onEnded={() => setPlaying(false)}
               onPause={() => setPlaying(false)} />
      )}

      <div className="app-c5__next">
        {t.lang === 'ar' ? (
          <>
            <button className="app-c5__nav-btn" disabled={sorted.length < 2}
                    onClick={() => setI((i + 1) % sorted.length)}>‹</button>
            <span>{sorted.length === 0 ? '—' : `${i + 1} من ${sorted.length}`}</span>
            <button className="app-c5__nav-btn" disabled={sorted.length < 2}
                    onClick={() => setI((i - 1 + sorted.length) % sorted.length)}>›</button>
          </>
        ) : (
          <>
            <button className="app-c5__nav-btn" disabled={sorted.length < 2}
                    onClick={() => setI((i - 1 + sorted.length) % sorted.length)}>‹</button>
            <span>{sorted.length === 0 ? '—' : `${i + 1} of ${sorted.length}`}</span>
            <button className="app-c5__nav-btn" disabled={sorted.length < 2}
                    onClick={() => setI((i + 1) % sorted.length)}>›</button>
          </>
        )}
      </div>

      <button className="app-c7__send" onClick={startRec} style={{ marginTop: 16, padding: '10px 24px' }}>
        {t('● record new', '● تسجيل جديد')}
      </button>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 6 — A photograph
   ════════════════════════════════════════════════════════════ */

function PhotoSVG({ kind }) {
  const palettes = {
    sunset:   { sky: '#f4c098', glow: '#e88c5b', land: '#3a1f2a' },
    city:     { sky: '#2a3554', glow: '#5a6b8c', land: '#0d1426' },
    garden:   { sky: '#3a5240', glow: '#5a7858', land: '#1a2818' },
    sea:      { sky: '#5a96b8', glow: '#7ab0c8', land: '#1a384a' },
    portrait: { sky: '#3a2a38', glow: '#5a3a4a', land: '#2a1a28' },
  };
  const p = palettes[kind] || palettes.sunset;
  return (
    <svg viewBox="0 0 400 800" preserveAspectRatio="xMidYMid slice" style={{ width: '100%', height: '100%' }}>
      <defs>
        <linearGradient id={`pg-${kind}`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%"   stopColor={p.sky} />
          <stop offset="55%"  stopColor={p.glow} />
          <stop offset="100%" stopColor={p.land} />
        </linearGradient>
      </defs>
      <rect width="400" height="800" fill={`url(#pg-${kind})`} />
    </svg>
  );
}

function RealPhoto({ photo }) {
  const url = V2.useImageUrl(photo.imageRef || photo.url);
  const [imgError, setImgError] = useState(false);
  const [imgLoaded, setImgLoaded] = useState(false);

  // Resolving step (waiting for useImageUrl to return a URL).
  if (!url) {
    return (
      <div style={{ width: '100%', height: '100%',
                    background: 'linear-gradient(180deg, #2a1f15 0%, #1a1410 100%)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    color: '#8B6F47', fontSize: 11, fontStyle: 'italic',
                    fontFamily: "'EB Garamond', serif" }}>
        resolving photo…
      </div>
    );
  }

  // Image errored out — show why so we don't just go black.
  if (imgError) {
    return (
      <div style={{ width: '100%', height: '100%',
                    background: '#1a1410', padding: 20, color: '#ffb0a0',
                    display: 'flex', flexDirection: 'column',
                    alignItems: 'center', justifyContent: 'center',
                    fontFamily: 'monospace', fontSize: 10, textAlign: 'center' }}>
        <div style={{ fontSize: 24, marginBottom: 8 }}>⚠</div>
        <div>Image failed to load</div>
        <div style={{ opacity: 0.6, marginTop: 6, wordBreak: 'break-all', maxWidth: 280 }}>{url}</div>
      </div>
    );
  }

  return (
    <>
      {/* Lighter "loading" backdrop until the image fully decodes — so a
          slow load doesn't look like a black screen. */}
      {!imgLoaded && (
        <div style={{ position: 'absolute', inset: 0,
                      background: 'linear-gradient(180deg, #2a1f15 0%, #1a1410 100%)',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      color: '#8B6F47', fontSize: 11, fontStyle: 'italic' }}>
          loading photo…
        </div>
      )}
      <img src={url} alt={photo.caption_en || ''}
           onLoad={() => setImgLoaded(true)}
           onError={() => { console.error('[RealPhoto] <img> errored for url:', url); setImgError(true); }}
           style={{
             width: '100%', height: '100%',
             objectFit: 'contain',
             background: '#1a1410',
             display: 'block',
             position: 'relative', zIndex: 1,
           }} />
    </>
  );
}

function Card6() {
  const t = V2.useTG();
  const realPhotos = V2.usePhotos();
  const [uploading, setUploading] = useState(false);
  const [uploadError, setUploadError] = useState(null);
  const photos = realPhotos || [];
  const [i, setI] = useState(0);
  useEffect(() => { if (i >= photos.length) setI(0); }, [photos.length]);
  const p = photos[i];

  // Generic onChange handler used by both empty and non-empty file inputs.
  // CRITICAL iOS Safari fix: the <input> is wrapped inside a visible <label>
  // (not triggered programmatically via .click() on a hidden input). iOS
  // refuses to open the file picker for programmatically-clicked hidden inputs.
  const handleUpload = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploading(true);
    setUploadError(null);
    try {
      await V2.uploadPhoto(file);
    } catch (err) {
      console.error('[Card6] upload failed:', err);
      setUploadError(err?.message || String(err) || 'Upload failed');
      // Show the user — alert is intentionally loud so they don't think nothing happened
      alert(t(
        'Upload failed: ' + (err?.message || err),
        'فشل الرفع: ' + (err?.message || err)
      ));
    }
    setUploading(false);
    e.target.value = ''; // allow re-selecting the same file
  };

  // Empty state — no photos yet. Show prompt with upload button.
  if (photos.length === 0) {
    return (
      <div className="app__card app-c6" dir={dirOf(t.lang)}>
        <div className="app-c6__photo" style={{ background: '#1a1410',
                                                 display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <div style={{ textAlign: 'center', color: '#8B6F47' }}>
            <div style={{ fontSize: 48, opacity: 0.4, marginBottom: 12 }}>📷</div>
            <div style={{ fontSize: 13, opacity: 0.8, fontStyle: 'italic' }}>
              {t('No photographs yet.', 'لا توجد صور بعد.')}
            </div>
            {uploadError && (
              <div style={{ fontSize: 11, opacity: 0.7, color: '#ff8a8a', marginTop: 8, maxWidth: 260 }}>
                {uploadError}
              </div>
            )}
          </div>
        </div>
        <div className="app-c6__top"><span>{t('Plates', 'لوحات')}</span><span>00 / 00</span></div>
        <div className="app-c6__overlay">
          {/* iOS-safe upload trigger.
              The <input> is positioned absolutely INSIDE the label, covering
              the whole label area at opacity 0. Tapping anywhere on the label
              hits the input directly — bypasses any iOS quirk around
              programmatic .click() on off-screen inputs. Works on every
              browser, every device.  */}
          <label className="app-c5__nav-btn"
                 style={{ width: 'auto', marginTop: 10, padding: '10px 20px', fontSize: 12,
                          background: 'rgba(0,0,0,0.4)', borderColor: 'rgba(197,160,94,0.5)',
                          cursor: 'pointer', display: 'inline-block', position: 'relative',
                          opacity: uploading ? 0.5 : 1 }}>
            {uploading
              ? t('uploading…', 'جارٍ الرفع…')
              : t('+ upload the first photograph', '+ ارفع أول صورة', '+ ارفعي أول صورة')}
            <input type="file" accept="image/*" onChange={handleUpload}
                   disabled={uploading}
                   style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%',
                            opacity: 0, cursor: 'pointer', fontSize: 0 }} />
          </label>
        </div>
      </div>
    );
  }

  const useReal = true;

  const caption = useReal
    ? ((t.lang === 'ar' ? p.caption_ar : p.caption_en) || p.caption_en || p.caption_ar || t('untitled', 'بلا عنوان'))
    : (t.lang === 'ar' ? p.capAr : p.cap);

  return (
    <div className="app__card app-c6" dir={dirOf(t.lang)}>
      <div className="app-c6__photo">
        {useReal ? <RealPhoto photo={p} /> : <PhotoSVG kind={p.kind} />}
      </div>
      <div className="app-c6__top">
        <span>{t('Plates', 'لوحات')}</span>
        <span dir="ltr">{String(i+1).padStart(2,'0')} / {photos.length}</span>
      </div>
      <div className="app-c6__overlay">
        <div className="app-c6__num" dir="ltr">№ {String(i + 1).padStart(2, '0')}</div>
        <div className="app-c6__caption">{caption}</div>
        {/* iOS-safe upload: see comment in Card6 empty-state branch. */}
        <label className="app-c5__nav-btn"
               style={{ width: 'auto', marginTop: 10, padding: '10px 20px', fontSize: 12,
                        background: 'rgba(0,0,0,0.4)', borderColor: 'rgba(197,160,94,0.5)',
                        cursor: 'pointer', display: 'inline-block', position: 'relative',
                        opacity: uploading ? 0.5 : 1 }}>
          {uploading ? t('uploading…', 'جارٍ الرفع…') : t('+ add a photograph', '+ أضف صورة', '+ أضيفي صورة')}
          <input type="file" accept="image/*" onChange={handleUpload}
                 disabled={uploading}
                 style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%',
                          opacity: 0, cursor: 'pointer', fontSize: 0 }} />
        </label>
        {uploadError && (
          <div style={{ fontSize: 10, color: '#ff8a8a', marginTop: 6, maxWidth: 260, textAlign: 'center' }}>
            {uploadError}
          </div>
        )}
      </div>
      <div className="app-c6__nav">
        {/* Arrows follow reading direction:
            LTR: ‹prev on left, next› on right
            RTL: ›prev on right (visually back),  ‹next on left (visually forward)
            Both arrow CHARACTERS and button POSITIONS swap. */}
        {t.lang === 'ar' ? (
          <>
            <button className="app-c6__nav-btn" disabled={photos.length < 2}
                    onClick={() => setI((i + 1) % photos.length)}>‹</button>
            <button className="app-c6__nav-btn" disabled={photos.length < 2}
                    onClick={() => setI((i - 1 + photos.length) % photos.length)}>›</button>
          </>
        ) : (
          <>
            <button className="app-c6__nav-btn" disabled={photos.length < 2}
                    onClick={() => setI((i - 1 + photos.length) % photos.length)}>‹</button>
            <button className="app-c6__nav-btn" disabled={photos.length < 2}
                    onClick={() => setI((i + 1) % photos.length)}>›</button>
          </>
        )}
      </div>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 7 — The Bell
   ════════════════════════════════════════════════════════════ */

function Card7() {
  const t = V2.useTG();
  const notifs = V2.useNotifications();
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [sending, setSending] = useState(false);
  const [sentOK, setSentOK]   = useState(false);

  const onSend = async () => {
    if (!title.trim() && !body.trim()) return;
    setSending(true);
    try {
      await V2.sendBell({ title, body });
      setSentOK(true);
      setTitle(''); setBody('');
      setTimeout(() => setSentOK(false), 2400);
    } catch (err) {
      alert(t('Could not send. Check your connection and try again.',
              'تعذّر الإرسال. تحقّقي من الاتصال وأعيدي المحاولة.'));
    }
    setSending(false);
  };

  const sortedRings = [...notifs].sort((a, b) =>
    (b.sentAt || b.addedAt || 0) - (a.sentAt || a.addedAt || 0)).slice(0, 6);

  return (
    <div className="app__card app-c7" style={{ overflow: 'auto' }} dir={dirOf(t.lang)}>
      <div className="app__stars" />
      <div className="app-c7__kicker">{t('Notifications', 'إشعارات')}</div>
      <h2 className="app-c7__title">
        {t.lang === 'ar'
          ? (t.gender === 'f'
              ? <>اقرعي <em>الجرس.</em></>
              : <>اقرع <em>الجرس.</em></>)
          : <>Ring the <em>bell.</em></>}
      </h2>

      <div className="app-c7__form">
        <div className="app-c7__field">
          <div className="app-c7__lbl">{t('Title', 'العنوان')}</div>
          <input className="app-c7__input" value={title} onChange={e => setTitle(e.target.value)}
                 maxLength={60}
                 placeholder={t.gender === 'f'
                   ? t('Thinking of you ✦', 'أفكّر بكَ ✦')   /* Duha thinking of Mo */
                   : t('Thinking of you ✦', 'أفكّر بكِ ✦')} /* Mo thinking of Duha */
                 />
        </div>
        <div className="app-c7__field">
          <div className="app-c7__lbl">{t('Message', 'الرسالة')}</div>
          <input className="app-c7__input" value={body} onChange={e => setBody(e.target.value)}
                 maxLength={200} placeholder={t('a small thing, for the bell —', 'شيء صغير، للجرس —')} />
        </div>
        <button className="app-c7__send" onClick={onSend}
                disabled={sending || (!title.trim() && !body.trim())}>
          {sending ? t('Ringing…', 'جارٍ القرع…') : sentOK ? t('Rang ♥', 'قُرع ♥') : t('Ring  ✦', 'اقرع ✦', 'اقرعي ✦')}
        </button>
      </div>

      {sortedRings.length > 0 && (
        <div className="app-c7__log">
          <div className="app-c7__log-head">{t('Recent rings', 'الرسائل الأخيرة')}</div>
          {sortedRings.map((r, i) => (
            <div key={r.id || i} className="app-c7__log-row">
              <div className="app-c7__log-top">
                <span className="app-c7__log-who">
                  {(r.sentBy || r.addedBy) === 'mo' ? t('Mo', 'محمد') : t('Duha', 'ضحى')}
                </span>
                <span className="app-c7__log-when">{V2.timeAgo(r.sentAt || r.addedAt)}</span>
              </div>
              <div className="app-c7__log-msg">
                <em>{r.title || t('(untitled)', '(بلا عنوان)')}.</em> {r.body || ''}
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 8 — The House
   ════════════════════════════════════════════════════════════ */

function HouseRoof({ kind }) {
  const stroke = "#C5A05E";
  if (kind === 'gable')
    return <svg viewBox="0 0 80 50" style={{ width: '100%', height: '100%' }}>
      <path d="M 8 50 L 40 6 L 72 50 Z" fill="none" stroke={stroke} strokeWidth="1.4" />
      <line x1="20" y1="50" x2="60" y2="50" stroke={stroke} strokeWidth="0.7" />
      <circle cx="40" cy="30" r="2.5" fill="none" stroke={stroke} strokeWidth="0.7" />
    </svg>;
  if (kind === 'hip')
    return <svg viewBox="0 0 80 50" style={{ width: '100%', height: '100%' }}>
      <path d="M 14 50 L 28 12 L 52 12 L 66 50 Z" fill="none" stroke={stroke} strokeWidth="1.4" />
      <line x1="28" y1="12" x2="52" y2="12" stroke={stroke} strokeWidth="0.7" />
      <rect x="36" y="28" width="8" height="14" fill="none" stroke={stroke} strokeWidth="0.6" />
    </svg>;
  return <svg viewBox="0 0 80 50" style={{ width: '100%', height: '100%' }}>
    <path d="M 14 50 Q 40 -4 66 50 Z" fill="none" stroke={stroke} strokeWidth="1.4" />
    <circle cx="40" cy="50" r="1.8" fill={stroke} />
    <line x1="40" y1="6" x2="40" y2="48" stroke={stroke} strokeWidth="0.5" strokeDasharray="1.5 2" />
  </svg>;
}

function Card8() {
  const t = V2.useTG();
  const liveGoals = V2.useFutureGoals();
  const [i, setI] = useState(0);
  const [adding, setAdding] = useState(false);
  const [draftEn, setDraftEn] = useState('');
  const [draftAr, setDraftAr] = useState('');

  // Localized column metadata
  const COL_META = [
    { col: 'year',    num: 'I',   short: t('Year', 'سنة'),       label: t('This year', 'هذه السنة'),    roof: 'gable' },
    { col: 'five',    num: 'II',  short: t('5 yrs', '٥ سنوات'),  label: t('Next 5 years', 'الخمس سنوات القادمة'), roof: 'hip' },
    { col: 'forever', num: 'III', short: t('Forever', 'إلى الأبد'), label: t('Our forever', 'إلى الأبد لنا'), roof: 'dome' },
  ];

  const cols = COL_META.map((meta) => {
    const items = liveGoals.filter(g => g.column === meta.col);
    return { ...meta, items };
  });
  const c = cols[i];

  const onAdd = async () => {
    if (!draftEn && !draftAr) { setAdding(false); return; }
    await V2.addFutureGoal({ text_en: draftEn, text_ar: draftAr, column: c.col });
    setDraftEn(''); setDraftAr(''); setAdding(false);
  };

  return (
    <div className="app__card app-c8" style={{ overflow: 'auto' }} dir={dirOf(t.lang)}>
      <div className="app__stars" />
      <div className="app-c8__kicker">{t('The house', 'البيت')}</div>
      <h2 className="app-c8__title">
        {t.lang === 'ar'
          ? <>بيتٌ <em>نبنيه.</em></>
          : <>A house we are <em>building.</em></>}
      </h2>

      <div className="app-c8__tabs">
        {cols.map((h, j) => (
          <button key={j} className={`app-c8__tab ${i === j ? 'on' : ''}`}
                  onClick={() => { setI(j); setAdding(false); }}>
            {h.short}
          </button>
        ))}
      </div>

      <div className="app-c8__roof"><HouseRoof kind={c.roof} /></div>
      <div className="app-c8__num">{c.num}</div>
      <div className="app-c8__sub">{c.label}</div>

      <div className="app-c8__items">
        {c.items.length === 0 ? (
          <div style={{ opacity: 0.55, fontStyle: 'italic', fontSize: 13, padding: '20px 0' }}>
            {t(`Empty — add the first goal for "${c.short}" below.`,
               `فارغ — أضف أول هدف لـ "${c.short}" أدناه.`,
               `فارغ — أضيفي أول هدف لـ "${c.short}" أدناه.`)}
          </div>
        ) : c.items.map((it, j) => (
          <div key={it.id || j} className="app-c8__item">
            <div className="app-c8__bullet" />
            <div>{(t.lang === 'ar' ? (it.text_ar || it.ar) : (it.text_en || it.en)) || ''}</div>
          </div>
        ))}
      </div>

      {adding ? (
        <div className="app-c7__form" style={{ marginTop: 14 }}>
          <input className="app-c7__input" value={draftEn} onChange={e => setDraftEn(e.target.value)}
                 placeholder={t(`A new ${c.short.toLowerCase()} goal…`, `هدف جديد لـ ${c.short}…`)} autoFocus />
          <input className="app-c7__input" value={draftAr} onChange={e => setDraftAr(e.target.value)}
                 placeholder={t('In Arabic…', 'بالعربية…')} dir="rtl" />
          <button className="app-c7__send" onClick={onAdd}>{t('Add ✦', 'إضافة ✦')}</button>
          <button className="app-c5__nav-btn" style={{ width: 'auto', marginTop: 6 }} onClick={() => setAdding(false)}>
            {t('cancel', 'إلغاء')}
          </button>
        </div>
      ) : (
        <button className="app-c5__nav-btn"
                style={{ width: 'auto', padding: '6px 16px', marginTop: 14, fontSize: 11 }}
                onClick={() => setAdding(true)}>
          {t(`+ add to ${c.short.toLowerCase()}`, `+ إضافة إلى ${c.short}`)}
        </button>
      )}
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Card 9 — Closing
   ════════════════════════════════════════════════════════════ */

function Card9() {
  const t = V2.useTG();
  return (
    <div className="app__card app-c9" dir={dirOf(t.lang)}>
      <div className="app-c9__mark">{t('the last page  ·  for now', 'الصفحة الأخيرة · في الوقت الحاضر')}</div>
      <div className="app-c9__line">{t('Forever yours, Mo', 'إلى الأبد لكِ، محمد')}</div>
      <div className="app-c9__line" style={{ marginTop: 6, opacity: 0.85 }}>
        {t('✦ Always mine, Duha', '✦ دائماً لي، ضحى')}
      </div>
      <div className="app-c9__seal">M&amp;D</div>
      <p className="app-c9__sig">
        {t.lang === 'ar'
          ? <>هذا المُجلَّد يستمرّ — كلّ رسالة، كلّ صورة، كلّ قرعة جرس تُضيف صفحة. الكتاب <em>غير منتهٍ.</em> لن يكون.</>
          : <>This volume continues — every letter, every photograph, every ring of the bell adds a page. The book is <em>not finished.</em> It will not be.</>}
      </p>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Deck — swipe, dots, chapter name
   ════════════════════════════════════════════════════════════ */

const CHAPTERS_EN = ['M & D', 'Days', 'A reason', 'A letter', 'A voice', 'A photograph', 'The bell', 'The house', 'Forever yours'];
const CHAPTERS_AR = ['محمد و ضحى', 'الأيام', 'سبب', 'رسالة', 'صوت', 'صورة', 'الجرس', 'البيت', 'إلى الأبد'];

function Deck() {
  const t = V2.useTG();
  const trackRef = useRef(null);
  const [idx, setIdx] = useState(0);
  const chapters = t.lang === 'ar' ? CHAPTERS_AR : CHAPTERS_EN;

  useEffect(() => {
    const el = trackRef.current;
    if (!el) return;
    let raf = null;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        const w = el.clientWidth;
        const newIdx = Math.round(el.scrollLeft / w);
        if (newIdx !== idx) setIdx(newIdx);
        raf = null;
      });
    };
    el.addEventListener('scroll', onScroll, { passive: true });
    return () => {
      el.removeEventListener('scroll', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [idx]);

  return (
    <div className="app">
      <div className="app__track" ref={trackRef}>
        <Card1 /><Card2 /><Card3 /><Card4 /><Card5 /><Card6 /><Card7 /><Card8 /><Card9 />
      </div>
      <div className="app__dots">
        {chapters.map((_, i) => (<div key={i} className={`app__dot ${i === idx ? 'on' : ''}`} />))}
      </div>
      <div className="app__chapname">{chapters[idx]}</div>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════
   Wrapper
   ════════════════════════════════════════════════════════════ */

function App() {
  return (
    <CrashBoundary>
      <LoginGate>
        <PhoneShell>
          <Deck />
        </PhoneShell>
      </LoginGate>
    </CrashBoundary>
  );
}

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