// qiaopi-app-letter.jsx — Letter detail page (cinematic)
// Sealed envelope → "展信" → opening animation → reading state with scan + transcript.
// Bottom ribbon to jump between letters without going back to map.

function LetterPage({ letter, letters, onBack, onPick, tweaks, width, height, isMobile = false }) {
  const [stage, setStage] = React.useState(0); // 0 envelope, 1 opening, 2 reading
  // For real-scan records the "opening" animation shows the same image twice
  // (the synthetic letter-paper flying out of the envelope is moot when the
  // envelope IS a photograph). Skip Stage 1 there and cross-fade straight to Stage 2.
  const hasRealScan = !!(letter.scans && letter.scans.length);
  const open = () => {
    if (hasRealScan) {
      setStage(2);
    } else {
      setStage(1);
      setTimeout(() => setStage(2), 1300);
    }
  };
  const close = () => setStage(0);
  React.useEffect(() => { setStage(0); }, [letter.id]);

  const idx = letters.findIndex(l => l.id === letter.id);

  return (
    <div style={{
      position: 'relative', width, height,
      background: '#1a130c', fontFamily: '"Noto Serif SC", serif',
      color: '#f1e8d4', overflow: 'hidden',
      animation: 'lp-in .4s',
    }}>
      <style>{`@keyframes lp-in { from { opacity: 0; } to { opacity: 1; } }`}</style>
      <PaperBg tone="ink" style={{ position: 'absolute', inset: 0 }}>
        <div style={{
          position: 'absolute', inset: 0,
          background: 'radial-gradient(ellipse 60% 50% at 50% 30%, rgba(255,200,120,0.18) 0%, transparent 65%)',
          pointerEvents: 'none',
        }} />

        {/* TOP NAV */}
        <div style={{
          position: 'absolute', top: isMobile ? 14 : 28,
          left: isMobile ? 14 : 40, right: isMobile ? 14 : 40, zIndex: 10,
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        }}>
          <button onClick={onBack} style={{
            background: 'transparent', border: '1px solid rgba(200,164,90,0.3)',
            color: '#c8a45a', fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: isMobile ? 12 : 13,
            padding: isMobile ? '6px 10px' : '8px 14px',
            cursor: 'pointer', letterSpacing: 2,
            display: 'flex', alignItems: 'center', gap: 8, whiteSpace: 'nowrap',
          }}>
            <span style={{ fontFamily: '"Noto Serif SC", serif', letterSpacing: isMobile ? 0 : 4, fontStyle: 'normal' }}>{isMobile ? '←' : '← 回到信路'}</span>
            {!isMobile && <span style={{ opacity: 0.7 }}>back to map</span>}
          </button>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: isMobile ? 10 : 12, letterSpacing: isMobile ? 2 : 4,
            color: '#c8a45a', textTransform: 'uppercase', whiteSpace: 'nowrap',
          }}>
            <span style={{ fontFamily: '"Noto Serif SC", serif', fontStyle: 'normal', letterSpacing: isMobile ? 2 : 6, color: '#f1e8d4', fontWeight: 700 }}>家書</span>
            <span style={{ margin: isMobile ? '0 6px' : '0 14px', opacity: 0.4 }}>·</span>
            № {String(idx + 1).padStart(2, '0')}{!isMobile && ` of ${String(letters.length).padStart(2, '0')}`}
          </div>
          {!isMobile && <div style={{ width: 200 }} />}
        </div>

        {stage === 0 && <Stage0 letter={letter} onOpen={open} isMobile={isMobile} />}
        {stage === 1 && <Stage1 letter={letter} />}
        {stage === 2 && <Stage2 letter={letter} onClose={close} tweaks={tweaks} isMobile={isMobile} />}

        {stage !== 2 && !isMobile && <BottomRibbon letters={letters} activeId={letter.id} onPick={onPick} />}
      </PaperBg>
    </div>
  );
}

// ─── Stage 0 ──────────────────────────────────────────────────────────
function Stage0({ letter, onOpen, isMobile = false }) {
  if (isMobile) return <Stage0Mobile letter={letter} onOpen={onOpen} />;
  return (
    <div style={{
      position: 'absolute', inset: '90px 0 180px',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 's0-in .5s',
    }}>
      <style>{`@keyframes s0-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }`}</style>
      <div style={{ display: 'flex', gap: 70, alignItems: 'center' }}>
        {/* LEFT: caption */}
        <div style={{ width: 320, textAlign: 'right' }}>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: 13, letterSpacing: 3, color: '#c8a45a', textTransform: 'uppercase',
          }}>This letter · 这通家书</div>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: letter.year ? 'italic' : 'normal',
            fontSize: letter.year ? 92 : 28, lineHeight: 1, fontWeight: 700,
            color: '#f1e8d4', margin: '14px 0 8px',
          }}>{letter.year || '近代'}</div>
          <div style={{
            fontFamily: '"Noto Serif SC", serif', fontSize: 22, fontWeight: 700,
            color: '#f1e8d4', letterSpacing: 2,
          }}>自 {letter.from.city} 寄回 {letter.to.city}</div>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: 14, color: '#c8a45a', marginTop: 4, letterSpacing: 1,
          }}>From {letter.from.cityEn} to {letter.to.regionEn}</div>
          {letter.bodyZh ? (
            <div style={{
              marginTop: 26,
              fontFamily: '"LXGW WenKai", "Noto Serif SC", serif',
              fontSize: 16, lineHeight: 1.95, color: '#e8d6b0',
              fontStyle: 'italic', opacity: 0.92,
            }}>
              「{letter.bodyZh.split('，')[0]}，{(letter.bodyZh.split('，')[1] || '').slice(0, 14)}……」
            </div>
          ) : (
            <div style={{
              marginTop: 26,
              fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
              fontSize: 13, color: '#a08560', opacity: 0.75, letterSpacing: 1,
            }}>
              原信尚未释读 · transcription pending
            </div>
          )}
        </div>

        {/* ENVELOPE */}
        <button onClick={onOpen} style={{ background: 'transparent', border: 'none', cursor: 'pointer', padding: 0 }}>
          <EnvelopeFront letter={letter} />
          <div style={{
            marginTop: 30, color: '#c8a45a',
            fontFamily: '"Noto Serif SC", serif', fontSize: 14, letterSpacing: 6,
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 14,
          }}>
            <span style={{ width: 26, height: 1, background: '#c8a45a' }} />
            展　信
            <span style={{ fontFamily: '"EB Garamond", serif', fontStyle: 'italic', letterSpacing: 1 }}>· open the letter</span>
            <span style={{ width: 26, height: 1, background: '#c8a45a' }} />
          </div>
        </button>

        {/* RIGHT: metadata */}
        <div style={{ width: 250, color: '#e8d6b0' }}>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: 11, letterSpacing: 3, color: '#c8a45a', textTransform: 'uppercase',
          }}>The hand · 寄存者</div>
          <div style={{ marginTop: 14, display: 'grid', gap: 12, fontSize: 13 }}>
            <DarkMeta label="Sender 寄信人">{letter.sender.name}{letter.sender.nameEn && (<><br/><i style={{ opacity: 0.7, fontFamily: '"EB Garamond", serif' }}>{letter.sender.nameEn}</i></>)}</DarkMeta>
            <DarkMeta label="Recipient 收信人">{letter.recipient.name}{letter.recipient.nameEn && (<><br/><i style={{ opacity: 0.7, fontFamily: '"EB Garamond", serif' }}>{letter.recipient.nameEn}</i></>)}</DarkMeta>
            {letter.amount && (
              <DarkMeta label="Remittance 汇款">{letter.amount.value} {letter.amount.currency}<br/><i style={{ opacity: 0.7, fontFamily: '"EB Garamond", serif' }}>{letter.amount.value} {letter.amount.currencyEn}</i></DarkMeta>
            )}
            <DarkMeta label="Dated 日期">{letter.postmark || <span style={{ opacity: 0.5 }}>日期不详</span>}</DarkMeta>
            {letter.sourceUrl && (
              <DarkMeta label="Source 出处">
                <a href={letter.sourceUrl} target="_blank" style={{ color: '#c8a45a', textDecoration: 'none', borderBottom: '1px dotted #c8a45a' }}>CADAL №{letter.ssno} ↗</a>
              </DarkMeta>
            )}
          </div>
          <div style={{
            marginTop: 18, height: 130,
            background: 'rgba(245,236,214,0.04)', border: '1px solid rgba(200,164,90,0.3)',
          }}>
            <MiniMap letter={letter} all={[letter]} w={250} h={130} />
          </div>
          <div style={{
            marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 4,
          }}>
            {(letter.tags || []).map(t => (
              <span key={t} style={{
                fontFamily: '"Noto Serif SC", serif', fontSize: 11,
                color: '#c8a45a', border: '1px solid rgba(200,164,90,0.35)',
                padding: '2px 8px',
              }}>#{t}</span>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}
function DarkMeta({ label, children }) {
  return (
    <div>
      <div style={{
        fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
        fontSize: 10, letterSpacing: 2, color: '#c8a45a', textTransform: 'uppercase',
      }}>{label}</div>
      <div style={{ fontFamily: '"Noto Serif SC", serif', fontSize: 13, marginTop: 2, color: '#f1e8d4' }}>{children}</div>
    </div>
  );
}

// ─── Stage 0 (mobile) — vertical stack ───────────────────────────────
function Stage0Mobile({ letter, onOpen }) {
  const [showMeta, setShowMeta] = React.useState(false);
  const envW = Math.min(window.innerWidth - 48, 320);
  return (
    <div style={{
      position: 'absolute', inset: '54px 0 16px',
      overflowY: 'auto', WebkitOverflowScrolling: 'touch',
      animation: 's0-in .5s',
    }}>
      <style>{`@keyframes s0-in { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: scale(1); } }`}</style>
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '12px 16px 24px' }}>
        {/* caption */}
        <div style={{ textAlign: 'center', marginBottom: 18 }}>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: 11, letterSpacing: 3, color: '#c8a45a', textTransform: 'uppercase',
          }}>This letter · 这通家书</div>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: letter.year ? 'italic' : 'normal',
            fontSize: letter.year ? 56 : 22, lineHeight: 1, fontWeight: 700,
            color: '#f1e8d4', margin: '10px 0 6px',
          }}>{letter.year || '近代'}</div>
          <div style={{
            fontFamily: '"Noto Serif SC", serif', fontSize: 17, fontWeight: 700,
            color: '#f1e8d4', letterSpacing: 1.5,
          }}>自 {letter.from.city} 寄回 {letter.to.city}</div>
          <div style={{
            fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: 12, color: '#c8a45a', marginTop: 3, letterSpacing: 1,
          }}>From {letter.from.cityEn} to {letter.to.regionEn}</div>
        </div>

        {/* envelope */}
        <button onClick={onOpen} style={{ background: 'transparent', border: 'none', cursor: 'pointer', padding: 0 }}>
          <EnvelopeFront letter={letter} maxW={envW} />
        </button>

        <button onClick={onOpen} style={{
          marginTop: 22, padding: '10px 24px',
          background: 'rgba(200,164,90,0.12)', border: '1px solid rgba(200,164,90,0.5)',
          color: '#c8a45a', fontFamily: '"Noto Serif SC", serif',
          fontSize: 14, letterSpacing: 6, cursor: 'pointer',
        }}>展　信</button>

        {/* metadata — collapsible */}
        <button onClick={() => setShowMeta(!showMeta)} style={{
          marginTop: 24, background: 'transparent', border: 'none',
          color: '#c8a45a', fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
          fontSize: 11, letterSpacing: 3, textTransform: 'uppercase', cursor: 'pointer',
        }}>{showMeta ? '× 收起' : '+ 详情 · details'}</button>

        {showMeta && (
          <div style={{
            marginTop: 14, width: '100%', maxWidth: 360,
            display: 'grid', gap: 10, fontSize: 13, color: '#e8d6b0',
          }}>
            <DarkMeta label="Sender 寄信人">{letter.sender.name}</DarkMeta>
            <DarkMeta label="Recipient 收信人">{letter.recipient.name}</DarkMeta>
            {letter.amount && (
              <DarkMeta label="Remittance 汇款">{letter.amount.value} {letter.amount.currency}</DarkMeta>
            )}
            <DarkMeta label="Dated 日期">{letter.postmark || <span style={{ opacity: 0.5 }}>日期不详</span>}</DarkMeta>
            {letter.sourceUrl && (
              <DarkMeta label="Source 出处">
                <a href={letter.sourceUrl} target="_blank" style={{ color: '#c8a45a', textDecoration: 'none', borderBottom: '1px dotted #c8a45a' }}>CADAL №{letter.ssno} ↗</a>
              </DarkMeta>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// ─── Envelope front ───────────────────────────────────────────────────
function EnvelopeFront({ letter, opening = false, maxW = null }) {
  // If the letter has real scans, show the real envelope-front photograph
  // instead of the synthetic illustration.
  if (letter.scans && letter.scans.length) {
    const W = maxW || 380, H = Math.round((maxW || 380) * (540 / 380));
    return (
      <div style={{
        width: W, height: H, position: 'relative',
        background: '#1a130c',
        boxShadow: '0 30px 70px rgba(0,0,0,0.55), 0 8px 24px rgba(0,0,0,0.35)',
        transition: 'transform .5s',
        transform: opening ? 'translateY(-30px)' : 'translateY(0)',
      }}>
        <img src={letter.scans[0]} alt={letter.title}
          style={{ width: '100%', height: '100%', objectFit: 'contain', display: 'block', background: '#0d0905' }}
          onError={(e) => { e.target.style.opacity = 0.2; }} />
        {/* discrete ID/year strip at bottom */}
        <div style={{
          position: 'absolute', bottom: 0, left: 0, right: 0,
          padding: '8px 14px',
          background: 'linear-gradient(to top, rgba(0,0,0,0.65), transparent)',
          fontFamily: '"IBM Plex Mono", monospace', fontSize: 10, letterSpacing: 2,
          color: '#e0c98a', display: 'flex', justifyContent: 'space-between',
        }}>
          <span>№ {letter.ssno}</span>
          <span>{letter.year || '近代'}</span>
        </div>
      </div>
    );
  }

  // Fallback: synthetic illustrated envelope (original code path)
  const W = maxW || 380, H = Math.round((maxW || 380) * (250 / 380));
  return (
    <div style={{
      width: W, height: H, position: 'relative',
      background: '#e8d6b0',
      boxShadow: '0 30px 70px rgba(0,0,0,0.55), 0 8px 24px rgba(0,0,0,0.35)',
      transition: 'transform .5s',
      transform: opening ? 'translateY(-30px)' : 'translateY(0)',
    }}>
      <div style={{ position: 'absolute', inset: 0, filter: 'url(#paper-grain)', background: '#fff', mixBlendMode: 'multiply', opacity: 0.7 }} />
      <svg viewBox={`0 0 ${W} ${H}`} style={{ position: 'absolute', inset: 0 }}>
        <path d={`M 0 ${H} L ${W / 2} ${H * 0.55} L 0 0 Z`} fill="rgba(70,40,15,0.06)" />
        <path d={`M ${W} ${H} L ${W / 2} ${H * 0.55} L ${W} 0 Z`} fill="rgba(70,40,15,0.06)" />
        <path d={`M 0 ${H} L ${W / 2} ${H * 0.55} L ${W} ${H} Z`} fill="rgba(70,40,15,0.12)" />
        <line x1="0" y1="0" x2={W / 2} y2={H * 0.55} stroke="rgba(70,40,15,0.30)" strokeWidth="0.8" />
        <line x1={W} y1="0" x2={W / 2} y2={H * 0.55} stroke="rgba(70,40,15,0.30)" strokeWidth="0.8" />
        <line x1="0" y1={H} x2={W / 2} y2={H * 0.55} stroke="rgba(70,40,15,0.30)" strokeWidth="0.8" />
        <line x1={W} y1={H} x2={W / 2} y2={H * 0.55} stroke="rgba(70,40,15,0.30)" strokeWidth="0.8" />
      </svg>
      {/* addressee (vertical, center) */}
      <div style={{
        position: 'absolute', top: 30, left: '50%', transform: 'translateX(-50%)',
        fontFamily: '"LXGW WenKai", "Noto Serif SC", serif',
        fontSize: 28, color: '#2a1f17',
        writingMode: 'vertical-rl', textOrientation: 'upright',
        letterSpacing: 4, fontWeight: 500,
      }}>
        中國 {letter.to.region} {letter.recipient.name} 收
      </div>
      {/* sender (smaller, right) */}
      <div style={{
        position: 'absolute', top: 38, right: 18,
        fontFamily: '"LXGW WenKai", "Noto Serif SC", serif',
        fontSize: 14, color: '#3a2818',
        writingMode: 'vertical-rl', textOrientation: 'upright',
        letterSpacing: 2,
      }}>
        南洋 {letter.from.city} {letter.sender.name} 寄
      </div>
      {/* stamp */}
      <div style={{ position: 'absolute', top: 16, left: 18 }}>
        <Stamp year={letter.year || ''} country={(letter.from.countryEn || '').split(' ').slice(-1)[0].slice(0, 5)} w={54} h={66} rotate={-3} />
      </div>
      {/* postmark */}
      <div style={{
        position: 'absolute', top: 56, left: 66,
        width: 58, height: 58, borderRadius: '50%',
        border: '1.5px solid #6a3a26', color: '#6a3a26',
        opacity: 0.6,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        transform: 'rotate(-12deg)',
        fontFamily: '"EB Garamond", serif',
        mixBlendMode: 'multiply',
      }}>
        <div style={{ fontSize: 7, letterSpacing: 1, fontStyle: 'italic' }}>{(letter.from.cityEn || '').toUpperCase()}</div>
        <div style={{ fontSize: 12, fontWeight: 700 }}>{letter.year || ''}</div>
        <div style={{ fontSize: 7, letterSpacing: 1, fontStyle: 'italic' }}>{letter.month ? String(letter.month).padStart(2, '0') : ''}</div>
      </div>
      {/* chop */}
      <div style={{ position: 'absolute', bottom: 18, left: 22 }}>
        <ChopSeal text="僑批" size={44} rotate={-6} />
      </div>
      <div style={{
        position: 'absolute', bottom: 18, right: 22,
        fontFamily: '"IBM Plex Mono", monospace',
        fontSize: 11, color: '#7a4828', letterSpacing: 2,
      }}>№ {letter.id.split('-').slice(-2).join(' / ')}</div>
    </div>
  );
}

// ─── Stage 1: opening ────────────────────────────────────────────────
function Stage1({ letter }) {
  return (
    <div style={{ position: 'absolute', inset: '90px 0 180px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div style={{ position: 'relative' }}>
        <div style={{ animation: 'env-tilt 1.3s cubic-bezier(.4,0,.2,1) forwards' }}>
          <EnvelopeFront letter={letter} opening />
        </div>
        <div style={{
          position: 'absolute', left: '50%', top: '20%',
          transform: 'translateX(-50%)',
          animation: 'letter-out 1.3s cubic-bezier(.2,.7,.3,1) forwards',
          opacity: 0,
        }}>
          <LetterScan letter={letter} scale={0.62} />
        </div>
        <style>{`
          @keyframes env-tilt { 0% { transform: translateY(0) rotate(0); } 100% { transform: translateY(140px) rotate(-3deg); opacity: 0.4; } }
          @keyframes letter-out { 0% { transform: translate(-50%, 0) scale(0.4); opacity: 0; } 40% { opacity: 1; } 100% { transform: translate(-50%, -200px) scale(1.1); opacity: 1; } }
        `}</style>
      </div>
    </div>
  );
}

// ─── Stage 2: read ───────────────────────────────────────────────────
function Stage2({ letter, onClose, tweaks, isMobile = false }) {
  if (isMobile) return <Stage2Mobile letter={letter} onClose={onClose} tweaks={tweaks} />;
  const [pageIdx, setPageIdx] = React.useState(0);
  const hasTranscript = !!letter.bodyZh;
  const hasScans = letter.scans && letter.scans.length > 0;
  return (
    <div style={{
      position: 'absolute', inset: '88px 0 30px',
      display: 'flex', justifyContent: 'center', alignItems: 'stretch',
      animation: 's2-in .5s', overflow: 'hidden', padding: '0 20px',
    }}>
      <style>{`@keyframes s2-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }`}</style>
      <div style={{ display: 'flex', gap: 48, alignItems: 'stretch', height: '100%', maxWidth: '100%' }}>
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <LetterScan letter={letter} scale={1.05}
            page={pageIdx} onPageChange={setPageIdx} />
        </div>
        {/* read column — always THE PIECE metadata; bottom slot shows transcript if any */}
        <div style={{ width: 380, color: '#f1e8d4', height: '100%', display: 'flex', flexDirection: 'column', paddingTop: 8, paddingBottom: 8 }}>
          <button onClick={onClose} style={{
            background: 'transparent', border: '1px solid rgba(200,164,90,0.4)',
            color: '#c8a45a', fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            padding: '6px 12px', cursor: 'pointer', fontSize: 12, letterSpacing: 2,
            alignSelf: 'flex-start',
          }}>← close · 闭信</button>

          <div style={{
            marginTop: 18,
            fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
            fontSize: 12, color: '#c8a45a', letterSpacing: 3, textTransform: 'uppercase',
          }}>The piece · 这件批信</div>
          <div style={{ marginTop: 16, display: 'grid', gap: 14, fontSize: 13 }}>
            <DarkMeta label="Title 標題">{letter.title}</DarkMeta>
            <DarkMeta label="From 寄出">{letter.from.city} <span style={{opacity:0.6}}>· {letter.from.country}</span><br/><i style={{ opacity: 0.7, fontFamily: '"EB Garamond", serif' }}>{letter.from.cityEn}, {letter.from.countryEn}</i></DarkMeta>
            <DarkMeta label="To 收件">{letter.to.region} {letter.recipient.name !== '—' && letter.recipient.name}<br/><i style={{ opacity: 0.7, fontFamily: '"EB Garamond", serif' }}>{letter.to.regionEn}</i></DarkMeta>
            {letter.sender.name && letter.sender.name !== '—' && (
              <DarkMeta label="Sender 寄信人">{letter.sender.name}</DarkMeta>
            )}
            <DarkMeta label="Pages 页数">共 {hasScans ? letter.scans.length : 0} 页</DarkMeta>
            <DarkMeta label="Provenance 出处">
              <a href={letter.sourceUrl} target="_blank" style={{ color: '#c8a45a', textDecoration: 'none', borderBottom: '1px dotted #c8a45a' }}>
                CADAL №{letter.ssno} ↗
              </a>
            </DarkMeta>
          </div>

          {/* Bottom slot — transcript if available, otherwise the "untranscribed" notice */}
          <div style={{
            marginTop: 26, paddingTop: 16, borderTop: '1px dotted rgba(200,164,90,0.3)',
            flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column',
          }}>
            {hasTranscript ? (
              (() => {
                // Strip the 〔机器转录，未经校对〕 marker — show it as a separate caveat note
                const MARKER = /^〔机器转录[，,]\s*未经校对〕\s*\n?/;
                const hasMarker = MARKER.test(letter.bodyZh);
                const cleanBody = letter.bodyZh.replace(MARKER, '').trim();
                return (
                  <>
                    <div style={{
                      display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
                      marginBottom: 10,
                    }}>
                      <div style={{
                        fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
                        fontSize: 11, color: '#c8a45a', letterSpacing: 3, textTransform: 'uppercase',
                      }}>Letter body · 信文</div>
                      {hasMarker && (
                        <div style={{
                          fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
                          fontSize: 10, color: '#a08560', opacity: 0.75, letterSpacing: 1,
                        }}>机器转录 · 未经校对</div>
                      )}
                    </div>
                    <div style={{
                      fontFamily: tweaks.bodyFont === 'kaiti' ? '"LXGW WenKai", "Noto Serif SC", serif' : '"Noto Serif SC", serif',
                      fontSize: 14, lineHeight: 1.9, whiteSpace: 'pre-wrap',
                      color: '#f1e8d4', overflowY: 'auto', paddingRight: 8,
                      flex: 1, minHeight: 0,
                    }}>{cleanBody}</div>
                  </>
                );
              })()
            ) : (
              <div style={{
                fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
                fontSize: 12, lineHeight: 1.7, color: '#a08560', opacity: 0.8,
              }}>
                The body of this letter has not yet been transcribed.
                The scan on the left preserves the writer's hand exactly as it left the south seas.
                <br/><br/>
                此件正文尚未释读。左侧扫描原物保留了寄信人当年的手迹。
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

// ─── Stage 2 (mobile) — vertical stack ───────────────────────────────
function Stage2Mobile({ letter, onClose, tweaks }) {
  const [pageIdx, setPageIdx] = React.useState(0);
  const hasTranscript = !!letter.bodyZh;
  const hasScans = letter.scans && letter.scans.length > 0;
  const scanScale = Math.min((window.innerWidth - 32) / 540, 0.7);
  return (
    <div style={{
      position: 'absolute', inset: '54px 0 16px',
      overflowY: 'auto', WebkitOverflowScrolling: 'touch',
      animation: 's2-in .5s',
    }}>
      <style>{`@keyframes s2-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }`}</style>
      <div style={{ padding: '8px 16px 32px', color: '#f1e8d4' }}>
        <button onClick={onClose} style={{
          background: 'transparent', border: '1px solid rgba(200,164,90,0.4)',
          color: '#c8a45a', fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
          padding: '6px 12px', cursor: 'pointer', fontSize: 12, letterSpacing: 2,
          marginBottom: 14,
        }}>← close · 闭信</button>

        {/* scan — centered, fluid */}
        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 22 }}>
          <LetterScan letter={letter} scale={scanScale}
            page={pageIdx} onPageChange={setPageIdx} />
        </div>

        {/* metadata */}
        <div style={{
          fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
          fontSize: 12, color: '#c8a45a', letterSpacing: 3, textTransform: 'uppercase',
        }}>The piece · 这件批信</div>
        <div style={{ marginTop: 14, display: 'grid', gap: 12, fontSize: 13 }}>
          <DarkMeta label="Title 標題">{letter.title}</DarkMeta>
          <DarkMeta label="From 寄出">{letter.from.city} <span style={{opacity:0.6}}>· {letter.from.country}</span><br/><i style={{ opacity: 0.7, fontFamily: '"EB Garamond", serif' }}>{letter.from.cityEn}, {letter.from.countryEn}</i></DarkMeta>
          <DarkMeta label="To 收件">{letter.to.region} {letter.recipient.name !== '—' && letter.recipient.name}<br/><i style={{ opacity: 0.7, fontFamily: '"EB Garamond", serif' }}>{letter.to.regionEn}</i></DarkMeta>
          {letter.sender.name && letter.sender.name !== '—' && (
            <DarkMeta label="Sender 寄信人">{letter.sender.name}</DarkMeta>
          )}
          <DarkMeta label="Pages 页数">共 {hasScans ? letter.scans.length : 0} 页</DarkMeta>
          <DarkMeta label="Provenance 出处">
            <a href={letter.sourceUrl} target="_blank" style={{ color: '#c8a45a', textDecoration: 'none', borderBottom: '1px dotted #c8a45a' }}>
              CADAL №{letter.ssno} ↗
            </a>
          </DarkMeta>
        </div>

        {/* body / placeholder */}
        <div style={{
          marginTop: 22, paddingTop: 16, borderTop: '1px dotted rgba(200,164,90,0.3)',
        }}>
          {hasTranscript ? (() => {
            const MARKER = /^〔机器转录[，,]\s*未经校对〕\s*\n?/;
            const hasMarker = MARKER.test(letter.bodyZh);
            const cleanBody = letter.bodyZh.replace(MARKER, '').trim();
            return (
              <>
                <div style={{
                  display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
                  marginBottom: 10, gap: 8, flexWrap: 'wrap',
                }}>
                  <div style={{
                    fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
                    fontSize: 11, color: '#c8a45a', letterSpacing: 3, textTransform: 'uppercase',
                  }}>Letter body · 信文</div>
                  {hasMarker && (
                    <div style={{
                      fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
                      fontSize: 10, color: '#a08560', opacity: 0.75, letterSpacing: 1,
                    }}>机器转录 · 未经校对</div>
                  )}
                </div>
                <div style={{
                  fontFamily: tweaks.bodyFont === 'kaiti' ? '"LXGW WenKai", "Noto Serif SC", serif' : '"Noto Serif SC", serif',
                  fontSize: 15, lineHeight: 1.95, whiteSpace: 'pre-wrap',
                  color: '#f1e8d4',
                }}>{cleanBody}</div>
              </>
            );
          })() : (
            <div style={{
              fontFamily: '"EB Garamond", serif', fontStyle: 'italic',
              fontSize: 12, lineHeight: 1.7, color: '#a08560', opacity: 0.8,
            }}>
              The body of this letter has not yet been transcribed.
              <br/><br/>
              此件正文尚未释读。
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Bottom ribbon ────────────────────────────────────────────────────
function BottomRibbon({ letters, activeId, onPick }) {
  return (
    <div style={{
      position: 'absolute', left: 0, right: 0, bottom: 0, zIndex: 5,
      background: 'rgba(20,12,6,0.85)',
      borderTop: '1px solid rgba(200,164,90,0.25)',
      padding: '18px 40px',
    }}>
      <div style={{
        display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
        marginBottom: 12,
      }}>
        <div style={{ fontFamily: '"EB Garamond", serif', fontStyle: 'italic', fontSize: 12, letterSpacing: 3, color: '#c8a45a', textTransform: 'uppercase' }}>More from the archive · 续读</div>
        <div style={{ fontFamily: '"Noto Serif SC", serif', fontSize: 12, color: '#c8a45a', letterSpacing: 3 }}>共 {letters.length} 通</div>
      </div>
      <div style={{ display: 'flex', gap: 14, overflowX: 'auto', paddingBottom: 6 }}>
        {letters.map((l) => (
          <button key={l.id} onClick={() => onPick(l)} style={{
            flexShrink: 0,
            background: '#e8d6b0',
            border: l.id === activeId ? '2px solid #c8a45a' : '2px solid transparent',
            outline: 'none', padding: 0,
            width: 160, height: 96, position: 'relative',
            cursor: 'pointer',
            boxShadow: l.id === activeId ? '0 0 0 4px rgba(200,164,90,0.18)' : '0 4px 10px rgba(0,0,0,0.4)',
            transition: 'all .2s',
          }}>
            <svg viewBox="0 0 160 96" style={{ position: 'absolute', inset: 0 }}>
              <path d="M 0 0 L 160 0 L 80 40 Z" fill="rgba(70,40,15,0.10)" />
              <line x1="0" y1="0" x2="80" y2="40" stroke="rgba(70,40,15,0.25)" strokeWidth="0.6" />
              <line x1="160" y1="0" x2="80" y2="40" stroke="rgba(70,40,15,0.25)" strokeWidth="0.6" />
            </svg>
            <div style={{
              position: 'absolute', bottom: 8, left: 10, right: 10, textAlign: 'left',
              fontFamily: '"Noto Serif SC", serif', color: '#2a1f17',
            }}>
              <div style={{ fontSize: 11, fontWeight: 700 }}>{l.from.city} → {l.to.city}</div>
              <div style={{ fontFamily: '"EB Garamond", serif', fontStyle: 'italic', fontSize: 13 }}>{l.year || '近代'}</div>
            </div>
          </button>
        ))}
      </div>
    </div>
  );
}

window.LetterPage = LetterPage;
