// app.jsx — TehillimToken landing + circles app
const { useState, useEffect, useCallback } = React;

function api(path, opts = {}) {
  return fetch(path, {
    headers: { 'Content-Type': 'application/json' },
    ...opts,
    body: opts.body ? (typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body)) : undefined
  }).then(r => r.json());
}

// ───────── Hebrew numeral (gematria) — Yakir 2026-05-14 ─────────
// Religious texts conventionally use letters for chapter numbers
// (פרק כ״ג, not פרק 23). 15→טו / 16→טז avoid divine-name combinations.
function hebrewNumeral(n) {
  n = Number(n);
  if (!Number.isFinite(n) || n < 1 || n > 999) return String(n);
  const ones = ['', 'א', 'ב', 'ג', 'ד', 'ה', 'ו', 'ז', 'ח', 'ט'];
  const tens = ['', 'י', 'כ', 'ל', 'מ', 'נ', 'ס', 'ע', 'פ', 'צ'];
  const hundreds = ['', 'ק', 'ר', 'ש', 'ת', 'תק', 'תר', 'תש', 'תת', 'תתק'];
  let s = hundreds[Math.floor(n / 100)];
  const t = Math.floor((n % 100) / 10);
  const o = n % 10;
  if (t === 1 && (o === 5 || o === 6)) {
    s += o === 5 ? 'טו' : 'טז';
  } else {
    s += tens[t] + ones[o];
  }
  return s.length === 1 ? s + '׳' : s.slice(0, -1) + '״' + s.slice(-1);
}
// Combined: "פרק כ״ג" (use everywhere a single chapter is named to a user).
function chapterLabel(n) { return `פרק ${hebrewNumeral(n)}`; }

// ───────── Tanya 5-parts metadata (mirror of server TANYA_PARTS) ─────────
// Used both by the CreateModal picker (when /api/tanya/parts is unreachable
// — extremely unlikely but cheap to handle) and by the reader header to
// resolve a circle chapter # to its part for display.
const TANYA_PARTS_CLIENT = [
  { id: 'likutei',          name: 'לקוטי אמרים',       chapters: 53, icon: '📘',
    shaar: 'ספר של בינונים — פרקי היסוד של עבודת ה׳ לאדם הבינוני' },
  { id: 'shaar_yichud',     name: 'שער היחוד והאמונה', chapters: 13, icon: '📗',
    shaar: 'יסוד אחדות ה׳ בכל המציאות — "אין עוד מלבדו" ממש. נפתח ב״חינוך קטן״ וי״ב פרקים.' },
  { id: 'iggeret_teshuvah', name: 'אגרת התשובה',       chapters: 12, icon: '📙',
    shaar: 'הדרך לתשובה — שתי בחינות של תשובה תתאה ועילאה' },
  { id: 'iggeret_hakodesh', name: 'אגרת הקודש',        chapters: 32, icon: '📕',
    shaar: 'אגרות אדמו"ר הזקן לחסידיו — צדקה, עבודה ודבקות' },
  { id: 'kuntres_acharon',  name: 'קונטרס אחרון',       chapters: 9,  icon: '📓',
    shaar: 'תוספות והשגות על לקוטי אמרים — עומק הסוגיות' },
];
const TANYA_PART_BY_ID_CLIENT = Object.fromEntries(TANYA_PARTS_CLIENT.map(p => [p.id, p]));

// Resolve a circle-chapter (1..total) to the relevant Tanya part for display.
// For Tanya circles with no text_parts (legacy created before 2026-05-16),
// defaults to ['likutei'] so every Tanya chapter has a known part — this is
// what those circles always were conceptually (53 chapters of לקוטי אמרים).
// Returns { part_id, part_name, local_chapter } or null if not a Tanya circle.
function tanyaPartForChapter(circle, chapter) {
  if (!circle || circle.text_type !== 'tanya') return null;
  let parts = circle.text_parts;
  if (typeof parts === 'string') { try { parts = JSON.parse(parts); } catch { parts = null; } }
  if (!Array.isArray(parts) || parts.length === 0) {
    // [damri 2026-05-20] If text_parts is null/empty BUT total_chapters !== 53,
    // legacy default ['likutei'] is wrong (Likutei has 53). Infer from total.
    const tot = circle.total_chapters;
    if (tot === 13) parts = ['shaar_yichud'];
    else if (tot === 12) parts = ['iggeret_teshuvah'];
    else if (tot === 32) parts = ['iggeret_hakodesh'];
    else if (tot === 9) parts = ['kuntres_acharon'];
    else parts = ['likutei'];
    try { console.warn('[tanyaPartForChapter] circle', circle.id, 'missing text_parts; inferred', parts, 'from total_chapters=', tot); } catch (_) {}
  }
  let offset = 0;
  for (const id of parts) {
    const meta = TANYA_PART_BY_ID_CLIENT[id];
    if (!meta) continue;
    if (chapter <= offset + meta.chapters) {
      return { part_id: id, part_name: meta.name, local_chapter: chapter - offset };
    }
    offset += meta.chapters;
  }
  return null;
}

// In-app display label for a circle chapter.
// Tanya base case: "<part_name> — פרק <heb>" using the LOCAL chapter
// number inside the part.
// Special case — שער היחוד והאמונה chapter 1 is "חינוך קטן" (the intro
// that opens "חנוך לנער על פי דרכו"), so we don't call it פרק. Chapters
// 2..13 within shaar_yichud display as פרק א׳..י״ב — matching the
// traditional numbering even though they live at local index 2..13.
function chapterLabelInCircle(circle, ch) {
  const part = tanyaPartForChapter(circle, ch);
  if (!part) return chapterLabel(ch);
  if (part.part_id === 'shaar_yichud') {
    if (part.local_chapter === 1) return `${part.part_name} — חינוך קטן`;
    return `${part.part_name} — ${chapterLabel(part.local_chapter - 1)}`;
  }
  return `${part.part_name} — ${chapterLabel(part.local_chapter)}`;
}

// Map a Tanya part_id to the DB collection name where its chapters live.
// Server side: see TANYA_PARTS in server/index.js — keep in sync.
const TANYA_PART_COLLECTION = {
  likutei:          'tanya',
  shaar_yichud:     'tanya_shaar_yichud',
  iggeret_teshuvah: 'tanya_iggeret_teshuvah',
  iggeret_hakodesh: 'tanya_iggeret_hakodesh',
  kuntres_acharon:  'tanya_kuntres_acharon',
};

// Where does the text for this circle-chapter actually live in the DB?
// Returns { collection, chapter } — the DB collection name AND the local
// chapter number within that collection. For non-Tanya circles this is a
// pass-through (collection = circle.text_type, chapter unchanged). For
// Tanya circles it resolves the part via tanyaPartForChapter and then
// maps the part_id to its underlying collection.
// Critical because a multi-part Tanya circle uses a single running chapter
// index (1..total) on the UI side but the actual text lives across
// multiple DB collections — this function bridges between the two.
function dbCoordsForChapter(circle, chapter) {
  if (!circle) return { collection: 'tehillim', chapter };
  if (circle.text_type !== 'tanya') {
    return { collection: circle.text_type || 'tehillim', chapter };
  }
  const part = tanyaPartForChapter(circle, chapter);
  if (!part) return { collection: 'tanya', chapter };
  return {
    collection: TANYA_PART_COLLECTION[part.part_id] || 'tanya',
    chapter: part.local_chapter,
  };
}

// Friendly book label for circle-list cards and similar surfaces.
// Tehillim/Rambam/Tikkunei Zohar return the obvious Hebrew names.
// For Tanya:
//   single part → 'תניא · <part_name>'           e.g. 'תניא · שער היחוד והאמונה'
//   2+ parts    → 'תניא · N שערים'                e.g. 'תניא · 3 שערים'
//   legacy/all  → 'תניא · לקוטי אמרים' (default)
// This way two Tanya circles in the list never look identical.
function circleBookLabel(c) {
  const tt = (c && c.text_type) || 'tehillim';
  if (tt === 'tehillim')       return 'תהילים';
  if (tt === 'rambam')         return 'רמב"ם';
  if (tt === 'tikkunei_zohar') return 'תיקוני הזוהר';
  if (tt !== 'tanya')          return tt;
  // Tanya: read text_parts
  let parts = c.text_parts;
  if (typeof parts === 'string') { try { parts = JSON.parse(parts); } catch { parts = null; } }
  if (!Array.isArray(parts) || parts.length === 0) parts = ['likutei'];
  if (parts.length === 1) {
    const meta = TANYA_PART_BY_ID_CLIENT[parts[0]];
    return meta ? `תניא · ${meta.name}` : 'תניא';
  }
  return `תניא · ${parts.length} שערים`;
}

// Share-message label. Composes a natural Hebrew sentence fragment for
// WhatsApp/messaging where the recipient may not know the multi-part
// structure of Tanya. For Likutei (the default — "ספר התניא" in colloquial
// speech): "פרק כ״ג בתניא". For any other part (where the part name carries
// more meaning than "תניא" alone): "פרק ה׳ בשער היחוד והאמונה (תניא)".
function chapterShareLabel(circle, ch) {
  const part = tanyaPartForChapter(circle, ch);
  if (!part) return `${chapterLabel(ch)} ב${(circle && circle.text_type === 'tehillim') ? 'תהילים' : 'תניא'}`;
  if (part.part_id === 'likutei') return `${chapterLabel(part.local_chapter)} בתניא`;
  if (part.part_id === 'shaar_yichud' && part.local_chapter === 1) {
    return `חינוך קטן (תניא, ${part.part_name})`;
  }
  if (part.part_id === 'shaar_yichud') {
    return `${chapterLabel(part.local_chapter - 1)} ב${part.part_name} (תניא)`;
  }
  return `${chapterLabel(part.local_chapter)} ב${part.part_name} (תניא)`;
}

// ───────── Toast (transient inline notification) ─────────
function showToast(text, ms = 2200) {
  let el = document.getElementById('thl-toast');
  if (!el) {
    el = document.createElement('div');
    el.id = 'thl-toast';
    el.className = 'thl-toast';
    document.body.appendChild(el);
  }
  el.textContent = text;
  el.classList.add('show');
  clearTimeout(el._timer);
  el._timer = setTimeout(() => el.classList.remove('show'), ms);
}

// ───────── Share modal (desktop fallback) ─────────
// ───── [yakir pass 50] UpcomingPage — standalone /upcoming roadmap page ─────
// Same data + voting logic as UpcomingModal, but rendered as a full page so
// it can be shared via direct URL. Accessible at bayitdavid.com/upcoming
function UpcomingPage({ userId, onSignInRequest }) {
  const [features, setFeatures] = React.useState(null);
  const [voting, setVoting] = React.useState(null);

  const load = React.useCallback(() => {
    const qs = userId ? `?userId=${userId}` : '';
    api(`/api/upcoming-features${qs}`)
      .then(d => setFeatures(d.features || []))
      .catch(() => setFeatures([]));
  }, [userId]);
  React.useEffect(() => { load(); }, [load]);

  const vote = async (feature_id) => {
    if (!userId) {
      if (onSignInRequest) onSignInRequest();
      return;
    }
    if (voting === feature_id) return;
    setVoting(feature_id);
    try {
      const r = await api('/api/feature-vote', {
        method: 'POST',
        body: { user_id: userId, feature_id }
      });
      if (r && !r.error) {
        setFeatures(prev => prev.map(f =>
          f.id === feature_id
            ? { ...f, vote_count: r.vote_count, viewer_voted: r.voted ? 1 : 0 }
            : f
        ));
      } else {
        showToast(r && r.error ? r.error : 'שגיאה');
      }
    } catch (e) {
      showToast(e.message || 'שגיאה');
    } finally {
      setVoting(null);
    }
  };

  return (
    <div style={{ maxWidth: 720, margin: '0 auto', padding: '20px 16px 60px' }}>
      <div style={{ textAlign: 'center', marginBottom: 24 }}>
        <h1 style={{
          fontFamily: "'Frank Ruhl Libre', serif",
          fontSize: 28, fontWeight: 700,
          color: '#5a3d0a', marginBottom: 8,
        }}>
          🔮 בקרוב — מה כדאי לנו לבנות?
        </h1>
        <div style={{
          fontSize: 15, color: '#6d5d3d', lineHeight: 1.7,
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
          maxWidth: 520, margin: '0 auto',
        }}>
          הקול שלך משפיע על הסדר. הצבע למה שחשוב לך לקבל הכי קודם — ונראה
          איך הקהילה מצביעה ביחד.
        </div>
      </div>

      {features === null && (
        <div style={{ color: '#6d5d3d', fontStyle: 'italic', textAlign: 'center', padding: 30 }}>
          טוען...
        </div>
      )}

      {features && features.length === 0 && (
        <div style={{ color: '#6d5d3d', textAlign: 'center', padding: 30 }}>
          אין כרגע פיצ&apos;רים פתוחים להצבעה
        </div>
      )}

      {features && features.length > 0 && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {features.map(f => (
            <div key={f.id} style={{
              display: 'flex', alignItems: 'center', gap: 12,
              padding: '16px 16px',
              background: f.viewer_voted
                ? 'linear-gradient(135deg, rgba(212,175,55,0.18) 0%, rgba(184,148,31,0.10) 100%)'
                : 'rgba(212,175,55,0.06)',
              border: `1.5px solid ${f.viewer_voted ? 'rgba(212,175,55,0.60)' : 'rgba(212,175,55,0.25)'}`,
              borderRadius: 14,
              transition: 'all 0.2s ease',
            }}>
              <div style={{
                fontSize: 32, lineHeight: 1, flexShrink: 0,
                width: 50, height: 50,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                background: 'rgba(255,255,255,0.55)', borderRadius: 12,
              }}>
                {f.icon || '✨'}
              </div>
              <div style={{ flex: 1, minWidth: 0, textAlign: 'right' }}>
                <div style={{
                  fontFamily: "'Frank Ruhl Libre', serif",
                  fontSize: 17, fontWeight: 700, color: '#1a1a2e',
                  marginBottom: 3,
                }}>
                  {f.title}
                </div>
                <div style={{
                  fontSize: 13, color: '#5a3d0a',
                  lineHeight: 1.6, fontWeight: 500,
                }}>
                  {f.description}
                </div>
              </div>
              <button
                type="button"
                onClick={() => vote(f.id)}
                disabled={voting === f.id}
                style={{
                  display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2,
                  minWidth: 60, padding: '10px 12px',
                  background: f.viewer_voted
                    ? 'linear-gradient(135deg, #d4af37 0%, #b8941f 100%)'
                    : 'rgba(255,255,255,0.75)',
                  border: `1.5px solid ${f.viewer_voted ? '#5a3d0a' : 'rgba(212,175,55,0.55)'}`,
                  borderRadius: 12,
                  color: f.viewer_voted ? '#1a1a2e' : '#5a3d0a',
                  cursor: voting === f.id ? 'wait' : 'pointer',
                  opacity: voting === f.id ? 0.6 : 1,
                  transition: 'all 0.2s ease',
                  fontFamily: 'inherit',
                }}
                aria-label={f.viewer_voted ? 'בטל הצבעה' : 'הצבע'}
              >
                <span style={{ fontSize: 20 }}>{f.viewer_voted ? '❤️' : '🤍'}</span>
                <span style={{ fontSize: 14, fontWeight: 800 }}>{f.vote_count}</span>
              </button>
            </div>
          ))}
        </div>
      )}

      {!userId && features && features.length > 0 && (
        <div style={{
          marginTop: 18, padding: '12px 16px',
          background: 'rgba(95,163,114,0.10)',
          border: '1.5px solid rgba(95,163,114,0.40)',
          borderRadius: 12,
          fontSize: 14, color: '#2e5a3d',
          textAlign: 'center', fontWeight: 600,
          fontFamily: "'Frank Ruhl Libre', serif",
        }}>
          כדי להצביע צריך להזדהות בשם — לחץ &quot;היכנס&quot; למעלה
        </div>
      )}
    </div>
  );
}

// ───── [yakir pass 49] UpcomingModal — public roadmap with voting ─────
// Shows the list of planned features. Each card has icon, title, description,
// vote count, and a heart button. Voting toggles - one vote per user per
// feature. Anonymous viewers see the list and counts but the vote button
// prompts sign-in.
function UpcomingModal({ userId, onClose, onSignInRequest }) {
  const [features, setFeatures] = React.useState(null);
  const [voting, setVoting] = React.useState(null);  // feature_id currently voting

  const load = React.useCallback(() => {
    const qs = userId ? `?userId=${userId}` : '';
    api(`/api/upcoming-features${qs}`)
      .then(d => setFeatures(d.features || []))
      .catch(() => setFeatures([]));
  }, [userId]);

  React.useEffect(() => { load(); }, [load]);

  const vote = async (feature_id) => {
    if (!userId) {
      if (onSignInRequest) onSignInRequest();
      return;
    }
    if (voting === feature_id) return;
    setVoting(feature_id);
    try {
      const r = await api('/api/feature-vote', {
        method: 'POST',
        body: { user_id: userId, feature_id }
      });
      if (r && !r.error) {
        // Update locally for instant feedback
        setFeatures(prev => prev.map(f =>
          f.id === feature_id
            ? { ...f, vote_count: r.vote_count, viewer_voted: r.voted ? 1 : 0 }
            : f
        ));
      } else {
        showToast(r && r.error ? r.error : 'שגיאה');
      }
    } catch (e) {
      showToast(e.message || 'שגיאה');
    } finally {
      setVoting(null);
    }
  };

  return (
    <div className="modal-bg" style={{ zIndex: 9999 }} onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="modal wide" onClick={e => e.stopPropagation()} style={{ position: 'relative' }}>
        <button
          type="button"
          onClick={onClose}
          aria-label="סגור"
          style={{
            position: 'absolute', top: 14, left: 14,
            background: 'transparent', border: 'none',
            color: '#5a3d0a', fontSize: 24, cursor: 'pointer',
            padding: 6, lineHeight: 1, fontWeight: 700,
          }}
        >×</button>

        <h3 style={{ marginTop: 8, marginBottom: 6, color: '#5a3d0a' }}>
          🔮 בקרוב — מה כדאי לנו לבנות?
        </h3>
        <div style={{
          fontSize: 14, color: '#6d5d3d', marginBottom: 22, lineHeight: 1.7,
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
        }}>
          הקול שלך משפיע על הסדר. הצבע למה שחשוב לך לקבל הכי קודם.
        </div>

        {features === null && (
          <div style={{ color: '#6d5d3d', fontStyle: 'italic', textAlign: 'center', padding: 30 }}>
            טוען...
          </div>
        )}

        {features && features.length === 0 && (
          <div style={{ color: '#6d5d3d', textAlign: 'center', padding: 30 }}>
            אין כרגע פיצ&apos;רים פתוחים להצבעה
          </div>
        )}

        {features && features.length > 0 && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 14 }}>
            {features.map(f => (
              <div key={f.id} style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '14px 14px',
                background: f.viewer_voted
                  ? 'linear-gradient(135deg, rgba(212,175,55,0.15) 0%, rgba(184,148,31,0.08) 100%)'
                  : 'rgba(212,175,55,0.05)',
                border: `1.5px solid ${f.viewer_voted ? 'rgba(212,175,55,0.55)' : 'rgba(212,175,55,0.22)'}`,
                borderRadius: 12,
                transition: 'all 0.2s ease',
              }}>
                <div style={{
                  fontSize: 28, lineHeight: 1, flexShrink: 0,
                  width: 44, height: 44,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  background: 'rgba(255,255,255,0.5)', borderRadius: 12,
                }}>
                  {f.icon || '✨'}
                </div>
                <div style={{ flex: 1, minWidth: 0, textAlign: 'right' }}>
                  <div style={{
                    fontFamily: "'Frank Ruhl Libre', serif",
                    fontSize: 16, fontWeight: 700, color: '#1a1a2e',
                    marginBottom: 2,
                  }}>
                    {f.title}
                  </div>
                  <div style={{
                    fontSize: 12, color: '#5a3d0a',
                    lineHeight: 1.55, fontWeight: 500,
                  }}>
                    {f.description}
                  </div>
                </div>
                <button
                  type="button"
                  onClick={() => vote(f.id)}
                  disabled={voting === f.id}
                  style={{
                    display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2,
                    minWidth: 56, padding: '8px 10px',
                    background: f.viewer_voted
                      ? 'linear-gradient(135deg, #d4af37 0%, #b8941f 100%)'
                      : 'rgba(255,255,255,0.7)',
                    border: `1.5px solid ${f.viewer_voted ? '#5a3d0a' : 'rgba(212,175,55,0.50)'}`,
                    borderRadius: 12,
                    color: f.viewer_voted ? '#1a1a2e' : '#5a3d0a',
                    cursor: voting === f.id ? 'wait' : 'pointer',
                    opacity: voting === f.id ? 0.6 : 1,
                    transition: 'all 0.2s ease',
                    fontFamily: 'inherit',
                  }}
                  aria-label={f.viewer_voted ? 'בטל הצבעה' : 'הצבע'}
                >
                  <span style={{ fontSize: 18 }}>{f.viewer_voted ? '❤️' : '🤍'}</span>
                  <span style={{ fontSize: 13, fontWeight: 800 }}>{f.vote_count}</span>
                </button>
              </div>
            ))}
          </div>
        )}

        {!userId && features && features.length > 0 && (
          <div style={{
            padding: '10px 14px', marginBottom: 8,
            background: 'rgba(95,163,114,0.10)',
            border: '1.5px solid rgba(95,163,114,0.40)',
            borderRadius: 10,
            fontSize: 13, color: '#2e5a3d',
            textAlign: 'center', fontWeight: 600,
          }}>
            כדי להצביע צריך להזדהות בשם 🙂
          </div>
        )}

        <button
          type="button"
          onClick={onClose}
          className="btn ghost"
          style={{ width: '100%', marginTop: 6 }}
        >
          חזרה
        </button>
      </div>
    </div>
  );
}

function ShareModal({ data, onClose }) {
  const { link, title, text } = data;
  const waText = encodeURIComponent(`${text}\n${link}`);
  const tgText = encodeURIComponent(text);
  const tgUrl  = encodeURIComponent(link);
  const copyLink = async () => {
    try {
      await navigator.clipboard.writeText(link);
      showToast('הקישור הועתק ✓');
      onClose();
    } catch {
      prompt('העתק את הלינק:', link);
    }
  };
  return (
    <div className="share-modal-backdrop" onClick={onClose}>
      <div className="share-modal" onClick={e => e.stopPropagation()}>
        <h3>שתף את המעגל</h3>
        <div style={{ fontSize: 13, color: '#555' }}>{title}</div>
        <div className="share-link">{link}</div>
        <div className="share-row">
          <a className="share-btn wa"
             href={`https://wa.me/?text=${waText}`}
             target="_blank" rel="noopener noreferrer"
             onClick={() => setTimeout(onClose, 200)}>
            ✉️ WhatsApp
          </a>
          <a className="share-btn tg"
             href={`https://t.me/share/url?url=${tgUrl}&text=${tgText}`}
             target="_blank" rel="noopener noreferrer"
             onClick={() => setTimeout(onClose, 200)}>
            ✈️ Telegram
          </a>
          <button className="share-btn copy" onClick={copyLink}>📋 העתק</button>
        </div>
        <button className="share-close" onClick={onClose}>סגור</button>
      </div>
    </div>
  );
}

// ───────── Native share with graceful fallback ─────────
// Build invite URL using the appropriate door path (per Damri 2026-05-11).
// Chained shares keep going through the human door, not /c/:token old route.
function _circleDoorPath(textType, token) {
  const seg = textType === 'tanya' ? 'tanya'
            : textType === 'tikkunei_zohar' ? 'tikkun-zohar'
            : (textType === 'tehillim' ? 'tehillim' : null);
  return seg ? `/${seg}/invite/${encodeURIComponent(token)}`
             : `/invite/${encodeURIComponent(token)}`;
}

async function shareCircle(c) {
  // [lev-hadavar pass 30] Append ?from=<current_user_id> so the WhatsApp
  // OG preview can render personalized title: 'X מזמין/ה אותך למעגל Y'.
  // Per Damri 2026-05-11: 'חסר הקול של המזמין בתוך ה־preview עצמו.'
  const fromId = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
  const fromSuffix = fromId > 0 ? `?from=${fromId}` : '';
  const link = `${window.location.origin}${_circleDoorPath(c.text_type, c.share_token)}${fromSuffix}`;
  const title = c.name ? `מעגל: ${c.name}` : 'מעגל תפילה';
  // Per Damri 2026-05-11: text is minimal identification; OG card does
  // the personalization. Avoid duplication between typed body + preview.
  const text = c.name
    ? `מעגל ${c.name}${c.purpose ? ' — ' + c.purpose : ''}`
    : 'מעגל תפילה';

  // Native share sheet (mobile) — single tap, opens iOS/Android share UI
  if (navigator.share) {
    try {
      await navigator.share({ title, text, url: link });
      return;
    } catch (e) {
      if (e && e.name === 'AbortError') return; // user cancelled, no error
      // fall through to clipboard fallback
    }
  }

  // Desktop fallback — open the dedicated share modal
  if (typeof window.__openShareModal === 'function') {
    window.__openShareModal({ link, title, text });
    return;
  }

  // Last-resort fallback — copy to clipboard with toast
  try {
    await navigator.clipboard.writeText(link);
    showToast('הקישור הועתק ✓');
  } catch {
    prompt('העתק את הלינק:', link);
  }
}

// ───────── HeroDoor — the first encounter, per לב הדבר ─────────
// Quiet door. Serif headline. One line. One soft entry. Living moment
// below. No multi-CTA, no feature grid, no ideology, no token tickers.
// ───────── RefInviteBanner — human invite copy ─────────
// Per Damri 2026-05-11: 'X פתח לך את הדלת לבית דויד.' One sentence.
// No 'invited', no '🎁', no BEU mention, no 'הזכות שלכם תיפתח ביחד'.
// Focus on the PLACE that opened, not on system-talk.
// Falls back to generic 'חבר פתח לך את הדלת לבית דויד.' if name lookup fails.
function RefInviteBanner({ refCode, variant }) {
  const [firstName, setFirstName] = React.useState(null);

  React.useEffect(() => {
    if (!refCode) return;
    let cancelled = false;
    api(`/api/refcode/${encodeURIComponent(refCode)}`).then(d => {
      if (cancelled) return;
      if (d && d.first_name) setFirstName(d.first_name);
    }).catch(() => {});
    return () => { cancelled = true; };
  }, [refCode]);

  const subject = firstName || 'חבר';
  const sentence = `${subject} פתח לך את הדלת לבית דויד.`;

  if (variant === 'top') {
    // Anonymous user landing via ref link — small gold strip
    return (
      <div style={{
        background: 'linear-gradient(90deg, #1a1a2e 0%, #2d1b4e 100%)',
        color: '#e8e6f0',
        padding: '10px 16px',
        textAlign: 'center',
        fontSize: 14,
        fontFamily: "'Frank Ruhl Libre', serif",
        fontStyle: 'italic',
        borderBottom: '1px solid rgba(212,175,55,0.25)',
      }}>
        <span style={{ color: '#d4af37', fontStyle: 'normal' }}>✦</span>&nbsp;
        {sentence}
      </div>
    );
  }
  // variant === 'modal' — inside onboarding
  return (
    <div style={{
      background: 'rgba(212,175,55,0.06)',
      border: '1px solid rgba(212,175,55,0.2)',
      borderRadius: 8,
      padding: '10px 14px',
      fontSize: 13,
      fontFamily: "'Frank Ruhl Libre', serif",
      fontStyle: 'italic',
      color: '#d4af37',
      marginBottom: 12,
      textAlign: 'center',
    }}>
      {sentence}
    </div>
  );
}

// ───────── InviteDoor — door = WhatsApp card expanded (per Damri 2026-05-11, pass 31) ─────────
// 'הדף שמוצג בוואצאפ והדף שנפתח זה לא אותו דף.'
// Fix: door HERO matches OG title EXACTLY. The WhatsApp card promises
// 'X מזמין/ה אותך למעגל Y' — the door opens by confirming the same
// sentence, then expands with the rest of the content.
//
// Two surfaces, ONE protagonist (the inviter). Same sentence in both.
function _doorLine(textType) {
  if (textType === 'tanya')          return 'יש אנשים שמרגישים שיש בתוכם משהו עמוק שעוד לא פגשו.';
  if (textType === 'tikkunei_zohar') return 'יש רגעים שצריך לתקן יחד את מה שנשבר בנפרד.';
  return 'יש רגעים שאדם לא רוצה להתפלל לבד.';
}
function _doorBookLabel(textType) {
  if (textType === 'tanya')          return 'תניא';
  if (textType === 'tikkunei_zohar') return 'תיקוני הזוהר';
  return 'תהילים';
}
function _doorBookIcon(textType) {
  if (textType === 'tanya')          return '📗';
  if (textType === 'tikkunei_zohar') return '📕';
  return '📖';
}
function _progressPhrase(pct, isComplete, hasReaders) {
  if (isComplete) return 'המעגל הושלם.';
  if (pct >= 80)  return 'המעגל מתקרב לסיום.';
  if (pct >= 30)  return 'המעגל בעיצומו.';
  if (pct >= 1)   return 'המעגל כבר התחיל לנוע.';
  if (hasReaders) return 'אנשים כבר נכנסו לכאן.';
  return null;
}
function _doorInvitePath(textType, token) {
  const seg = textType === 'tanya' ? 'tanya'
            : textType === 'tikkunei_zohar' ? 'tikkun-zohar'
            : 'tehillim';
  return `/${seg}/invite/${encodeURIComponent(token)}`;
}

function InviteDoor({ token }) {
  const [info, setInfo] = React.useState(null);
  const [err, setErr] = React.useState(null);

  // Extract ?from=<user_id> from URL so the door matches the OG card
  const fromParam = React.useMemo(() => {
    try {
      const p = new URLSearchParams(window.location.search).get('from');
      const n = parseInt(p, 10);
      return Number.isFinite(n) && n > 0 ? n : null;
    } catch { return null; }
  }, []);

  React.useEffect(() => {
    if (!token) return;
    const q = fromParam ? `?from=${fromParam}` : '';
    api(`/api/circle/door/${encodeURIComponent(token)}${q}`).then(d => {
      if (d && !d.error) setInfo(d);
      else setErr(d?.error || 'not found');
    }).catch(() => setErr('network'));
  }, [token, fromParam]);

  if (err) {
    return (
      <div className="tanya-landing" style={{ minHeight: '100vh' }}>
        <section className="tl-hero" style={{ padding: '40vh 20px', textAlign: 'center' }}>
          <div className="tl-hero-bg" aria-hidden="true">
            <div className="tl-orb tl-orb-a" />
            <div className="tl-orb tl-orb-b" />
          </div>
          <div className="tl-hero-inner">
            <div style={{ fontSize: 18, fontStyle: 'italic', color: '#a89fbf',
                          fontFamily: "'Frank Ruhl Libre', serif" }}>המעגל לא נמצא.</div>
            <a href="/" style={{ color: '#d4af37', marginTop: 16, display: 'inline-block' }}>
              חזרה לבית דויד
            </a>
          </div>
        </section>
      </div>
    );
  }

  if (!info) {
    return (
      <div className="tanya-landing" style={{ minHeight: '100vh', display: 'flex',
                                              alignItems: 'center', justifyContent: 'center' }}>
        <div style={{ color: '#8a7c9a', fontStyle: 'italic',
                      fontFamily: "'Frank Ruhl Libre', serif" }}>...</div>
      </div>
    );
  }

  const enter = () => {
    // [P0 fix 2026-05-13] Per Yakir: anonymous visitors must land DIRECTLY
    // on the chapter — no registration prompt. Friend was stuck 2 days.
    // - Signed-in users keep the explicit /?circle_token= path so the
    //   inviter still gets BEU credit for the referral.
    // - Anonymous tanya visitors go straight to /read/tanya — the intent
    //   flow there opens the ReadModal for anyone, no sign-in gate.
    const currentUserId = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
    if (!currentUserId && info && info.text_type === 'tanya') {
      const ref = fromParam ? `?from=${fromParam}` : '';
      window.location.href = `/read/tanya${ref}`;
      return;
    }
    window.location.href = `/?circle_token=${encodeURIComponent(token)}`;
  };

  const total = info.total_chapters || 0;
  const done  = info.chapters_completed || 0;
  const pct   = total ? Math.min(100, Math.round((done / total) * 100)) : 0;
  const progressText = _progressPhrase(pct, info.is_complete, info.distinct_readers > 0);

  // Share URL preserves ?from= so chained shares keep their inviter context.
  // If the current viewer is logged in, they become the new inviter for
  // anyone they share to.
  const currentUserId = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
  const shareFromId = currentUserId > 0 ? currentUserId : fromParam;
  const shareSuffix = shareFromId ? `?from=${shareFromId}` : '';
  const shareUrl  = `${window.location.origin}${_doorInvitePath(info.text_type, token)}${shareSuffix}`;
  const shareText = info.name
    ? `${info.name} — מעגל ${_doorBookLabel(info.text_type)}`
    : `מעגל ${_doorBookLabel(info.text_type)}`;
  const waUrl = `https://wa.me/?text=${encodeURIComponent(shareText + ' — ' + shareUrl)}`;
  const tgUrl = `https://t.me/share/url?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareText)}`;
  const fbUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`;

  // The hero sentence — IDENTICAL to OG title.
  // 'X מזמין/ה אותך' when inviter known; opening line as fallback.
  const inviter = info.inviter_first_name;
  const heroLine1 = inviter ? `${inviter} מזמין/ה אותך` : 'כאן אנשים נפגשים';
  const heroLine2 = inviter ? `למעגל ${_doorBookLabel(info.text_type)}` : `דרך מה שהם קוראים`;
  // Subtitle — IDENTICAL to OG description.
  const heroSubtitle = inviter ? 'בוא/י לקרוא יחד איתנו.' : null;

  return (
    <div className="tanya-landing" style={{ minHeight: '100vh' }}>
      <section className="tl-hero" style={{
        padding: '40px 16px 56px',
        minHeight: '100vh',
        display: 'flex',
        alignItems: 'flex-start',
      }}>
        <div className="tl-hero-bg" aria-hidden="true">
          <div className="tl-orb tl-orb-a" />
          <div className="tl-orb tl-orb-b" />
          <div className="tl-orb tl-orb-c" />
        </div>

        <div className="tl-hero-inner" style={{
          maxWidth: 480, margin: '0 auto', textAlign: 'center', position: 'relative',
          width: '100%',
        }}>

          {/* HERO — the same sentence the WhatsApp card showed.
              When inviter known: 'דמרי מזמין/ה אותך / למעגל תניא'.
              Fallback: 'כאן אנשים נפגשים / דרך מה שהם קוראים'. */}
          <h1 className="tl-title" style={{
            fontSize: 'clamp(26px, 6vw, 36px)',
            margin: '14px 0 8px',
            lineHeight: 1.25,
            fontWeight: 800,
          }}>
            <span style={{ color: '#fff' }}>{heroLine1}</span>
            <br />
            <span className="tl-shine">{heroLine2}</span>
          </h1>

          {heroSubtitle && (
            <div style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontStyle: 'italic',
              fontSize: 'clamp(15px, 3.5vw, 17px)',
              color: '#d8d3c0',
              marginBottom: 32,
              opacity: 0.95,
            }}>
              {heroSubtitle}
            </div>
          )}

          {/* Quiet divider between the WhatsApp echo and the expansion */}
          <div style={{
            color: '#d4af37', fontSize: 16, opacity: 0.5,
            letterSpacing: 6,
            marginBottom: 28,
          }}>
            ── ✦ ──
          </div>

          {/* The expansion — what this circle specifically is */}
          <div style={{
            fontSize: 64, marginBottom: 14,
            filter: 'drop-shadow(0 4px 20px rgba(212,175,55,0.45))',
          }}>
            {_doorBookIcon(info.text_type)}
          </div>

          {info.name && (
            <h2 style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 'clamp(22px, 5vw, 28px)',
              margin: '0 0 6px',
              lineHeight: 1.2,
              fontWeight: 700,
              color: '#fff',
            }}>
              {info.name}
            </h2>
          )}

          {/* Creator attribution — secondary, only if different from inviter */}
          {info.creator_name && (
            <div style={{
              fontSize: 13, color: '#a89fbf', marginBottom: 24,
              fontFamily: "'Frank Ruhl Libre', serif",
              fontStyle: 'italic',
            }}>
              ✨ {info.creator_first_name === inviter ? 'נפתח דרך' : 'המעגל נפתח על ידי'} {info.creator_name}
            </div>
          )}

          {/* Purpose box — the specific 'why' of this circle */}
          {info.purpose && (
            <div style={{
              margin: '0 0 22px',
              padding: '16px 20px',
              borderRadius: 14,
              background: 'rgba(255,255,255,0.05)',
              border: '1px solid rgba(212,175,55,0.18)',
              backdropFilter: 'blur(8px)',
              fontSize: 16, lineHeight: 1.6, color: '#e6e0d4',
              fontFamily: "'Frank Ruhl Libre', serif",
            }}>
              {info.purpose}
            </div>
          )}

          {/* Door-line — why people gather around THIS book */}
          <div style={{
            margin: '8px 0 22px',
            padding: '18px 8px',
            fontFamily: "'Frank Ruhl Libre', serif",
            fontStyle: 'italic',
            fontSize: 'clamp(14px, 3.4vw, 16px)',
            color: '#d8d3c0',
            lineHeight: 1.7,
            borderTop: '1px solid rgba(212,175,55,0.18)',
            borderBottom: '1px solid rgba(212,175,55,0.18)',
          }}>
            "{_doorLine(info.text_type)}"
          </div>

          {/* System-level voice — answers 'איפה אני?' (quiet, contextual) */}
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontStyle: 'italic',
            fontSize: 'clamp(12px, 2.8vw, 14px)',
            color: '#d4af37',
            opacity: 0.85,
            lineHeight: 1.6,
            marginBottom: 26,
            padding: '0 6px',
          }}>
            ✦ כאן אנשים לא רק קוראים — הם נפגשים דרך מה שהם קוראים.
          </div>

          {/* Progress bar — visual only */}
          {total > 0 && (
            <div style={{ marginBottom: 28 }}>
              <div style={{
                height: 8, background: 'rgba(255,255,255,0.08)',
                borderRadius: 999, overflow: 'hidden',
                border: '1px solid rgba(212,175,55,0.18)',
                boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.3)',
              }}>
                <div style={{
                  height: '100%',
                  width: `${Math.max(pct, 2)}%`,
                  background: 'linear-gradient(90deg, #f1c40f 0%, #d4af37 100%)',
                  borderRadius: 999,
                  boxShadow: '0 0 12px rgba(212,175,55,0.5)',
                  transition: 'width 0.6s ease-out',
                }} />
              </div>
              {progressText && (
                <div style={{
                  marginTop: 10, fontSize: 13, color: '#a89fbf',
                  fontStyle: 'italic',
                  fontFamily: "'Frank Ruhl Libre', serif",
                }}>{progressText}</div>
              )}
            </div>
          )}

          {/* Primary CTA */}
          <button
            onClick={enter}
            className="tl-cta-primary"
            style={{
              display: 'inline-block',
              minWidth: 240,
              padding: '16px 38px',
              fontSize: 17,
              fontWeight: 700,
              fontFamily: 'inherit',
              cursor: 'pointer',
            }}
          >
            להיכנס למעגל ✨
          </button>

          {/* Share row — extends the chain */}
          <div style={{
            marginTop: 36,
            paddingTop: 22,
            borderTop: '1px dashed rgba(212,175,55,0.22)',
          }}>
            <div style={{
              fontSize: 13, color: '#d4af37',
              marginBottom: 14,
              fontFamily: "'Frank Ruhl Libre', serif",
              fontStyle: 'italic',
            }}>
              ✦ הזמינו עוד אנשים שיוכלו להצטרף
            </div>
            <div style={{
              display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10,
            }}>
              <a href={waUrl} target="_blank" rel="noopener noreferrer"
                style={{
                  background: 'linear-gradient(135deg, #25D366, #1ea952)',
                  color: '#fff', padding: '12px 8px', borderRadius: 12,
                  textDecoration: 'none', textAlign: 'center',
                  fontSize: 13, fontWeight: 700,
                  boxShadow: '0 4px 14px rgba(37,211,102,0.25)',
                }}>WhatsApp</a>
              <a href={tgUrl} target="_blank" rel="noopener noreferrer"
                style={{
                  background: 'linear-gradient(135deg, #229ED9, #1a87b8)',
                  color: '#fff', padding: '12px 8px', borderRadius: 12,
                  textDecoration: 'none', textAlign: 'center',
                  fontSize: 13, fontWeight: 700,
                  boxShadow: '0 4px 14px rgba(34,158,217,0.25)',
                }}>Telegram</a>
              <a href={fbUrl} target="_blank" rel="noopener noreferrer"
                style={{
                  background: 'linear-gradient(135deg, #1877F2, #1466d4)',
                  color: '#fff', padding: '12px 8px', borderRadius: 12,
                  textDecoration: 'none', textAlign: 'center',
                  fontSize: 13, fontWeight: 700,
                  boxShadow: '0 4px 14px rgba(24,119,242,0.25)',
                }}>Facebook</a>
            </div>
          </div>

          {/* Footer */}
          <div style={{
            marginTop: 30,
            fontSize: 13, color: '#8a7c9a',
            fontFamily: "'Frank Ruhl Libre', serif",
            fontStyle: 'italic',
            letterSpacing: 1,
          }}>
            ✦ בית דויד
          </div>
        </div>
      </section>
    </div>
  );
}

// ───────── HeroDoor + VisionCommitment (per Damri 2026-05-11, pass 35) ─────────
// Damri: 'איך עושים שדף הבית יהיה מרהיב יותר ושאנשים יוכלו לראות
//         את החזון שלנו והמחויבות שלנו, ושהעיצוב יהיה דומה קצת
//         לדף נחיתה לפחות בחלק העליון שלו.'
//
// Home page is Layer 3 territory — public identity surface where the
// full vision CAN appear (unlike the door, which is Layer 1).
//
// Structure:
//   1. Hero  — dark navy + orbs + shimmer + 2 CTAs + LivingMoment
//   2. Vision + 3 commitments — 'why we're here'
//   (LivingWindow + Circles continue below on the cream body)
function VisionAndCommitment({ onPage }) {
  const commitments = [
    {
      icon: '✦',
      title: 'שקיפות',
      desc: 'כל ההוקרה גלויה. אין אלגוריתם נסתר, אין מטרות סמויות.',
    },
    {
      icon: '🤝',
      title: 'אנושיות',
      desc: 'אנשים אמיתיים שנפגשים סביב משמעות. לא משחק, לא נתונים.',
    },
    {
      icon: '🌿',
      title: 'שורש',
      desc: 'מעוגן בספרי היסוד — תניא, תהילים, תיקוני הזוהר.',
    },
  ];

  return (
    <section className="tl-section" style={{
      padding: '70px 20px 80px',
      maxWidth: 920, margin: '0 auto',
      position: 'relative',
    }}>
      <div style={{
        textAlign: 'center', marginBottom: 18,
        fontSize: 12, letterSpacing: 4, color: '#d4af37',
        fontWeight: 600, textTransform: 'uppercase',
      }}>
        ✦ למה אנחנו כאן
      </div>

      <p style={{
        fontFamily: "'Frank Ruhl Libre', serif",
        fontSize: 'clamp(17px, 3vw, 22px)',
        lineHeight: 1.75,
        textAlign: 'center',
        color: '#e8e0c8',
        maxWidth: 640, margin: '0 auto 48px',
      }}>
        אנחנו בונים מרחב שבו אנשים נפגשים דרך לימוד משותף —
        והמפגש עצמו הופך לערך.
        <br /><br />
        <span style={{ color: '#a89fbf', fontSize: '0.92em' }}>
          לא ניקוד. לא משחק. אמת אנושית שגלויה לכל.
        </span>
      </p>

      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
        gap: 16,
        maxWidth: 880, margin: '0 auto',
      }}>
        {commitments.map(c => (
          <div key={c.title} style={{
            background: 'rgba(255,255,255,0.04)',
            border: '1px solid rgba(212,175,55,0.22)',
            borderRadius: 16, padding: '26px 22px',
            textAlign: 'center',
            backdropFilter: 'blur(8px)',
            transition: 'all 0.25s ease',
          }}>
            <div style={{
              fontSize: 30, marginBottom: 12,
              color: '#d4af37',
              filter: 'drop-shadow(0 2px 12px rgba(212,175,55,0.35))',
            }}>{c.icon}</div>
            <h3 style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 20, fontWeight: 700,
              margin: '0 0 10px', color: '#fff',
              letterSpacing: 0.3,
            }}>{c.title}</h3>
            <p style={{
              fontSize: 14, lineHeight: 1.65,
              color: '#c8c3d8', margin: 0,
              fontFamily: "'Frank Ruhl Libre', serif",
            }}>{c.desc}</p>
          </div>
        ))}
      </div>

      <div style={{ textAlign: 'center', marginTop: 44 }}>
        <button
          onClick={() => onPage && onPage('about')}
          style={{
            background: 'transparent',
            border: '1px solid rgba(212,175,55,0.45)',
            color: '#d4af37',
            padding: '12px 30px',
            borderRadius: 999,
            fontSize: 14, fontWeight: 600,
            fontFamily: 'inherit', cursor: 'pointer',
            letterSpacing: 0.5,
            transition: 'all 0.2s',
          }}
          onMouseEnter={e => {
            e.target.style.background = 'rgba(212,175,55,0.08)';
            e.target.style.borderColor = '#d4af37';
          }}
          onMouseLeave={e => {
            e.target.style.background = 'transparent';
            e.target.style.borderColor = 'rgba(212,175,55,0.45)';
          }}
        >
          קרא את החזון המלא ←
        </button>
      </div>
    </section>
  );
}

function HeroDoor({ onSignIn, isSignedIn, onPage }) {
  return (
    <div className="tanya-landing" style={{ position: 'relative' }}>
      {/* Hero — rich landing-page treatment */}
      <section className="tl-hero" style={{
        padding: '90px 20px 60px',
        minHeight: 'auto',
        overflow: 'hidden',
      }}>
        {/* Glowing orbs in background */}
        <div className="tl-hero-bg" aria-hidden="true">
          <div className="tl-orb tl-orb-a" />
          <div className="tl-orb tl-orb-b" />
          <div className="tl-orb tl-orb-c" />
        </div>

        <div className="tl-hero-inner">
          {/* Eyebrow chip */}
          <div className="tl-eyebrow" style={{ marginBottom: 30 }}>
            ✦ קהילה של ערך אנושי
          </div>

          {/* Title with shimmer */}
          <h1 className="tl-title" style={{
            fontSize: 'clamp(54px, 12vw, 92px)',
            margin: '0 0 26px',
            lineHeight: 1,
            letterSpacing: 2,
          }}>
            <span className="tl-shine">בית דויד</span>
          </h1>

          {/* Primary tagline — the soul */}
          <p style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontStyle: 'italic',
            fontSize: 'clamp(20px, 4vw, 26px)',
            color: '#e8d8b0',
            lineHeight: 1.5,
            maxWidth: 520, margin: '0 auto 22px',
          }}>
            מקום שבו האדם נמדד<br />
            לפי האור שהוא מביא לעולם
          </p>

          {/* Vision sub-tagline — quieter */}
          <p style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 'clamp(14px, 2.5vw, 16px)',
            color: '#a89fbf',
            lineHeight: 1.7,
            maxWidth: 480, margin: '0 auto 38px',
          }}>
            קהילה שבה לימוד הופך לחיבור,<br />
            וחיבור הופך לערך אמיתי.
          </p>

          {/* Two CTAs — primary + ghost */}
          <div className="tl-cta-row" style={{ marginBottom: 18 }}>
            <button
              className="btn tl-cta-primary"
              onClick={() => {
                if (isSignedIn && onPage) onPage('profile');
                else onSignIn && onSignIn();
              }}
              style={{ fontFamily: 'inherit' }}
            >
              {isSignedIn ? '✨ המשך פנימה' : '✨ התחל את המסע'}
            </button>
            <button
              className="btn ghost tl-cta-ghost"
              onClick={() => onPage && onPage('about')}
              style={{
                background: 'transparent',
                border: '1.5px solid rgba(212,175,55,0.5)',
                color: '#d4af37',
                padding: '14px 30px',
                borderRadius: 999,
                fontSize: 15, fontWeight: 600,
                fontFamily: 'inherit', cursor: 'pointer',
                letterSpacing: 0.5,
              }}
            >
              גלה את החזון
            </button>
          </div>

          {/* Trust line — subtle assurances */}
          <div style={{
            display: 'flex', flexWrap: 'wrap', justifyContent: 'center',
            gap: '14px 22px', marginTop: 22,
            fontSize: 12, color: '#8a7c9a',
            fontFamily: "'Frank Ruhl Libre', serif",
            fontStyle: 'italic',
          }}>
            <span>✓ ללא תשלום</span>
            <span>✓ ללא ספאם</span>
            <span>✓ אנושי, לא אלגוריתם</span>
          </div>

          {/* LivingMoment — quiet aliveness signal */}
          <div style={{ marginTop: 48 }}>
            <LivingMoment />
          </div>

          {/* [yakir 2026-05-17] FeaturedCircleBadge — circle-centric replacement
              for the older chapter-centric DailyChapterBadge. The latter is
              still defined below for legacy callers but is no longer wired
              into the home hero. */}
          <div style={{ marginTop: 32 }}>
            <FeaturedCircleBadge onPage={onPage} />
          </div>
        </div>
      </section>

      {/* Vision + Commitments — still in dark zone */}
      <VisionAndCommitment onPage={onPage} />
    </div>
  );
}

// ───────── FeaturedCircleBadge — circle-centric home entry (yakir 2026-05-17) ─────────
// Replaces DailyChapterBadge in HeroDoor. Where the previous widget
// surfaced "פרק היום: פרק כ״ג", this surfaces the entire circle as
// a living project: name, book/part, progress, participants. The whole
// card is a single tap target that routes to /tanya — which then
// re-introduces the circle as a project before letting the user enter
// it. Per Yakir: "Home → 'המעגל הנבחר' → /tanya → hero של המעגל →
// כניסה למעגל עצמו".
function FeaturedCircleBadge({ onPage }) {
  const [c, setC] = React.useState(null);
  React.useEffect(() => {
    api('/api/circles/featured').then(d => {
      if (d && d.featured) setC(d.featured);
    }).catch(() => {});
  }, []);
  if (!c) return null;
  const bookLbl  = circleBookLabel(c);
  const total    = c.total_chapters || 0;
  const verified = c.verified_count || 0;
  const parts    = c.participants_count || 0;
  const progress = total > 0 ? Math.round((verified / total) * 100) : 0;
  return (
    <div
      className="featured-circle-badge"
      role="button"
      tabIndex={0}
      onClick={() => onPage && onPage('tanya')}
      onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onPage && onPage('tanya'); } }}
    >
      <div className="fcb-eyebrow">המעגל הנבחר</div>
      <h3 className="fcb-name">{c.name}</h3>
      <div className="fcb-sub">📗 {bookLbl}</div>
      <div className="fcb-stats">
        <span><strong>{verified}</strong> / {total} פרקים</span>
        <span className="fcb-dot">·</span>
        <span><strong>{parts}</strong> משתתפים</span>
        {progress > 0 && (<>
          <span className="fcb-dot">·</span>
          <span>{progress}%</span>
        </>)}
      </div>
      <div className="fcb-cta">להיכנס למעגל ←</div>
    </div>
  );
}

// ───────── DailyChapterBadge — quiet daily-chapter invitation ─────────
// Yakir 2026-05-14. Shows today's suggested Tehillim chapter (in Hebrew
// letters, e.g. "פרק כ״ג") and, for signed-in users, a soft "ממשיך כבר N
// ימים" — deliberately not "streak: X 🔥". shaar.md: no language of
// success/measurement. If already read today: a quiet ✓ ackn.
function DailyChapterBadge({ onPage }) {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    const uid = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
    const url = uid > 0 ? `/api/daily-streak/${uid}` : '/api/daily-chapter';
    api(url).then(d => { if (d && !d.error) setData(d); }).catch(() => {});
  }, []);
  if (!data) return null;
  const ch = data.today_chapter || data.chapter;
  if (!ch) return null;
  const streak = data.current_streak || 0;
  const done = !!data.today_completed;
  return (
    <div className="daily-chapter-badge">
      <div className="dcb-row">
        <span className="dcb-label">פרק היום</span>
        <span className="dcb-chapter">{chapterLabel(ch)}</span>
        {done && <span className="dcb-done" title="קראת היום">✓</span>}
      </div>
      {streak >= 2 && (
        <div className="dcb-soft">ממשיך כבר {streak} ימים</div>
      )}
      {!done && (
        <button
          type="button"
          className="btn dcb-cta"
          onClick={() => onPage && onPage('tanya')}
        >
          {streak >= 1 ? 'להמשיך' : 'להתחיל'} ←
        </button>
      )}
    </div>
  );
}

// ───────── LivingMoment — one alive number + one recent action ─────────
// Subtle. Below the entry button. Fed by /api/community/presence.
// The aliveness IS the explanation.
function LivingMoment() {
  const [data, setData] = React.useState(null);
  React.useEffect(() => {
    api('/api/community/presence').then(d => { if (d && !d.error) setData(d); }).catch(() => {});
  }, []);
  if (!data) return null;
  const events = data.events || [];
  const recent = events[0];
  const total = data.today_total_readers || 0;
  if (total === 0 && !recent) return null;
  return (
    <div style={{
      marginTop: 56,
      fontFamily: "'Frank Ruhl Libre', serif",
      fontSize: 18, color: '#f5e8c4',
      lineHeight: 1.9, textAlign: 'center',
    }}>
      <div style={{ marginBottom: 10, letterSpacing: 2, color: '#d4af37', fontSize: 16 }}>
        ── ✦ ──
      </div>
      {total > 0 && (
        <div style={{
          color: '#f5e8c4', fontWeight: 700, fontSize: 20,
          textShadow: '0 0 24px rgba(243,198,74,0.30)',
        }}>
          {total === 1 ? 'אדם אחד קרא היום' : `${total} אנשים קוראים היום יחד`}
        </div>
      )}
      {recent && recent.name && recent.action_text && (
        <div style={{
          fontStyle: 'italic', fontSize: 17, marginTop: 10,
          color: '#e8dcb0', fontWeight: 500, lineHeight: 1.7,
        }}>
          <strong style={{ color: '#f3c64a', fontWeight: 700, fontStyle: 'normal',
                           textShadow: '0 0 12px rgba(243,198,74,0.40)' }}>
            {recent.name}
          </strong>
          {' '}{recent.action_text.length > 50 ? recent.action_text.slice(0, 50) + '…' : recent.action_text}
        </div>
      )}
    </div>
  );
}

// ───────── WhatHappensHere — invitations, not features ─────────
// Per לב הדבר: אם צריך להסביר יותר מדי — זה מוקדם מדי.
// Four soft invitations. No tokens, no marketplace, no platform-language.
// ───────── LivingWindow — 'מה חי כאן עכשיו' ─────────
// Per Damri 2026-05-11: 'פיד חייב להיות איטי ושקט. לא activity feed.
// יותר כמו: חלון הצצה לקהילה חיה.'
// Replaces WhatHappensHere on home page. Real moments, not marketing.
function timeAgoHe(iso) {
  if (!iso) return '';
  // SQLite returns 'YYYY-MM-DD HH:MM:SS' (UTC). Add Z to parse as UTC.
  const then = new Date(String(iso).replace(' ', 'T') + 'Z');
  if (isNaN(then.getTime())) return '';
  const now = new Date();
  const diffMs = now - then;
  const mins = Math.floor(diffMs / 60000);
  if (mins < 5) return 'כעת';
  if (mins < 60) return 'לפני כמה דקות';
  const hours = Math.floor(mins / 60);
  if (hours < 3) return 'לפני שעה';
  if (hours < 12) {
    const h = then.getHours();
    if (h >= 4 && h < 12) return 'הבוקר';
    if (h >= 18 && h < 23) return 'הערב';
    return 'השעות האחרונות';
  }
  if (hours < 36) return 'אתמול';
  const days = Math.floor(hours / 24);
  if (days < 7) return `לפני ${days} ימים`;
  return 'לאחרונה';
}

function LivingWindow() {
  const [events, setEvents] = React.useState(null);
  React.useEffect(() => {
    api('/api/living-window').then(d => {
      if (d && !d.error) setEvents(d.events || []);
      else setEvents([]);
    }).catch(() => setEvents([]));
  }, []);

  // While loading: render the wrapper at the right size but empty (no skeleton)
  // so the page doesn't jump when data arrives.
  if (!events) return null;

  return (
    <section style={{
      maxWidth: 580, margin: '4vh auto 6vh',
      padding: '32px 28px',
      background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
      border: '1px solid rgba(212,175,55,0.25)',
      boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
      borderRadius: 16,
    }}>
      <div style={{
        textAlign: 'center', marginBottom: 28,
        fontFamily: "'Frank Ruhl Libre', serif",
        fontSize: 24, color: '#f3c64a',
        fontStyle: 'italic',
        fontWeight: 600,
        letterSpacing: 1,
        textShadow: '0 0 24px rgba(243,198,74,0.30)',
      }}>
        מה חי כאן עכשיו
      </div>

      {events.length === 0 ? (
        <div style={{
          textAlign: 'center', padding: '14px 8px',
          fontFamily: "'Frank Ruhl Libre', serif",
          fontStyle: 'italic', fontSize: 17, color: '#d4b878',
          lineHeight: 1.9, fontWeight: 500,
        }}>
          השדה שקט הבוקר.<br/>
          ייתכן שדווקא היום אתה הקול הראשון.
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
          {events.map((e, i) => (
            <div key={i} style={{
              display: 'flex', alignItems: 'flex-start', gap: 14,
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 17, color: '#f5e8c4',
              lineHeight: 1.65, fontWeight: 500,
            }}>
              <div style={{
                fontSize: 22, lineHeight: 1.2,
                flexShrink: 0, marginTop: 2, opacity: 1,
              }}>{e.icon}</div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontStyle: 'italic' }}>
                  {e.text}
                  {e.actor && (e.actor_id ? (
                    <a href={`#/user/${e.actor_id}`} style={{ color: '#f3c64a', fontStyle: 'normal', fontWeight: 700, textDecoration: 'none', borderBottom: '1px dotted rgba(243,198,74,0.5)' }}> · {e.actor}</a>
                  ) : (
                    <span style={{ color: '#f3c64a', fontStyle: 'normal', fontWeight: 700 }}> · {e.actor}</span>
                  ))}
                </div>
                <div style={{
                  fontSize: 12, color: '#a89878',
                  marginTop: 4, opacity: 1, fontWeight: 500,
                }}>
                  {timeAgoHe(e.created_at)}
                </div>
              </div>
            </div>
          ))}
        </div>
      )}

      {/* Quiet footer — same shape as before, different message */}
      <div style={{
        textAlign: 'center', marginTop: 28, paddingTop: 18,
        borderTop: '1px dashed rgba(243,198,74,0.40)',
        fontFamily: "'Frank Ruhl Libre', serif",
        fontSize: 15, color: '#d4b878',
        fontStyle: 'italic', fontWeight: 500,
      }}>
        <div style={{ color: '#f3c64a', marginBottom: 6, fontSize: 16 }}>── ✦ ──</div>
        <span>אתה יכול להיכנס לכל אחד מהרגעים האלה</span>
      </div>
    </section>
  );
}

function WhatHappensHere() {
  const items = [
    { ico: '📖', text: 'קוראים יחד עד שהספר נסגר' },
    { ico: '✨', text: 'אנשים פותחים מעגלים סביב מה שכואב להם' },
    { ico: '🤝', text: 'אנשים מציעים מה שהשם חנן בהם' },
    { ico: '🌱', text: 'אנשים מבקשים עזרה כשחסר' },
  ];
  return (
    <section style={{
      paddingTop: '4vh', paddingBottom: '6vh',
      maxWidth: 580, margin: '0 auto',
      padding: '32px 28px',
      background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
      border: '1px solid rgba(212,175,55,0.25)',
      boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
      borderRadius: 16,
    }}>
      <div style={{
        textAlign: 'center', marginBottom: 32,
        fontFamily: "'Frank Ruhl Libre', serif",
        fontSize: 24, color: '#d4af37',
        fontStyle: 'italic',
      }}>
        מה קורה כאן
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
        {items.map((it, i) => (
          <div key={i} style={{
            display: 'flex', alignItems: 'center', gap: 16,
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 18, color: '#e8e6f0', fontWeight: 500,
            lineHeight: 1.6,
            padding: '4px 8px',
          }}>
            <div style={{ fontSize: 26, lineHeight: 1 }}>{it.ico}</div>
            <div style={{ flex: 1 }}>{it.text}</div>
          </div>
        ))}
      </div>
      <div style={{
        textAlign: 'center', marginTop: 32, paddingTop: 24,
        borderTop: '1px dashed rgba(212,175,55,0.25)',
        fontFamily: "'Frank Ruhl Libre', serif",
        fontSize: 15, color: '#c8c8d8',
        fontStyle: 'italic',
      }}>
        <div style={{ color: '#d4af37', marginBottom: 8 }}>── ✦ ──</div>
        <span style={{ fontSize: 14 }}>כל מי שכאן — נראה</span>
      </div>
    </section>
  );
}

function Nav({ onPage, page, isAdmin, isSignedIn, userId, onStartJourney }) {
  // Order follows user journey: discovery → books → action → earnings → utility → support.
  // Trimmed per Damri 2026-05-11: 'לא להציג מערכת. להציג דרך חיים אחת
  // עם כניסות טבעיות.' 6 visible items + signin/admin appended below.
  // Hidden but reachable via URL/context:
  //   tanya, tikkun-zohar, sefer-revii — accessed via share links and Circles
  //   coins — accessed via Profile (click on '✦ N מטבעות הוקרה')
  //   leaderboard — accessed via LivingMoment click on home
  //   tasks — admins reach through ⚙️ ניהול
  // Renamed 'קהילה' → 'מה חי בקהילה' — tone shift from social-network feel
  // to community-aliveness (consciousness change per Damri).
  const items = [
    ['home', '🏠 בית'],
    // [damri ss#4 2026-05-24] 'מעגלים' removed from nav — circles already at top of home page
    // ['circles', '⭐ מעגלים'],
    ['profile', '👤 הפרופיל שלי'],
    // [damri 2026-05-24 round D] חצר הרבים now lives as a floating widget bottom-right (not nav).
    // Old route still exists for direct URL access (#/rebbim).
    // ['rebbim', '🕊️ חצר הרבים'],
    ['ecosystem', '💎 האקוסיסטם'],
    ['marketplace', '🌾 מרכז השפע'],
    ['community', '👥 מי חי בקהילה'],
    ['transparency', '🌐 מסע הקהילה'],
    // [yakir-fb#5 2026-05-24] hidden from nav per yakir request - page accessible by URL only
    // ['journal', '📜 יומן המסע'],
    ['contact', '✉️ צור קשר']
  ];
  // Explicit sign-in tab for users who scan the nav for "התחברות" rather
  // than spotting the gold CTA. Goes at the end of the items list, just
  // before the admin entry. Renders as a normal-looking link but its
  // 'signin' key is intercepted in go() to open the modal instead of nav.
  if (!isSignedIn) items.push(['signin', '🔐 התחברות / הירשם']);
  if (isAdmin) items.push(['admin', '⚙️ ניהול']);
  const [open, setOpen] = useState(false);
  // Close drawer on page change (e.g. user taps a link inside it)
  // 'circles' is a virtual nav item — go to home, then scroll to #circles anchor.
  const go = (k) => {
    if (k === 'signin') {
      // Sign-in is a modal, not a page. Same handler the gold CTA uses.
      if (onStartJourney) onStartJourney();
      setOpen(false);
      return;
    }
    if (k === 'circles') {
      onPage('home');
      setTimeout(() => {
        const el = document.getElementById('circles');
        if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
      }, 80);
    } else {
      onPage(k);
    }
    setOpen(false);
  };
  // Lock body scroll when drawer is open
  React.useEffect(() => {
    document.body.style.overflow = open ? 'hidden' : '';
    return () => { document.body.style.overflow = ''; };
  }, [open]);
  return (
    <>
      <nav className="nav nav-stacked">
        {/* [damri ss#3 2026-05-24] header: hamburger top-right, brand center, avatar+bell top-left */}
        <div className="nav-row nav-row-brand" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
          <button className="nav-burger" aria-label="תפריט" aria-expanded={open}
                  onClick={() => setOpen(o => !o)} type="button"
                  style={{ order: 0 }}>
            <span /><span /><span />
          </button>
          <div className="brand" onClick={() => go('home')} style={{ cursor: 'pointer', order: 1, flex: 1, textAlign: 'center' }}>בית דויד ✡</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, order: 2 }}>
            <NotificationsBell userId={userId} isSignedIn={isSignedIn} onPage={onPage} />
            <UserBadge isSignedIn={isSignedIn} onClick={() => go('profile')} />
          </div>
        </div>
        <div className="nav-row nav-row-controls">
          <div className="links nav-links-desktop">
            {items.map(([k, l]) => (
              <a key={k} onClick={() => go(k)} style={{ cursor: 'pointer', color: page === k ? 'var(--gold)' : undefined }}>{l}</a>
            ))}
            {!isSignedIn && (
              <button
                className="nav-journey-cta"
                onClick={() => { setOpen(false); if (onStartJourney) onStartJourney(); }}
                style={{
                  background: 'linear-gradient(135deg, #d4af37, #f1c40f)',
                  color: '#1a1a2e', border: 'none', padding: '8px 16px',
                  borderRadius: 20, fontWeight: 700, fontSize: 14,
                  cursor: 'pointer', marginRight: 8,
                  boxShadow: '0 2px 8px rgba(212,175,55,0.35)'
                }}
                type="button"
              >✨ התחל את המסע</button>
            )}
          </div>
        </div>
      </nav>
      {/* Mobile drawer */}
      <div className={`nav-drawer-bg ${open ? 'open' : ''}`} onClick={() => setOpen(false)} />
      <aside className={`nav-drawer ${open ? 'open' : ''}`} aria-hidden={!open}>
        <button className="nav-drawer-close" onClick={() => setOpen(false)} aria-label="סגור">×</button>
        <div className="nav-drawer-brand">בית דויד ✡</div>
        <div className="nav-drawer-links">
          {items.map(([k, l]) => (
            <a key={k} onClick={() => go(k)}
               className={page === k ? 'active' : ''}>{l}</a>
          ))}
        </div>
        {!isSignedIn && (
          <div style={{ padding: '20px 24px', borderTop: '1px solid rgba(255,255,255,0.1)' }}>
            <button
              onClick={() => { setOpen(false); if (onStartJourney) onStartJourney(); }}
              style={{
                width: '100%', background: 'linear-gradient(135deg, #d4af37, #f1c40f)',
                color: '#1a1a2e', border: 'none', padding: '14px 20px',
                borderRadius: 28, fontWeight: 800, fontSize: 16,
                cursor: 'pointer', boxShadow: '0 4px 14px rgba(212,175,55,0.4)'
              }}
              type="button"
            >✨ התחל את המסע</button>
            <div style={{ textAlign: 'center', marginTop: 10, fontSize: 12, opacity: 0.7 }}>
              הירשמו למעטפת הקהילתית · התחלת המסע לזהות האמיתית
            </div>
          </div>
        )}
      </aside>
    </>
  );
}

// ───────── 8-step Journey (added 2026-05-04) ─────────
function BookProgressBars() {
  const [books, setBooks] = useState(null);

  const load = useCallback(() => {
    api('/api/book-status').then(r => {
      if (r && Array.isArray(r.books)) setBooks(r.books);
    }).catch(() => {});
  }, []);

  useEffect(() => {
    load();
    const t = setInterval(load, 30000); // refresh every 30s
    return () => clearInterval(t);
  }, [load]);

  if (!books) return null;

  return (
    <div style={{ maxWidth: 720, margin: '30px auto 0' }}>
      <div style={{
        fontSize: 13, color: '#8b6508', fontWeight: 800,
        letterSpacing: 0.3, marginBottom: 12, textAlign: 'center'
      }}>
        🏆 התקדמות קהילתית — מחזור נוכחי
      </div>
      {books.map(b => {
        const meta = BOOK_META[b.book] || {};
        const pct = Math.min(100, Math.round((b.unique_read / b.total_chapters) * 100));
        return (
          <div key={b.book} style={{ marginBottom: 16 }}>
            <div style={{
              display: 'flex', justifyContent: 'space-between',
              alignItems: 'baseline', marginBottom: 4,
              fontSize: 14, color: '#1a1a2e',
            }}>
              <span>
                <span style={{ fontSize: 18, marginLeft: 6 }}>{meta.icon || '📚'}</span>
                <strong style={{ color: '#1a1a2e' }}>{meta.label || b.book}</strong>
                {b.current_cycle > 1 && (
                  <span style={{ color: '#4b5563', fontSize: 12, marginRight: 8 }}>
                    · מחזור {b.current_cycle}
                  </span>
                )}
              </span>
              <span style={{ color: '#374151', fontSize: 13, fontWeight: 600 }}>
                {b.unique_read} / {b.total_chapters} פרקים
                {' · '}
                <span style={{ color: '#8b6508', fontWeight: 800 }}>🏆 {b.bonus_per_user} {b.symbol}</span>
              </span>
            </div>
            <div style={{
              width: '100%', height: 10, borderRadius: 6,
              background: 'rgba(26,26,46,0.08)',
              border: '1px solid rgba(26,26,46,0.1)',
              overflow: 'hidden',
            }}>
              <div style={{
                width: pct + '%', height: '100%',
                background: 'linear-gradient(90deg, #a8800a, #d4a017)',
                borderRadius: 6,
                transition: 'width 1s ease-out',
                boxShadow: pct === 100 ? '0 0 12px rgba(168,128,10,0.6)' : 'none',
              }} />
            </div>
            <div style={{
              fontSize: 11, color: '#4b5563', marginTop: 3,
              textAlign: 'end', fontWeight: 500,
            }}>
              {b.participants_count} משתתפים במחזור הזה
              {b.completion_count > 0 && ` · ${b.completion_count} מחזורים קודמים הושלמו`}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ───────── UserBadge — visible in nav on every page ─────────
// Per Damri 2026-05-10: 'למה כשאני בדף הבית לא רואים איזה משתמש מחובר?'
// Small chip in the nav header. Always visible. Click → goes to profile.
function UserBadge({ isSignedIn, onClick }) {
  const [info, setInfo] = React.useState(null);
  React.useEffect(() => {
    if (!isSignedIn) { setInfo(null); return; }
    try {
      const name = localStorage.getItem('tehillim_user_name') || '';
      const sub  = localStorage.getItem('tehillim_google_sub') || '';
      const uid  = localStorage.getItem('tehillim_user_id') || '';
      setInfo({ name, hasGoogle: !!sub, uid });
    } catch { setInfo(null); }
  }, [isSignedIn]);
  if (!isSignedIn || !info) return null;
  const name = info.name || 'משתמש';
  const initial = (name.trim()[0] || '?').toUpperCase();
  // Color coded by auth method (matches LoggedInAs in profile)
  const accent = info.hasGoogle ? '#2ecc71' : '#e67e22';
  // [yakir-fb#3 2026-05-24] reduced to circle-only (no name text).
  // Name shown via title= on hover/long-press; ARIA still announces it.
  return (
    <button
      onClick={onClick}
      aria-label={`מחובר/ת כ ${name}`}
      title={name}
      style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        width: 32, height: 32,
        background: accent,
        border: '1.5px solid ' + accent,
        borderRadius: '50%', padding: 0,
        cursor: 'pointer', fontFamily: 'inherit',
        color: '#fff', fontSize: 14, fontWeight: 800,
        boxShadow: '0 2px 6px rgba(0,0,0,0.18)',
      }}
    >
      {initial}
    </button>
  );
}

// ───────── LoggedInAs — show how user is authenticated ─────────
// Per לב הדבר 2026-05-10: invitation, not warning. No countdown.
// No threats about losing data. Open door, gentle light.
function LoggedInAs({ profile, onUpgrade }) {
  if (!profile) return null;
  const isAnon = !profile.email || String(profile.email).endsWith('@anon.tehillim.local');
  const hasGoogle = !!profile.google_sub;
  const hasPassword = !!profile.has_password;
  const secured = hasGoogle || (!isAnon && hasPassword);
  let methodIcon, methodLabel, color, bg;
  if (hasGoogle)        { methodIcon = '🟢'; methodLabel = 'Google';   color = '#2ecc71'; bg = 'rgba(46,204,113,0.10)'; }
  else if (hasPassword) { methodIcon = '✉️'; methodLabel = 'אימייל';    color = '#3498db'; bg = 'rgba(52,152,219,0.10)'; }
  else                  { methodIcon = '🌱'; methodLabel = 'אנונימי';  color = '#a8801f'; bg = 'rgba(168,128,31,0.10)'; }
  return (
    <div style={{
      maxWidth: 600, margin: '0 auto 14px', padding: '12px 14px',
      background: bg, border: '1px solid ' + color + '40',
      borderRadius: 10,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <span style={{ fontSize: 16 }}>{methodIcon}</span>
        <span style={{ fontSize: 13, color: '#e8e6f0' }}>
          מחובר/ת כ: <strong style={{ color: '#fff' }}>{profile.name || 'משתמש'}</strong>
          <span style={{ color: '#8a8da8', marginRight: 8 }}> · {methodLabel}</span>
        </span>
      </div>
      {!secured && (
        <div style={{
          fontSize: 13, color: '#e8e6f0', marginTop: 10,
          paddingTop: 10, borderTop: '1px dashed ' + color + '40',
          lineHeight: 1.7,
        }}>
          <div style={{ fontWeight: 600, color: '#d4af37', marginBottom: 6 }}>
            רוצה שהמסע שלך ימשיך איתך?
          </div>
          <div>
            הפרקים שקראת והזכות שנוצרה רשומים. כרגע הכל חי במכשיר הזה בלבד —
            אם תרצה שזה יילך איתך לכל מכשיר, אפשר לחבר חשבון Google או להירשם בקצרה.
          </div>
          <button
            onClick={onUpgrade}
            style={{
              marginTop: 12, padding: '10px 16px', fontSize: 13, fontWeight: 700,
              background: 'linear-gradient(135deg, #d4af37, #f1c40f)',
              color: '#1a1a2e', border: 'none', borderRadius: 8,
              cursor: 'pointer', fontFamily: 'inherit', width: '100%',
              boxShadow: '0 2px 8px rgba(212,175,55,0.25)',
            }}
          >
            🌱 שמור את המקום שלך
          </button>
        </div>
      )}
    </div>
  );
}

// ───────── Notifications Bell — replaces auto-pop banners ─────────
// Per Damri: opt-in updates only, no surprise popups. Click bell → panel.
// "Dismissed" announcements stored per-user in localStorage so the badge
// shows only what's new since last visit.
function NotificationsBell({ userId, isSignedIn, onPage }) {
  // [yakir pass 28] Real inbox bell — connected to /api/notifications/:userId.
  // Shows accurate unread count, opens a full inbox modal with delete,
  // mark-read, pagination, and special styling for system_announcement.
  // Mirrors UpdatesPanel functionality so the bell is a true global inbox.
  const [open, setOpen] = React.useState(false);
  const [items, setItems] = React.useState([]);
  const [unread, setUnread] = React.useState(0);
  const [total, setTotal] = React.useState(0);
  const [shown, setShown] = React.useState(15);
  const [tick, setTick] = React.useState(0);
  const [busyId, setBusyId] = React.useState(null);

  // Poll for unread count every 60s while signed in (lightweight)
  React.useEffect(() => {
    if (!userId) return;
    const fetchNow = () => {
      api(`/api/notifications/${userId}?limit=${shown}&offset=0`).then(d => {
        if (!d || d.error) return;
        setItems(Array.isArray(d.items) ? d.items : []);
        setUnread(d.unread || 0);
        setTotal(d.total || 0);
      }).catch(() => {});
    };
    fetchNow();
    if (!open) {
      const t = setInterval(fetchNow, 60000);
      return () => clearInterval(t);
    }
  }, [userId, tick, shown, open]);

  // Anonymous: hide the bell entirely (sign-in CTA exists elsewhere)
  if (!isSignedIn || !userId) return null;

  const markRead = async (id) => {
    try { await api(`/api/notifications/${id}/read`, { method: 'PUT' }); setTick(t => t + 1); }
    catch (_) {}
  };
  const markAllRead = async () => {
    try { await api(`/api/notifications/${userId}/read-all`, { method: 'PUT' }); setTick(t => t + 1); }
    catch (_) {}
  };
  const deleteOne = async (id, e) => {
    if (e) e.stopPropagation();
    setBusyId(id);
    try {
      setItems(arr => arr.filter(x => x.id !== id));
      await api(`/api/notifications/${id}?user_id=${userId}`, { method: 'DELETE' });
      setTick(t => t + 1);
    } catch (_) { setTick(t => t + 1); }
    finally { setBusyId(null); }
  };
  const clearAllRead = async () => {
    if (!window.confirm('למחוק את כל ההודעות שכבר נקראו?')) return;
    try { await api(`/api/notifications/${userId}/clear-read`, { method: 'DELETE' }); setTick(t => t + 1); }
    catch (_) {}
  };
  const loadMore = () => setShown(s => s + 15);

  const openLink = (url) => {
    if (!url) return;
    setOpen(false);
    // External URL — full navigation
    if (!url.startsWith('/') || url.startsWith('//')) {
      window.location.href = url;
      return;
    }
    // Internal path: take first segment as page name
    const seg = url.replace(/^\/+/, '').split('/')[0];
    // [yakir pass 35] Legacy 'coins' route removed - silently redirect home.
    if (!seg || seg === 'coins') { if (onPage) onPage('home'); return; }
    // Known top-level page — navigate in-app via onPage + update hash.
    if (onPage) {
      onPage(seg);
      try { window.location.hash = '#/' + seg; } catch (_) {}
      return;
    }
    // Fallback if no onPage (shouldn't happen)
    window.location.href = url;
  };

  return (
    <>
      <button
        onClick={() => setOpen(true)}
        aria-label="עדכונים"
        style={{
          background: 'none', border: 'none', cursor: 'pointer',
          padding: 6, position: 'relative',
          fontSize: 22, lineHeight: 1, color: 'var(--gold, #d4af37)',
        }}
      >
        🔔
        {unread > 0 && (
          <span style={{
            position: 'absolute', top: 0, left: 0,
            background: '#c0392b', color: '#fff',
            borderRadius: 10, padding: '1px 6px',
            fontSize: 10, fontWeight: 800, lineHeight: 1.4,
            minWidth: 16, textAlign: 'center',
            boxShadow: '0 2px 8px rgba(192,57,43,0.5)',
          }}>{unread > 99 ? '99+' : unread}</span>
        )}
      </button>

      {open && (
        <div onClick={() => setOpen(false)} style={{
          position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.65)',
          zIndex: 9000, display: 'flex', justifyContent: 'center',
          alignItems: 'flex-start', padding: '60px 16px',
        }}>
          <div onClick={e => e.stopPropagation()} style={{
            background: 'linear-gradient(135deg, #1a1625 0%, #2d1b4e 100%)',
            border: '1.5px solid rgba(243,198,74,0.35)',
            borderRadius: 16, padding: '22px 20px', width: '100%', maxWidth: 520,
            color: '#f0e2bc',
            boxShadow: '0 20px 60px rgba(0,0,0,0.65), 0 0 40px rgba(243,198,74,0.10)',
            maxHeight: '82vh', overflowY: 'auto', position: 'relative',
          }}>
            <button onClick={() => setOpen(false)} aria-label="סגור" style={{
              position: 'absolute', top: 10, left: 10, background: 'none',
              border: 'none', color: '#8a8da8', fontSize: 26, cursor: 'pointer',
              padding: 4, lineHeight: 1,
            }}>×</button>

            <div style={{
              display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
              marginBottom: 16, paddingLeft: 32, gap: 12, flexWrap: 'wrap',
            }}>
              <div style={{
                fontSize: 18, fontWeight: 800, color: '#f3c64a',
                letterSpacing: 1.5, textShadow: '0 0 18px rgba(243,198,74,0.25)',
              }}>
                🔔 העדכונים שלי{unread > 0 ? ` · ${unread} חדשים` : total > 0 ? ` · ${total}` : ''}
              </div>
              <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
                {unread > 0 && (
                  <button
                    onClick={markAllRead}
                    style={{ background: 'transparent', border: 'none', color: '#c9b88a',
                             fontSize: 12, cursor: 'pointer', textDecoration: 'underline',
                             fontFamily: 'inherit' }}
                  >סמן הכל כנקרא</button>
                )}
                {items.some(n => n.read) && (
                  <button
                    onClick={clearAllRead}
                    style={{ background: 'transparent', border: 'none', color: '#a89878',
                             fontSize: 12, cursor: 'pointer', textDecoration: 'underline',
                             fontFamily: 'inherit' }}
                  >🗑 נקה נקראים</button>
                )}
              </div>
            </div>

            {items.length === 0 ? (
              <div style={{ color: '#a89878', fontStyle: 'italic',
                            textAlign: 'center', padding: 36, fontSize: 15,
                            fontFamily: "'Frank Ruhl Libre', serif" }}>
                התיבה שקטה — כשמשהו יקרה תדע.
              </div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                {items.map(n => (
                  <div
                    key={n.id}
                    onClick={() => { if (!n.read) markRead(n.id); if (n.url) openLink(n.url); }}
                    style={{
                      padding: '12px 14px',
                      borderRadius: 12,
                      background: n.kind === 'system_announcement'
                        ? (n.read
                            ? 'linear-gradient(135deg, rgba(180,140,250,0.06), rgba(243,198,74,0.04))'
                            : 'linear-gradient(135deg, rgba(180,140,250,0.18), rgba(243,198,74,0.14))')
                        : (n.read
                            ? 'linear-gradient(135deg, rgba(255,255,255,0.025), rgba(243,198,74,0.02))'
                            : 'linear-gradient(135deg, rgba(243,198,74,0.18), rgba(200,151,32,0.10))'),
                      border: n.kind === 'system_announcement'
                        ? (n.read ? '1px solid rgba(180,140,250,0.20)' : '1.5px solid rgba(180,140,250,0.55)')
                        : (n.read ? '1px solid rgba(243,198,74,0.12)' : '1.5px solid rgba(243,198,74,0.55)'),
                      cursor: 'pointer', transition: 'all 0.2s',
                      textAlign: 'right', direction: 'rtl',
                      boxShadow: n.read ? 'none'
                        : (n.kind === 'system_announcement'
                            ? '0 3px 12px rgba(120, 80, 220, 0.22)'
                            : '0 3px 12px rgba(138, 95, 16, 0.20)'),
                      position: 'relative',
                      opacity: busyId === n.id ? 0.4 : 1,
                    }}
                  >
                    <button
                      onClick={(e) => deleteOne(n.id, e)}
                      disabled={busyId === n.id}
                      title="מחק הודעה"
                      style={{
                        position: 'absolute', top: 6, left: 6,
                        width: 22, height: 22, borderRadius: '50%',
                        background: 'rgba(0,0,0,0.25)', color: '#a89878',
                        border: 'none', cursor: 'pointer', fontSize: 14, lineHeight: 1,
                        padding: 0, display: 'flex', alignItems: 'center',
                        justifyContent: 'center', fontFamily: 'inherit',
                        transition: 'all 0.15s', zIndex: 2,
                      }}
                    >×</button>
                    {n.kind === 'system_announcement' && (
                      <div style={{
                        display: 'inline-block', fontSize: 10, letterSpacing: 1.5,
                        color: '#b48aff', fontWeight: 700,
                        background: 'rgba(180,140,250,0.15)',
                        padding: '2px 8px', borderRadius: 100, marginBottom: 6,
                      }}>📣 מערכת</div>
                    )}
                    <div style={{ fontSize: 15, fontWeight: 700, color: '#f3c64a', marginBottom: 4, paddingRight: 28 }}>
                      {n.title}
                    </div>
                    {n.body && (
                      <div style={{
                        fontSize: 14, color: '#e8dcb0', lineHeight: 1.6,
                        fontFamily: "'Frank Ruhl Libre', serif",
                      }}>{n.body}</div>
                    )}
                    <div style={{ fontSize: 11, color: '#a89878', marginTop: 6 }}>
                      {(n.created_at || '').replace('T', ' ').slice(0, 16)}
                      {n.url && <span style={{ color: '#b48aff', marginRight: 8 }}>· פתח ›</span>}
                    </div>
                  </div>
                ))}
              </div>
            )}

            {total > items.length && (
              <div style={{ textAlign: 'center', marginTop: 14 }}>
                <button onClick={loadMore} style={{
                  background: 'linear-gradient(135deg, rgba(243,198,74,0.14), rgba(200,151,32,0.08))',
                  border: '1px solid rgba(243,198,74,0.35)',
                  color: '#f3c64a', fontSize: 12, fontWeight: 700,
                  padding: '8px 22px', borderRadius: 100,
                  cursor: 'pointer', fontFamily: 'inherit', letterSpacing: 1,
                }}>טען עוד ↓ ({total - items.length} ישנים)</button>
              </div>
            )}
          </div>
        </div>
      )}
    </>
  );
}

function ChapterGrid({ circleId }) {
  // [BOOK-COMPLETION-FIX] Use circle.total_chapters (53 for tanya, 88 for
  // rambam, 150 for tehillim) instead of hardcoded 150.
  // [PENDING-MARKER] Distinguish 3 states per chapter:
  //   verified by anyone → 'read'   (gold)
  //   my unverified try  → 'pending' (sky) — recording captured, didn't pass
  //   else               → blank
  const myUserId = parseInt((typeof localStorage !== 'undefined' && localStorage.getItem('tehillim_user_id')) || '0', 10);
  const [data, setData] = useState(null);
  useEffect(() => {
    api(`/api/circle/${circleId}`).then(c => {
      const verified = new Set((c.readings || []).filter(r => r.verified === 1).map(r => r.chapter));
      const myPending = new Set(
        (c.readings || [])
          .filter(r => r.verified === 0 && parseInt(r.reader_id || r.user_id, 10) === myUserId)
          .map(r => r.chapter)
      );
      setData({ total: c.total_chapters || 150, verified, myPending });
    });
  }, [circleId]);
  if (!data) return null;
  return (
    <div className="ch-grid">
      {Array.from({ length: data.total }, (_, i) => i + 1).map(n => {
        const cls = data.verified.has(n) ? 'read' : (data.myPending.has(n) ? 'pending' : '');
        const title = data.verified.has(n)
          ? `${chapterLabel(n)} — נקרא ואומת`
          : (data.myPending.has(n) ? `${chapterLabel(n)} — ניסית, לא עבר אימות. אפשר לקרוא שוב.` : chapterLabel(n));
        return <div key={n} className={`ch-cell ${cls}`} title={title}>{n}</div>;
      })}
    </div>
  );
}

// ───────── Member avatar (Hebrew initials, color-keyed by uid) ─────────
function MemberAvatar({ user, size = 28 }) {
  // Color palette tuned to the gold/cream theme.
  const palette = ['#d4a017','#a8800a','#b07d3a','#7c5e2c','#9a6b1a','#c08a25','#88611a','#b89033'];
  const color = palette[(user.id || 0) % palette.length];
  const name = (user.name || '?').trim();
  // First character of first 1-2 words. Hebrew & latin friendly.
  const initials = name.split(/\s+/).slice(0, 2).map(w => w[0] || '').join('') || '?';
  const inner = (
    <div
      className="circle-avatar"
      title={name}
      style={{
        background: color,
        width: size, height: size,
        fontSize: Math.round(size * 0.42),
      }}
    >
      {initials}
    </div>
  );
  // [handoff §10 r3 2026-05-24] If user.id is real (>0), link to public profile.
  if (user && user.id && user.id > 0) {
    return (
      <a href={`#/user/${user.id}`}
         onClick={(e) => e.stopPropagation()}
         style={{ display: 'inline-block', lineHeight: 0, textDecoration: 'none' }}>
        {inner}
      </a>
    );
  }
  return inner;
}

function Circles({ onOpen, refreshKey, onJoin, userId }) {
  const [circles, setCircles] = useState([]);
  const [stats, setStats] = useState(null);
  const [loading, setLoading] = useState(true);
  const [expanded, setExpanded] = useState(null);
  // Phase 2: filter & view state
  const [view, setView] = useState('browse'); // 'browse' | (create handled by onOpen modal)
  const [textTypeFilter, setTextTypeFilter] = useState('all');
  const [searchQ, setSearchQ] = useState('');
  const [hideComplete, setHideComplete] = useState(false);
  // Round 5: default = top 3 most-progressed circles. User can reveal all.
  const [showAllCircles, setShowAllCircles] = useState(false);

  const load = useCallback(() => {
    setLoading(true);
    Promise.all([api('/api/circles'), api('/api/stats')])
      .then(([c, s]) => { setCircles(Array.isArray(c) ? c : []); setStats(s); })
      .catch(() => {})
      .finally(() => setLoading(false));
  }, []);

  useEffect(() => { load(); }, [load, refreshKey]);

  // Filter the visible list
  const filtered = circles.filter(c => {
    if (textTypeFilter !== 'all' && (c.text_type || 'tehillim') !== textTypeFilter) return false;
    if (hideComplete && c.is_complete) return false;
    if (searchQ.trim()) {
      const q = searchQ.trim().toLowerCase();
      const hay = `${c.name || ''} ${c.purpose || ''} ${c.creator_name || ''}`.toLowerCase();
      if (!hay.includes(q)) return false;
    }
    return true;
  });

  const TYPE_LABELS = {
    all: 'הכל',
    tehillim: '\ud83d\udcd6 תהילים',
    tanya: '\ud83d\udcd7 תניא',
    rambam: '\ud83d\udcd8 רמב״ם',
    tikkunei_zohar: '\ud83d\udcd5 תיקוני הזוהר',
  };

  return (
    <section id="circles" className="circles">
      <div className="container">
        <h2 className="section-title">המעגלים</h2>
        <p className="section-sub" style={{ color: '#1a1a2e', fontWeight: 600 }}>הדרך הישרה להוסיף 🔥</p>

        {/* [damri ss#3 2026-05-24] stats strip removed per damri 'מיותר'.
            State 'stats' still fetched for backward compat (used elsewhere if needed). */}

        {/* [damri 2026-05-21] Single primary CTA — browse is the default view below. */}
        <div className="circles-cta-row" style={{ justifyContent: 'center' }}>
          <button
            type="button"
            className="circles-cta-btn primary"
            onClick={onOpen}
            style={{ minWidth: 320, maxWidth: 480 }}
          >
            {/* [damri 2026-05-24 round D] no ✨ icon; bigger, bolder title */}
            <div className="circles-cta-title" style={{ fontSize: 22, fontWeight: 900, letterSpacing: 0.5 }}>פתח מעגל חדש</div>
            <div className="circles-cta-sub">שם, מטרה, ויעד משתתפים</div>
          </button>
        </div>

        {/* Filter strip — visible when browsing */}
        {view === 'browse' && (
          <div className="circle-filters">
            {/* [damri 2026-05-24 round D] search row FIRST, then book-type chips */}
            <div className="circle-filter-row">
              <input
                type="text"
                className="circle-search"
                value={searchQ}
                onChange={e => setSearchQ(e.target.value)}
                placeholder="חפש לפי שם, מטרה או יוצר..."
              />
              <label className="circle-toggle">
                <input
                  type="checkbox"
                  checked={hideComplete}
                  onChange={e => setHideComplete(e.target.checked)}
                />
                <span>הסתר מעגלים שהושלמו</span>
              </label>
            </div>
            <div className="circle-filter-chips">
              {Object.entries(TYPE_LABELS).map(([k, l]) => (
                <button
                  key={k}
                  type="button"
                  className={`circle-chip ${textTypeFilter === k ? 'active' : ''}`}
                  onClick={() => setTextTypeFilter(k)}
                >
                  {l}
                </button>
              ))}
            </div>
          </div>
        )}

        {loading && <p style={{ color: '#8a8da8', textAlign: 'center' }}>טוען מעגלים...</p>}
        {!loading && circles.length === 0 && (
          <p style={{ color: '#8a8da8', textAlign: 'center', fontStyle: 'italic' }}>
            עדיין אין מעגלים. היה הראשון לפתוח אחד.
          </p>
        )}
        {!loading && circles.length > 0 && (() => {
          const hasActiveFilter = !!searchQ.trim() || textTypeFilter !== 'all' || hideComplete;
          if (!hasActiveFilter && !showAllCircles && circles.length > 3) {
            return (
              <div className="circles-section-header">
                <span className="circles-section-icon">⭐</span>
                <span className="circles-section-title">המעגלים המתקדמים ביותר</span>
                <span className="circles-section-sub">{Math.min(3, circles.length)} מתוך {circles.length}</span>
              </div>
            );
          }
          if (hasActiveFilter && filtered.length === 0) {
            return (
              <p style={{ color: '#8a8da8', textAlign: 'center', fontStyle: 'italic' }}>
                אין מעגלים שתואמים את הסינון.
              </p>
            );
          }
          return null;
        })()}

        <div className="circle-grid">
          {(() => {
            const hasActiveFilter = !!searchQ.trim() || textTypeFilter !== 'all' || hideComplete;
            if (hasActiveFilter) return filtered;
            if (showAllCircles) return circles;
            // Default: top 3 by chapter progress (verified / total)
            const ranked = [...circles].sort((a, b) => {
              const pa = (a.verified_count || 0) / Math.max(1, a.total_chapters || 150);
              const pb = (b.verified_count || 0) / Math.max(1, b.total_chapters || 150);
              if (pb !== pa) return pb - pa;
              // Tie-break: more participants first
              return (b.participants_count || 0) - (a.participants_count || 0);
            });
            return ranked.slice(0, 3);
          })().map(c => {
            const top5 = Array.isArray(c.participants_top5) ? c.participants_top5 : [];
            const totalParts = c.participants_count || 0;
            const more = Math.max(0, totalParts - top5.length);
            return (
              <div key={c.id} className="circle-card">
                <h4>{c.name} {c.is_complete ? <span className="complete-badge">הושלם ✓</span> : null}</h4>
                <div className="purpose">{c.purpose || 'ללא מטרה ספציפית'}</div>
                <div className="meta" style={{ display:'flex', alignItems:'center', gap:6 }}>
                  {/* [damri 2026-05-21] avatar instead of bare name */}
                  <MemberAvatar user={{ id: c.creator_id, name: c.creator_name || 'א' }} size={22} />
                  <span style={{ fontSize: 12, color: '#6b5a37' }}>{(c.creator_name || 'אנונימי').split(' ')[0]}</span>
                </div>
                {/* Member avatars */}
                {top5.length > 0 && (
                  <div className="circle-members" aria-label={`${totalParts} משתתפים`}>
                    {top5.map(u => <MemberAvatar key={u.id} user={u} />)}
                    {more > 0 && (
                      <div className="circle-avatar circle-avatar-more" title={`+${more} ועוד`}>
                        +{more}
                      </div>
                    )}
                    <span className="circle-members-count">{c.target_participants ? `${totalParts} / ${c.target_participants} משתתפים` : `${totalParts} משתתפים`}</span>
                  </div>
                )}
                <div className="progress-row">
                  <span>{Math.min(c.verified_count || 0, c.total_chapters || 150)} / {c.total_chapters || 150} פרקים · {circleBookLabel(c)}</span>
                  <span>{Math.min(100, Math.round(((c.verified_count || 0) / (c.total_chapters || 150)) * 100))}%</span>
                </div>
                <div className="progress"><div style={{ width: `${Math.min(100, ((c.verified_count || 0) / (c.total_chapters || 150)) * 100)}%` }} /></div>
                <div style={{ display: 'flex', gap: 8, marginTop: 6 }}>
                  {!c.is_complete && (() => {
                    // [damri 2026-05-21] Show the next chapter label inline.
                    const nextCh = Math.min((c.verified_count || 0) + 1, c.total_chapters || 150);
                    const lbl = c.text_type === 'tanya'
                      ? chapterLabelInCircle(c, nextCh)
                      : chapterLabel(nextCh);
                    return (
                      <button className="btn primary" style={{ padding: '10px 14px', fontSize: 13, flex: 1, fontWeight: 700 }}
                              onClick={() => onJoin(c)}
                              title={`קרא את ${lbl}`}>
                        📖 קרא {lbl}
                      </button>
                    );
                  })()}
                  <button className="btn ghost" style={{ padding: '10px 16px', fontSize: 13 }}
                          onClick={() => setExpanded(expanded === c.id ? null : c.id)}>
                    {expanded === c.id ? 'הסתר' : 'פרקים'}
                  </button>
                  {userId && c.creator_id === userId && (
                    <button className="btn ghost circle-delete-btn"
                            style={{ padding: '10px 12px', fontSize: 13 }}
                            title="מחק את המעגל הזה"
                            onClick={async () => {
                              // Surface real impact in the confirm dialog so
                              // the creator sees what's about to disappear.
                              const readings = c.verified_count || 0;
                              const parts = c.participants_count || 0;
                              const msg = `המעגל "${c.name}" יימחק לצמיתות.` +
                                          (readings > 0 || parts > 0
                                            ? `\n\nנקראו בו ${readings} פרקים על ידי ${parts} משתתפים.`
                                            : '') +
                                          `\n\nלמחוק?`;
                              if (!window.confirm(msg)) return;
                              try {
                                const r = await api(`/api/circle/${c.id}`, {
                                  method: 'DELETE',
                                  body: { user_id: userId }
                                });
                                if (r && r.error) {
                                  showToast(`לא נמחק: ${r.error}`, 3500);
                                } else {
                                  showToast(`המעגל "${c.name}" נמחק`, 2500);
                                  load();
                                }
                              } catch (e) {
                                showToast('שגיאה במחיקה — נסה שוב', 3500);
                              }
                            }}>🗑️</button>
                  )}
                  {c.share_token && (
                    <button className="btn ghost" style={{ padding: '10px 14px', fontSize: 13 }}
                            title="שתף את המעגל"
                            onClick={() => shareCircle(c)}>📤 שתף</button>
                  )}
                </div>
                {expanded === c.id && <ChapterGrid circleId={c.id} />}
              </div>
            );
          })}
        </div>

        {/* Round 5: reveal-all toggle. Hidden when filter active or <=3 circles. */}
        {!loading && circles.length > 3 && !(searchQ.trim() || textTypeFilter !== 'all' || hideComplete) && (
          <div className="circles-reveal-row">
            <button
              type="button"
              className="circles-reveal-btn"
              onClick={() => setShowAllCircles(v => !v)}
            >
              {showAllCircles
                ? `הצג רק את 3 המתקדמים ביותר`
                : `הצג את כל ${circles.length} המעגלים ↓`}
            </button>
          </div>
        )}
      </div>
    </section>
  );
}

function CommunityPresence({ refreshKey }) {
  // [לב הדבר 2026-05-10] Replaces Leaderboard. No rankings, no medals.
  // 'מי איתך עכשיו' — list of recent activity events from across the
  // community, plus one human aggregate ('X אנשים קראו היום יחד').
  // Auto-refreshes every 30s while visible.
  const [data, setData] = React.useState(null);
  const [loading, setLoading] = React.useState(true);

  const load = () => {
    api('/api/community/presence').then(d => {
      if (d && !d.error) setData(d);
    }).finally(() => setLoading(false));
  };

  React.useEffect(() => {
    load();
    const t = setInterval(load, 30000);  // soft heartbeat
    return () => clearInterval(t);
  }, [refreshKey]);

  // Relative time helper — gentle, no precise minutes
  const relTime = (iso) => {
    if (!iso) return '';
    try {
      const ms = Date.now() - Date.parse(iso.replace(' ', 'T') + 'Z');
      const min = Math.floor(ms / 60000);
      if (min < 1) return 'לפני רגע';
      if (min < 60) return `לפני ${min} דק׳`;
      const hr = Math.floor(min / 60);
      if (hr < 24) return `לפני ${hr} שע׳`;
      const dy = Math.floor(hr / 24);
      return `לפני ${dy} ימים`;
    } catch { return ''; }
  };

  const actionIco = (a) => {
    if (a === 'read_chapter') return '📖';
    if (a === 'opened_circle') return '✨';
    if (a === 'offered_gift') return '🤝';
    if (a === 'posted_wish') return '🌱';
    return '✦';
  };

  return (
    <section id="leaderboard" className="leaderboard">
      <div className="container">
        <h2 className="section-title">✦ מי איתך עכשיו</h2>
        <p className="section-sub">נוכחות חיה של הקהילה — לא דירוג, נוכחות.</p>

        {!loading && data && (
          <div style={{
            textAlign: 'center', margin: '8px auto 22px',
            padding: '20px 24px', maxWidth: 360,
            background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
            border: '1px solid rgba(212,175,55,0.25)',
            boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
            borderRadius: 14,
          }}>
            <div style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 32, color: '#d4af37', fontWeight: 600,
            }}>{data.today_total_readers}</div>
            <div style={{ fontSize: 13, color: '#c8c8d8', marginTop: 4 }}>
              {data.today_total_readers === 1
                ? 'אדם קרא היום'
                : 'אנשים קראו היום יחד'}
            </div>
          </div>
        )}

        {loading && <p style={{ color: '#8a8da8', textAlign: 'center' }}>טוען...</p>}
        {!loading && data && (data.events || []).length === 0 && (
          <p style={{ color: '#8a8da8', textAlign: 'center', fontStyle: 'italic' }}>
            הקהילה שקטה כעת. הזמן הכי טוב להתחיל הוא עכשיו.
          </p>
        )}
        {!loading && data && (data.events || []).length > 0 && (
          <div style={{
            display: 'flex', flexDirection: 'column', gap: 10,
            maxWidth: 580, margin: '0 auto',
          }}>
            {data.events.map((e, i) => (
              <div key={i} style={{
                display: 'flex', alignItems: 'flex-start', gap: 12,
                padding: '12px 14px',
                background: 'rgba(255,255,255,0.03)',
                border: '1px solid rgba(168,128,31,0.18)',
                borderRadius: 12,
                fontSize: 13,
              }}>
                <div style={{ fontSize: 18, lineHeight: 1.4 }}>{actionIco(e.action)}</div>
                <div style={{ flex: 1, lineHeight: 1.5 }}>
                  <div style={{ color: '#e8e6f0' }}>
                    <strong style={{ color: '#d4af37' }}>{e.name || 'אנונימי'}</strong>
                    {' '}<span style={{ color: '#c8c6d8' }}>{e.action_text}</span>
                  </div>
                  <div style={{ fontSize: 11, color: '#8a8597', marginTop: 2 }}>
                    {relTime(e.created_at)}
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </section>
  );
}

function CreatePurposeMic({ onAppend }) {
  const [recording, setRecording] = useState(false);
  const [status, setStatus] = useState('');
  const recRef = React.useRef(null);
  const supportedRef = React.useRef(true);

  React.useEffect(() => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    const ua = navigator.userAgent || '';
    const isSafari = /^((?!chrome|android).)*safari/i.test(ua);
    const isIOS = /iPad|iPhone|iPod/.test(ua);
    if (!SR || isSafari || isIOS) supportedRef.current = false;
    return () => { try { recRef.current && recRef.current.stop(); } catch {} };
  }, []);

  const start = () => {
    if (!supportedRef.current) {
      setStatus('זיהוי קולי לא נתמך בדפדפן זה — פתח ב-Chrome');
      return;
    }
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    const r = new SR();
    r.lang = 'he-IL'; r.continuous = false; r.interimResults = false; r.maxAlternatives = 1;
    r.onresult = (e) => {
      const t = (e.results[0] && e.results[0][0] && e.results[0][0].transcript) || '';
      if (t.trim()) onAppend(t.trim());
    };
    r.onerror = (e) => setStatus(e.error === 'not-allowed' ? 'אשר גישה למיקרופון' : 'שגיאת זיהוי: ' + e.error);
    r.onend = () => { setRecording(false); setStatus(''); recRef.current = null; };
    try { r.start(); recRef.current = r; setRecording(true); setStatus('🎙️ מקליט — דבר עכשיו'); }
    catch { setStatus('כבר מקליט'); }
  };
  const stop = () => { try { recRef.current && recRef.current.stop(); } catch {} };

  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 6 }}>
      <button type="button"
              className={`btn ghost ${recording ? 'recording' : ''}`}
              onClick={recording ? stop : start}
              style={{ padding: '6px 14px', fontSize: 13 }}>
        {recording ? '⏹ עצור' : '🎙️ הקלט מטרה בקול'}
      </button>
      <span style={{ fontSize: 12, color: '#8a8da8' }}>{status || 'דבר בעברית — הטקסט יתווסף לתיבת המטרה'}</span>
    </div>
  );
}

// ───────── TextSelector — choose between texts (tehillim/tanya/rambam/zohar) ─────────
// Restored 2026-05-16 — was missing from the bundle and create-circle was
// crashing silently on render. Now also exposes the `multi` flag so callers
// (CreateModal) can decide to show the Tanya parts picker.
function TextSelector({ value, onChange }) {
  const [texts, setTexts] = useState([]);
  useEffect(() => {
    api('/api/texts').then(t => setTexts(Array.isArray(t) ? t : [])).catch(() => setTexts([]));
  }, []);
  return (
    <div className="text-tabs">
      {texts.map(t => (
        <button
          key={t.id}
          type="button"
          className={`text-tab ${value === t.id ? 'active' : ''}`}
          onClick={() => onChange(t)}
        >
          <span className="ico">{t.icon}</span>
          <span>{t.name}</span>
          <span className="ch">({t.chapters})</span>
        </button>
      ))}
    </div>
  );
}

// ───────── TanyaPartsPicker — "כל ספר התניא נפתח לפני האדם" (Damri 2026-05-16) ─────────
// Renders the five parts of the Tanya as selectable cards. Multi-select. The
// caller (CreateModal) controls `value` (array of part IDs) and `onChange`.
// Quick-action buttons make the most common flows one tap:
//   • "לקוטי אמרים בלבד" (default — single-part circle, the legacy behaviour)
//   • "הספר המלא" (all five parts → 118 chapters)
// Live sum below the grid: "תסיים יחד 65 פרקים".
function TanyaPartsPicker({ value, onChange }) {
  const [parts, setParts] = React.useState(TANYA_PARTS_CLIENT);
  const [total, setTotal] = React.useState(TANYA_PARTS_CLIENT.reduce((s, p) => s + p.chapters, 0));

  React.useEffect(() => {
    api('/api/tanya/parts')
      .then(d => {
        if (d && Array.isArray(d.parts) && d.parts.length) {
          setParts(d.parts);
          setTotal(d.total_all_parts || d.parts.reduce((s, p) => s + p.chapters, 0));
        }
      })
      .catch(() => { /* keep the client-side fallback */ });
  }, []);

  const selectedSet = new Set(value || []);
  const toggle = (id) => {
    const next = new Set(selectedSet);
    if (next.has(id)) next.delete(id); else next.add(id);
    // Preserve canonical order (the order they appear in the source list)
    onChange(parts.filter(p => next.has(p.id)).map(p => p.id));
  };
  const selectOnly = (ids) => onChange(parts.filter(p => ids.includes(p.id)).map(p => p.id));

  const selectedTotalChapters = parts
    .filter(p => selectedSet.has(p.id))
    .reduce((s, p) => s + p.chapters, 0);
  const selectedNames = parts
    .filter(p => selectedSet.has(p.id))
    .map(p => p.name);

  return (
    <div style={{ marginTop: 14, marginBottom: 14 }}>
      <div style={{
        display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
        marginBottom: 8, flexWrap: 'wrap', gap: 8,
      }}>
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif",
          fontSize: 15, fontWeight: 700, color: '#5a3d0a',
        }}>
          📗 חמשת חלקי התניא — בחר מה ייכנס למעגל
        </div>
        <div style={{ display: 'flex', gap: 6 }}>
          <button
            type="button"
            className="btn ghost"
            style={{ padding: '4px 10px', fontSize: 12 }}
            onClick={() => selectOnly(['likutei'])}
          >
            לקוטי אמרים בלבד
          </button>
          <button
            type="button"
            className="btn ghost"
            style={{ padding: '4px 10px', fontSize: 12 }}
            onClick={() => selectOnly(parts.map(p => p.id))}
          >
            הספר המלא
          </button>
        </div>
      </div>

      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))',
        gap: 10,
      }}>
        {parts.map(p => {
          const sel = selectedSet.has(p.id);
          return (
            <button
              key={p.id}
              type="button"
              onClick={() => toggle(p.id)}
              style={{
                textAlign: 'right',
                padding: '12px 12px 14px',
                background: sel
                  ? 'linear-gradient(135deg, rgba(212,175,55,0.20) 0%, rgba(184,148,31,0.10) 100%)'
                  : 'rgba(255,255,255,0.04)',
                border: `1.5px solid ${sel ? 'rgba(212,175,55,0.75)' : 'rgba(168,128,31,0.25)'}`,
                borderRadius: 14,
                cursor: 'pointer',
                color: '#e8e6f0',
                fontFamily: 'inherit',
                transition: 'all 0.18s ease',
                position: 'relative',
                boxShadow: sel ? '0 2px 10px rgba(212,175,55,0.18)' : 'none',
              }}
              aria-pressed={sel}
            >
              <div style={{
                position: 'absolute', top: 8, left: 10,
                width: 20, height: 20, borderRadius: 6,
                background: sel ? '#d4af37' : 'rgba(255,255,255,0.06)',
                border: `1.5px solid ${sel ? '#5a3d0a' : 'rgba(212,175,55,0.4)'}`,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                color: '#1a1a2e', fontSize: 13, fontWeight: 800,
              }}>{sel ? '✓' : ''}</div>
              <div style={{ fontSize: 28, lineHeight: 1, marginBottom: 6 }}>{p.icon}</div>
              <div style={{
                fontFamily: "'Frank Ruhl Libre', serif",
                fontSize: 16, fontWeight: 700, color: '#d4af37',
                marginBottom: 4, lineHeight: 1.25,
              }}>
                {p.name}
              </div>
              <div style={{
                fontSize: 11.5, color: '#c8c6d8',
                lineHeight: 1.5, marginBottom: 6,
                minHeight: 32,
              }}>
                {p.shaar}
              </div>
              <div style={{
                fontSize: 11, color: '#8a8597', fontWeight: 600,
              }}>
                {p.chapters} פרקים
              </div>
            </button>
          );
        })}
      </div>

      <div style={{
        marginTop: 10, padding: '10px 12px',
        background: selectedTotalChapters > 0
          ? 'linear-gradient(90deg, rgba(212,175,55,0.10) 0%, rgba(212,175,55,0.04) 100%)'
          : 'rgba(255,107,157,0.10)',
        border: `1.5px solid ${selectedTotalChapters > 0 ? 'rgba(212,175,55,0.30)' : 'rgba(255,107,157,0.30)'}`,
        borderRadius: 10,
        fontSize: 13, color: selectedTotalChapters > 0 ? '#d4af37' : '#ff6b9d',
        fontWeight: 600, lineHeight: 1.55,
      }}>
        {selectedTotalChapters === 0 ? (
          <>בחר לפחות חלק אחד כדי לפתוח מעגל</>
        ) : (
          <>
            המעגל יכלול <strong>{selectedTotalChapters} פרקים</strong> —{' '}
            {selectedNames.join(' + ')}
            {' '}<span style={{ color: '#8a8597', fontWeight: 500 }}>
              (מתוך {total} פרקים שיש בתניא כולו)
            </span>
          </>
        )}
      </div>
    </div>
  );
}

// SefariaBookSuggestions [damri 2026-05-20]
// Debounced search against /api/books/search. Shows results inline as a
// small list below the 4 default books. Only renders when query is non-empty
// and no matches found in the local 4.
function SefariaBookSuggestions({ query, onPick }) {
  const [results, setResults] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  React.useEffect(() => {
    const q = (query || '').trim();
    if (q.length < 2) { setResults([]); return; }
    // Skip if query matches our 4 local books — no need to hit Sefaria
    const locals = ['תהילים','תניא','תיקוני הזוהר','משנה תורה','רמב'];
    if (locals.some(l => l.includes(q) || q.includes(l))) { setResults([]); return; }
    setLoading(true);
    const ctl = new AbortController();
    const t = setTimeout(() => {
      fetch('/api/books/search?q=' + encodeURIComponent(q), { signal: ctl.signal })
        .then(r => r.json())
        .then(d => setResults(Array.isArray(d.results) ? d.results : []))
        .catch(() => setResults([]))
        .finally(() => setLoading(false));
    }, 350);
    return () => { clearTimeout(t); ctl.abort(); };
  }, [query]);

  if (!query || query.trim().length < 2) return null;
  if (loading) return <div style={{ fontSize: 12, color: '#8a8da8', padding: '6px 4px' }}>מחפש ב-Sefaria...</div>;
  if (results.length === 0) return null;

  return (
    <div style={{ marginBottom: 14 }}>
      <div style={{ fontSize: 12, color: '#6b6d73', marginBottom: 6, fontWeight: 600 }}>
        📚 ספרים נוספים מ-Sefaria:
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 4,
                    maxHeight: 220, overflowY: 'auto', padding: '4px',
                    border: '1px solid rgba(0,0,0,0.08)', borderRadius: 8 }}>
        {results.slice(0, 12).map((r, i) => (
          <button key={r.key || i} type="button" onClick={() => onPick(r)}
                  style={{ padding: '8px 10px', textAlign: 'right', background: 'transparent',
                           border: '1px solid transparent', borderRadius: 6,
                           cursor: 'pointer', fontSize: 13.5, color: '#1d4029' }}>
            {r.he_title || r.title}
            <span style={{ color: '#8a8da8', fontSize: 11, marginRight: 8 }}>
              {r.type || 'ספר'}
            </span>
          </button>
        ))}
      </div>
    </div>
  );
}

// CreateModal v2 [damri 2026-05-20]
// Council guidance: design must feel like honor + joy. Bonus = appreciation,
// not prize. Multiple paths to be seen. Heart-warming, not gamified.
//
// 9 improvements applied:
//   1. Book selection — searchable visual grid
//   2. "כוונת המעגל" replaces "מטרת המעגל"
//   3. Image upload (hero image)
//   4. Story field — "הסיפור מאחורי המעגל"
//   5. Generic copy (not "תהילים" specific)
//   6. System bonus per completer (display only — system-set)
//   7. Creator can prepay community bonus pot
//   8. WhatsApp link from creator
//   9. Layout cleanup — single scrollable modal
function CreateModal({ onClose, onCreated }) {
  const [name, setName] = useState('');
  const [intention, setIntention] = useState('');
  const [storyMd, setStoryMd] = useState('');
  const [textType, setTextType] = useState({ id: 'tehillim', name: 'תהילים', chapters: 150, icon: '📖' });
  const [tanyaParts, setTanyaParts] = useState(['likutei']);
  const [targetParticipants, setTargetParticipants] = useState('');
  const [whatsappUrl, setWhatsappUrl] = useState('');
  const [bonusPrepay, setBonusPrepay] = useState(0);
  const [heroImageB64, setHeroImageB64] = useState(null);
  const [heroImagePreview, setHeroImagePreview] = useState(null);
  const [heroImageMime, setHeroImageMime] = useState(null);
  const [bookSearch, setBookSearch] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');

  // System-set bonus per completer (display only — fixed by system per book type)
  const SYSTEM_BONUS_PER_COMPLETER = {
    tehillim: 50, tanya: 100, tikkunei_zohar: 80, rambam: 60,
  };
  const systemBonus = SYSTEM_BONUS_PER_COMPLETER[textType.id] || 50;

  const liveTotalChapters = (() => {
    if (textType.id !== 'tanya') return textType.chapters;
    return TANYA_PARTS_CLIENT
      .filter(p => tanyaParts.includes(p.id))
      .reduce((s, p) => s + p.chapters, 0);
  })();

  const ALL_BOOKS = [
    { id: 'tehillim',      name: 'תהילים',         chapters: 150, icon: '📖', desc: 'מאה חמישים פרקים של דוד המלך' },
    { id: 'tanya',         name: 'תניא',           chapters: 53,  icon: '🔥', desc: 'ספר היסוד של חב״ד' },
    { id: 'tikkunei_zohar',name: 'תיקוני הזוהר',   chapters: 70,  icon: '✨', desc: 'שבעים תיקונים על פסוק "בראשית"' },
    { id: 'rambam',        name: 'משנה תורה',      chapters: 365, icon: '📜', desc: 'יום-יומי, סדר הלכתי שלם' },
  ];
  const filteredBooks = bookSearch.trim()
    ? ALL_BOOKS.filter(b => b.name.includes(bookSearch.trim()) || b.desc.includes(bookSearch.trim()))
    : ALL_BOOKS;

  const handleImagePick = (e) => {
    const f = e.target.files && e.target.files[0];
    if (!f) return;
    if (f.size > 5 * 1024 * 1024) { setErr('תמונה גדולה מ-5MB.'); return; }
    const reader = new FileReader();
    reader.onload = (ev) => {
      const dataUrl = ev.target.result;
      setHeroImagePreview(dataUrl);
      setHeroImageMime(f.type);
      const comma = dataUrl.indexOf(',');
      setHeroImageB64(comma > 0 ? dataUrl.substring(comma + 1) : dataUrl);
    };
    reader.readAsDataURL(f);
  };

  const submit = async () => {
    if (!name.trim()) { setErr('שם המעגל חובה'); return; }
    if (textType.id === 'tanya' && tanyaParts.length === 0) {
      setErr('בחר לפחות חלק אחד מהתניא'); return;
    }
    setBusy(true); setErr('');
    try {
      const creator_id = parseInt(localStorage.getItem('tehillim_user_id') || '1', 10);
      let tp = null;
      if (targetParticipants !== '') {
        const n = parseInt(targetParticipants, 10);
        if (Number.isFinite(n) && n >= 1 && n <= 1000) tp = n;
      }
      const body = {
        name: name.trim(),
        purpose: intention.trim(),  // keep legacy field name for backend compat
        intention: intention.trim(),
        story_md: storyMd.trim() || null,
        whatsapp_url: whatsappUrl.trim() || null,
        bonus_per_completer_beu: systemBonus,
        creator_bonus_prepay_beu: parseInt(bonusPrepay, 10) || 0,
        creator_id,
        text_type: textType.id,
        total_chapters: liveTotalChapters,
        target_participants: tp,
      };
      if (textType.id === 'tanya') body.text_parts = tanyaParts;
      const c = await api('/api/circle', { method: 'POST', body });
      if (c.error) throw new Error(c.error);
      // Upload image if any
      if (heroImageB64 && c.id) {
        try {
          await fetch(`/api/circles/${c.id}/upload-image`, {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ image_b64: heroImageB64, mime: heroImageMime }),
          });
        } catch {} // non-fatal — circle is created
      }
      onCreated(c);
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}
           style={{ maxWidth: 640, maxHeight: '92vh', overflowY: 'auto', direction: 'rtl' }}>
        <span className="close-x" onClick={onClose}>×</span>

        {/* Hero header — council guidance: feel honored, not gamified */}
        <div style={{ textAlign: 'center', marginBottom: 18 }}>
          <div style={{ fontSize: 36, marginBottom: 4 }}>🕯️</div>
          <h3 style={{ margin: '0 0 4px', color: '#5a3d0a' }}>פתיחת מעגל</h3>
          <p style={{ fontSize: 13, color: '#6b6d73', lineHeight: 1.5, margin: 0 }}>
            מעגל הוא תפילה קולקטיבית — כמה אנשים נוטלים על עצמם ספר ביחד.<br/>
            כל פרק שנקרא — מטבע. כל סיום — הוקרה.
          </p>
        </div>

        {/* 1. Book selection — visual cards + search (with Sefaria fallback) */}
        <label style={{ fontWeight: 700, color: '#1d4029' }}>איזה ספר?</label>
        <input type="text" placeholder="חיפוש... (תניא, זוהר, ליקוטי מוהר״ן, מסילת ישרים...)"
               value={bookSearch} onChange={e => setBookSearch(e.target.value)}
               style={{ marginBottom: 8 }} />
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))',
                      gap: 8, marginBottom: 14 }}>
          {filteredBooks.map(b => (
            <button key={b.id} type="button"
                    onClick={() => setTextType(b)}
                    style={{
                      padding: 12, borderRadius: 10,
                      background: textType.id === b.id ? 'linear-gradient(135deg, #5fa372, #4a8a5a)' : '#fff',
                      color: textType.id === b.id ? '#fff' : '#1d4029',
                      border: '1.5px solid ' + (textType.id === b.id ? '#5fa372' : 'rgba(0,0,0,0.10)'),
                      cursor: 'pointer', textAlign: 'right',
                      transition: 'all .15s',
                    }}>
              <div style={{ fontSize: 26, marginBottom: 4 }}>{b.icon}</div>
              <div style={{ fontWeight: 700, fontSize: 15 }}>{b.name}</div>
              <div style={{ fontSize: 11.5, opacity: 0.8, marginTop: 2 }}>{b.chapters} פרקים</div>
            </button>
          ))}
        </div>

        <SefariaBookSuggestions query={bookSearch} onPick={(b) => {
          // Custom book from Sefaria → become text_type 'sefaria:<key>', user fills chapters manually
          setTextType({ id: 'sefaria:' + b.key, name: b.he_title || b.title,
                        chapters: 100, icon: '📚', desc: 'Sefaria · ' + b.title });
          setBookSearch('');
        }} />

        {textType.id === 'tanya' && (
          <TanyaPartsPicker value={tanyaParts} onChange={setTanyaParts} />
        )}

        {/* Name */}
        <label>שם המעגל</label>
        <input value={name} onChange={e => setName(e.target.value)}
               placeholder="לדוגמה: מעגל לרפואת אבא" />

        {/* 2. Intention (renamed from purpose) */}
        <label>כוונת המעגל</label>
        <textarea value={intention} onChange={e => setIntention(e.target.value)} rows="3"
                  placeholder="בשביל מי? לאיזה תיקון? מה תרצה שהמעגל הזה ייצור?" />
        <CreatePurposeMic onAppend={(t) => setIntention(p => (p ? p.trim() + ' ' : '') + t)} />

        {/* 4. Story (new) */}
        <label>הסיפור מאחורי המעגל <span style={{ color: '#8a8da8', fontWeight: 400, fontSize: 12 }}>(אופציונלי)</span></label>
        <textarea value={storyMd} onChange={e => setStoryMd(e.target.value)} rows="3"
                  placeholder="איך זה התחיל אצלך? מה את/ה מבקש/ת לבקש?" />

        {/* 3. Image upload */}
        <label>תמונה שמעוררת את הכוונה <span style={{ color: '#8a8da8', fontWeight: 400, fontSize: 12 }}>(אופציונלי, עד 5MB)</span></label>
        <input type="file" accept="image/*" onChange={handleImagePick} />
        {heroImagePreview && (
          <div style={{ margin: '8px 0' }}>
            <img src={heroImagePreview} alt="תצוגה מקדימה"
                 style={{ maxWidth: '100%', maxHeight: 200, borderRadius: 10, border: '1px solid rgba(0,0,0,0.10)' }} />
          </div>
        )}

        {/* 8. WhatsApp link */}
        <label>קישור לקבוצת WhatsApp <span style={{ color: '#8a8da8', fontWeight: 400, fontSize: 12 }}>(אופציונלי)</span></label>
        <input type="url" value={whatsappUrl} onChange={e => setWhatsappUrl(e.target.value)}
               placeholder="https://chat.whatsapp.com/..." />

        {/* Participants target */}
        <label>כמה משתתפים? <span style={{ color: '#8a8da8', fontWeight: 400, fontSize: 12 }}>(אופציונלי)</span></label>
        <input type="number" min="1" max="1000" inputMode="numeric"
               value={targetParticipants}
               onChange={e => setTargetParticipants(e.target.value)}
               placeholder="לדוגמה: 10" />

        {/* 6+7. Bonus pot — framed as appreciation, not prize */}
        <div style={{
          margin: '14px 0 4px', padding: 14,
          background: 'linear-gradient(135deg, rgba(212,175,55,0.08), rgba(95,163,114,0.06))',
          border: '1.5px solid rgba(168,128,31,0.30)',
          borderRadius: 12,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
            <span style={{ fontSize: 22 }}>🪙</span>
            <strong style={{ color: '#5a3d0a' }}>הוקרה למסיימים</strong>
          </div>
          <div style={{ fontSize: 13, color: '#1d4029', lineHeight: 1.6, marginBottom: 10 }}>
            המערכת נותנת <strong>{systemBonus} BEU</strong> לכל מי שיסיים את כל הפרקים שלו במעגל.
            <br/>אתה יכול להוסיף הוקרה משלך מהיתרה — וגם הקהילה תוכל להוסיף בהמשך.
          </div>
          <label style={{ fontSize: 13, fontWeight: 600 }}>
            הוקרה אישית שלך לבונוס (אופציונלי, BEU)
          </label>
          <input type="number" min="0" inputMode="numeric"
                 value={bonusPrepay} onChange={e => setBonusPrepay(e.target.value)}
                 placeholder="0" />
          {parseInt(bonusPrepay, 10) > 0 && (
            <div style={{ fontSize: 12, color: '#a8801f', marginTop: 6 }}>
              ⓘ {bonusPrepay} BEU ירדו מהיתרה שלך מיד עם פתיחת המעגל. הם יישמרו בקופת ההוקרה.
            </div>
          )}
        </div>

        <div style={{ color: '#8a8da8', fontSize: 12, marginTop: 12 }}>
          המעגל יכלול {liveTotalChapters} פרקים.
          {targetParticipants && parseInt(targetParticipants, 10) >= 1 &&
            ` יעד: ${parseInt(targetParticipants, 10)} משתתפים.`}
        </div>

        {err && <div style={{ color: '#dc2626', fontSize: 13, marginTop: 10 }}>{err}</div>}

        <div className="actions" style={{ marginTop: 16 }}>
          <button className="btn" onClick={submit}
                  disabled={busy || (textType.id === 'tanya' && tanyaParts.length === 0)}>
            {busy ? 'פותח...' : '🕯️ פתח מעגל'}
          </button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

// [yakir 2026-05-12 — "הספר חי"] A thin pulse strip + follow toggle.
// Renders only when reading a tanya circle. Sits at the top of ReadModal,
// right under the title. Polls /api/tanya/pulse on mount + every 60s.
function TanyaPulseStrip({ userId, circleId }) {
  const [data, setData] = React.useState(null);
  const [following, setFollowing] = React.useState(false);
  const [followerCount, setFollowerCount] = React.useState(0);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  // Fetch pulse (and follow status) on mount + every 60s while open.
  React.useEffect(() => {
    let cancelled = false;
    let timer = null;
    const load = async () => {
      try {
        const p = await api('/api/tanya/pulse');
        if (cancelled) return;
        if (p && !p.error) setData(p);
      } catch (e) {}
      if (userId) {
        try {
          const s = await api(`/api/tanya/follow/status?user_id=${userId}`);
          if (cancelled) return;
          if (s && !s.error) {
            setFollowing(!!s.following);
            setFollowerCount(s.follower_count || 0);
          }
        } catch (e) {}
      }
    };
    load();
    timer = setInterval(load, 60000);
    return () => { cancelled = true; if (timer) clearInterval(timer); };
  }, [userId, circleId]);

  const toggleFollow = async () => {
    if (!userId) { setErr('התחבר/י כדי לעקוב'); return; }
    if (busy) return;
    setBusy(true); setErr('');
    try {
      const r = following
        ? await api('/api/tanya/follow', { method: 'DELETE', body: JSON.stringify({ user_id: userId }) })
        : await api('/api/tanya/follow', { method: 'POST',   body: JSON.stringify({ user_id: userId }) });
      if (r && !r.error) {
        setFollowing(!!r.following);
        setFollowerCount(r.follower_count || 0);
      } else if (r && r.error) {
        setErr(r.error);
      }
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  if (!data) return null;

  return (
    <div style={{
      margin: '6px 0 14px',
      padding: '10px 12px',
      background: 'rgba(95, 163, 114, 0.08)',
      border: '1px solid rgba(95, 163, 114, 0.22)',
      borderRadius: 12,
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'space-between',
      gap: 10,
      flexWrap: 'wrap',
      direction: 'rtl',
    }}>
      <div style={{ flex: '1 1 240px', minWidth: 0 }}>
        <div style={{ color: '#2e5a3d', fontSize: 14, lineHeight: 1.5, fontWeight: 700 }}>
          הספר חי · {data.pulse_line}
        </div>
        {data.followers > 0 && (
          <div style={{ color: '#5d6d4d', fontSize: 12, marginTop: 4, fontWeight: 500 }}>
            {data.followers} עוקבים אחרי הספר
          </div>
        )}
      </div>
      {userId && (
        <button
          type="button"
          onClick={toggleFollow}
          disabled={busy}
          aria-pressed={following}
          title={following ? 'להפסיק לעקוב' : 'לעקוב אחרי הספר'}
          style={{
            border: '1.5px solid ' + (following ? '#2e5a3d' : '#5fa372'),
            background: following ? 'rgba(95,163,114,0.22)' : 'rgba(95,163,114,0.06)',
            color: following ? '#2e5a3d' : '#3a5d44',
            padding: '6px 12px',
            borderRadius: 999,
            fontSize: 12,
            fontWeight: 700,
            cursor: busy ? 'wait' : 'pointer',
            whiteSpace: 'nowrap',
          }}
        >
          {following ? '✔ עוקב/ת' : '🔔 עקוב אחרי הספר'}
        </button>
      )}
      {err && (
        <div style={{ color: '#e2716e', fontSize: 11, flex: '1 1 100%' }}>{err}</div>
      )}
    </div>
  );
}

// ───────── ErrorBoundary — never blank the whole app on a render throw ─────────
// [bugfix 2026-05-25] The post-verify result screen renders several dynamic
// children (FounderJourney, CircleMosaic, BookCompletionCelebration). If any
// of them throws during render, React with no boundary unmounts everything →
// a white screen (exactly the "דף לבן אחרי אימות" Yakir reported). This
// boundary catches the throw, logs it for diagnosis, and shows a quiet line
// so the coin + progress above it stay visible.
class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { failed: false }; }
  static getDerivedStateFromError() { return { failed: true }; }
  componentDidCatch(error, info) {
    try { console.error('[ErrorBoundary]', this.props.label || '', error, info); } catch (_) {}
  }
  render() {
    if (this.state.failed) {
      if (this.props.silent) return null;
      return (
        <div style={{ padding: '12px 14px', margin: '10px 0', borderRadius: 10,
          background: 'rgba(212,175,55,0.08)', border: '1px solid rgba(212,175,55,0.3)',
          color: '#6d5d3d', fontSize: 13, textAlign: 'center', lineHeight: 1.6 }}>
          {this.props.fallback || 'הקריאה נקלטה ✓ — חלק מהתצוגה לא נטען, אבל הכול נשמר.'}
        </div>
      );
    }
    return this.props.children;
  }
}

// ───────── FounderJourney — מסע גילוי הגרעין (per Damri 2026-05-24) ─────────
// Not "profile completion" — a rolling discovery that helps a person remember
// who they are and what they can bring. One treasure at a time. The coin is
// recognition that a treasure was revealed, never payment. Deep questions
// always offer a human hand. The system doesn't demand answers — it helps
// people find words for their light. Tiers + amounts are server-authoritative.
function FounderJourney({ userId, firstChapter }) {
  const [profile, setProfile] = React.useState(null);
  const [loaded, setLoaded] = React.useState(false);
  const [idx, setIdx] = React.useState(-1);
  const [busy, setBusy] = React.useState(false);
  const [draft, setDraft] = React.useState('');
  const [reward, setReward] = React.useState(null);
  const [coins, setCoins] = React.useState(0);
  const [done, setDone] = React.useState(false);
  const [hd, setHd] = React.useState(''); const [hm, setHm] = React.useState(''); const [hy, setHy] = React.useState('');
  // [damri ss#1 2026-05-24] gregorian + dateMode state
  const [dateMode, setDateMode] = React.useState('heb');
  const [gd, setGd] = React.useState('');
  const [gm, setGm] = React.useState('');
  const [gy, setGy] = React.useState('');
  const [afterSunset, setAfterSunset] = React.useState(false);
  const [chapterResult, setChapterResult] = React.useState(null);
  const [mq, setMq] = React.useState(''); const [mResults, setMResults] = React.useState([]);

  const BANK = [
    { field: 'name',              kind: 'text',   tier: 1, q: 'איך נקרא לך?',                       why: 'שנדע את מי אנחנו מקבלים בשמחה', ph: 'השם שלך' },
    { field: 'hebrew_birth_date', kind: 'hdate',  tier: 1, q: 'מתי נולדת? (תאריך עברי)',            why: 'ומכאן ייפתח פרק התהילים האישי שלך' },
    { field: 'location',          kind: 'text',   tier: 1, q: 'מאיפה אתה מצטרף אלינו?',             why: 'כדי לחבר אותך לאנשים לידך', ph: 'עיר / שכונה' },
    { field: 'arrived_via',       kind: 'text',   tier: 1, q: 'מי הכניס אותך לכאן?',                why: 'נשמח להוקיר את מי שהביא אותך', ph: 'שם' },
    { field: 'full_name',         kind: 'text',   tier: 1, q: 'איך קוראים לך לתפילה?',             why: 'אם יש לך כמה שמות — כתוב את כולם', ph: 'שם לתפילה' },
    { field: 'mother_name',       kind: 'text',   tier: 1, q: 'מה שם אמך — לתפילה?',               why: 'כדי שנוכל להתפלל עליך בשם', ph: 'שם האם' },
    { field: 'mission',           kind: 'text',   tier: 2, q: 'מה אתה מרגיש שנועד לעבור דרכך לעולם?', why: 'זה הגרעין — אין כאן תשובה נכונה', ph: 'מה שעולה בלב', deep: true },
    { field: 'gift',              kind: 'text',   tier: 2, q: 'איזו מתנה אנשים תמיד באים לקבל ממך?', why: 'לפעמים אחרים רואים בנו את זה לפנינו', ph: 'המתנה שלך' },
    { field: 'aliveness',         kind: 'text',   tier: 2, q: 'איפה אתה מרגיש הכי חי?',             why: 'שם בדרך כלל מסתתר הייעוד', ph: 'רגע / מקום / עשייה' },
    { field: 'influential_book',  kind: 'text',   tier: 2, q: 'איזה ספר הכי השפיע על חייך?',        why: 'נכיר דרכו חלק ממך', ph: 'שם הספר' },
    { field: 'dream',             kind: 'text',   tier: 3, q: 'איזה חלום עדיין לא קיבל בית?',        why: 'אולי דווקא כאן הוא יתחיל לקבל אחד', ph: 'החלום שלך', deep: true },
    { field: 'gather',            kind: 'text',   tier: 3, q: 'איזה סוג אנשים אתה הכי רוצה לחבר סביבך?', why: 'נעזור לך למצוא אותם', ph: 'מי הם', deep: true },
    { field: 'tzadik',            kind: 'text',   tier: 3, q: 'איזה צדיק הכי מלווה אותך?',           why: 'נחבר אותך לאחרים שהולכים באותו אור', ph: 'שם הצדיק' },
    { field: 'mentor',            kind: 'mentor', tier: 3, q: 'מי האיר לך את הדרך?',                why: 'לא מאמן ולא סמכות — אדם שנתן לך כוח להיפתח', deep: true },
  ];
  const HEB_MONTHS = [['ניסן',1],['אייר',2],['סיון',3],['תמוז',4],['אב',5],['אלול',6],['תשרי',7],['חשוון',8],['כסלו',9],['טבת',10],['שבט',11],['אדר',12],['אדר ב׳',13]];
  const REWARD_LINES = {
    1: ['🤍 תודה ששיתפת. עוד חלק מהאור שלך נכנס לבית.', '🤍 קיבלנו. עוד פיסה ממך מצאה כאן מקום.'],
    2: ['🌱 בדיוק מהדברים שעוזרים לנו לבנות את הקהילה נכון.', '🌱 עוד יכולת שלך שהקהילה תוכל להתחבר אליה.'],
    3: ['✨ מה ששיתפת עכשיו יכול לפתוח דרך גם לאחרים.', '✨ עוד יסוד אמיתי בבניין שנבנה סביבך.'],
  };
  const HELP_LINE = 'קשה לענות לבד? אפשר לחשוב על זה יחד עם מישהו מהקהילה, או עם המנטור שלך 🤍';

  const isAnswered = (pr, f) => {
    if (!pr) return false;
    if (f === 'mentor') return !!pr.mentor_user_id;
    if (f === 'hebrew_birth_date') return !!pr.hebrew_birth_date;
    return !!(pr[f] && String(pr[f]).trim());
  };
  const nextIdx = (pr) => BANK.findIndex(qq => !isAnswered(pr, qq.field));

  React.useEffect(() => {
    let alive = true;
    (async () => {
      try { const pr = await api('/api/profile/' + userId); if (alive) { setProfile(pr); setIdx(nextIdx(pr)); } }
      catch (e) { if (alive) setIdx(-1); }
      finally { if (alive) setLoaded(true); }
    })();
    return () => { alive = false; };
  }, [userId]);

  if (!loaded || !userId) return null;

  const card = { margin: '14px 0 26px', padding: '18px', borderRadius: 16, direction: 'rtl', textAlign: 'right', background: 'linear-gradient(135deg, rgba(212,175,55,0.13), rgba(184,148,31,0.06))', border: '1.5px solid rgba(212,175,55,0.5)' };
  const sel = { padding: '10px 8px', borderRadius: 10, border: '1.5px solid rgba(212,175,55,0.55)', fontSize: 13.5, fontFamily: 'inherit', background: '#fffdf6' };
  const goldBtn = { padding: '0 16px', borderRadius: 10, border: '1.5px solid #8a5f10', background: 'linear-gradient(135deg,#f5d75c,#d4af37)', color: '#1a1a2e', fontWeight: 800, fontSize: 13.5, fontFamily: 'inherit', cursor: 'pointer' };
  const pickLine = (t) => { const a = REWARD_LINES[t] || REWARD_LINES[1]; return a[Math.floor(Math.random() * a.length)]; };

  async function afterAward(t, amount) {
    setCoins(c => c + (amount || 0));
    setReward({ line: pickLine(t), amount: amount || 0 });
    try { const pr = await api('/api/profile/' + userId); setProfile(pr); } catch (e) {}
  }
  async function submitText(q) {
    const v = draft.trim(); if (!v || busy) return; setBusy(true);
    try { const r = await api('/api/profile/' + userId + '/quick-answer', { method: 'POST', body: { field: q.field, value: v } }); await afterAward(q.tier, r && r.rewarded ? r.amount : 0); } catch (e) {}
    setBusy(false); setDraft('');
  }
  async function submitHebrew(q) {
    if (busy || !hd || !hm || !hy) return; setBusy(true);
    try { const r = await api('/api/profile/' + userId + '/hebrew-birth-date', { method: 'POST', body: { hy: parseInt(hy, 10), hm: parseInt(hm, 10), hd: parseInt(hd, 10) } }); if (r && r.chapter) setChapterResult(r); await afterAward(q.tier, r && r.rewarded ? r.amount : 0); } catch (e) {}
    setBusy(false);
  }
  async function searchMentor(val) {
    setMq(val);
    if (!val || val.trim().length < 1) { setMResults([]); return; }
    try { const r = await api('/api/users/search?q=' + encodeURIComponent(val.trim()) + '&exclude=' + userId); setMResults((r && r.results) || []); } catch (e) { setMResults([]); }
  }
  async function chooseMentor(q, mid) {
    if (busy) return; setBusy(true);
    try { const r = await api('/api/profile/' + userId + '/mentor', { method: 'POST', body: { mentor_user_id: mid } }); await afterAward(q.tier, r && r.rewarded ? r.amount : 0); } catch (e) {}
    setBusy(false); setMResults([]); setMq('');
  }
  function advance() { setReward(null); setChapterResult(null); const ni = nextIdx(profile); if (ni < 0) setDone(true); else setIdx(ni); }

  // [damri ss#1 2026-05-24] gregorian date mode for hdate question
  async function submitGregorian(q) {
    if (busy || !gd || !gm || !gy) return; setBusy(true);
    try {
      const r = await api('/api/profile/' + userId + '/hebrew-birth-date', {
        method: 'POST',
        body: { gy: parseInt(gy, 10), gm: parseInt(gm, 10), gd: parseInt(gd, 10), afterSunset: !!afterSunset },
      });
      if (r && r.chapter) setChapterResult(r);
      await afterAward(q.tier, r && r.rewarded ? r.amount : 0);
    } catch (e) {}
    setBusy(false);
  }
  // [damri ss#2 2026-05-24] auto-advance: after a reward shows, keep it visible 2.8s
  // so user reads "+BEU" and the gratitude line, then open the next question on its own.
  React.useEffect(() => {
    if (!reward) return;
    const t = setTimeout(() => {
      setReward(null); setChapterResult(null); setDraft('');
      const ni = nextIdx(profile);
      if (ni < 0) setDone(true);
      else setIdx(ni);
    }, 2800);
    return () => clearTimeout(t);
  }, [reward, profile]);

  if (done) {
    return (
      <div style={{ ...card, textAlign: 'center' }}>
        <div style={{ fontSize: 22, marginBottom: 6 }}>🤍</div>
        <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 16, fontWeight: 700, color: '#1a1a2e', marginBottom: 6 }}>הפרופיל שלך עדיין מתגלה</div>
        <div style={{ fontSize: 13, color: '#5a3d0a', lineHeight: 1.7 }}>אנשים בקהילה יוכלו להתחבר אליך הרבה יותר ככל שתמשיך את המסע. נמשיך בהמשך הדרך.</div>
      </div>
    );
  }
  if (reward) {
    return (
      <div style={{ ...card, textAlign: 'center' }}>
        <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 16.5, fontWeight: 700, color: '#1a1a2e', lineHeight: 1.6, marginBottom: 10 }}>{reward.line}</div>
        {chapterResult && chapterResult.chapter && (
          <div style={{ fontSize: 13.5, color: '#5a3d0a', marginBottom: 10, lineHeight: 1.7 }}>פרק התהילים האישי שלך: <strong>פרק {chapterResult.chapter}</strong> · {chapterResult.hebrew_label}</div>
        )}
        {reward.amount > 0 && (
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '8px 18px', borderRadius: 100, background: 'linear-gradient(135deg,#f5d75c,#d4af37)', border: '1.5px solid #8a5f10', color: '#1a1a2e', fontWeight: 800, fontSize: 15, marginBottom: 14 }}>🪙 +{reward.amount} BEU</div>
        )}
        <div style={{ display: 'flex', gap: 8, marginTop: 4 }}>
          <button type="button" onClick={advance} style={{ flex: 1, padding: '11px', borderRadius: 10, border: '1.5px solid #8a5f10', background: 'linear-gradient(135deg,#f5d75c,#d4af37)', color: '#1a1a2e', fontWeight: 800, fontSize: 13.5, cursor: 'pointer', fontFamily: 'inherit' }}>לגלות עוד אוצר 🤍</button>
          <button type="button" onClick={() => setDone(true)} style={{ flex: 1, padding: '11px', borderRadius: 10, border: '1.5px solid rgba(212,175,55,0.5)', background: '#fffdf6', color: '#5a3d0a', fontWeight: 700, fontSize: 13.5, cursor: 'pointer', fontFamily: 'inherit' }}>להמשיך לקרוא</button>
        </div>
      </div>
    );
  }
  if (idx < 0) return null;
  const q = BANK[idx];
  return (
    <div style={card}>
      {firstChapter && coins === 0 && (
        <div style={{ textAlign: 'center', marginBottom: 16 }}>
          <div style={{ fontSize: 24, marginBottom: 6 }}>🕊️</div>
          <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 17.5, fontWeight: 700, color: '#1a1a2e', lineHeight: 1.5 }}>סיימת את הפרק הראשון שלך</div>
          <div style={{ fontSize: 13.5, color: '#5a3d0a', lineHeight: 1.7, marginTop: 6 }}>נשמח להכיר אותך — לא טופס, פשוט שיחה. כל דבר שתשתף, עוד חלק ממך מקבל כאן מקום.</div>
        </div>
      )}
      <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 17, fontWeight: 700, color: '#1a1a2e', lineHeight: 1.5, marginBottom: 4 }}>{q.q}</div>
      {q.why && <div style={{ fontSize: 12.5, color: 'rgba(255,255,255,0.72)', marginBottom: 12, lineHeight: 1.5 }}>{q.why}</div>}
      {q.kind === 'text' && (
        <div style={{ display: 'flex', gap: 8 }}>
          <input value={draft} onChange={e => setDraft(e.target.value)} placeholder={q.ph || ''} disabled={busy}
            onKeyDown={e => { if (e.key === 'Enter') submitText(q); }}
            style={{ flex: 1, padding: '11px 12px', borderRadius: 10, border: '1.5px solid rgba(212,175,55,0.55)', fontSize: 14, fontFamily: 'inherit', background: '#fffdf6' }} />
          <button type="button" disabled={busy || !draft.trim()} onClick={() => submitText(q)}
            style={{ padding: '0 18px', borderRadius: 10, border: '1.5px solid #8a5f10', cursor: draft.trim() ? 'pointer' : 'default', background: draft.trim() ? 'linear-gradient(135deg,#f5d75c,#d4af37)' : '#e8e2cf', color: '#1a1a2e', fontWeight: 800, fontSize: 13.5, fontFamily: 'inherit' }}>{busy ? '...' : 'שלח'}</button>
        </div>
      )}
      {q.kind === 'hdate' && (
        <div>
          {/* [damri ss#1 2026-05-24] pill toggle: עברי | לועזי */}
          <div role="tablist" aria-label="סוג תאריך" style={{ display: 'flex', gap: 14, marginBottom: 10, fontSize: 12.5 }}>
            {['heb','greg'].map(m => (
              <button key={m} type="button" role="tab" aria-selected={dateMode === m}
                onClick={() => setDateMode(m)}
                style={{
                  background: 'transparent', border: 'none', cursor: 'pointer',
                  fontFamily: 'inherit', padding: '4px 2px',
                  color: dateMode === m ? '#1a1a2e' : '#8a7a4a',
                  fontWeight: dateMode === m ? 700 : 500,
                  borderBottom: dateMode === m ? '2px solid #d4af37' : '2px solid transparent',
                  transition: 'color .2s, border-color .2s',
                }}>{m === 'heb' ? 'עברי' : 'לועזי'}</button>
            ))}
          </div>
          {dateMode === 'heb' ? (
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              <select value={hd} onChange={e => setHd(e.target.value)} style={sel}><option value="">יום</option>{Array.from({ length: 30 }, (_, i) => i + 1).map(d => <option key={d} value={d}>{d}</option>)}</select>
              <select value={hm} onChange={e => setHm(e.target.value)} style={sel}><option value="">חודש</option>{HEB_MONTHS.map(mm => <option key={mm[1]} value={mm[1]}>{mm[0]}</option>)}</select>
              <input value={hy} onChange={e => setHy(e.target.value.replace(/[^0-9]/g, ''))} placeholder="שנה (5785)" inputMode="numeric" style={{ ...sel, width: 90 }} />
              <button type="button" disabled={busy || !hd || !hm || !hy} onClick={() => submitHebrew(q)} style={{ ...goldBtn, opacity: (hd && hm && hy) ? 1 : 0.5 }}>{busy ? '...' : 'גלה'}</button>
            </div>
          ) : (
            <div>
              <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                <select value={gd} onChange={e => setGd(e.target.value)} style={sel}><option value="">יום</option>{Array.from({ length: 31 }, (_, i) => i + 1).map(d => <option key={d} value={d}>{d}</option>)}</select>
                <select value={gm} onChange={e => setGm(e.target.value)} style={sel}><option value="">חודש</option>{['ינואר','פברואר','מרץ','אפריל','מאי','יוני','יולי','אוגוסט','ספטמבר','אוקטובר','נובמבר','דצמבר'].map((mn, i) => <option key={i+1} value={i+1}>{mn}</option>)}</select>
                <input value={gy} onChange={e => setGy(e.target.value.replace(/[^0-9]/g, ''))} placeholder="שנה (1990)" inputMode="numeric" style={{ ...sel, width: 90 }} />
                <button type="button" disabled={busy || !gd || !gm || !gy} onClick={() => submitGregorian(q)} style={{ ...goldBtn, opacity: (gd && gm && gy) ? 1 : 0.5 }}>{busy ? '...' : 'גלה'}</button>
              </div>
              <label style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 8, fontSize: 12, color: 'rgba(255,255,255,0.72)' }}>
                <input type="checkbox" checked={afterSunset} onChange={e => setAfterSunset(e.target.checked)} />
                <span>נולדתי אחרי שקיעה</span>
              </label>
              <div style={{ marginTop: 6, fontSize: 11, color: 'rgba(255,255,255,0.55)' }}>נמיר אותו לתאריך עברי לפי הלוח</div>
            </div>
          )}
        </div>
      )}
      {q.kind === 'mentor' && (
        <div>
          <input value={mq} onChange={e => searchMentor(e.target.value)} placeholder="חפש שם בקהילה..." disabled={busy}
            style={{ width: '100%', padding: '11px 12px', borderRadius: 10, border: '1.5px solid rgba(212,175,55,0.55)', fontSize: 14, fontFamily: 'inherit', background: '#fffdf6', boxSizing: 'border-box' }} />
          {mResults.length > 0 && (
            <div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
              {mResults.map(u => (
                <button key={u.id} type="button" disabled={busy} onClick={() => chooseMentor(q, u.id)}
                  style={{ textAlign: 'right', padding: '10px 12px', borderRadius: 10, border: '1.5px solid rgba(212,175,55,0.4)', background: '#fffdf6', color: '#1a1a2e', fontSize: 14, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>{u.name}</button>
              ))}
            </div>
          )}
        </div>
      )}
      {q.deep && <div style={{ marginTop: 12, padding: '10px 12px', borderRadius: 10, background: 'rgba(255,255,255,0.85)', fontSize: 12.5, color: '#3a2f15', lineHeight: 1.6 }}>{HELP_LINE}</div>}
      <div style={{ textAlign: 'center', marginTop: 12 }}>
        <button type="button" onClick={() => setDone(true)} style={{ background: 'none', border: 'none', color: 'rgba(255,255,255,0.78)', fontSize: 12.5, cursor: 'pointer', fontFamily: 'inherit', textDecoration: 'underline' }}>אולי בהמשך</button>
      </div>
    </div>
  );
}

function ReadModal({ circle, onClose, onDone, initialResult }) {
  const collection = circle.text_type || 'tehillim';
  const totalChapters = circle.total_chapters || 150;
  // Yakir 2026-05-14: unified recording. iOS Safari doesn't support
  // SpeechRecognition for Hebrew reliably, so the primary mic button
  // routes directly to the native voice recorder there. One button,
  // one UX, no "iPhone users tap here" branch in the UI.
  const isIOSDevice = typeof navigator !== 'undefined'
    && /iPad|iPhone|iPod/.test(navigator.userAgent)
    && !window.MSStream;
  // [UNIVERSAL-FRESH-DEFAULT] Damri 2026-05-10: in ALL circles (tehillim
  // included), the default-pick must be a chapter no one has read yet.
  // Everyone walks into the circle on fresh ground first; if they want
  // to also read a chapter someone already did (tehillim mode allows
  // multiple readers per chapter), they can pick it manually.
  const myUserId = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
  // [yakir 2026-05-14] Anonymous flag for the 'save your coin' CTA.
  // True when user has a local user_id but never signed in with Google.
  // We re-read on every render so the CTA disappears the moment they sign in.
  const isAnonUser = myUserId > 0 && !localStorage.getItem('tehillim_google_sub');
  const myReadingsFromProp = (circle.readings || []).filter(
    r => r.verified === 1 && parseInt(r.reader_id || r.user_id, 10) === myUserId
  );
  const othersReadingsFromProp = (circle.readings || []).filter(
    r => r.verified === 1 && parseInt(r.reader_id || r.user_id, 10) !== myUserId
  );
  // Default-block set is universal: any chapter ANY reader verified.
  const defaultBlockSetFromProp = new Set(
    [...myReadingsFromProp, ...othersReadingsFromProp].map(r => r.chapter)
  );
  const [readSet, setReadSet] = useState(() => new Set(myReadingsFromProp.map(r => r.chapter)));
  const [othersReadSet, setOthersReadSet] = useState(() => new Set(othersReadingsFromProp.map(r => r.chapter)));
  // [yakir 2026-05-18] picker meta: tier + readers per chapter
  const [chapterMeta, setChapterMeta] = useState({});
  React.useEffect(() => {
    let cancelled = false;
    api(`/api/circle/${circle.id}/chapter-meta`).then(d => {
      if (!cancelled) setChapterMeta((d && d.meta) || {});
    }).catch(() => {});
    return () => { cancelled = true; };
  }, [circle.id]);
  const firstFreeFor = (set) => {
    for (let i = 1; i <= totalChapters; i++) if (!set.has(i)) return i;
    return 1;
  };
  // [yakir 2026-05-12 read-tanya direct entry] If the entry path pre-computed
  // a chapter for THIS user (via /api/tanya/next-chapter), honor it. Falls
  // back to universal-fresh-default otherwise.
  const _initialChapterFromIntent = (
    Number.isFinite(circle._initialChapter) && circle._initialChapter >= 1 && circle._initialChapter <= totalChapters
  ) ? circle._initialChapter : null;
  const [chapter, setChapter] = useState(() =>
    _initialChapterFromIntent || firstFreeFor(defaultBlockSetFromProp)
  );
  const [showPicker, setShowPicker] = useState(false);
  const [readingsLoading, setReadingsLoading] = useState(true);
  const userChoseRef = React.useRef(false);
  // [A1] If the prop's readings were empty on mount, any "user pick" before the
  // API resolves was based on stale data — Damri's "default still shows 1" bug.
  // Track this so server truth can override the userChoseRef flag.
  const initialReadingsEmptyRef = React.useRef(
    !((circle.readings || []).some(r => r.verified === 1))
  );

  // [A1] Defensive: explicit reset on every modal mount.
  React.useEffect(() => { userChoseRef.current = false; }, []);

  // On mount AND when modal re-opens: refresh from server so default reflects truth.
  React.useEffect(() => {
    let cancelled = false;
    setReadingsLoading(true);
    api(`/api/circle/${circle.id}`).then((c) => {
      if (cancelled) return;
      const fresh = new Set(
        (c.readings || [])
          .filter(r => r.verified === 1 && parseInt(r.reader_id || r.user_id, 10) === myUserId)
          .map(r => r.chapter)
      );
      const freshOthers = new Set(
        (c.readings || [])
          .filter(r => r.verified === 1 && parseInt(r.reader_id || r.user_id, 10) !== myUserId)
          .map(r => r.chapter)
      );
      setReadSet(fresh);
      setOthersReadSet(freshOthers);
      // [UNIVERSAL-FRESH-DEFAULT] Auto-bump always avoids ANY verified chapter.
      const blockSet = new Set([...fresh, ...freshOthers]);
      // [yakir 2026-05-12] If we entered with an intent-chapter and the user
      // hasn't picked anything yet AND hasn't read that chapter yet, KEEP it.
      // Otherwise fall back to universal-fresh.
      if (_initialChapterFromIntent && !userChoseRef.current && !fresh.has(_initialChapterFromIntent)) {
        setChapter(_initialChapterFromIntent);
      } else if (!userChoseRef.current || initialReadingsEmptyRef.current) {
        setChapter(firstFreeFor(blockSet));
      }
    }).catch(() => {}).finally(() => {
      if (!cancelled) setReadingsLoading(false);
    });
    return () => { cancelled = true; };
  }, [circle.id]);

  const [name, setName] = useState('');
  const [headIndex, setHeadIndex] = useState(0);
  // [yakir 2026-05-12 pass 9] Transcript autosave/restore.
  // Yakir read an entire chapter, accidentally tapped backdrop, transcript
  // was wiped. Critical UX bug. Defense: persist to localStorage on every
  // change, restore on mount, clear only on verified submit.
  const _draftKey = (ch) => `thl_draft_${circle.id}_${ch}`;
  const [transcript, setTranscript] = useState(() => {
    try {
      const ch0 = _initialChapterFromIntent || firstFreeFor(defaultBlockSetFromProp);
      return localStorage.getItem(_draftKey(ch0)) || '';
    } catch (_) { return ''; }
  });
  // Auto-save: every transcript change → localStorage.
  React.useEffect(() => {
    try {
      if (transcript && transcript.length > 0) {
        localStorage.setItem(_draftKey(chapter), transcript);
      }
    } catch (_) {}
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [transcript, chapter]);
  // Chapter switch → restore that chapter's draft (or clear if none).
  React.useEffect(() => {
    try {
      const saved = localStorage.getItem(_draftKey(chapter));
      if (saved && saved !== transcript) setTranscript(saved);
      else if (!saved && transcript) setTranscript('');
    } catch (_) {}
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [chapter]);
  // Trust-code path: user with admin-issued personal code can verify without recording.
  const [showTrustCode, setShowTrustCode] = useState(false);
  const [trustCode, setTrustCode] = useState('');
  // [yakir 2026-05-12 pass 10] When user clicks the trust-code button, the
  // form appears at the bottom of the modal — far from where they tapped.
  // Yakir: "שורה קופצת למטה מידי וזה לא מובן ולא נוח".
  // Solution: ref + smooth scroll into view as soon as the form expands.
  const trustCodeFormRef = React.useRef(null);
  React.useEffect(() => {
    if (showTrustCode && trustCodeFormRef.current) {
      // Wait a tick so the DOM has the new element before we scroll.
      setTimeout(() => {
        try {
          trustCodeFormRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
        } catch (_) {
          trustCodeFormRef.current.scrollIntoView();
        }
      }, 60);
    }
  }, [showTrustCode]);
  const [psalm, setPsalm] = useState(null);
  const [lang, setLang] = useState('he');
  const [recording, setRecording] = useState(false);
  const [recStatus, setRecStatus] = useState('');
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState(initialResult ?? null);
  const [err, setErr] = useState('');
  // [yakir pass 11] Kavana (intention) — optional reflection after a chapter.
  // Captured only if the reader chooses to write something. Fire-and-forget save.
  const [kavanaText, setKavanaText] = useState('');
  const [kavanaSaved, setKavanaSaved] = useState(false);
  // [yakir pass 48] kavana collapsed by default - most readers won't write;
  // those who want to, tap the link to expand. Existing text auto-expands.
  const [kavanaOpen, setKavanaOpen] = React.useState(false);
  React.useEffect(() => { if (kavanaText && kavanaText.trim()) setKavanaOpen(true); }, []);

  // [yakir pass 49] Roadmap modal open state
  const [upcomingOpen, setUpcomingOpen] = React.useState(false);

  // [yakir pass 41] Detect inviter from URL ?from=<user_id>.
  // When someone arrives via an invite link (X invited Y), Y reads,
  // and at the post-chapter gate Y gets a one-click 'thank X' button.
  // This closes the share loop: invite → read → thank → X gets a
  // notification → X is more likely to invite again.
  // Falls back silently if no ?from param or fetch fails — just no strip.
  const [inviterId, _setInviterId] = React.useState(null);  // unused setter, kept for symmetry
  const [inviterName, setInviterName] = React.useState(null);
  const [thankSent, setThankSent] = React.useState(false);

  // [yakir 2026-05-14] Share-kind picker modal — opens when user taps the
  // "🤝 להוסיף יהודי למעגל" door in the post-chapter gate. Lets the sender
  // choose between (a) circle invite, (b) direct next-chapter, (c) the
  // tanya coin landing page. Each kind has its own text + link.
  const ShareKindPicker = () => {
    if (!shareKindOpen) return null;
    const pick = (kind) => {
      setShareKindOpen(false);
      _dispatchShare(_buildSharePayload(kind));
    };
    return (
      <div
        onClick={() => setShareKindOpen(false)}
        style={{
          position: 'fixed', inset: 0, zIndex: 10000,
          background: 'rgba(0,0,0,0.55)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          padding: 16, direction: 'rtl',
        }}
      >
        <div
          onClick={(e) => e.stopPropagation()}
          style={{
            background: '#fffbef',
            borderRadius: 16,
            padding: '22px 18px 18px',
            maxWidth: 420, width: '100%',
            boxShadow: '0 20px 50px rgba(0,0,0,0.30)',
            fontFamily: "'Frank Ruhl Libre', serif",
          }}
        >
          <h3 style={{
            margin: '0 0 6px', textAlign: 'center',
            fontSize: 20, fontWeight: 700, color: '#1a1a2e',
          }}>איזה קישור תרצה לשלוח?</h3>
          <div style={{
            fontSize: 13, color: '#6d5d3d', textAlign: 'center',
            fontStyle: 'italic', marginBottom: 18,
          }}>תבחר לפי המצב של האדם שאתה מזמין</div>

          {[
            { kind: 'circle',  emoji: '🪷', title: 'הזמנה למעגל הזה',
              sub: 'דף נחיתה עם שם המעגל, פס התקדמות ומטבע אישי. הכי טבעי לחבר חדש.' },
            { kind: 'direct',  emoji: '📖', title: 'כניסה ישירה לפרק הבא',
              sub: 'בלי דף נחיתה — לחיצה אחת והפרק הבא נפתח. למישהו שכבר באירוע.' },
            { kind: 'about',   emoji: '🪙', title: 'הסבר על מטבע התניא',
              sub: 'דף הרעיון השלם. למישהו שרוצה להבין למה הכל בנוי.' },
          ].map((o) => (
            <button
              key={o.kind}
              type="button"
              onClick={() => pick(o.kind)}
              style={{
                display: 'block', width: '100%',
                textAlign: 'right',
                padding: '14px 16px',
                marginBottom: 10,
                background: 'linear-gradient(135deg, #fff 0%, rgba(212,175,55,0.10) 100%)',
                border: '2px solid rgba(212,175,55,0.50)',
                borderRadius: 12,
                cursor: 'pointer',
                fontFamily: 'inherit',
                transition: 'all 0.2s ease',
              }}
              onMouseEnter={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(212,175,55,0.18) 0%, rgba(212,175,55,0.10) 100%)'; }}
              onMouseLeave={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, #fff 0%, rgba(212,175,55,0.10) 100%)'; }}
            >
              <div style={{ fontSize: 17, fontWeight: 700, color: '#1a1a2e', marginBottom: 4 }}>
                {o.emoji}  {o.title}
              </div>
              <div style={{ fontSize: 12, color: '#5a3d0a', lineHeight: 1.5, fontStyle: 'italic' }}>
                {o.sub}
              </div>
            </button>
          ))}

          <button
            type="button"
            onClick={() => setShareKindOpen(false)}
            style={{
              display: 'block', width: '100%',
              padding: '10px',
              background: 'transparent',
              border: 'none',
              color: '#777',
              fontSize: 14,
              cursor: 'pointer',
              marginTop: 4,
              fontFamily: 'inherit',
            }}
          >
            ביטול
          </button>
        </div>
      </div>
    );
  };
  const [thankBusy, setThankBusy] = React.useState(false);

  // [yakir pass 44] User-controlled reader font size. Persisted across
  // sessions so the user only sets it once. 'md' = default (no CSS override,
  // responsive sizes from media queries apply). 'sm' / 'lg' override.
  const [readerSize, setReaderSize] = React.useState(() => {
    try {
      const v = localStorage.getItem('thl_reader_size');
      if (v === 'sm' || v === 'lg') return v;
    } catch (_) {}
    return 'md';
  });
  React.useEffect(() => {
    try { localStorage.setItem('thl_reader_size', readerSize); } catch (_) {}
  }, [readerSize]);

  // [yakir pass 47] Speech Synthesis - hardened for iOS Safari.
  //   - Pre-load voices on mount (iOS returns empty list on first call)
  //   - Pick a Hebrew voice explicitly (without it, falls back to English
  //     voice trying to say Hebrew chars → silence/nonsense)
  //   - Listen for voiceschanged event (Android Chrome loads voices async)
  //   - Toast helpful message if no Hebrew voice found
  //   - User-gesture spec: speak() called SYNCHRONOUSLY inside onClick
  //     handler with no awaits between, so iOS doesn't kill the request.
  const [speaking, setSpeaking] = React.useState(false);
  const [heVoice, setHeVoice] = React.useState(null);
  const [ttsAvailable] = React.useState(() => {
    return typeof window !== 'undefined' && 'speechSynthesis' in window && 'SpeechSynthesisUtterance' in window;
  });

  // Load voices on mount + on voiceschanged event
  React.useEffect(() => {
    if (!ttsAvailable) return;
    const pickHe = () => {
      const voices = window.speechSynthesis.getVoices() || [];
      // Prefer he-IL > he > Carmit (iOS) by name > any voice whose lang starts with 'he'
      const he = voices.find(v => v.lang === 'he-IL')
              || voices.find(v => v.lang === 'he')
              || voices.find(v => v.name && /carmit/i.test(v.name))
              || voices.find(v => v.lang && /^he/i.test(v.lang))
              || null;
      setHeVoice(he);
    };
    pickHe();
    // Voices often load async - listen for the event too
    if ('onvoiceschanged' in window.speechSynthesis) {
      window.speechSynthesis.onvoiceschanged = pickHe;
    }
    return () => {
      try {
        if (window.speechSynthesis && window.speechSynthesis.speaking) window.speechSynthesis.cancel();
        if ('onvoiceschanged' in window.speechSynthesis) window.speechSynthesis.onvoiceschanged = null;
      } catch (_) {}
    };
  }, [ttsAvailable]);

  // [2026-05-18] Server-side TTS via Edge (Microsoft) — better Hebrew voices than
  // browser speechSynthesis (esp. on iOS where Hebrew voices are weak).
  // Falls back to speechSynthesis if /api/tts fails.
  const ttsAudioRef = React.useRef(null);
  const _stopServerTts = () => {
    const a = ttsAudioRef.current;
    if (a) { try { a.pause(); a.currentTime = 0; } catch(_){} }
    setSpeaking(false);
  };
  const playChapterServer = async () => {
    // Stop any in-progress browser TTS first
    try { if (window.speechSynthesis) window.speechSynthesis.cancel(); } catch(_){}
    if (speaking) { _stopServerTts(); return; }
    const url = `/api/tts/${encodeURIComponent(collection)}/${encodeURIComponent(chapter)}?voice=avri`;
    // Create/reuse audio element
    let a = ttsAudioRef.current;
    if (!a) {
      a = new Audio();
      a.preload = 'auto';
      a.onended = () => setSpeaking(false);
      a.onpause = () => { /* may be user pause; setSpeaking handled by onended */ };
      a.onerror = () => { setSpeaking(false); try { showToast('הקראה נכשלה — מנסה דפדפן'); } catch(_){} playChapterBrowser(); };
      ttsAudioRef.current = a;
    }
    a.src = url;
    try {
      await a.play();
      setSpeaking(true);
    } catch (e) {
      setSpeaking(false);
      // user-gesture issues or playback blocked — fall back
      playChapterBrowser();
    }
  };
  // Browser-TTS fallback (legacy path)
  const playChapterBrowser = () => {
  /* renamed from playChapter */
    if (!ttsAvailable) { try { showToast('הדפדפן לא תומך בהקראה'); } catch(_){} return; }
    if (!psalm.hebrew) return;

    // Toggle off - if already speaking, stop
    try {
      if (window.speechSynthesis.speaking || window.speechSynthesis.pending) {
        window.speechSynthesis.cancel();
        setSpeaking(false);
        return;
      }
    } catch (_) {}

    // Build utterance synchronously (iOS requirement: no awaits before speak())
    const u = new SpeechSynthesisUtterance(psalm.hebrew);
    u.lang = 'he-IL';
    if (heVoice) u.voice = heVoice;
    u.rate = 0.9;
    u.pitch = 1.0;
    u.volume = 1.0;

    u.onstart = () => setSpeaking(true);
    u.onend = () => setSpeaking(false);
    u.onpause = () => setSpeaking(false);
    u.onerror = (e) => {
      setSpeaking(false);
      const code = (e && e.error) || 'unknown';
      console.warn('TTS error:', code, e);
      if (code === 'not-allowed' || code === 'audio-busy' || code === 'audio-hardware') {
        try { showToast('המכשיר חוסם הקראה. בדוק שמתג ההשתקה כבוי'); } catch (_){}
      } else if (code === 'synthesis-failed' || code === 'language-unavailable' || code === 'voice-unavailable') {
        try { showToast('לא נמצא קול עברי במכשיר. נסה דפדפן אחר או עדכון מערכת'); } catch (_){}
      }
    };

    // If no Hebrew voice was found, warn ahead so user knows it'll sound wrong
    if (!heVoice) {
      try { showToast('💡 לא נמצא קול עברי. בדוק: הגדרות → נגישות → תוכן מדובר → קולות → עברית'); } catch (_){}
    }

    setSpeaking(true);
    try {
      window.speechSynthesis.speak(u);
    } catch (e) {
      setSpeaking(false);
      console.warn('speak() threw:', e);
      try { showToast('שגיאה: ' + (e.message || 'לא ניתן להקריא')); } catch (_){}
    }
  };
  React.useEffect(() => {
    try {
      const fromParam = new URLSearchParams(window.location.search).get('from');
      const fromId = parseInt(fromParam, 10);
      if (!Number.isFinite(fromId) || fromId <= 0) return;
      // Don't show 'thank yourself' if user came back via their own link
      if (myUserId && fromId === myUserId) return;
      _setInviterId(fromId);
      api(`/api/user/${fromId}`).then(u => {
        if (u && u.name && !u.error) setInviterName(u.name);
      }).catch(() => {});
    } catch (_) {}
  }, []);  // mount-only
  // [yakir pass 13 — gate-brief.md + 13-May-evening brief] Phased reveal.
  //   Phase 0 (0–3s):  silence + ONE serif line. No coin. No buttons. No UI.
  //   Phase 1 (3–6s):  second serif line fades in.
  //   Phase 2 (6s+):   the gentle pivot question 'מה היית רוצה שייפתח עכשיו?'
  //                    appears, and below it the MAP OF DOORS — three equal-
  //                    weight options (write+close / talk to Yakir / next
  //                    chapter), no dominant CTA, no funnel.
  // Failed verification (result.verified=false) skips the phased reveal so
  // the user sees the issue immediately.
  const [resultPhase, setResultPhase] = useState(0);
  React.useEffect(() => {
    if (!result) { setResultPhase(0); return; }
    if (!result.verified) { setResultPhase(2); return; }
    setResultPhase(0);
    // [yakir pass 40] compressed reveal: was 3s/6s (felt slow), now 500/1100ms.
    // Still a brief moment of integration, but the doors arrive fast.
    const t1 = setTimeout(() => setResultPhase(p => Math.max(p, 1)), 500);
    const t2 = setTimeout(() => setResultPhase(p => Math.max(p, 2)), 1100);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [result]);
  const recognitionRef = React.useRef(null);
  const finalRef = React.useRef('');
  const lastFinalRef = React.useRef('');
  // [2026-05-24 highlight-sync] forward cursor in trWords — see lesson same date.
  const trCursorRef = React.useRef(0);
  const recordingRef = React.useRef(false);

  // Load text whenever chapter, parts, or circle identity changes.
  // dbCoordsForChapter resolves the underlying DB collection AND local
  // chapter — required for multi-part Tanya circles where the running
  // circle-chapter index 1..total spans several DB collections.
  useEffect(() => {
    setPsalm(null);
    const { collection: _c, chapter: _ch } = dbCoordsForChapter(circle, chapter);
    api(`/api/text/${_c}/${_ch}`).then(p => { if (!p.error) setPsalm(p); });
  }, [chapter, circle && circle.id, circle && circle.text_type, circle && circle.text_parts]);

  // [2026-05-24 highlight-sync] Forward-cursor matcher.
  // Why the old window-set matcher drifted (lesson 2026-05-12 + 2026-05-24):
  //   - It built a Set of the LAST N transcript words and tested src[head]
  //     against the set. If user reads ahead of speech-recog, or speech
  //     delivers a 50-word batch after auto-restart, src[head] falls out of
  //     the window and is never matched. There is no resync.
  //   - The `advanced<10` cap also throttled catch-up to 10 words per
  //     transcript change, so a Tanya chapter that fluctuated by 30+ words
  //     at once couldn't ever close the gap on the same render.
  //
  // New: maintain a forward cursor `trCursorRef` into the transcript word
  // stream. For each next src[head], linear-scan trWords[cursor..cursor+W]
  // for a fuzzy match. On hit, advance head AND cursor. No cap on head
  // steps per render. Cursor only ever moves forward (monotonic), so
  // cumulative-final retransmissions of the same words are idempotent.
  useEffect(() => {
    if (!psalm || lang !== 'he' || !recording) return;
    const src = psalm.hebrew || '';
    if (!src) return;
    const norm = (s) => (s || '')
      .normalize('NFD')
      .replace(/[\u0591-\u05C7]/g, '')           // strip nikud + ta'amim
      .replace(/[\u05BE\u05F3\u05F4\u200C\u200D]/g, '') // maqaf, geresh, gershayim, ZWNJ, ZWJ
      .replace(/[^\u0590-\u05FFA-Za-z\s]/g, ' ')// keep letters only
      .replace(/\s+/g, ' ')
      .trim();
    const stripPrefix = (w) => {
      if (!w || w.length < 3) return w;
      const PREFIXES = ['ה', 'ב', 'ל', 'כ', 'מ', 'ש', 'ו'];
      return PREFIXES.includes(w[0]) ? w.slice(1) : w;
    };
    // Pair-wise fuzzy match (cand vs single transcript word). Faster and
    // accurate enough for our purposes — exact, prefix-stripped, stem (-1
    // letter), and 3-char-prefix-overlap.
    const wordMatchesOne = (cand, tw) => {
      if (!cand || cand.length < 2 || !tw || tw.length < 2) return false;
      if (cand === tw) return true;
      const candStripped = stripPrefix(cand);
      const twStripped = stripPrefix(tw);
      if (candStripped === tw || cand === twStripped || candStripped === twStripped) return true;
      if (cand.length >= 3) {
        const stem = cand.slice(0, -1);
        if (stem === tw || stem === twStripped) return true;
        const stemStripped = stripPrefix(stem);
        if (stemStripped === tw || stemStripped === twStripped) return true;
      }
      if (cand.length >= 4 && tw.length >= 3) {
        const pre = cand.slice(0, 3);
        if (tw.startsWith(pre) || twStripped.startsWith(pre)) return true;
      }
      return false;
    };
    // Kri/Ktiv: divine-name written-vs-spoken handled by replacing the
    // source word with what the reader actually says aloud.
    const KRI_KETIV = {
      'יהוה': 'אדוני',
      'יהוה\'': 'אדוני',
      'יי': 'אדוני',
      'אלהים': 'אלוקים',
      'אלהי': 'אלוקי',
      'אלהיכם': 'אלוקיכם',
      'אלהינו': 'אלוקינו',
    };
    const applyKri = (w) => KRI_KETIV[w] || w;
    const srcWords = norm(src).split(' ').filter(Boolean).map(applyKri);
    const trWords = norm(transcript).split(' ').filter(Boolean);
    if (trWords.length === 0) return;
    // Defensive: if transcript shrank (manual edit), clamp cursor.
    if (trCursorRef.current > trWords.length) trCursorRef.current = 0;
    setHeadIndex((prev) => {
      let head = prev;
      let cursor = trCursorRef.current;
      const LOOK_TR = 30;       // search 30 transcript words ahead per src word
      const MAX_SRC_SKIP = 3;   // misrecognition: skip up to 3 source words
      const findFrom = (target, startIdx, limitExclusive) => {
        for (let p = startIdx; p < limitExclusive; p++) {
          if (wordMatchesOne(target, trWords[p])) return p;
        }
        return -1;
      };
      // Keep advancing until we run out of source OR can't find next anywhere
      // in the look-ahead window of transcript.
      while (head < srcWords.length) {
        const limit = Math.min(trWords.length, cursor + LOOK_TR);
        if (limit <= cursor) break; // no more transcript to search
        const found = findFrom(srcWords[head], cursor, limit);
        if (found >= 0) {
          head++;
          cursor = found + 1;
          continue;
        }
        // src-skip: maybe srcWords[head] was misheard. Look for head+k.
        let skipped = false;
        for (let k = 1; k <= MAX_SRC_SKIP && head + k < srcWords.length; k++) {
          const foundK = findFrom(srcWords[head + k], cursor, limit);
          if (foundK >= 0) {
            head += k + 1;
            cursor = foundK + 1;
            skipped = true;
            break;
          }
        }
        if (!skipped) break;
      }
      trCursorRef.current = cursor;
      return head;
    });
  }, [transcript, psalm, lang, recording]);

  const startRec = () => {
    setErr('');
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) {
      setErr('הדפדפן לא תומך בזיהוי קולי. לחץ על מילה בטקסט לסימון הקריאה ידנית, או פתח ב-Chrome.');
      return;
    }
    // [yakir pass 53] iPhone block lifted — Web Speech sometimes works on
    // iOS 14.5+ with Hebrew dictation enabled. If it fails, the user has
    // 2 fallbacks: native recording button + trust code. Try anyway.
    const recognition = new SR();
    recognition.lang = 'he-IL';
    recognition.continuous = true;
    recognition.interimResults = true;
    recognition.maxAlternatives = 1;
    finalRef.current = '';
    lastFinalRef.current = '';
    trCursorRef.current = 0;
    setHeadIndex(0);

    recognition.onresult = (event) => {
      // Chrome's continuous=true with Hebrew emits multiple final results
      // where each new final extends the previous one (e.g. "אשרי" → "אשרי האיש"
      // → "אשרי האיש אשר"), causing huge duplication. We track the last final
      // phrase separately and REPLACE it when the next final extends it,
      // instead of blindly appending. See chromium issue 40901393.
      let interim = '';
      for (let i = event.resultIndex; i < event.results.length; i++) {
        if (event.results[i].isFinal) {
          const fresh = event.results[i][0].transcript.trim();
          if (!fresh) continue;
          const last = lastFinalRef.current;
          if (last && fresh.startsWith(last)) {
            // Extends last phrase: replace last phrase inside finalRef.
            const cut = finalRef.current.length - (last.length + 1);
            finalRef.current = (cut >= 0 ? finalRef.current.slice(0, cut) : '') + fresh + ' ';
            lastFinalRef.current = fresh;
          } else if (last && last.startsWith(fresh)) {
            // New is a strict prefix of last — skip (older partial).
          } else {
            // New independent phrase — append.
            finalRef.current += fresh + ' ';
            lastFinalRef.current = fresh;
          }
        } else {
          interim += event.results[i][0].transcript;
        }
      }
      setTranscript(finalRef.current + interim);
    };

    recognition.onerror = (e) => {
      console.error('Speech error:', e.error);
      if (e.error === 'not-allowed') setErr('יש לאשר גישה למיקרופון');
      else if (e.error === 'no-speech') setRecStatus('לא זוהה קול — נסה שוב');
      else setErr('שגיאת זיהוי: ' + e.error);
    };

    recognition.onend = () => {
      // Chrome auto-stops Web Speech (he-IL, continuous=true) after ~60s.
      // Auto-restart if user still recording. Chromium workaround.
      if (recordingRef.current) {
        try { recognition.start(); return; } catch (e) {}
      }
      setRecording(false);
      setRecStatus(finalRef.current ? '✓ תמלול מוכן — אשר את הקריאה למטה' : 'הופסק');
      recognitionRef.current = null;
    };

    try {
      recognition.start();
      recognitionRef.current = recognition;
      recordingRef.current = true;
      setRecording(true);
      setRecStatus('🔴 מקשיב... דבר בבירור, התמלול מופיע בזמן אמת');
    } catch (e) {
      setErr('שגיאה בהפעלת זיהוי: ' + e.message);
    }
  };

  const stopRec = () => {
    recordingRef.current = false;
    if (recognitionRef.current) {
      try { recognitionRef.current.stop(); } catch (e) {}
    }
    setRecording(false);
  };

  // [2026-05-18 fix] iOS in-page MediaRecorder. Avoids Voice Memos preempting
  // the chapter text. iOS Safari 14.5+ supports MediaRecorder + getUserMedia.
  const iosMediaRecorderRef = React.useRef(null);
  const iosChunksRef = React.useRef([]);
  // [yakir 2026-05-19] Live waveform refs — Web Audio API analyser
  const iosStreamRef = React.useRef(null);
  const iosAudioCtxRef = React.useRef(null);
  const iosAnalyserRef = React.useRef(null);
  const iosWaveAnimRef = React.useRef(null);
  const iosCanvasRef = React.useRef(null);

  const _blobToB64 = (blob) => new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const url = reader.result || '';
      const i = url.indexOf(',');
      resolve(i > 0 ? url.substring(i + 1) : url);
    };
    reader.onerror = () => reject(new Error('FileReader failed'));
    reader.readAsDataURL(blob);
  });

  const _pickMime = () => {
    if (typeof MediaRecorder === 'undefined') return null;
    const candidates = ['audio/mp4', 'audio/webm;codecs=opus', 'audio/webm', 'audio/ogg'];
    for (const m of candidates) {
      if (MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported(m)) return m;
    }
    return '';  // empty → browser default
  };

  const iosStartRec = async () => {
    if (uploading || recording) return;
    if (typeof MediaRecorder === 'undefined') {
      setErr('הדפדפן לא תומך בהקלטה');
      return;
    }
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      iosChunksRef.current = [];
      // [yakir 2026-05-19] Set up live waveform analyser. Non-fatal on failure.
      iosStreamRef.current = stream;
      try {
        const AC = window.AudioContext || window.webkitAudioContext;
        if (AC) {
          const ctx = new AC();
          const source = ctx.createMediaStreamSource(stream);
          const analyser = ctx.createAnalyser();
          analyser.fftSize = 256;
          source.connect(analyser);
          iosAudioCtxRef.current = ctx;
          iosAnalyserRef.current = analyser;
          const buf = new Uint8Array(analyser.frequencyBinCount);
          const draw = () => {
            const canvas = iosCanvasRef.current;
            const a = iosAnalyserRef.current;
            if (!canvas || !a) return;
            a.getByteTimeDomainData(buf);
            const dpr = window.devicePixelRatio || 1;
            const w = canvas.width = canvas.clientWidth * dpr;
            const h = canvas.height = canvas.clientHeight * dpr;
            const c = canvas.getContext('2d');
            c.clearRect(0, 0, w, h);
            c.lineWidth = 2 * dpr;
            c.strokeStyle = '#16a34a';
            c.beginPath();
            const slice = w / buf.length;
            let x = 0;
            for (let i = 0; i < buf.length; i++) {
              const v = buf[i] / 128.0;
              const y = (v * h) / 2;
              if (i === 0) c.moveTo(x, y); else c.lineTo(x, y);
              x += slice;
            }
            c.stroke();
            iosWaveAnimRef.current = requestAnimationFrame(draw);
          };
          iosWaveAnimRef.current = requestAnimationFrame(draw);
        }
      } catch (waveErr) {
        console.warn('[mic] waveform setup failed (non-fatal):', waveErr);
      }
      const mime = _pickMime();
      const mr = mime ? new MediaRecorder(stream, { mimeType: mime })
                      : new MediaRecorder(stream);
      mr.ondataavailable = (e) => {
        if (e.data && e.data.size > 0) iosChunksRef.current.push(e.data);
      };
      mr.onstop = async () => {
        try {
          const blob = new Blob(iosChunksRef.current, { type: mime || 'audio/webm' });
          stream.getTracks().forEach(t => t.stop());
          setUploading(true);
          setRecStatus('🎵 מעלה להמרה לטקסט...');
          const b64 = await _blobToB64(blob);
          const r = await fetch('/api/transcribe', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ chapter, audio_b64: b64 }),
          }).then(res => res.json());
          if (r && r.text) {
            finalRef.current = r.text + ' ';
            lastFinalRef.current = '';
            trCursorRef.current = 0;
            setHeadIndex(0);
            setTranscript(r.text);
            setRecStatus('✓ תמלול מוכן — אשר את הקריאה למטה');
          } else {
            setErr(r && r.error ? r.error : 'התמלול נכשל');
            setRecStatus('');
          }
        } catch (err) {
          setErr('שגיאה: ' + (err.message || 'לא ניתן להעלות'));
          setRecStatus('');
        } finally {
          setUploading(false);
        }
      };
      mr.start();
      iosMediaRecorderRef.current = mr;
      setRecording(true);
      setRecStatus('🎙️ מקליט... לחץ שוב לסיום');
      setErr('');
    } catch (e) {
      // Common reasons: user denied permission, or HTTPS-only mic blocked.
      const msg = e.name === 'NotAllowedError'
        ? 'נדרשת הרשאת מיקרופון. הגדרות → Safari → מיקרופון'
        : 'שגיאה במיקרופון: ' + (e.message || e.name || '');
      setErr(msg);
    }
  };

  const iosStopRec = () => {
    const mr = iosMediaRecorderRef.current;
    if (mr && mr.state === 'recording') {
      try { mr.stop(); } catch (_) {}
    }
    // [yakir 2026-05-19] Tear down waveform animation + audio context
    if (iosWaveAnimRef.current) {
      try { cancelAnimationFrame(iosWaveAnimRef.current); } catch (_) {}
      iosWaveAnimRef.current = null;
    }
    if (iosAudioCtxRef.current) {
      try { iosAudioCtxRef.current.close(); } catch (_) {}
      iosAudioCtxRef.current = null;
    }
    iosAnalyserRef.current = null;
    iosStreamRef.current = null;
    setRecording(false);
  };

  const iosToggleRec = () => {
    if (recording) iosStopRec(); else iosStartRec();
  };

  const toggleRec = () => {
    if (isIOSDevice) {
      // [2026-05-18] iOS: use in-page MediaRecorder (no Voice Memos popup).
      // Keep fileInputRef as fallback only if MediaRecorder unsupported.
      if (typeof MediaRecorder !== 'undefined' && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
        return iosToggleRec();
      }
      // Fallback: legacy file-input → Voice Memos
      if (uploading) return;
      if (fileInputRef.current) fileInputRef.current.click();
      return;
    }
    recording ? stopRec() : startRec();
  };

  // [yakir pass 53] iPhone-friendly fallback: native voice recorder.
  // <input type="file" accept="audio/*" capture="microphone"> opens the
  // device's built-in Voice Memos (iOS) or recorder (Android). User records
  // there, file comes back, we send to /api/transcribe (Whisper) and
  // populate the transcript textbox automatically.
  const [uploading, setUploading] = React.useState(false);
  const fileInputRef = React.useRef(null);
  const handleAudioFile = async (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = '';  // allow re-selecting same file
    if (!file) return;
    if (file.size > 25 * 1024 * 1024) {
      showToast('קובץ גדול מדי (מקס 25MB). תקליט הקלטה קצרה יותר');
      return;
    }
    setUploading(true);
    setRecStatus('🎵 מעלה להמרה לטקסט...');
    try {
      // Read as base64
      const b64 = await new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => {
          const dataUrl = reader.result || '';
          const comma = dataUrl.indexOf(',');
          resolve(comma > 0 ? dataUrl.substring(comma + 1) : dataUrl);
        };
        reader.onerror = () => reject(new Error('FileReader failed'));
        reader.readAsDataURL(file);
      });

      const r = await fetch('/api/transcribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ chapter, audio_b64: b64 }),
      }).then(res => res.json());

      if (r && r.text) {
        finalRef.current = r.text + ' ';
        lastFinalRef.current = '';
        trCursorRef.current = 0;
        setHeadIndex(0);
        setTranscript(r.text);
        setRecStatus('✓ תמלול מוכן — אשר את הקריאה למטה');
      } else {
        setErr(r && r.error ? r.error : 'התמלול נכשל');
        setRecStatus('');
      }
    } catch (err) {
      setErr('שגיאה: ' + (err.message || 'לא ניתן להעלות'));
      setRecStatus('');
    } finally {
      setUploading(false);
    }
  };

  // [yakir pass 11] Helper: save kavana if user wrote anything. Fire-and-forget;
  // never blocks the action. Anonymous users skip (no user_id to link).
  const _saveKavanaIfAny = async () => {
    const txt = (kavanaText || '').trim();
    if (!txt || kavanaSaved) return;
    const uid = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
    if (!uid) return;
    try {
      await api('/api/kavana', { method: 'POST', body: {
        user_id: uid, circle_id: circle.id, chapter: parseInt(chapter, 10), text: txt,
      }});
      setKavanaSaved(true);
    } catch (_) { /* silent */ }
  };

  // [yakir pass 40] Post-chapter invite share — the most important door per Yakir.
  // After completing a chapter, the natural next move is to invite another Jew
  // into the merit of the circle. This builds a PERSONAL invitation message
  // (with the just-read chapter mentioned) and offers the user every send
  // channel we have: native share, WhatsApp, Telegram, copy link.
  // [yakir 2026-05-14] Build the share payload for a given KIND.
  // Three kinds offered to the sender after a chapter:
  //   - 'circle'  → invite link to THIS circle's landing page (the original)
  //   - 'direct'  → /read/tanya — auto-opens next unread chapter, no landing
  //   - 'about'   → /tanya — deep landing page explaining the TNY coin idea
  // The text adapts to each: greeting includes the sender's first name when
  // we have it ('היי, אני יקיר.') so the recipient knows who's reaching out
  // even on forwarded messages where the WhatsApp 'from' chip is lost.
  // [yakir 2026-05-14] Natural-voice rewrite. Removed the auto-prepended
  // "היי, אני [name]" greeting — Yakir said it felt like a chatbot opener,
  // and a friend doesn't introduce themselves when texting (the WhatsApp
  // from-chip already identifies the sender). Rewrote each variant to
  // read like a real WhatsApp message: one or two flowing sentences,
  // gentle invitation, no "join us" sales-pitch register.
  const _buildSharePayload = (kind) => {
    const ch = parseInt(chapter, 10);
    const bookLbl = collection === 'tanya' ? 'תניא'
                  : collection === 'tikkunei_zohar' ? 'תיקוני הזוהר'
                  : 'תהילים';
    const seg = collection === 'tanya' ? 'tanya'
              : collection === 'tikkunei_zohar' ? 'tikkun-zohar'
              : 'tehillim';
    const circLbl = circle.name ? ` במעגל "${circle.name}"` : '';
    const myUid = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
    const fromSuffix = myUid > 0 ? `?from=${myUid}` : '';

    if (kind === 'direct') {
      // [damri 2026-05-20] Carry circle_id + chapter explicitly so recipient
      // lands on the EXACT chapter the sender just finished. Without these,
      // viewer falls through to their own next-unread (which is the wrong
      // chapter — different from what the sender's text actually mentioned).
      const _qs = [];
      if (myUid > 0) _qs.push(`from=${myUid}`);
      if (circle && circle.id) _qs.push(`c=${circle.id}`);
      if (Number.isFinite(ch) && ch > 0) _qs.push(`ch=${ch}`);
      const link = `${window.location.origin}/read/tanya${_qs.length ? '?' + _qs.join('&') : ''}`;
      // [yakir 2026-05-16] chapterShareLabel handles multi-part Tanya
      // (chinukh katan, shaar yichud, etc.) — "פרק ה׳ בשער היחוד והאמונה
      // (תניא)" rather than the old generic "פרק ה׳ בתניא".
      const text = `סיימתי עכשיו ${chapterShareLabel(circle, ch)}${circLbl}.\nאנחנו במעגל קהילתי, סוגרים את הספר פרק־פרק.\n\nאם בא לך, יש פרק שמחכה לך:`;
      const title = 'פוקחים את העיניים — פרק ביום';
      return { link, text, title };
    }
    if (kind === 'about') {
      const link = `${window.location.origin}/tanya`;
      const text = `משהו שחשבתי שיעניין אותך —\nאנחנו בונים את מטבע התניא הראשון בעולם. כל פרק שאדם קורא נחתם כמטבע על שמו.\n\nדף ההסבר כאן:`;
      const title = 'מטבע תניא הראשון בעולם';
      return { link, text, title };
    }
    // default: 'circle' — invite to THIS circle
    const link = `${window.location.origin}/${seg}/invite/${encodeURIComponent(circle.share_token || '')}${fromSuffix}`;
    // [yakir 2026-05-16] same multi-part fix as the 'direct' branch above.
    const text = `סיימתי עכשיו ${chapterShareLabel(circle, ch)}${circLbl}.\nאנחנו עושים את זה ביחד — כל אחד לוקח פרק, ולאט סוגרים את הספר. הזכות של הסיום נחתמת גם בכל מי שהשתתף.\n\nבא לך לקחת פרק?`;
    const title = circle.name ? `מעגל ${circle.name}` : `מעגל ${bookLbl}`;
    return { link, text, title };
  };

  // Show the kind picker → on selection, hand off to native/share-modal
  const [shareKindOpen, setShareKindOpen] = React.useState(false);
  const _dispatchShare = ({ link, text, title }) => {
    if (navigator.share) {
      // [yakir 2026-05-14] Fix duplicate-link bug: was passing link inside
      // `text` AND as `url` — iOS WhatsApp pasted both, resulting in the
      // link appearing twice. Now text is link-free, url carries the link.
      navigator.share({ title, text, url: link }).catch((e) => {
        if (e && e.name === 'AbortError') return;
        if (typeof window.__openShareModal === 'function') {
          window.__openShareModal({ link, title, text });
        }
      });
      return;
    }
    if (typeof window.__openShareModal === 'function') {
      window.__openShareModal({ link, title, text });
      return;
    }
    try {
      // For clipboard we DO append the link (no platform to combine for us).
      navigator.clipboard.writeText(`${text}\n${link}`);
      showToast('הקישור הועתק ✓');
    } catch (_) {
      prompt('העתק:', link);
    }
  };
  const _sharePostChapter = () => {
    // Open the kind-picker. Selection inside dispatches to the share flow.
    setShareKindOpen(true);
  };

  // [yakir pass 41] One-click thank to inviter.
  // Uses existing /api/thanks endpoint which creates a notification +
  // logs to thanks_log for the transparency feed.
  // Requires myUserId (the reader's id - exists after verification).
  const _sendThankToInviter = async () => {
    if (!inviterId || !myUserId || thankSent || thankBusy) return;
    setThankBusy(true);
    try {
      const ch = parseInt(chapter, 10);
      const bookLbl = collection === 'tanya' ? 'תניא'
                    : collection === 'tikkunei_zohar' ? 'תיקוני הזוהר'
                    : 'תהילים';
      const circLbl = circle.name ? ` במעגל "${circle.name}"` : '';
      const _label = chapterShareLabel(circle, ch);
      const msg = `תודה שזימנת אותי${circLbl}. סיימתי עכשיו ${_label} בזכותך.`;
      const ctx = `${_label}${circLbl}`;
      const r = await api('/api/thanks', { method: 'POST', body: {
        from_user_id: myUserId, to_user_id: inviterId, message: msg, context: ctx,
      }});
      if (r && !r.error) {
        setThankSent(true);
        showToast('התודה נשלחה ✓');
      } else {
        showToast(r && r.error ? r.error : 'שגיאה בשליחת התודה');
      }
    } catch (e) {
      showToast(e.message || 'שגיאה');
    } finally {
      setThankBusy(false);
    }
  };

  const submit = async () => {
    const usingTrustCode = showTrustCode && trustCode.trim().length >= 4;
    if (!usingTrustCode && (!transcript.trim() || transcript.length < 30)) {
      setErr('צריך תמלול (הקלט או הדבק טקסט באורך 30+) או קוד אמון תקין');
      return;
    }
    setBusy(true); setErr('');
    try {
      let user_id = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
      const noteForCreator = user_id ? name.trim() : '';  // when logged in, name field is repurposed as a note
      if (!user_id) {
        if (!name.trim()) { setErr('שם חובה'); setBusy(false); return; }
        const u = await api('/api/user', { method: 'POST', body: { name: name.trim() } });
        user_id = u.id;
      }
      const body = usingTrustCode
        ? { user_id, circle_id: circle.id, chapter: parseInt(chapter, 10), trust_code: trustCode.trim().toUpperCase() }
        : { user_id, circle_id: circle.id, chapter: parseInt(chapter, 10), transcript };
      const r = await api('/api/reading', { method: 'POST', body });
      if (r.error) throw new Error(r.error);
      // Fire-and-forget: notify circle creator if reader left a note
      if (noteForCreator && circle.creator_id && circle.creator_id !== user_id) {
        api('/api/notifications', { method: 'POST', body: {
          user_id: circle.creator_id,
          kind: 'circle_note',
          title: `הערה על מעגל ${circle.name || ''}`,
          body: noteForCreator.slice(0, 500),
          url: `/?circle_token=${encodeURIComponent(circle.share_token || '')}`,
        }}).catch(() => {});
      }
      setResult(r);
      // [yakir 2026-05-12 pass 9] Draft consumed — clear the autosave backup
      // so the next time someone opens this chapter+circle, they start fresh.
      try { localStorage.removeItem(`thl_draft_${circle.id}_${parseInt(chapter, 10)}`); } catch (_) {}
      // [yakir 2026-05-12 pass 10] NO auto-close.
      // Yakir: "after the chapter, the coin window pops up and disappears too
      // fast — I want to harness the user to our vision and collaboration."
      // The result panel below now offers vision + a 3-way choice instead of
      // a 3.5-second flash. The user chooses when to leave.
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-bg" onClick={(e) => {
      // [yakir 2026-05-12 pass 9] Backdrop tap NO LONGER closes if there's
      // unsaved content. Forces explicit X. Prevents transcript loss from
      // accidental edge taps on mobile.
      const hasContent = (transcript && transcript.trim().length > 5) ||
                         (showTrustCode && trustCode && trustCode.length > 0) ||
                         recording;
      if (hasContent) {
        e.stopPropagation();
        return; // silently ignore backdrop tap
      }
      onClose();
    }}>
      <div className="modal wide" onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={(e) => {
          // [yakir 2026-05-12 pass 9] X always closes, but warns when there's
          // unsaved content so the user can choose to discard intentionally.
          e.stopPropagation();
          const hasContent = (transcript && transcript.trim().length > 5) ||
                             (showTrustCode && trustCode && trustCode.length > 0) ||
                             recording;
          if (hasContent) {
            const proceed = window.confirm(
              'יש לך תמלול שעדיין לא אומת. אם תסגור, הוא נשמר במכשיר אבל לא נרשם במעגל. לסגור בכל זאת?'
            );
            if (!proceed) return;
          }
          // Stop recording cleanly if active so the SpeechRecognition releases the mic.
          if (recording && recognitionRef.current) {
            try { recognitionRef.current.stop(); } catch (_) {}
          }
          onClose();
        }}>×</span>
        <h3>קריאה במעגל: {circle.name}</h3>

        {/* [yakir 2026-05-18 pass2] 📗 תניא subtitle dropped — the part
            name already appears in chapterLabelInCircle below ("ליקוטי
            אמרים — פרק ל״ט"), so the standalone badge was duplication. */}
        {/* [yakir 2026-05-18] TanyaPulseStrip moved out of the reading
            modal — book-level stats live on the circle page, not here. */}

        {!result && (
          <>
            <div className="rm-chapter-row">
              <div className="rm-chapter-current">
                {(() => {
                  if (circle.text_type === 'tanya') {
                    const part = tanyaPartForChapter(circle, chapter);
                    if (part) {
                      if (part.part_id === 'shaar_yichud' && part.local_chapter === 1) return 'חינוך קטן';
                      if (part.part_id === 'shaar_yichud') return chapterLabel(part.local_chapter - 1);
                      return chapterLabel(part.local_chapter);
                    }
                  }
                  return chapterLabelInCircle(circle, chapter);
                })()}{readSet.has(chapter) ? ' ✓ (כבר נקרא)' : ''}
                {readingsLoading && (
                  <span aria-label="טוען קריאות"
                        style={{ marginInlineStart: 8, opacity: 0.7, fontSize: 12, fontWeight: 'normal' }}>
                    ⏳ טוען קריאות...
                  </span>
                )}
              </div>
              {psalm && psalm.tier && (() => {
                const tierColor = psalm.tier === 'short' ? '#5fa372'
                                : psalm.tier === 'medium' ? '#d4af37'
                                : '#9b59b6';
                return (
                  <span style={{
                    background: tierColor + '22',
                    color: tierColor,
                    border: `1px solid ${tierColor}66`,
                    padding: '3px 10px',
                    borderRadius: 999,
                    fontWeight: 700,
                    fontSize: 12,
                    whiteSpace: 'nowrap',
                  }}>
                    🪙 {psalm.tier_label} · {psalm.tier_reward} {psalm.tier_symbol || 'מטבעות'}
                  </span>
                );
              })()}
              <button type="button" className="btn ghost rm-chapter-toggle"
                      onClick={() => setShowPicker(s => !s)}>
                {showPicker ? 'סגור בחירה' : 'החלף פרק'}
              </button>
            </div>
            {showPicker && (() => {
              // Per-cell renderer — used by both the flat grid (non-Tanya) and
              // the sectioned grid (Tanya). `n` is the circle-chapter number.
              const _renderCh = (n) => {
                const isMineRead = readSet.has(n);
                const isOthersRead = othersReadSet.has(n) && !isMineRead;
                const isCur = n === chapter;
                const cls = `rm-ch-cell${isMineRead ? ' read' : ''}${isCur ? ' cur' : ''}`;
                const inlineStyle = isMineRead
                  ? { opacity: 0.55, cursor: 'not-allowed' }
                  : (isOthersRead && !isCur
                      ? { background: 'rgba(154,160,184,0.10)', borderColor: 'rgba(154,160,184,0.30)', color: '#9aa0b8' }
                      : undefined);
                const _gridLbl = chapterLabelInCircle(circle, n);
                const title = isMineRead ? `${_gridLbl} — כבר השלמת אותו במעגל זה`
                            : isOthersRead ? `${_gridLbl} — נקרא ע״י משתתף אחר במעגל. אתה עדיין יכול לקרוא ולקבל מטבעות.`
                            : _gridLbl;
                // For the cell *content* in a Tanya sectioned grid we show
                // the local chapter number (1..N within the part) instead of
                // the running circle index. This matches what the user reads
                // in the printed book — "שער היחוד פרק ה׳" — and what the
                // header subtitle says. Detection: if part exists *and* the
                // circle is sectioned, use the local number; otherwise the
                // running circle index.
                const _part = tanyaPartForChapter(circle, n);
                // Chinukh Katan special-case for the grid cell label:
                // shaar_yichud ch 1 → "חק" (the intro), 2..13 → "1".."12".
                let cellLabel;
                if (!_part) cellLabel = n;
                else if (_part.part_id === 'shaar_yichud' && _part.local_chapter === 1) cellLabel = 'חק';
                else if (_part.part_id === 'shaar_yichud') cellLabel = _part.local_chapter - 1;
                else cellLabel = _part.local_chapter;
                // [yakir 2026-05-18] cell with tier-coin badge + first-reader initial
                const _meta = chapterMeta[n];
                const _tierReward = _meta && _meta.tier_reward;
                const _firstReader = (_meta && _meta.readers && _meta.readers[0]) || null;
                const _palette = ['#d4a017','#a8800a','#b07d3a','#7c5e2c','#9a6b1a','#c08a25','#88611a','#b89033'];
                const _avColor = _firstReader ? _palette[(_firstReader.id || 0) % _palette.length] : null;
                const _avInit = _firstReader ? ((_firstReader.name || '?').trim()[0] || '?') : '';
                return (
                  <button key={n} type="button" className={cls} style={{ position: 'relative', ...(inlineStyle || {}) }}
                          disabled={isMineRead}
                          aria-disabled={isMineRead}
                          title={title + (_firstReader ? ` — קרא: ${_firstReader.name}` : '')}
                          onClick={() => {
                            if (isMineRead) return;
                            userChoseRef.current = true;
                            setChapter(n);
                            setShowPicker(false);
                          }}>
                    <div className="rm-ch-num">{cellLabel}{isMineRead ? ' ✓' : isOthersRead ? ' 👥' : ''}</div>
                    {_tierReward != null && (
                      <span className="rm-ch-tier" title={`${_meta.tier_label} · ${_tierReward} מטבעות`}>{_tierReward}🪙</span>
                    )}
                    {_firstReader && (
                      <span className="rm-ch-avatar" style={{ background: _avColor }} title={_firstReader.name}>{_avInit}</span>
                    )}
                  </button>
                );
              };

              // Resolve circle parts list for sectioning. Same defaulting
              // rule as tanyaPartForChapter: text_type='tanya' with no
              // text_parts → ['likutei']. So even legacy single-part Tanya
              // circles render with the section header — "📘 לקוטי אמרים"
              // on top of 53 cells — and feel like they live inside the
              // bigger book.
              let _circleParts = null;
              if (circle.text_type === 'tanya') {
                let raw = circle.text_parts;
                if (typeof raw === 'string') { try { raw = JSON.parse(raw); } catch { raw = null; } }
                _circleParts = (Array.isArray(raw) && raw.length) ? raw : ['likutei'];
              }

              return (
                <>
                  {/* [yakir 2026-05-18] counts row → expandable legend.
                      Counts stay visible always (in <summary>). Tap to
                      reveal the full color/badge key. */}
                  <details className="rm-picker-legend">
                    <summary>
                      <span><span style={{ color: '#2e5a3d', fontWeight: 800 }}>✓</span> אתה קראת ({readSet.size})</span>
                      <span><span style={{ color: '#5a5d7a', fontWeight: 800 }}>👥</span> אחרים קראו ({othersReadSet.size})</span>
                      <span style={{ fontWeight: 600 }}>סה״כ: {new Set([...readSet, ...othersReadSet]).size}/{totalChapters}</span>
                      <span className="rm-legend-toggle">ℹ️ מקרא</span>
                    </summary>
                    <div className="rm-legend-body">
                      <div className="rm-legend-row">
                        <span className="rm-legend-swatch swatch-current"></span>
                        <span><strong>שחור עם מסגרת זהב</strong> — הפרק שפתוח לפניך עכשיו</span>
                      </div>
                      <div className="rm-legend-row">
                        <span className="rm-legend-swatch swatch-mine"></span>
                        <span><strong>זהב עם ✓</strong> — פרקים שכבר השלמת ואומתו</span>
                      </div>
                      {/* [yakir 2026-05-20] removed "gray with 👥" legend row — confusing per yakir's note */}
                      <div className="rm-legend-row">
                        <span className="rm-legend-swatch swatch-empty"></span>
                        <span><strong>לבן</strong> — פרק פנוי, אף אחד עוד לא קרא</span>
                      </div>
                      <div className="rm-legend-row">
                        <span className="rm-legend-demo rm-legend-demo-coin">5🪙</span>
                        <span><strong>מספר עם מטבע</strong> — כמה מטבעות הפרק שווה (קצר 1, בינוני 3, ארוך 5)</span>
                      </div>
                      <div className="rm-legend-row">
                        <span className="rm-legend-demo rm-legend-demo-avatar" style={{ background: '#d4a017' }}>ש</span>
                        <span><strong>אות בעיגול</strong> — האות הראשונה של מי שקרא ראשון את הפרק</span>
                      </div>
                    </div>
                  </details>

                  {_circleParts ? (
                    // Sectioned grid — one section per Tanya part the
                    // circle includes, with a sticky header showing icon,
                    // name and chapter range. Crossing parts feels like
                    // turning a שער in the book.
                    <div className="rm-multipart-wrap" role="grid" aria-label="בחירת פרק">
                      {(() => {
                        let offset = 0;
                        return _circleParts.map((partId) => {
                          const meta = TANYA_PART_BY_ID_CLIENT[partId];
                          if (!meta) return null;
                          const start = offset + 1;
                          const end = offset + meta.chapters;
                          const range = Array.from({ length: meta.chapters }, (_, i) => start + i);
                          offset += meta.chapters;
                          // Per-part progress for the sticky header so the
                          // user can see at a glance how this part stands.
                          const partDone = range.filter(n => readSet.has(n) || othersReadSet.has(n)).length;
                          return (
                            <section key={partId} className="rm-part-section">
                              <div className="rm-part-header">
                                <span className="rm-part-icon" aria-hidden="true">{meta.icon}</span>
                                <span className="rm-part-name">{meta.name}</span>
                                <span className="rm-part-count">
                                  {partDone}/{meta.chapters} פרקים
                                </span>
                              </div>
                              <div className="rm-chapter-grid">
                                {range.map(_renderCh)}
                              </div>
                            </section>
                          );
                        });
                      })()}
                    </div>
                  ) : (
                    // Flat grid for non-Tanya circles (Tehillim/Rambam/Zohar)
                    // — unchanged behaviour.
                    <div className="rm-chapter-grid" role="grid" aria-label="בחירת פרק">
                      {Array.from({ length: totalChapters }, (_, i) => i + 1).map(_renderCh)}
                    </div>
                  )}
                </>
              );
            })()}

            {psalm && readSet.has(chapter) && (
              <div style={{
                marginTop: 16, padding: '18px 14px',
                background: 'linear-gradient(135deg, rgba(95,163,114,0.18), rgba(95,163,114,0.08))',
                border: '1px solid rgba(95,163,114,0.45)',
                borderRadius: 14, textAlign: 'center',
              }}>
                <div style={{ fontSize: 38, lineHeight: 1, marginBottom: 6 }}>✓</div>
                <div style={{ color: '#2e5a3d', fontSize: 19, fontWeight: 800, marginBottom: 6 }}>
                  השלמת את {chapterLabelInCircle(circle, chapter)}
                </div>
                <button
                  type="button"
                  className="btn"
                  onClick={() => {
                    userChoseRef.current = true;
                    setChapter(firstFreeFor(readSet));
                  }}
                  style={{ background: '#5fa372', color: '#fff', border: 'none', padding: '10px 18px', borderRadius: 10, fontWeight: 700, cursor: 'pointer', marginTop: 10 }}
                >
                  החלף פרק
                </button>
              </div>
            )}
            {psalm && !readSet.has(chapter) && (
              <>
                {/* [damri 2026-05-18] iOS mic UI removed — phone can't both
                    record (Voice Memos pops out fullscreen) AND show the
                    chapter text. iOS readers use trust-code OR tap-on-words
                    instead, both below. Non-iOS keeps Web Speech + native
                    recorder fallback. */}
                {!isIOSDevice ? (
                <>
                <div className="mic-row mic-row-top">
                  <button
                    className={`mic-btn ${(isIOSDevice ? uploading : recording) ? 'recording' : ''}`}
                    onClick={toggleRec}
                    type="button"
                    disabled={isIOSDevice && uploading}
                  >
                    {(isIOSDevice ? uploading : recording) ? '⏹' : '🎙️'}
                  </button>
                  <div className="mic-status">{recStatus || (isIOSDevice
                    ? 'לחץ על המיקרופון כדי להקליט את הקריאה'
                    : 'לחץ על המיקרופון כדי להתחיל לקרוא ולאמת את הפרק')}</div>
                </div>

                {/* Yakir 2026-05-14: unified recorder.
                    File input is always mounted (iOS primary mic button uses it
                    via toggleRec). The visible "אם המיקרופון לא תופס" fallback
                    button is shown only on non-iOS, where the primary path is
                    Web Speech and this is a recovery option. */}
                <input
                  ref={fileInputRef}
                  type="file"
                  accept="audio/*"
                  capture="microphone"
                  style={{ display: 'none' }}
                  onChange={handleAudioFile}
                />

                {/* [yakir 2026-05-18 pass3] tips + Spotify link, collapsed by default. */}
                <details style={{
                  marginTop: 8,
                  background: 'rgba(95, 163, 114, 0.07)',
                  border: '1px solid rgba(95, 163, 114, 0.25)',
                  borderRadius: 10,
                  direction: 'rtl',
                }}>
                  <summary style={{
                    padding: '8px 14px',
                    cursor: 'pointer',
                    fontSize: 13, fontWeight: 700, color: '#2e5a3d',
                    listStyle: 'none',
                    userSelect: 'none',
                  }}>
                    💡 לקריאה שתתפוס טוב — טיפים ושמיעה
                  </summary>
                  <div style={{
                    padding: '4px 14px 12px',
                    fontSize: 12.5, color: '#2e5a3d', lineHeight: 1.75,
                    textAlign: 'right',
                  }}>
                    <div>• דבר/י ברור ובקצב נינוח, לא מהר מדי</div>
                    <div>• החזק/י את הטלפון 20–30 ס״מ מהפה</div>
                    <div>• מצא/י מקום שקט, בלי רעשי רקע</div>
                    {circle.text_type === 'tanya' && (
                      <div style={{ marginTop: 8, paddingTop: 8, borderTop: '1px dashed rgba(95,163,114,0.30)' }}>
                        🎵 רוצה לשמוע את הפרק לפני שתקרא?{' '}
                        <a
                          href="https://open.spotify.com/playlist/161DjLJvD4sohchw6PLosx?si=mey4hIcXQ7eJ_2IIqjN1Wg&pi=7E72crDGQGu9w"
                          target="_blank"
                          rel="noopener noreferrer"
                          style={{ color: '#1d4029', fontWeight: 700, textDecoration: 'underline' }}
                        >
                          פתח/י את הפלייליסט ב-Spotify
                        </a>
                      </div>
                    )}
                  </div>
                </details>
                <div style={{ marginTop: 6, fontSize: 12.5, color: '#5a3d0a', textAlign: 'center' }}>
                  המיקרופון לא תופס?{' '}
                  <a
                    href={`https://wa.me/972502570997?text=${encodeURIComponent(
                      `היי יקיר, אני בקריאה במעגל "${circle.name}" — ${chapterLabelInCircle(circle, chapter)}. המיקרופון לא תופס לי. אפשר קוד אימות?`
                    )}`}
                    target="_blank"
                    rel="noopener noreferrer"
                    style={{ color: '#1d4029', fontWeight: 700, textDecoration: 'underline' }}
                  >
                    בקש/י קוד אימות מיקיר ב-WhatsApp
                  </a>
                </div>
                </>
                ) : (
                  <>
                  {/* [2026-05-18] iOS in-page MediaRecorder mic.
                      Stays in the chapter view — no Voice Memos popup. */}
                  <div className="mic-row mic-row-top">
                    <button
                      className={`mic-btn ${recording ? 'recording' : ''}`}
                      onClick={toggleRec}
                      type="button"
                      disabled={uploading}
                    >
                      {uploading ? '⏳' : (recording ? '⏹' : '🎙️')}
                    </button>
                    {/* [yakir 2026-05-19] Show live waveform while recording,
                        text status otherwise. */}
                    {recording ? (
                      <div className="mic-waveform">
                        <canvas ref={iosCanvasRef} />
                      </div>
                    ) : (
                      <div className="mic-status">{recStatus || (uploading
                        ? 'מעלה...'
                        : 'לחץ על המיקרופון כדי להקליט את הקריאה')}</div>
                    )}
                  </div>
                  <div style={{ marginTop: 10, fontSize: 12.5, color: '#5a3d0a', textAlign: 'center', lineHeight: 1.6 }}>
                    קראת בספר אמיתי?{' '}
                    <a
                      href={`https://wa.me/972502570997?text=${encodeURIComponent(
                        `היי יקיר, אני בקריאה במעגל "${circle.name}" — ${chapterLabelInCircle(circle, chapter)}. אפשר קוד אימות?`
                      )}`}
                      target="_blank"
                      rel="noopener noreferrer"
                      style={{ color: '#1d4029', fontWeight: 700, textDecoration: 'underline' }}
                    >
                      בקש/י קוד אימות מיקיר
                    </a>
                  </div>
                  </>
                )}

                {/* [yakir 2026-05-18 pass2] tier-badge moved up into rm-chapter-row. */}

                {/* [yakir pass 46] Reader controls — redesigned for clarity.
                    Two ROWS now:
                      1. AUDIO button (full-width, emerald) - 'השמע'
                      2. SIZE control (gold pill) with explicit −/+ and the
                         current size word in middle
                    Inline style application on text (not CSS class) to
                    guarantee the change shows regardless of browser
                    inheritance behavior. */}
                {psalm.hebrew && (
                  <div className="reader-controls">
                    {ttsAvailable && (
                      <button
                        type="button"
                        className={`reader-play-row ${speaking ? 'speaking' : ''}`}
                        onClick={playChapterServer}
                        aria-label={speaking ? 'עצור הקראה' : 'השמע'}
                      >
                        <span className="reader-play-icon">{speaking ? '⏸' : '▶'}</span>
                        <span className="reader-play-label">{speaking ? 'עצור הקראה' : 'השמע'}</span>
                      </button>
                    )}
                    <div className="reader-size-row">
                      <span className="reader-size-title">גודל הטקסט</span>
                      <div className="reader-size-pill">
                        <button
                          type="button"
                          className="reader-size-step"
                          onClick={() => {
                            const order = ['sm','md','lg'];
                            const i = order.indexOf(readerSize);
                            if (i > 0) setReaderSize(order[i - 1]);
                          }}
                          disabled={readerSize === 'sm'}
                          aria-label="הקטן את הטקסט"
                          title="הקטן"
                        >−</button>
                        <span className="reader-size-state">
                          {readerSize === 'sm' ? 'קטן' : readerSize === 'lg' ? 'גדול' : 'רגיל'}
                        </span>
                        <button
                          type="button"
                          className="reader-size-step"
                          onClick={() => {
                            const order = ['sm','md','lg'];
                            const i = order.indexOf(readerSize);
                            if (i < order.length - 1) setReaderSize(order[i + 1]);
                          }}
                          disabled={readerSize === 'lg'}
                          aria-label="הגדל את הטקסט"
                          title="הגדל"
                        >+</button>
                      </div>
                    </div>
                  </div>
                )}

                {psalm.hebrew && (
                  <div style={{ fontSize: 10, color: '#8a8a95', margin: '4px 0 4px', lineHeight: 1.5, fontWeight: 400 }}>
                    💡 קרי וכתיב: היכן שכתוב <span dir="rtl">יהוה</span> קרא <strong>הויה</strong>; היכן שכתוב <span dir="rtl">אלהים</span> קרא <strong>אלוקים</strong>.
                  </div>
                )}
                <div className="psalm-box psalm-box-large">
                  <div className="ref">{psalm.he_ref}</div>
                  {psalm.hebrew ? (
                    <HighlightedPsalm
                      text={psalm.hebrew}
                      headIndex={headIndex}
                      onWordClick={(idx) => {
                        setHeadIndex(idx + 1);
                        // Sync transcript cursor to end so matcher continues from here.
                        try {
                          const tw = (transcript || '').normalize('NFD')
                            .replace(/[\u0591-\u05C7]/g, '')
                            .replace(/[\u05BE\u05F3\u05F4\u200C\u200D]/g, '')
                            .replace(/[^\u0590-\u05FFA-Za-z\s]/g, ' ')
                            .trim().split(/\s+/).filter(Boolean);
                          trCursorRef.current = tw.length;
                        } catch (_) {}
                      }}
                      recording={recording}
                      fontSize={readerSize === 'sm' ? 15 : readerSize === 'lg' ? 23 : undefined}
                      lineHeight={readerSize === 'sm' ? 1.95 : readerSize === 'lg' ? 2.20 : undefined}
                    />
                  ) : (
                    <div style={{ color: '#5d5d6d', fontStyle: 'italic', fontWeight: 500 }}>טוען טקסט...</div>
                  )}
                </div>
              </>
            )}

            <textarea rows="5" className="transcript-large" value={transcript} onChange={e => setTranscript(e.target.value)}
                      placeholder="הטקסט שתקרא יופיע כאן אחרי שתלחץ על המיקרופון למעלה" />

            {!showTrustCode && (
              <div style={{ textAlign: 'center', marginTop: 10 }}>
                <button
                  type="button"
                  onClick={() => { setShowTrustCode(true); setErr(''); }}
                  style={{
                    background: 'none', border: 'none', color: '#8b6508',
                    fontSize: 13, cursor: 'pointer', textDecoration: 'underline',
                    fontFamily: 'inherit', padding: '4px 6px', fontWeight: 600,
                  }}
                >
                  ✋ יש לך קוד אימות? לחץ כאן!
                </button>
              </div>
            )}

            {/* Trust-code expanded form (toggle moved BELOW textarea per Yakir 2026-05-18) */}
            {showTrustCode && (
              <div ref={trustCodeFormRef} style={{ marginTop: 14, paddingTop: 12, borderTop: '1px dashed #d0c8b0', display: 'flex', flexDirection: 'column', gap: 8 }}>
                <label style={{ fontSize: 13, color: '#8b6508' }}>
                  🔑 קוד אמון אישי (ניתן ע״י דמרי לקוראים מספר אמיתי)
                </label>
                <input
                  type="text"
                  value={trustCode}
                  onChange={e => setTrustCode(e.target.value.toUpperCase())}
                  maxLength={12}
                  placeholder="למשל: 7K3M9PQ4"
                  style={{
                    padding: '11px 13px', fontSize: 16, borderRadius: 8,
                    border: '1.5px solid #d4af37', direction: 'ltr', textAlign: 'center',
                    letterSpacing: 2, fontWeight: 700, fontFamily: 'monospace',
                    outline: 'none', background: '#fff8e1', color: '#1a1a2e',
                  }}
                />
                <button
                  type="button"
                  onClick={() => { setShowTrustCode(false); setTrustCode(''); setErr(''); }}
                  style={{
                    background: 'none', border: 'none', color: '#5d5d6d',
                    fontSize: 13, cursor: 'pointer', fontFamily: 'inherit',
                    padding: 0, alignSelf: 'flex-start', fontWeight: 600,
                    textDecoration: 'underline',
                  }}
                >
                  ביטול — חזור לקריאה רגילה
                </button>
              </div>
            )}

            {(() => {
              const isLoggedIn = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10) > 0;
              return (
                <>
                  <label style={{ marginTop: 14 }}>{isLoggedIn ? 'הערות ליוצר המעגל (אופציונלי)' : 'שמך'}</label>
                  <input value={name} onChange={e => setName(e.target.value)}
                         placeholder={isLoggedIn ? 'מילה טובה, תפילה, או הערה ליוצר המעגל' : 'הראל'} />
                </>
              );
            })()}

            {err && <div style={{ color: '#ff6b9d', fontSize: 13, marginTop: 10 }}>{err}</div>}
            {/* Per Damri 2026-05-11 (lev-hadavar pass 41): submit must be
                easy to discover below the reading box. Full-width primary
                + small ghost cancel underneath. Verb changed from technical
                'שלח לאימות' to user-action 'אשר את הקריאה'. */}
            <div className="actions" style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch', gap: 10, marginTop: 18 }}>
              <button className="btn" onClick={submit} disabled={busy}
                      style={{ width: '100%', padding: '16px 24px', fontSize: 17, letterSpacing: 0.3 }}>
                {busy ? 'מאמת...' : '✓ אשר את הקריאה'}
              </button>
              <button className="btn ghost" onClick={onClose}
                      style={{ alignSelf: 'center', padding: '8px 20px', fontSize: 13 }}>
                ביטול
              </button>
            </div>
          </>
        )}

        {result && (
          <div style={{ textAlign: 'center', padding: '40px 16px', minHeight: 320 }}>
            {/* ───── PHASE 0+ : The quiet line. The whole gate.
                  No coin. No button. No icon. Just one sentence in serif.
                  This is the 'silent moment of integration' from gate-brief.md §1. */}
            {result.verified && (
              <div style={{
                fontFamily: "'Frank Ruhl Libre', serif",
                fontSize: 26, lineHeight: 1.8,
                color: '#5a3d0a', fontStyle: 'italic',
                fontWeight: 600,
                opacity: 1, transition: 'opacity 0.5s ease',
              }}>
                מה שקראת עכשיו עדיין בתוכך
              </div>
            )}

            {/* ───── PHASE 1+ : Second serif line — appears after ~3s.
                  Reflection of experience (gate-brief.md §2). */}
            {result.verified && (
              <div style={{
                fontFamily: "'Frank Ruhl Libre', serif",
                fontSize: 20, lineHeight: 1.8,
                color: '#3a2208', fontStyle: 'italic',
                fontWeight: 500,
                marginTop: 22,
                opacity: resultPhase >= 1 ? 1 : 0,
                transform: resultPhase >= 1 ? 'translateY(0)' : 'translateY(8px)',
                transition: 'opacity 0.6s ease, transform 0.6s ease',
                pointerEvents: resultPhase >= 1 ? 'auto' : 'none',
              }}>
                משהו בך כבר התחיל לעבוד
              </div>
            )}

            {/* [yakir pass 13] The soft pivot to options.
                After 'something has started to work in you' — the gentle
                question 'what would you like to open now?' arrives, and
                from it the doors emerge.
                This is the bridge from the silent moment (shaar.md) to
                the map of possibilities (yakir's brief, 13 May, evening). */}
            {result.verified && (
              <div style={{
                fontFamily: "'Frank Ruhl Libre', serif",
                fontSize: 17, lineHeight: 1.8,
                color: '#8a5f10', fontWeight: 600,
                marginTop: 18,
                opacity: resultPhase >= 2 ? 1 : 0,
                transition: 'opacity 0.6s ease',
              }}>
                מה היית רוצה שייפתח עכשיו?
              </div>
            )}

            {/* ───── PHASE 2+ : The continuation — appears after ~6s.
                  Quiet coin, soft progress, kavana field, ONE button.
                  No invite-friend, no done-for-now — those would be a
                  decision screen. Per gate-brief.md §3+§5, only ONE
                  natural movement that emerges from the experience. */}
            <div style={{
              opacity: resultPhase >= 2 ? 1 : 0,
              transform: resultPhase >= 2 ? 'translateY(0)' : 'translateY(12px)',
              transition: 'opacity 0.7s ease, transform 0.7s ease',
              pointerEvents: resultPhase >= 2 ? 'auto' : 'none',
              marginTop: 36,
            }}>
              {/* Error case: only path that needs visibility before phase 2 */}
              {!result.verified && (
                <div>
                  <div style={{ color: '#c92456', fontSize: 19, marginBottom: 8, fontWeight: 700 }}>
                    האימות לא הושלם
                  </div>
                  <div style={{ color: '#3a3a4a', fontSize: 14, marginBottom: 16, lineHeight: 1.6, fontWeight: 500 }}>
                    {result.verification?.reason || ''}
                  </div>
                </div>
              )}

              {/* [pass 22] Coin pill — richer, with real gold presence not ghostly tint. */}
              {result.verified && result.chapter_reward && result.chapter_reward.amount > 0 && (
                <div style={{
                  display: 'inline-flex', alignItems: 'center', gap: 10,
                  padding: '12px 22px', borderRadius: 100,
                  background: 'linear-gradient(135deg, #d4af37 0%, #b8941f 100%)',
                  border: '1.5px solid #8a5f10',
                  color: '#1a1a2e', fontSize: 16, fontWeight: 800,
                  marginBottom: 16,
                  boxShadow: '0 4px 18px rgba(138, 95, 16, 0.35)',
                  letterSpacing: 0.3,
                }}>
                  🪙 {result.chapter_reward.amount} {result.chapter_reward.symbol}
                  {result.chapter_reward.founder_awarded && (
                    <span style={{ color: '#3a2208', fontSize: 12, fontStyle: 'italic', fontWeight: 600 }}>
                      · גם למייסד
                    </span>
                  )}
                </div>
              )}

              {result.verified && (
                <ErrorBoundary label="FounderJourney" silent={true}>
                  <FounderJourney userId={myUserId} firstChapter={result.first_chapter} />
                </ErrorBoundary>
              )}
              {/* [yakir pass 42] Mosaic of the whole circle: every chapter as a cell.
                  The just-completed chapter blooms in gold; done ones glow; empty
                  ones sit quietly. Replaces the bare 'X מתוך Y' text with a vivid
                  sense of being part of a larger work. */}
              {/* [damri 2026-05-20] Book completion celebration. Triggers
                  on the last verified chapter of the book. */}
              {result.verified && result.circle_progress
                && totalChapters > 0
                && parseInt(result.circle_progress, 10) >= parseInt(totalChapters, 10) && (
                <ErrorBoundary label="BookCompletionCelebration" silent={true}><BookCompletionCelebration
                  bookLabel={(circle.text_type === 'tanya' ? 'התניא'
                           : circle.text_type === 'tikkunei_zohar' ? 'תיקוני הזוהר'
                           : circle.text_type === 'rambam' ? 'הרמב"ם'
                           : 'התהילים')}
                  circleName={circle.name}
                  onEnterEcosystem={() => {
                    window.dispatchEvent(new CustomEvent('app:navigate', { detail: { page: 'ecosystem' } }));
                  }}
                  onClose={() => { /* no-op; user closes by navigating */ }}
                /></ErrorBoundary>
              )}

              {result.verified && result.chapter && (
                <ErrorBoundary label="CircleMosaic" silent={true}>
                  <CircleMosaic
                    totalChapters={totalChapters}
                    doneChapters={(() => {
                      const s = new Set();
                      (circle.readings || []).forEach(r => {
                        if (r.verified === 1) s.add(parseInt(r.chapter, 10));
                      });
                      readSet.forEach(c => s.add(c));
                      othersReadSet.forEach(c => s.add(c));
                      s.add(parseInt(result.chapter, 10));  // ensure just-completed is included
                      return s;
                    })()}
                    justCompletedChapter={result.chapter}
                  />
                  <div style={{
                    color: '#6d5d3d', fontSize: 13, marginBottom: 24, fontWeight: 600,
                    textAlign: 'center', lineHeight: 1.6, letterSpacing: 0.3,
                  }}>
                    {result.circle_progress} <span style={{ color: '#8a8da8' }}>/</span> {totalChapters} פרקים נפתחו במעגל
                  </div>
                </ErrorBoundary>
              )}

<ShareKindPicker />

                            {/* [yakir 2026-05-14] 'Save your coin' CTA - shown only when:
                  - chapter was successfully verified
                  - user is anonymous (no Google sign-in yet)
                  Server upgrades the anon user in place when they sign in with
                  Google + anon_user_id, so coins + history stay. */}
              {result.verified && isAnonUser && (
                <div style={{
                  marginBottom: 22, padding: '14px 16px',
                  background: 'linear-gradient(135deg, rgba(212,175,55,0.13) 0%, rgba(184,148,31,0.08) 100%)',
                  border: '1.5px dashed rgba(212,175,55,0.55)',
                  borderRadius: 12,
                  textAlign: 'right', direction: 'rtl',
                }}>
                  <div style={{
                    fontFamily: "'Frank Ruhl Libre', serif",
                    fontSize: 15, fontWeight: 700, color: '#1a1a2e',
                    marginBottom: 6, lineHeight: 1.5,
                  }}>
                    🪙 רוצה לשמור את המטבע על שמך?
                  </div>
                  <div style={{
                    fontSize: 13, color: '#5a3d0a',
                    lineHeight: 1.6, fontStyle: 'italic',
                    marginBottom: 10,
                  }}>
                    כל מה שצברת כאן יישמר וייקבע על שמך. בלי המטבע יישאר על המכשיר הזה בלבד.
                  </div>
                  <button
                    type="button"
                    onClick={() => {
                      if (typeof window !== 'undefined' && window.__openSignIn) {
                        window.__openSignIn();
                      }
                    }}
                    style={{
                      width: '100%',
                      padding: '12px 18px',
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      borderRadius: 10,
                      fontSize: 14, fontWeight: 800,
                      cursor: 'pointer',
                      fontFamily: 'inherit',
                      boxShadow: '0 3px 10px rgba(138,95,16,0.22), inset 0 1px 0 rgba(255,255,255,0.30)',
                    }}
                  >
                    💾 חיבור עם גוגל — שמירה אישית
                  </button>
                </div>
              )}

              {/* Circle complete celebration — only when truly complete */}
              {result.circle_complete && (
                <div style={{
                  marginBottom: 22, padding: 16,
                  background: 'linear-gradient(135deg, rgba(212,175,55,0.25), rgba(184,148,31,0.18))',
                  border: '1.5px solid #8a5f10',
                  borderRadius: 10,
                  color: '#1a1a2e', fontSize: 15, fontWeight: 700,
                  fontFamily: "'Frank Ruhl Libre', serif",
                }}>
                  🎉 המעגל הושלם! {result.participants_rewarded} משתתפים קיבלו {result.tokens_per_participant} {result.minted_symbol || 'THL'} כל אחד.
                </div>
              )}

              {/* [yakir pass 48] Kavana — collapsed by default. Most readers
                  don't write; offering it as a small inline link reduces
                  cognitive load and visual clutter. Click to expand. */}
              {result.verified && !kavanaOpen && (
                <div style={{ marginBottom: 22, textAlign: 'center' }}>
                  <button
                    type="button"
                    onClick={() => setKavanaOpen(true)}
                    style={{
                      background: 'transparent',
                      border: '1.5px dashed rgba(212,175,55,0.55)',
                      borderRadius: 100,
                      padding: '10px 22px',
                      color: '#5a3d0a',
                      fontFamily: "'Frank Ruhl Libre', serif",
                      fontSize: 14, fontWeight: 600,
                      cursor: 'pointer',
                      fontStyle: 'italic',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    ✍️ לכתוב מילה אישית על הרגע הזה
                  </button>
                </div>
              )}
              {result.verified && kavanaOpen && (
                <div style={{
                  marginTop: 4, marginBottom: 28,
                  textAlign: 'right',
                  fontFamily: "'Frank Ruhl Libre', serif",
                }}>
                  <div style={{
                    fontSize: 16, lineHeight: 1.7, color: '#3a2208',
                    fontStyle: 'italic', marginBottom: 12, fontWeight: 500,
                    display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                  }}>
                    <span>אם משהו נגע בך — תכתוב כאן.</span>
                    <button
                      type="button"
                      onClick={() => { setKavanaText(''); setKavanaOpen(false); }}
                      style={{
                        background: 'transparent', border: 'none', color: '#8a5f10',
                        fontSize: 14, cursor: 'pointer', fontFamily: 'inherit',
                        padding: 4, fontStyle: 'normal', fontWeight: 700,
                      }}
                      aria-label="סגור"
                    >✕</button>
                  </div>
                  <textarea
                    rows={2}
                    value={kavanaText}
                    onChange={e => { setKavanaText(e.target.value); if (kavanaSaved) setKavanaSaved(false); }}
                    placeholder=""
                    style={{
                      width: '100%', padding: '14px 16px',
                      background: '#fffbeb',
                      border: '2px solid rgba(243,198,74,0.55)',
                      borderRadius: 12, color: '#1a1a2e',
                      fontFamily: "'Frank Ruhl Libre', serif",
                      fontSize: 17, lineHeight: 1.75, fontWeight: 600,
                      resize: 'vertical', outline: 'none',
                      direction: 'rtl', textAlign: 'right',
                      boxSizing: 'border-box',
                      boxShadow: 'inset 0 2px 6px rgba(0,0,0,0.08)',
                      caretColor: '#1a1a2e',
                    }}
                  />
                  {kavanaText.trim() && (
                    <div style={{ fontSize: 12, color: '#6d5d3d', marginTop: 6, fontStyle: 'italic', fontWeight: 600 }}>
                      {kavanaSaved ? '✓ נשמר.' : 'נשמור כשתמשיך.'}
                    </div>
                  )}
                </div>
              )}

              {/* [yakir pass 41] THANK INVITER strip — closes the share loop.
                  Only shown when the reader arrived via ?from=<X> AND the
                  inviter's user lookup succeeded AND the inviter != reader.
                  Quiet emerald gradient (not gold) so it doesn't compete
                  with the share/action doors below. One-click thank. */}
              {result.verified && inviterId && inviterName && (
                <div style={{
                  marginBottom: 18, padding: '14px 16px',
                  background: 'linear-gradient(135deg, rgba(95,163,114,0.18), rgba(46,90,61,0.10))',
                  border: '1.5px solid rgba(95,163,114,0.55)',
                  borderRadius: 12, display: 'flex',
                  alignItems: 'center', gap: 12, flexWrap: 'wrap',
                  direction: 'rtl', textAlign: 'right',
                }}>
                  <div style={{ flex: '1 1 180px', minWidth: 0 }}>
                    <div style={{
                      fontFamily: "'Frank Ruhl Libre', serif",
                      fontSize: 16, lineHeight: 1.55, color: '#1d4029',
                      fontWeight: 700,
                    }}>
                      ✦ {inviterName} פתח/ה לך את הדלת
                    </div>
                    <div style={{
                      fontSize: 12, color: '#3a5d44', marginTop: 3,
                      fontStyle: 'italic', fontWeight: 500,
                    }}>
                      {thankSent ? 'התודה נשלחה — היא תגיע ישירות אליו/ה' : 'רגע אחד של הכרה לפני שתמשיך'}
                    </div>
                  </div>
                  <button
                    type="button"
                    onClick={_sendThankToInviter}
                    disabled={thankSent || thankBusy}
                    style={{
                      background: thankSent
                        ? 'linear-gradient(135deg, #2e5a3d 0%, #1d4029 100%)'
                        : 'linear-gradient(135deg, #5fa372 0%, #3a8050 100%)',
                      color: '#fff',
                      border: '1.5px solid #1d4029',
                      padding: '11px 18px', borderRadius: 100,
                      fontSize: 14, fontWeight: 800, cursor: thankSent || thankBusy ? 'default' : 'pointer',
                      fontFamily: 'inherit', whiteSpace: 'nowrap',
                      boxShadow: '0 3px 10px rgba(46,90,61,0.30), inset 0 1px 0 rgba(255,255,255,0.30)',
                      opacity: thankBusy ? 0.7 : 1,
                      transition: 'all 0.2s ease',
                    }}
                  >
                    {thankSent ? '✓ תודה נשלחה' : (thankBusy ? '...' : '💛 שלח/י תודה')}
                  </button>
                </div>
              )}

              {/* [yakir pass 15] EXPANDED MAP — 6 equal-weight doors.
                  Yakir's clarified voice (13 May, night):
                    'הקול שלי זה לפתוח אפשרויות לפעולות — לשיתופי פעולה!'
                  Each door leads to a REAL destination today:
                    - kavana + close → kavanot table + onDone
                    - next chapter → /read/tanya?next= re-trigger
                    - talk to Yakir → wa.me/972502570997 (3 conv types)
                    - talk to Damri → wa.me/972522221260 (life projects)
                    - offer to community → wa.me/972522221260 with offer prompt
                    - vision of beit david → /about page
                  No 'coming soon'. No empty rooms. All same visual weight. */}
              {result.verified && (
                <div className="gate-doors" style={{
                  display: 'flex', flexDirection: 'column', gap: 10,
                  marginTop: 8,
                }}>
                  {/* [yakir pass 40] PRIMARY DOOR — invite another to the merit of the circle.
                      Per Yakir: 'הכפתור הכי חשוב שאמור לפתוח כמה אופציות לשיתוף ורתימה
                      שנפרוש לו את כל האפשרויות שליחה שלנו ונהיה עבורו מעטפת בעניין הזה'.
                      Visually elevated (deeper gold + thicker border + larger padding +
                      bigger font + a subtitle line) so it reads as 'the natural next move'. */}
                  <button
                    type="button"
                    onClick={async () => { await _saveKavanaIfAny(); _sharePostChapter(); }}
                    style={{
                      background: 'linear-gradient(135deg, #f7d949 0%, #d4af37 50%, #a07a14 100%)',
                      color: '#1a1a2e',
                      border: '2.5px solid #5a3d0a',
                      padding: '20px 22px', borderRadius: 14,
                      fontSize: 17, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right', direction: 'rtl',
                      boxShadow: '0 6px 20px rgba(138, 95, 16, 0.40), inset 0 1px 0 rgba(255,255,255,0.45)',
                      transition: 'all 0.2s ease',
                      marginBottom: 6,
                      lineHeight: 1.45,
                    }}
                  >
                    <div>🤝 להוסיף יהודי למעגל</div>
                    <div style={{ fontSize: 12, fontWeight: 600, color: '#3a2208', marginTop: 4, fontStyle: 'italic' }}>
                      לזכות עוד אחד להיות חלק מהסיום
                    </div>
                  </button>

                  {/* [yakir pass 48] subtle separator from the primary share door */}
                  <div style={{ height: 4 }} />
                  {/* Door 2: Write intention and close. The act IS the writing. */}
                  <button
                    type="button"
                    onClick={async () => { await _saveKavanaIfAny(); onDone(); }}
                    style={{
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      padding: '17px 22px', borderRadius: 12,
                      fontSize: 16, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right',
                      direction: 'rtl',
                      boxShadow: '0 4px 14px rgba(138, 95, 16, 0.28), inset 0 1px 0 rgba(255,255,255,0.35)',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    ✍️ לשמור את הכוונה ולסיים כאן
                  </button>

                  {/* Door 2: Continue reading. */}
                  <button
                    type="button"
                    onClick={async () => {
                      await _saveKavanaIfAny();
                      try { window.location.href = '/read/tanya?next=' + Date.now(); } catch (_) { onDone(); }
                    }}
                    style={{
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      padding: '17px 22px', borderRadius: 12,
                      fontSize: 16, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right',
                      direction: 'rtl',
                      boxShadow: '0 4px 14px rgba(138, 95, 16, 0.28), inset 0 1px 0 rgba(255,255,255,0.35)',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    📖 להמשיך לפרק הבא
                  </button>

                  {/* [yakir pass 48] visual group separator → 'connect' section */}
                  <div style={{ height: 14, position: 'relative' }}>
                    <div style={{
                      position: 'absolute', left: '20%', right: '20%', top: '50%',
                      height: 1, background: 'linear-gradient(90deg, transparent, rgba(212,175,55,0.45), transparent)',
                    }} />
                  </div>
                  {/* Door 3: Talk to Yakir — 3 conversation types listed in
                      the prefilled WhatsApp message so the user self-selects
                      without needing a sub-menu. */}
                  <button
                    type="button"
                    onClick={async () => {
                      await _saveKavanaIfAny();
                      const txt = (kavanaText || '').trim();
                      const ch = parseInt(chapter, 10);
                      const greeting = `שלום יקיר, סיימתי עכשיו ${chapterShareLabel(circle, ch)} דרך בית דויד.`;
                      const kavanaLine = txt ? `\n\nמה שעלה לי: ${txt}` : '';
                      const offers = '\n\nהייתי שמח לתאם איתך אחד מאלה:\n• שיחת תניא או ליווי בלימוד\n• שיעור שחייה אישי (אתה זמין כמעט בכל הארץ)\n• שיחת פיצוח — לגלות מה אני באמת רוצה לממש\n\nאיזה מהם הכי מתאים?';
                      const waUrl = `https://wa.me/972502570997?text=${encodeURIComponent(greeting + kavanaLine + offers)}`;
                      window.open(waUrl, '_blank');
                    }}
                    style={{
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      padding: '17px 22px', borderRadius: 12,
                      fontSize: 16, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right',
                      direction: 'rtl',
                      boxShadow: '0 4px 14px rgba(138, 95, 16, 0.28), inset 0 1px 0 rgba(255,255,255,0.35)',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    🤝 לדבר עם יקיר — תניא / שחייה / פיצוח אישי
                  </button>

                  {/* Door 4: Talk to Damri — about a life project or platform support. */}
                  <button
                    type="button"
                    onClick={async () => {
                      await _saveKavanaIfAny();
                      const txt = (kavanaText || '').trim();
                      const ch = parseInt(chapter, 10);
                      const greeting = `שלום דמרי, סיימתי עכשיו ${chapterShareLabel(circle, ch)} דרך בית דויד.`;
                      const kavanaLine = txt ? `\n\nמה שעלה לי: ${txt}` : '';
                      const intro = '\n\nיש לי תחושה שיש פרויקט או חזון שאני רוצה לבנות, ושמעתי שאתה מסייע בהקמת פלטפורמות כאלה.\n\nאפשר לדבר?';
                      const waUrl = `https://wa.me/972522221260?text=${encodeURIComponent(greeting + kavanaLine + intro)}`;
                      window.open(waUrl, '_blank');
                    }}
                    style={{
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      padding: '17px 22px', borderRadius: 12,
                      fontSize: 16, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right',
                      direction: 'rtl',
                      boxShadow: '0 4px 14px rgba(138, 95, 16, 0.28), inset 0 1px 0 rgba(255,255,255,0.35)',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    🏛 לדבר עם דמרי — פרויקט חיים / פלטפורמה
                  </button>

                  {/* [yakir pass 48] visual group separator → 'contribute' section */}
                  <div style={{ height: 14, position: 'relative' }}>
                    <div style={{
                      position: 'absolute', left: '20%', right: '20%', top: '50%',
                      height: 1, background: 'linear-gradient(90deg, transparent, rgba(212,175,55,0.45), transparent)',
                    }} />
                  </div>
                  {/* Door 5: I want to offer something to the community.
                      Goes to Damri who curates the community side. */}
                  <button
                    type="button"
                    onClick={async () => {
                      await _saveKavanaIfAny();
                      const txt = (kavanaText || '').trim();
                      const ch = parseInt(chapter, 10);
                      const greeting = `שלום, סיימתי עכשיו ${chapterShareLabel(circle, ch)} דרך בית דויד.`;
                      const kavanaLine = txt ? `\n\nמה שעלה לי: ${txt}` : '';
                      const offer = '\n\nיש לי משהו שאני רוצה להציע לקהילה:\n[מתנה / שירות / נוכחות / רעיון / חלום]\n\nאפשר לדבר על איך אפשר לתת לזה מקום?';
                      const waUrl = `https://wa.me/972522221260?text=${encodeURIComponent(greeting + kavanaLine + offer)}`;
                      window.open(waUrl, '_blank');
                    }}
                    style={{
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      padding: '17px 22px', borderRadius: 12,
                      fontSize: 16, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right',
                      direction: 'rtl',
                      boxShadow: '0 4px 14px rgba(138, 95, 16, 0.28), inset 0 1px 0 rgba(255,255,255,0.35)',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    ✨ להציע משהו מעצמי לקהילה
                  </button>

                  {/* [yakir pass 49] Door 5b — public roadmap with voting */}
                  <button
                    type="button"
                    onClick={() => setUpcomingOpen(true)}
                    style={{
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      padding: '17px 22px', borderRadius: 12,
                      fontSize: 16, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right',
                      direction: 'rtl',
                      boxShadow: '0 4px 14px rgba(138, 95, 16, 0.28), inset 0 1px 0 rgba(255,255,255,0.35)',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    🔮 בקרוב — מה כדאי לנו לבנות?
                  </button>

                  {/* Door 6: Read the vision of beit david — soft entry to /about. */}
                  <a
                    href="/about"
                    target="_blank"
                    rel="noopener noreferrer"
                    onClick={() => { _saveKavanaIfAny(); }}
                    style={{
                      background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
                      color: '#1a1a2e',
                      border: '1.5px solid #8a5f10',
                      padding: '17px 22px', borderRadius: 12,
                      fontSize: 16, fontWeight: 800, cursor: 'pointer',
                      fontFamily: 'inherit', textAlign: 'right',
                      direction: 'rtl',
                      textDecoration: 'none',
                      display: 'block',
                      boxShadow: '0 4px 14px rgba(138, 95, 16, 0.28), inset 0 1px 0 rgba(255,255,255,0.35)',
                      transition: 'all 0.2s ease',
                    }}
                  >
                    📜 לקרוא את החזון של בית דויד
                  </a>

                  {/* Foot note: the door is open even when not entered. */}
                  <div style={{
                    fontSize: 13, color: '#6d5d3d',
                    marginTop: 22, fontStyle: 'italic', fontWeight: 500,
                    fontFamily: "'Frank Ruhl Libre', serif",
                    textAlign: 'center',
                    lineHeight: 1.7,
                  }}>
                    הדלת פתוחה גם כשלא נכנסים · אפשר פשוט לסגור ולחזור מחר
                  </div>
                </div>
              )}

        {/* [yakir pass 49] Roadmap modal - opens on top of the gate when door clicked */}
        {upcomingOpen && (
          <UpcomingModal
            userId={myUserId}
            onClose={() => setUpcomingOpen(false)}
            onSignInRequest={() => {
              setUpcomingOpen(false);
              if (typeof window !== 'undefined' && window.__openSignIn) window.__openSignIn();
            }}
          />
        )}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ───────── Onboarding ─────────
const VALUE_OPTIONS = ['חסד', 'אמונה', 'תורה', 'אחדות', 'צדקה', 'חינוך', 'שלום', 'רפואה'];
const SKILL_OPTIONS = ['שיעורי תורה', 'ייעוץ רוחני', 'מוזיקה', 'בישול', 'הסעות', 'עזרה טכנית', 'עיצוב', 'כתיבה', 'תרגום', 'ריפוי'];

function Chip({ label, selected, onClick }) {
  return <button type="button" className={`chip ${selected ? 'selected' : ''}`} onClick={onClick}>{label}</button>;
}

function OnboardingModal({ onClose, onDone, refCode, userId, googleSub, googleName }) {
  // Post-auth onboarding: user is already signed in via Google.
  // 3 steps: basic info → vision → first action. Autosaves to localStorage.
  const STORAGE_KEY = `tehillim_onb_draft_${userId || 'anon'}`;
  const EMPTY = {
    mission: '', values: [], skills: [], bio: '',
    location: '', contact_method: 'telegram', contact_value: ''
  };
  const [step, setStep] = useState(0);
  const [data, setData] = useState(() => {
    try {
      const raw = localStorage.getItem(STORAGE_KEY);
      if (raw) {
        const parsed = JSON.parse(raw);
        return { ...EMPTY, ...parsed };
      }
    } catch {}
    return EMPTY;
  });
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const update = (k, v) => setData(d => ({ ...d, [k]: v }));
  const toggle = (k, v) => setData(d => ({ ...d, [k]: d[k].includes(v) ? d[k].filter(x => x !== v) : [...d[k], v] }));

  // Persist draft on every change so refresh doesn't lose data.
  useEffect(() => {
    try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch {}
  }, [data, STORAGE_KEY]);

  const TOTAL_STEPS = 3;
  const next = () => { setErr(''); setStep(s => Math.min(TOTAL_STEPS - 1, s + 1)); };
  const back = () => setStep(s => Math.max(0, s - 1));

  const submit = async () => {
    setBusy(true); setErr('');
    try {
      if (!userId || !googleSub) throw new Error('חסר מזהה Google — נסה להתחבר שוב');
      const r = await api(`/api/profile/${userId}/onboard`, {
        method: 'PUT',
        body: { sub: googleSub, ...data, ref: refCode || undefined },
      });
      if (r.error) throw new Error(r.error);
      try { localStorage.removeItem(STORAGE_KEY); } catch {}
      if (r.referral_bonus) {
        // Per Damri 2026-05-11: minimum phrasing. The BEU bonus still
        // happens server-side; we just don't celebrate it loudly to the
        // new user. Focus on the PLACE that opened, not the bonus.
        if (r.referred_by_name) {
          const first = String(r.referred_by_name).trim().split(' ')[0];
          alert(`${first} פתח לך את הדלת לבית דויד.`);
        }
        localStorage.removeItem('tehillim_ref');
      }
      onDone(r);
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  const STEP_LABELS = ['פרטים בסיסיים', 'חזון וערכים', 'הצעד הראשון'];
  const progressPct = ((step + 1) / TOTAL_STEPS) * 100;

  return (
    <div className="modal-bg">
      <div className="modal" onClick={e => e.stopPropagation()} style={{ position: 'relative' }}>
        {/* [P0 fix 2026-05-13] X close so users aren't trapped — Yakir's friend was stuck 2 days */}
        {onClose && (
          <span
            className="close-x"
            onClick={(e) => { e.stopPropagation(); onClose(); }}
            style={{
              position: 'absolute', top: 8, left: 8,
              fontSize: 28, color: '#5d5868', cursor: 'pointer',
              lineHeight: 1, padding: '8px 14px', borderRadius: 8,
              transition: 'color .15s, background .15s',
              zIndex: 100, userSelect: 'none', fontWeight: 400,
            }}
            title="סגור (אפשר להשלים פרופיל אחר כך)"
          >×</span>
        )}
        <h3>ברוך הבא לבית דויד</h3>
        {refCode && (
          <RefInviteBanner refCode={refCode} variant="modal" />
        )}

        <div className="onb-progress" style={{ marginBottom: 6 }}>
          <div style={{ height: 6, background: 'rgba(255,255,255,0.08)', borderRadius: 3, overflow: 'hidden' }}>
            <div style={{ height: '100%', width: progressPct + '%', background: '#d4af37', transition: 'width 0.25s ease' }} />
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: '#8a8da8', marginTop: 6 }}>
            <span>שלב {step + 1} מתוך {TOTAL_STEPS}</span>
            <span style={{ color: '#d4af37' }}>{STEP_LABELS[step]}</span>
          </div>
        </div>

        {step === 0 && (
          <>
            {googleName && (
              <div style={{ fontSize: 13, color: '#8a8fa8', marginBottom: 10 }}>
                מחובר כ-<strong style={{ color: '#d4af37' }}>{googleName}</strong> ✓
              </div>
            )}
            <label>תיאור קצר עליך</label>
            <textarea rows="2" value={data.bio} onChange={e => update('bio', e.target.value)} placeholder="מלמד תורה מירושלים..." />
            <label style={{ marginTop: 12 }}>איפה אתה נמצא?</label>
            <input value={data.location} onChange={e => update('location', e.target.value)} placeholder="ירושלים / בני ברק / ניו יורק..." />
            <label style={{ marginTop: 12 }}>איך ליצור איתך קשר?</label>
            <select value={data.contact_method} onChange={e => update('contact_method', e.target.value)}>
              <option value="telegram">Telegram</option>
              <option value="whatsapp">WhatsApp</option>
              <option value="email">Email</option>
            </select>
            <input style={{ marginTop: 10 }} value={data.contact_value} onChange={e => update('contact_value', e.target.value)} placeholder="@username / מספר / אימייל" />
          </>
        )}

        {step === 1 && (
          <>
            <label>מהי השליחות שלך?</label>
            <textarea rows="3" value={data.mission} onChange={e => update('mission', e.target.value)} placeholder="לדוגמה: לחבר יהודים לתורה ולקהילה" />
            <label style={{ marginTop: 12 }}>אילו ערכים חשובים לך?</label>
            <div className="chip-row">
              {VALUE_OPTIONS.map(v => <Chip key={v} label={v} selected={data.values.includes(v)} onClick={() => toggle('values', v)} />)}
            </div>
          </>
        )}

        {step === 2 && (
          <>
            <label>אילו מתנות אתה מציע לקהילה?</label>
            <div className="chip-row">
              {SKILL_OPTIONS.map(s => <Chip key={s} label={s} selected={data.skills.includes(s)} onClick={() => toggle('skills', s)} />)}
            </div>
            <div style={{ marginTop: 14, padding: 12, background: 'rgba(212,175,55,0.08)', borderRadius: 8, fontSize: 13, color: '#c8c8d8' }}>
              ✨ הצעד הראשון שלך מתחיל עכשיו — לחץ "הצטרף" כדי להפעיל את החשבון, לקבל 500 BEU מתנה ולהתחיל לקרוא תהילים בקהילה.
            </div>
          </>
        )}

        {err && <div style={{ color: '#ff6b9d', fontSize: 13, marginTop: 10 }}>{err}</div>}

        <div className="actions">
          {step > 0 && <button className="btn ghost" onClick={back}>חזרה</button>}
          {step < TOTAL_STEPS - 1 && <button className="btn" onClick={next}>הבא ←</button>}
          {step === TOTAL_STEPS - 1 && <button className="btn" onClick={submit} disabled={busy}>{busy ? '...' : 'הצטרף'}</button>}
          {onClose && <button className="btn ghost" onClick={onClose}>דלג</button>}
        </div>
      </div>
    </div>
  );
}

// ───────── Self-service profile editor ─────────
// All 17 editable fields in one modal. Uses /api/user/me (sub-authenticated).
// Protected fields (email, wallet_address, google_sub, is_admin) are NOT sent.
function EditProfileModal({ googleSub, onClose, onSaved }) {
  const EMPTY = {
    name: '', mission: '', bio: '', location: '', tagline: '',
    values: [], skills: [],
    contact_method: '', contact_value: '',
    avatar_color: '#d4af37',
    website: '', youtube_url: '',
    external_links: [],
    service_title: '', service_body: '', cta_text: '', cta_url: '',
  };
  // Drafts are namespaced by Google sub so two users on the same browser don't
  // collide. The draft only contains form state — never tokens or secrets.
  const DRAFT_KEY = `tehillim_profile_draft_${googleSub || 'anon'}`;
  const [data, setData] = useState(EMPTY);
  const [loading, setLoading] = useState(true);
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [draftBanner, setDraftBanner] = useState(false);
  const [autosavedAt, setAutosavedAt] = useState(0);
  const [newValue, setNewValue] = useState('');
  const [newSkill, setNewSkill] = useState('');
  const [awardsToast, setAwardsToast] = useState(null);

  useEffect(() => {
    if (!googleSub) { setErr('חסר מזהה Google — התחבר מחדש'); setLoading(false); return; }
    api(`/api/user/me?sub=${encodeURIComponent(googleSub)}`)
      .then(r => {
        if (r.error) throw new Error(r.error);
        const fromServer = {
          name: r.user.name || '',
          mission: r.profile.mission || '',
          bio: r.profile.bio || '',
          location: r.profile.location || '',
          tagline: r.profile.tagline || '',
          values: Array.isArray(r.profile.values) ? r.profile.values : [],
          skills: Array.isArray(r.profile.skills) ? r.profile.skills : [],
          contact_method: r.profile.contact_method || '',
          contact_value: r.profile.contact_value || '',
          avatar_color: r.profile.avatar_color || '#d4af37',
          website: r.profile.website || '',
          youtube_url: r.profile.youtube_url || '',
          external_links: Array.isArray(r.profile.external_links) ? r.profile.external_links : [],
          service_title: r.profile.service_title || '',
          service_body: r.profile.service_body || '',
          cta_text: r.profile.cta_text || '',
          cta_url: r.profile.cta_url || '',
        };
        // Prefer an unsubmitted draft over server state if one exists. Drafts
        // get cleared on successful save, so a stale draft means the user
        // closed the modal mid-edit — the spec is to restore that.
        let restored = false;
        try {
          const raw = localStorage.getItem(DRAFT_KEY);
          if (raw) {
            const draft = JSON.parse(raw);
            if (draft && typeof draft === 'object') {
              setData({ ...fromServer, ...draft });
              restored = true;
            }
          }
        } catch {}
        if (!restored) setData(fromServer);
        setDraftBanner(restored);
      })
      .catch(e => setErr(e.message || 'שגיאה בטעינה'))
      .finally(() => setLoading(false));
  }, [googleSub, DRAFT_KEY]);

  // Autosave the in-progress form to localStorage every 5s while open.
  // Interval (not effect-on-data) so we don't thrash storage on every keystroke.
  useEffect(() => {
    if (loading) return;
    const id = setInterval(() => {
      try {
        localStorage.setItem(DRAFT_KEY, JSON.stringify(data));
        setAutosavedAt(Date.now());
      } catch {}
    }, 5000);
    return () => clearInterval(id);
  }, [data, loading, DRAFT_KEY]);

  const discardDraft = () => {
    try { localStorage.removeItem(DRAFT_KEY); } catch {}
    setDraftBanner(false);
    // Reload the modal to pull fresh server data.
    setLoading(true);
    api(`/api/user/me?sub=${encodeURIComponent(googleSub)}`)
      .then(r => {
        if (r.error) throw new Error(r.error);
        setData({
          name: r.user.name || '',
          mission: r.profile.mission || '',
          bio: r.profile.bio || '',
          location: r.profile.location || '',
          tagline: r.profile.tagline || '',
          values: Array.isArray(r.profile.values) ? r.profile.values : [],
          skills: Array.isArray(r.profile.skills) ? r.profile.skills : [],
          contact_method: r.profile.contact_method || '',
          contact_value: r.profile.contact_value || '',
          avatar_color: r.profile.avatar_color || '#d4af37',
          website: r.profile.website || '',
          youtube_url: r.profile.youtube_url || '',
          external_links: Array.isArray(r.profile.external_links) ? r.profile.external_links : [],
          service_title: r.profile.service_title || '',
          service_body: r.profile.service_body || '',
          cta_text: r.profile.cta_text || '',
          cta_url: r.profile.cta_url || '',
        });
      })
      .catch(e => setErr(e.message || 'שגיאה בטעינה'))
      .finally(() => setLoading(false));
  };

  const upd = (k, v) => setData(d => ({ ...d, [k]: v }));
  const toggleArr = (k, v) => setData(d => ({ ...d, [k]: d[k].includes(v) ? d[k].filter(x => x !== v) : [...d[k], v] }));
  const addToArr = (k, v, clear) => {
    const t = String(v || '').trim();
    if (!t) return;
    setData(d => d[k].includes(t) ? d : { ...d, [k]: [...d[k], t] });
    clear();
  };
  const addLink = () => setData(d => ({ ...d, external_links: [...d.external_links, { label: '', url: '' }] }));
  const updateLink = (idx, key, val) => setData(d => ({ ...d, external_links: d.external_links.map((e, i) => i === idx ? { ...e, [key]: val } : e) }));
  const removeLink = idx => setData(d => ({ ...d, external_links: d.external_links.filter((_, i) => i !== idx) }));

  const save = async () => {
    if (!data.name.trim()) { setErr('שם חובה'); return; }
    setBusy(true); setErr('');
    try {
      const r = await api('/api/user/me', { method: 'PUT', body: { sub: googleSub, ...data } });
      if (r.error) throw new Error(r.error);
      try { localStorage.setItem('tehillim_user_name', data.name); } catch {}
      try { localStorage.removeItem(DRAFT_KEY); } catch {}
      // If the server awarded BEU for newly-filled fields, celebrate before
      // closing — otherwise close immediately. Anchor: directive 9.5 reward
      // feedback loop.
      const awards = Array.isArray(r.awards) ? r.awards : [];
      if (awards.length > 0) {
        setAwardsToast(awards);
        setBusy(false);
        setTimeout(() => { onSaved && onSaved(awards); onClose(); }, 2200);
        return;
      }
      onSaved && onSaved([]);
      onClose();
    } catch (e) {
      setErr(e.message || 'שגיאה בשמירה');
    } finally { setBusy(false); }
  };

  const clearCard = async () => {
    if (!window.confirm('אתה בטוח? כל תוכן הכרטיס יימחק (השם נשאר).')) return;
    setBusy(true); setErr('');
    try {
      // send empty values for all profile fields; do NOT send name (keeps it)
      const r = await api('/api/user/me', {
        method: 'PUT',
        body: {
          sub: googleSub,
          mission: '', bio: '', location: '', tagline: '',
          values: [], skills: [],
          contact_method: '', contact_value: '',
          avatar_color: '',
          website: '', youtube_url: '',
          external_links: [],
          service_title: '', service_body: '', cta_text: '', cta_url: '',
        },
      });
      if (r.error) throw new Error(r.error);
      try { localStorage.removeItem(DRAFT_KEY); } catch {}
      onSaved && onSaved();
      onClose();
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally { setBusy(false); }
  };

  if (loading) {
    return <div className="modal-bg"><div className="modal"><p style={{ color: '#8a8da8' }}>טוען...</p></div></div>;
  }

  const COLOR_SWATCHES = ['#d4af37', '#ff6b9d', '#ffa502', '#2ecc71', '#3498db', '#9b59b6', '#e74c3c', '#1abc9c'];

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 640, maxHeight: '90vh', overflowY: 'auto', position: 'relative' }} onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={onClose} style={{ position: 'absolute', top: 14, left: 14, fontSize: 26, cursor: 'pointer', zIndex: 10, padding: '2px 10px' }}>×</span>
        <h3>✏️ עריכת הפרופיל שלי</h3>
        {draftBanner && (
          <div style={{ background: 'rgba(212,175,55,0.12)', border: '1px solid rgba(212,175,55,0.4)', padding: 10, borderRadius: 8, fontSize: 13, color: '#d4af37', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <span style={{ flex: 1 }}>📝 שחזרנו טיוטה לא שמורה מהפעם הקודמת.</span>
            <button className="btn ghost" style={{ padding: '4px 10px', fontSize: 12 }} onClick={discardDraft}>זרוק טיוטה</button>
          </div>
        )}
        {err && <div style={{ color: '#ff6b9d', fontSize: 13, marginBottom: 10, padding: 8, background: 'rgba(255,107,157,0.1)', borderRadius: 6 }}>{err}</div>}
        {awardsToast && (
          <div style={{ background: 'linear-gradient(135deg, rgba(212,175,55,0.25), rgba(46,204,113,0.18))', border: '1px solid rgba(212,175,55,0.5)', borderRadius: 10, padding: 12, marginBottom: 12, textAlign: 'center', animation: 'fadeIn 0.3s' }}>
            <div style={{ fontSize: 16, fontWeight: 700, color: '#d4af37', marginBottom: 6 }}>
              🪙 +{awardsToast.reduce((a, x) => a + (x.amount || 0), 0)} BEU
            </div>
            <div style={{ fontSize: 12, color: '#e8e6f0' }}>
              על שיתוף: {awardsToast.map(a => ({tagline:'שורת תקציר',bio:'ביוגרפיה',mission:'שליחות',location:'מיקום',skills:'כישורים',values:'ערכים',contact_value:'דרך קשר',avatar_url:'תמונה',service:'שירות',links:'קישורים'}[a.field] || a.field)).join(' · ')}
            </div>
          </div>
        )}

        <label>שם *</label>
        <input type="text" value={data.name} onChange={e => upd('name', e.target.value)} maxLength={100} />

        <label style={{ marginTop: 12 }}>שורת תקציר (tagline)</label>
        <input type="text" value={data.tagline} onChange={e => upd('tagline', e.target.value)} maxLength={200} placeholder="משפט קצר עליך" />

        <label style={{ marginTop: 12 }}>שליחות</label>
        <textarea rows="2" value={data.mission} onChange={e => upd('mission', e.target.value)} maxLength={1000} placeholder="מה המטרה שלך?" />

        <label style={{ marginTop: 12 }}>ביוגרפיה</label>
        <textarea rows="3" value={data.bio} onChange={e => upd('bio', e.target.value)} maxLength={2000} />

        <label style={{ marginTop: 12 }}>מיקום</label>
        <input type="text" value={data.location} onChange={e => upd('location', e.target.value)} maxLength={120} placeholder="עיר, מדינה" />

        <label style={{ marginTop: 12 }}>ערכים</label>
        <div className="chip-row">
          {[...new Set([...VALUE_OPTIONS, ...data.values])].map(v => (
            <Chip key={v} label={v} selected={data.values.includes(v)} onClick={() => toggleArr('values', v)} />
          ))}
        </div>
        <div style={{ display: 'flex', gap: 6, marginTop: 6 }}>
          <input type="text" value={newValue} onChange={e => setNewValue(e.target.value)} placeholder="הוסף ערך חדש..." style={{ flex: 1 }} maxLength={60}
            onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addToArr('values', newValue, () => setNewValue('')); } }} />
          <button type="button" className="btn ghost" onClick={() => addToArr('values', newValue, () => setNewValue(''))}>+</button>
        </div>

        <label style={{ marginTop: 12 }}>מתנות שאתה מציע</label>
        <div className="chip-row">
          {[...new Set([...SKILL_OPTIONS, ...data.skills])].map(s => (
            <Chip key={s} label={s} selected={data.skills.includes(s)} onClick={() => toggleArr('skills', s)} />
          ))}
        </div>
        <div style={{ display: 'flex', gap: 6, marginTop: 6 }}>
          <input type="text" value={newSkill} onChange={e => setNewSkill(e.target.value)} placeholder="הוסף מתנה חדשה..." style={{ flex: 1 }} maxLength={60}
            onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addToArr('skills', newSkill, () => setNewSkill('')); } }} />
          <button type="button" className="btn ghost" onClick={() => addToArr('skills', newSkill, () => setNewSkill(''))}>+</button>
        </div>

        <label style={{ marginTop: 12 }}>דרך ליצירת קשר</label>
        <div style={{ display: 'flex', gap: 6 }}>
          <select value={data.contact_method} onChange={e => upd('contact_method', e.target.value)} style={{ flex: '0 0 140px' }}>
            <option value="">— בחר —</option>
            <option value="whatsapp">WhatsApp</option>
            <option value="telegram">Telegram</option>
            <option value="email">Email</option>
            <option value="phone">טלפון</option>
          </select>
          <input type="text" value={data.contact_value} onChange={e => upd('contact_value', e.target.value)} style={{ flex: 1 }} placeholder="שם משתמש / טלפון / כתובת..." maxLength={200} />
        </div>

        <label style={{ marginTop: 12 }}>צבע אוואטר</label>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
          {COLOR_SWATCHES.map(c => (
            <div key={c} onClick={() => upd('avatar_color', c)} title={c} style={{
              width: 32, height: 32, borderRadius: '50%', background: c, cursor: 'pointer',
              border: data.avatar_color === c ? '3px solid #fff' : '2px solid rgba(255,255,255,0.2)',
            }} />
          ))}
          <input type="text" value={data.avatar_color} onChange={e => upd('avatar_color', e.target.value)} style={{ width: 110, fontFamily: 'monospace' }} placeholder="#RRGGBB" maxLength={7} />
        </div>

        <label style={{ marginTop: 12 }}>אתר אינטרנט</label>
        <input type="url" value={data.website} onChange={e => upd('website', e.target.value)} placeholder="https://..." maxLength={500} />

        <label style={{ marginTop: 12 }}>YouTube</label>
        <input type="url" value={data.youtube_url} onChange={e => upd('youtube_url', e.target.value)} placeholder="https://youtube.com/..." maxLength={500} />

        <label style={{ marginTop: 12 }}>קישורים נוספים (רשתות חברתיות וכו')</label>
        {data.external_links.map((link, i) => (
          <div key={i} style={{ display: 'flex', gap: 6, marginTop: 6 }}>
            <input type="text" value={link.label} onChange={e => updateLink(i, 'label', e.target.value)} placeholder="שם (Facebook...)" style={{ flex: '0 0 140px' }} maxLength={80} />
            <input type="url" value={link.url} onChange={e => updateLink(i, 'url', e.target.value)} placeholder="https://..." style={{ flex: 1 }} maxLength={500} />
            <button type="button" className="btn ghost" onClick={() => removeLink(i)} title="הסר">🗑️</button>
          </div>
        ))}
        <button type="button" className="btn ghost" style={{ marginTop: 6 }} onClick={addLink}>+ הוסף קישור</button>

        <h4 style={{ color: '#d4af37', marginTop: 22, paddingTop: 14, borderTop: '1px solid rgba(255,255,255,0.08)' }}>📢 הצעת שירות בכרטיס</h4>

        <label>כותרת שירות</label>
        <input type="text" value={data.service_title} onChange={e => upd('service_title', e.target.value)} maxLength={200} placeholder="לדוגמה: יועץ עסקי" />

        <label style={{ marginTop: 12 }}>תיאור שירות</label>
        <textarea rows="3" value={data.service_body} onChange={e => upd('service_body', e.target.value)} maxLength={4000} />

        <label style={{ marginTop: 12 }}>טקסט כפתור (CTA)</label>
        <input type="text" value={data.cta_text} onChange={e => upd('cta_text', e.target.value)} maxLength={80} placeholder="צור קשר / הזמן" />

        <label style={{ marginTop: 12 }}>יעד הכפתור (CTA URL)</label>
        <input type="url" value={data.cta_url} onChange={e => upd('cta_url', e.target.value)} maxLength={500} placeholder="https://..." />

        <div className="actions" style={{ marginTop: 22, display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
          <button className="btn ghost" onClick={onClose} disabled={busy}>ביטול</button>
          <button className="btn" onClick={save} disabled={busy}>{busy ? 'שומר...' : '💾 שמור'}</button>
          {autosavedAt > 0 && (
            <span style={{ fontSize: 11, color: '#8a8da8', marginInlineStart: 'auto' }}>
              טיוטה נשמרה אוטומטית
            </span>
          )}
        </div>

        <div style={{ marginTop: 26, paddingTop: 14, borderTop: '1px solid rgba(220,38,38,0.25)' }}>
          <div style={{ fontSize: 12, color: '#8a8da8', marginBottom: 6 }}>
            פעולה מסוכנת — מאפסת את כל שדות הכרטיס. השם לא יושפע.
          </div>
          <button className="btn" style={{ background: '#7f1d1d', color: '#fff', borderColor: '#991b1b' }} onClick={clearCard} disabled={busy}>🗑️ נקה את כל הכרטיס שלי</button>
        </div>
      </div>
    </div>
  );
}

// ───────── Google Sign-In Gate ─────────
// Blocks access to onboarding questions until Google auth completes.
// SECURITY: We do NOT decode the JWT client-side for auth. The server verifies
// the raw credential (id_token) with google-auth-library and returns the
// trusted identity. Client-decoded payloads are forgeable.
function decodeGoogleJwt(token) {
  try {
    const payload = token.split('.')[1];
    const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
    return JSON.parse(decodeURIComponent(Array.from(json).map(c =>
      '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')));
  } catch (e) {
    console.error('JWT decode failed', e);
    return null;
  }
}

function SignInGate({ onAuthed, onClose, pendingCircleToken }) {
  const [clientId, setClientId] = useState(null);
  const [configErr, setConfigErr] = useState('');
  const [authErr, setAuthErr] = useState('');
  const [busy, setBusy] = useState(false);
  const [circleCtx, setCircleCtx] = useState(null);
  const btnRef = React.useRef(null);

  // If user arrived via deep-link, show what circle they're joining
  useEffect(() => {
    if (!pendingCircleToken) return;
    api(`/api/circle/by-token/${encodeURIComponent(pendingCircleToken)}`)
      .then(c => { if (c && !c.error) setCircleCtx(c); })
      .catch(() => {});
  }, [pendingCircleToken]);

  // Load public config (Google client_id)
  useEffect(() => {
    api('/api/config').then(c => {
      if (c && c.google_client_id) setClientId(c.google_client_id);
      else setConfigErr('Google OAuth לא מוגדר בשרת (GOOGLE_CLIENT_ID חסר)');
    }).catch(() => setConfigErr('לא הצלחנו לטעון את הגדרות השרת'));
  }, []);

  // Initialize GIS when client_id is loaded and the script is ready.
  // Re-runs when showGoogleSection toggles, so the button is rendered
  // even if the section is opened AFTER the initial load (timing fix).
  useEffect(() => {
    if (!clientId) return;
    let cancelled = false;

    const handleCredential = async (response) => {
      if (cancelled) return;
      if (!response || !response.credential) {
        setAuthErr('תשובת Google לא תקינה');
        return;
      }
      setBusy(true); setAuthErr('');
      try {
        // Send the raw ID token + the current anonymous user_id if any.
        // The server will upgrade the anon account (preserving coins +
        // reading history) instead of creating a fresh user.
        const anonId = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
        const hasGoogle = !!localStorage.getItem('tehillim_google_sub');
        const r = await api('/api/auth/google', {
          method: 'POST',
          body: {
            credential: response.credential,
            anon_user_id: (anonId && !hasGoogle) ? anonId : undefined,
          },
        });
        if (r.error) throw new Error(r.error);
        // Trust ONLY the server-verified identity (sub/email/name).
        const sub = r.google_sub;
        if (!sub) throw new Error('שרת לא החזיר זהות מאומתת');
        localStorage.setItem('tehillim_user_id', r.user_id);
        localStorage.setItem('tehillim_user_name', r.name);
        localStorage.setItem('tehillim_google_sub', sub);
        onAuthed({
          user_id: r.user_id,
          name: r.name,
          email: r.email,
          google_sub: sub,
          needs_onboarding: !!r.needs_onboarding,
          referral_code: r.referral_code,
          is_admin: !!r.is_admin,
        });
      } catch (e) {
        setAuthErr(e.message || 'התחברות נכשלה');
      } finally {
        setBusy(false);
      }
    };

    let attempts = 0;
    const tryInit = () => {
      if (cancelled) return;
      attempts++;
      const g = window.google && window.google.accounts && window.google.accounts.id;
      if (!g) {
        if (attempts > 80) { // ~12s — give up waiting for GIS script
          setAuthErr('לא הצלחנו לטעון את ספריית Google. בדוק חיבור אינטרנט / חוסם פרסומות.');
          return;
        }
        setTimeout(tryInit, 150); return;
      }
      // initialize is idempotent — safe to call again
      try {
        g.initialize({ client_id: clientId, callback: handleCredential, auto_select: false });
      } catch (e) {
        setAuthErr('Google init נכשל: ' + (e.message || e));
        return;
      }
      // Wait for btnRef to be in the DOM (only present when showGoogleSection=true)
      if (!btnRef.current) {
        if (attempts > 80) return; // section never opened — that's fine
        setTimeout(tryInit, 150); return;
      }
      try {
        btnRef.current.innerHTML = '';
        g.renderButton(btnRef.current, {
          theme: 'filled_black', size: 'large', text: 'signin_with',
          shape: 'pill', logo_alignment: 'center', width: 280,
        });
      } catch (e) {
        setAuthErr('Google render נכשל: ' + (e.message || e));
        return;
      }
      // Diagnostic: if no iframe appears within 3.5s, surface the cause —
      // most common: domain not in OAuth client's Authorized JavaScript origins.
      setTimeout(() => {
        if (cancelled) return;
        if (btnRef.current && !btnRef.current.querySelector('iframe')) {
          setAuthErr('כפתור Google לא נטען — ייתכן שהדומיין bayitdavid.com לא מורשה ב-Google OAuth Client. נסה את ההרשמה האנונימית למעלה.');
        }
      }, 3500);
    };
    tryInit();
    return () => { cancelled = true; };
  }, [clientId, onAuthed, showGoogleSection]);

  const textTypeIcon = ({tehillim:'\ud83d\udcd6', tanya:'\ud83d\udcd7', rambam:'\ud83d\udcd8', tikkunei_zohar:'\ud83d\udcd5'})[circleCtx?.text_type] || '\ud83d\udcd6';
  const textTypeLabel = ({tehillim:'\u05ea\u05d4\u05d9\u05dc\u05d9\u05dd', tanya:'\u05ea\u05e0\u05d9\u05d0', rambam:'\u05e8\u05de\u05d1\u05f4\u05dd', tikkunei_zohar:'\u05ea\u05d9\u05e7\u05d5\u05e0\u05d9 \u05d4\u05d6\u05d5\u05d4\u05e8'})[circleCtx?.text_type] || '\u05ea\u05d4\u05d9\u05dc\u05d9\u05dd';

  // Anon registration flow — primary path for QR/circle deep-links
  const [anonName, setAnonName] = useState('');
  const [anonBusy, setAnonBusy] = useState(false);
  const [showGoogleSection, setShowGoogleSection] = useState(false);

  // Email + password sign-in/sign-up (added 2026-05-08 at Damri's request).
  // Backend already exposes /api/auth/register and /api/auth/login with
  // scrypt password hashing — we just wire the UI here.
  const [showEmailSection, setShowEmailSection] = useState(false);
  const [emailMode, setEmailMode] = useState('register'); // 'register' | 'login'
  const [emEmail, setEmEmail] = useState('');
  const [emPassword, setEmPassword] = useState('');
  const [emName, setEmName] = useState('');
  const [emBusy, setEmBusy] = useState(false);
  const submitEmail = async () => {
    const email = emEmail.trim().toLowerCase();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      setAuthErr('הזן כתובת אימייל תקינה');
      return;
    }
    if (emPassword.length < 6) {
      setAuthErr('הסיסמא חייבת להיות לפחות 6 תווים');
      return;
    }
    if (emailMode === 'register' && emName.trim().length < 2) {
      setAuthErr('הזן שם (לפחות 2 תווים)');
      return;
    }
    setEmBusy(true); setAuthErr('');
    try {
      const path = emailMode === 'register' ? '/api/auth/register' : '/api/auth/login';
      const body = emailMode === 'register'
        ? { email, password: emPassword, name: emName.trim() }
        : { email, password: emPassword };
      const r = await api(path, { method: 'POST', body });
      if (r.error) throw new Error(r.error === 'email exists' ? 'כתובת האימייל כבר רשומה — נסה להתחבר' :
                                   r.error === 'bad credentials' ? 'אימייל או סיסמא שגויים' :
                                   r.error === 'password too short' ? 'הסיסמא חייבת להיות לפחות 6 תווים' :
                                   r.error);
      localStorage.setItem('tehillim_user_id', r.user_id);
      localStorage.setItem('tehillim_user_name', r.name || emName.trim());
      onAuthed({
        user_id: r.user_id,
        name: r.name || emName.trim(),
        email: r.email || email,
        google_sub: null,
        needs_onboarding: emailMode === 'register',
        referral_code: r.referral_code || null,
        is_admin: !!r.is_admin,
      });
    } catch (e) {
      setAuthErr(e.message || 'התחברות נכשלה');
    } finally {
      setEmBusy(false);
    }
  };

  const submitAnon = async () => {
    const cleanName = anonName.trim();
    if (cleanName.length < 2) {
      setAuthErr('הזן שם (לפחות 2 תווים)');
      return;
    }
    setAnonBusy(true); setAuthErr('');
    try {
      const r = await api('/api/auth/anon', {
        method: 'POST',
        body: { name: cleanName },
      });
      if (r.error) throw new Error(r.error);
      localStorage.setItem('tehillim_user_id', r.user_id);
      localStorage.setItem('tehillim_user_name', r.name);
      onAuthed({
        user_id: r.user_id,
        name: r.name,
        email: '',
        google_sub: null,
        needs_onboarding: false,  // anon users skip onboarding
        referral_code: r.referral_code,
        is_admin: false,
      });
    } catch (e) {
      setAuthErr(e.message || 'הרשמה נכשלה');
    } finally {
      setAnonBusy(false);
    }
  };

  return (
    <div className="modal-bg" onClick={() => onClose && onClose()}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ textAlign: 'center', maxWidth: 460, position: 'relative' }}>
        {onClose && (
          <span
            className="close-x"
            onClick={(e) => { e.stopPropagation(); onClose(); }}
            style={{
              position: 'absolute', top: 8, left: 8,
              fontSize: 28, color: '#5d5868', cursor: 'pointer',
              lineHeight: 1, padding: '8px 14px', borderRadius: 8,
              transition: 'color .15s, background .15s',
              zIndex: 100, userSelect: 'none', fontWeight: 400,
            }}
            onMouseEnter={e => { e.currentTarget.style.color = '#2a2030'; e.currentTarget.style.background = 'rgba(0,0,0,0.06)'; }}
            onMouseLeave={e => { e.currentTarget.style.color = '#5d5868'; e.currentTarget.style.background = 'transparent'; }}
            title="סגור"
          >×</span>
        )}
        {circleCtx && (
          <div style={{
            background: 'linear-gradient(135deg, rgba(212,160,23,0.18), rgba(168,128,10,0.08))',
            border: '1px solid rgba(212,160,23,0.45)',
            borderRadius: 12,
            padding: '14px 16px',
            marginBottom: 18,
          }}>
            <div style={{ fontSize: 12, color: '#8b6508', fontWeight: 700, marginBottom: 4 }}>
              הוזמנת להצטרף למעגל:
            </div>
            <div style={{ fontSize: 18, fontWeight: 700, color: '#1a1a2e', marginBottom: 4 }}>
              {textTypeIcon} {circleCtx.name}
            </div>
            {circleCtx.purpose && (
              <div style={{ fontSize: 13, color: '#555', fontStyle: 'italic', marginBottom: 4 }}>
                {circleCtx.purpose}
              </div>
            )}
            {/* [yakir 2026-05-14] Progress strip + coin explanation -
                shows recipient: (a) the circle has real momentum,
                (b) every chapter they read gives them a personal coin. */}
            {(() => {
              const done = parseInt(circleCtx.verified_count || circleCtx.chapters_completed || 0, 10);
              const total = parseInt(circleCtx.total_chapters || 0, 10);
              const pct = total > 0 ? Math.min(100, Math.round((done / total) * 100)) : 0;
              // [yakir 2026-05-20] normalize tanya_* variants + align symbol with server
              const _tt = String(circleCtx.text_type || '').toLowerCase();
              const _ttBase = _tt.startsWith('tanya') ? 'tanya' : _tt;
              const coinName = _ttBase === 'tanya' ? 'התניא'
                           : _ttBase === 'tikkunei_zohar' ? 'הזוהר'
                           : _ttBase === 'rambam' ? 'הרמב״ם'
                           : 'התהילים';
              const coinSymbol = _ttBase === 'tanya' ? 'TNY'
                              : _ttBase === 'tikkunei_zohar' ? 'TKZ'
                              : _ttBase === 'rambam' ? 'RMB'
                              : 'THL';
              return (
                <>
                  {total > 0 && (
                    <div style={{ marginTop: 12, marginBottom: 10 }}>
                      <div style={{
                        height: 8, borderRadius: 6,
                        background: 'rgba(0,0,0,0.10)', overflow: 'hidden',
                        position: 'relative',
                      }}>
                        <div style={{
                          height: '100%',
                          width: `${pct}%`,
                          background: 'linear-gradient(90deg, #d4af37 0%, #f5d75c 100%)',
                          borderRadius: 6,
                          transition: 'width 0.6s ease',
                        }} />
                      </div>
                      <div style={{
                        fontSize: 12, fontWeight: 700, color: '#5a3d0a',
                        marginTop: 6, textAlign: 'center',
                      }}>
                        {done} <span style={{ color: '#999', fontWeight: 400 }}>מתוך</span> {total} פרקים כבר נפתחו במעגל
                      </div>
                    </div>
                  )}
                  <div style={{
                    marginTop: 10, marginBottom: 4,
                    padding: '8px 10px',
                    background: 'rgba(212,175,55,0.10)',
                    border: '1px dashed rgba(212,175,55,0.40)',
                    borderRadius: 8,
                    fontSize: 12, color: '#5a3d0a',
                    textAlign: 'center', lineHeight: 1.5,
                  }}>
                    🪙 כל פרק שתסיים = מטבע <strong>{coinName}</strong> ({coinSymbol}) אישי על שמך
                  </div>
                </>
              );
            })()}
            <div style={{ fontSize: 12, color: '#777', marginTop: 8 }}>
              {textTypeLabel} · {circleCtx.total_chapters} פרקים {circleCtx.creator_name && '· פתח/ה: ' + circleCtx.creator_name}
            </div>
          </div>
        )}
        <h3 style={{ marginBottom: 6 }}>{circleCtx ? 'הצטרף למעגל' : 'ברוך הבא לבית דויד'}</h3>
        <p style={{ color: '#666', fontSize: 14, margin: '8px 0 18px', lineHeight: 1.6 }}>
          {circleCtx ? 'הזן את שמך כדי להצטרף ולהתחיל לקרוא' : 'הזן את שמך כדי להתחיל'}
        </p>

        {/* Primary anon flow */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 14 }}>
          <input
            type="text"
            placeholder="השם שלך"
            value={anonName}
            onChange={e => setAnonName(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter') submitAnon(); }}
            maxLength={60}
            autoFocus
            style={{
              padding: '14px 16px',
              fontSize: 16,
              borderRadius: 10,
              border: '1.5px solid #d4af37',
              fontFamily: 'inherit',
              direction: 'rtl',
              outline: 'none',
              background: '#fff',
              color: '#1a1a2e',
            }}
          />
          <button
            className="btn"
            onClick={submitAnon}
            disabled={anonBusy || anonName.trim().length < 2}
            style={{ padding: '14px 22px', fontSize: 16, fontWeight: 700 }}
          >
            {anonBusy ? 'מצטרף...' : (circleCtx ? '✨ הצטרף וקרא' : '✨ התחל')}
          </button>
        </div>

        {authErr && <div style={{ color: '#dc2626', marginTop: 8, fontSize: 13 }}>{authErr}</div>}

        {/* Email + password — toggle to expand */}
        <div style={{ marginTop: 14, paddingTop: 12, borderTop: '1px solid #e5e0d0' }}>
          {!showEmailSection ? (
            <button
              onClick={() => { setShowEmailSection(true); setAuthErr(''); }}
              style={{
                background: 'none', border: 'none', color: '#8b6508',
                fontSize: 12, cursor: 'pointer', textDecoration: 'underline',
                fontFamily: 'inherit',
              }}
            >
              העדפת רישום עם אימייל וסיסמא?
            </button>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              <div style={{ display: 'flex', gap: 6, justifyContent: 'center', marginBottom: 4 }}>
                <button
                  type="button"
                  onClick={() => { setEmailMode('register'); setAuthErr(''); }}
                  style={{
                    flex: 1, padding: '8px 10px', fontSize: 13, fontWeight: 600,
                    border: emailMode === 'register' ? '1.5px solid #d4af37' : '1px solid #ddd',
                    background: emailMode === 'register' ? '#fff8e1' : '#fff',
                    color: '#1a1a2e', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit',
                  }}
                >הרשמה</button>
                <button
                  type="button"
                  onClick={() => { setEmailMode('login'); setAuthErr(''); }}
                  style={{
                    flex: 1, padding: '8px 10px', fontSize: 13, fontWeight: 600,
                    border: emailMode === 'login' ? '1.5px solid #d4af37' : '1px solid #ddd',
                    background: emailMode === 'login' ? '#fff8e1' : '#fff',
                    color: '#1a1a2e', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit',
                  }}
                >התחברות</button>
              </div>
              {emailMode === 'register' && (
                <input
                  type="text"
                  placeholder="השם שלך"
                  value={emName}
                  onChange={e => setEmName(e.target.value)}
                  maxLength={60}
                  style={{
                    padding: '11px 13px', fontSize: 15, borderRadius: 8,
                    border: '1px solid #d0c8b0', direction: 'rtl',
                    fontFamily: 'inherit', outline: 'none', background: '#fff', color: '#1a1a2e',
                  }}
                />
              )}
              <input
                type="email"
                placeholder="email@example.com"
                value={emEmail}
                onChange={e => setEmEmail(e.target.value)}
                autoComplete="email"
                style={{
                  padding: '11px 13px', fontSize: 15, borderRadius: 8,
                  border: '1px solid #d0c8b0', direction: 'ltr', textAlign: 'left',
                  fontFamily: 'inherit', outline: 'none', background: '#fff', color: '#1a1a2e',
                }}
              />
              <input
                type="password"
                placeholder="סיסמא (לפחות 6 תווים)"
                value={emPassword}
                onChange={e => setEmPassword(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter') submitEmail(); }}
                autoComplete={emailMode === 'register' ? 'new-password' : 'current-password'}
                style={{
                  padding: '11px 13px', fontSize: 15, borderRadius: 8,
                  border: '1px solid #d0c8b0', direction: 'rtl',
                  fontFamily: 'inherit', outline: 'none', background: '#fff', color: '#1a1a2e',
                }}
              />
              <button
                onClick={submitEmail}
                disabled={emBusy}
                style={{
                  padding: '12px 18px', fontSize: 15, fontWeight: 700,
                  background: 'linear-gradient(135deg, #d4af37, #f1c40f)',
                  color: '#1a1a2e', border: 'none', borderRadius: 8,
                  cursor: emBusy ? 'wait' : 'pointer', fontFamily: 'inherit',
                  boxShadow: '0 2px 8px rgba(212,175,55,0.25)',
                }}
              >
                {emBusy ? '...' : (emailMode === 'register' ? '✨ הירשם' : '🔓 התחבר')}
              </button>
            </div>
          )}
        </div>

        {/* Optional Google sign-in - hidden behind a toggle */}
        <div style={{ marginTop: 16, paddingTop: 14, borderTop: '1px solid #e5e0d0' }}>
          {!showGoogleSection ? (
            <button
              onClick={() => setShowGoogleSection(true)}
              style={{
                background: 'none',
                border: 'none',
                color: '#8b6508',
                fontSize: 12,
                cursor: 'pointer',
                textDecoration: 'underline',
                fontFamily: 'inherit',
              }}
            >
              יש לך חשבון Google? התחבר במקום
            </button>
          ) : (
            <>
              <div style={{ fontSize: 12, color: '#666', marginBottom: 10 }}>
                שמירת התקדמות בין מכשירים
              </div>
              <div ref={btnRef} style={{ display: 'flex', justifyContent: 'center', minHeight: 44 }} />
              {busy && <div style={{ color: '#666', marginTop: 10, fontSize: 12 }}>מתחבר...</div>}
              {configErr && <div style={{ color: '#dc2626', marginTop: 10, fontSize: 12 }}>{configErr}</div>}
              {!clientId && !configErr && (
                <div style={{ color: '#888', marginTop: 10, fontSize: 11 }}>טוען...</div>
              )}
            </>
          )}
        </div>
      </div>
    </div>
  );
}

// ───────── Profile Card ─────────
function Avatar({ url, color, name, size = 56 }) {
  const initials = (name || '?').slice(0, 1);
  const style = {
    width: size, height: size, borderRadius: '50%',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    fontSize: size * 0.45, fontWeight: 700, color: '#1a1a2e',
    background: color || '#d4af37', overflow: 'hidden', flexShrink: 0,
  };
  if (url) {
    return <div style={style}><img src={url} alt={name} style={{ width: '100%', height: '100%', objectFit: 'cover' }} /></div>;
  }
  return <div style={style}>{initials}</div>;
}

// ───────── Member Deep Card (public profile modal) ─────────
// Opens when a community member is clicked. Shows everything the user
// chose to reveal, their journey, abilities, and offers + ways to connect.
// Anchor: directive 9.5 — "click member, see everything exposed".
function MemberDeepCard({ memberId, onClose, currentUserId }) {
  const [m, setM] = useState(null);
  const [loading, setLoading] = useState(true);
  const [sendOpen, setSendOpen] = useState(false);
  const [myBalances, setMyBalances] = useState([]);
  useEffect(() => {
    if (!memberId) return;
    setLoading(true);
    api(`/api/community-member/${memberId}`)
      .then(d => setM(d && !d.error ? d : null))
      .finally(() => setLoading(false));
  }, [memberId]);
  if (!memberId) return null;
  const openSend = async () => {
    if (!currentUserId) { alert('צריך להירשם'); return; }
    const me = await api(`/api/profile/${currentUserId}`);
    setMyBalances(me.balances || []);
    setSendOpen(true);
  };
  const COLLECTION_LABELS = { tehillim: 'תהילים', tanya: 'תניא', rambam: 'רמב״ם', tikkunei_zohar: 'תיקוני זוהר' };
  const FIELD_LABELS = {
    tagline: 'שורת תקציר', bio: 'ביוגרפיה', mission: 'שליחות',
    location: 'מיקום', skills: 'כישורים', values: 'ערכים',
    contact_value: 'דרך קשר', avatar_url: 'תמונה', service: 'שירות', links: 'קישורים'
  };
  const contactUrl = m && m.contact ? (
    m.contact.method === 'telegram' ? `https://t.me/${String(m.contact.value).replace(/^@/, '')}`
    : m.contact.method === 'whatsapp' ? `https://wa.me/${String(m.contact.value).replace(/[^0-9]/g, '')}`
    : `mailto:${m.contact.value}`
  ) : '';
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" style={{ maxWidth: 640, maxHeight: '90vh', overflowY: 'auto', position: 'relative' }} onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={onClose} style={{ position: 'absolute', top: 14, left: 14, fontSize: 26, cursor: 'pointer', zIndex: 10, padding: '2px 10px' }}>×</span>
        {loading && <p style={{ color: 'var(--muted)', textAlign: 'center', padding: 20 }}>טוען...</p>}
        {!loading && !m && <p style={{ color: '#dc2626', textAlign: 'center', padding: 20 }}>לא נמצא</p>}
        {!loading && m && (
          <div>
            <div style={{ display: 'flex', gap: 12, alignItems: 'center', marginBottom: 16 }}>
              <Avatar url={m.avatar_url} color={m.avatar_color} name={m.name} size={72} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--text)' }}>{m.name}</div>
                {m.tagline && <div style={{ color: 'var(--gold)', fontSize: 14, marginTop: 4 }}>{m.tagline}</div>}
                {m.location && <div style={{ color: 'var(--muted)', fontSize: 13, marginTop: 2 }}>📍 {m.location}</div>}
              </div>
            </div>

            {m.disclosure && (
              <div style={{ background: 'rgba(212,175,55,0.08)', border: '1px solid rgba(212,175,55,0.25)', borderRadius: 8, padding: '8px 12px', fontSize: 12, color: 'var(--gold)', marginBottom: 12 }}>
                ✨ פרופיל חשוף ב־{m.disclosure.progress_pct}% · קיבל {m.disclosure.earned_beu} BEU על שיתוף
              </div>
            )}

            {m.journey_answers && m.journey_answers.length > 0 && (
              <div style={{ marginBottom: 18 }}>
                <div style={{ fontSize: 11, color: 'var(--gold)', marginBottom: 8, fontWeight: 600, letterSpacing: 0.5 }}>✨ המסע</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                  {m.journey_answers.map(a => (
                    <div key={a.question_id} style={{ borderRight: '2px solid rgba(212,175,55,0.4)', paddingRight: 12 }}>
                      <div style={{ fontSize: 12, color: 'var(--gold)', fontStyle: 'italic', marginBottom: 4, lineHeight: 1.4 }}>{a.question}</div>
                      <div style={{ fontSize: 14, color: 'var(--text)', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>{a.answer_text}</div>
                    </div>
                  ))}
                </div>
              </div>
            )}

            {m.mission && (
              <div style={{ marginBottom: 14 }}>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 4, fontWeight: 600 }}>שליחות</div>
                <div style={{ color: 'var(--text)', fontStyle: 'italic', borderRight: '3px solid #d4af37', paddingRight: 10, lineHeight: 1.5, whiteSpace: 'pre-wrap' }}>
                  "{m.mission}"
                </div>
              </div>
            )}

            {m.bio && (
              <div style={{ marginBottom: 14 }}>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 4, fontWeight: 600 }}>על עצמי</div>
                <div style={{ color: 'var(--text)', whiteSpace: 'pre-wrap', lineHeight: 1.5 }}>{m.bio}</div>
              </div>
            )}

            {m.values && m.values.length > 0 && (
              <div style={{ marginBottom: 14 }}>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6, fontWeight: 600 }}>ערכים</div>
                <div className="profile-badges">
                  {m.values.map((v, i) => <span key={i} className="badge">{v}</span>)}
                </div>
              </div>
            )}

            {m.skills && m.skills.length > 0 && (
              <div style={{ marginBottom: 14 }}>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6, fontWeight: 600 }}>יודע · יכול לעזור ב</div>
                <div className="profile-badges">
                  {m.skills.map((s, i) => <span key={i} className="badge gold">{s}</span>)}
                </div>
              </div>
            )}

            {m.service && (m.service.title || m.service.body) && (
              <div style={{ background: 'rgba(212,175,55,0.06)', border: '1px solid rgba(212,175,55,0.2)', borderRadius: 10, padding: 12, marginBottom: 14 }}>
                <div style={{ fontSize: 11, color: 'var(--gold)', marginBottom: 6, fontWeight: 600 }}>🪶 השירות שאני מציע</div>
                {m.service.title && <div style={{ color: 'var(--text)', fontWeight: 600, marginBottom: 4 }}>{m.service.title}</div>}
                {m.service.body && <div style={{ color: 'var(--text)', fontSize: 13, whiteSpace: 'pre-wrap', lineHeight: 1.5 }}>{m.service.body}</div>}
                {m.service.cta_url && (
                  <a className="btn" href={m.service.cta_url} target="_blank" rel="noreferrer" style={{ display: 'inline-block', marginTop: 10, padding: '8px 16px', fontSize: 13 }}>
                    {m.service.cta_text || 'למידע נוסף'} ←
                  </a>
                )}
              </div>
            )}

            <div style={{ marginBottom: 14 }}>
              <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6, fontWeight: 600 }}>המסע</div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                <span className="badge gold">📖 {m.journey.chapters_total} פרקים</span>
                <span className="badge">🔵 {m.journey.circles_joined} מעגלים</span>
                {m.journey.books_completed > 0 && <span className="badge">📚 {m.journey.books_completed} ספרים</span>}
                {m.journey.community_tasks_done > 0 && <span className="badge">✅ {m.journey.community_tasks_done} משימות</span>}
                {m.journey.services_offered > 0 && <span className="badge">🪶 {m.journey.services_offered} מתנות</span>}
              </div>
              {m.journey.chapters_by_collection && Object.keys(m.journey.chapters_by_collection).length > 1 && (
                <div style={{ marginTop: 8, fontSize: 12, color: 'var(--muted)' }}>
                  לפי ספר: {Object.entries(m.journey.chapters_by_collection).map(([k, v]) => `${COLLECTION_LABELS[k] || k}: ${v}`).join(' · ')}
                </div>
              )}
            </div>

            {m.communities && m.communities.length > 0 && (
              <div style={{ marginBottom: 14 }}>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6, fontWeight: 600 }}>קהילות</div>
                <div className="profile-badges">
                  {m.communities.map(c => <span key={c.id} className="badge">{c.name}{c.role !== 'member' ? ` (${c.role})` : ''}</span>)}
                </div>
              </div>
            )}

            {(m.links.website || m.links.youtube_url || (m.links.external_links && m.links.external_links.length > 0)) && (
              <div style={{ marginBottom: 14 }}>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6, fontWeight: 600 }}>קישורים</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                  {m.links.website && <a href={m.links.website} target="_blank" rel="noreferrer" style={{ color: 'var(--gold)', fontSize: 13 }}>🌐 {m.links.website}</a>}
                  {m.links.youtube_url && <a href={m.links.youtube_url} target="_blank" rel="noreferrer" style={{ color: 'var(--gold)', fontSize: 13 }}>▶️ YouTube</a>}
                  {(m.links.external_links || []).map((e, i) => (
                    <a key={i} href={e.url} target="_blank" rel="noreferrer" style={{ color: 'var(--gold)', fontSize: 13 }}>🔗 {e.label || e.url}</a>
                  ))}
                </div>
              </div>
            )}

            {m.journey_moments && m.journey_moments.length > 0 && (
              <div style={{ marginBottom: 14 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
                  <div style={{ fontSize: 11, color: 'var(--muted)', fontWeight: 600 }}>המסע שלי</div>
                  {m.total_testimony_beu > 0 && (
                    <div style={{ fontSize: 11, color: 'var(--gold)' }}>{m.total_testimony_beu} BEU של עדות</div>
                  )}
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                  {m.journey_moments.slice(0, 5).map(jm => (
                    <div key={jm.id} style={{ borderRight: '2px solid rgba(212,175,55,0.35)', paddingRight: 10, fontSize: 13 }}>
                      <div style={{ color: 'var(--gold)', fontSize: 11, fontWeight: 600 }}>
                        {jm.kind_label}{jm.reward_amount > 0 && <span style={{ color: 'var(--muted)', fontWeight: 400 }}> · +{jm.reward_amount} BEU</span>}
                      </div>
                      {jm.title && <div style={{ color: 'var(--text)', fontWeight: 600, marginTop: 2 }}>{jm.title}</div>}
                      <div style={{ color: 'var(--text)', marginTop: 2, lineHeight: 1.5 }}>{jm.body}</div>
                    </div>
                  ))}
                </div>
              </div>
            )}

            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 16 }}>
              {m.contact && (
                <a className="btn" href={contactUrl} target="_blank" rel="noreferrer" style={{ flex: '1 1 120px', padding: '10px 16px', fontSize: 13, textAlign: 'center' }}>
                  💬 צור קשר
                </a>
              )}
              {currentUserId && currentUserId !== m.id && (
                <button className="btn" style={{ flex: '1 1 120px', padding: '10px 16px', fontSize: 13 }} onClick={openSend}>
                  🪙 שלח טוקנים
                </button>
              )}
              <button className="btn ghost" style={{ flex: '0 0 auto', padding: '10px 16px', fontSize: 13 }} onClick={onClose}>סגור</button>
            </div>

            {sendOpen && (
              <TransferModal
                fromUserId={currentUserId}
                toUserId={m.id}
                toName={m.name}
                balances={myBalances}
                onClose={() => setSendOpen(false)}
                onSent={() => { setSendOpen(false); alert('נשלח!'); }}
              />
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// ───────── Share-a-Moment Modal (testimony of mission) ─────────
// Reward path: meaningful soul-witness gets BEU. No more field-fill rewards.
// Anchor: vision doc "מסע חי ומתפתח".
function ShareMomentModal({ userId, googleSub, onClose, onShared }) {
  const [kinds, setKinds] = useState([]);
  const [kind, setKind] = useState('');
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [result, setResult] = useState(null);
  useEffect(() => {
    api('/api/journey/kinds').then(k => {
      if (Array.isArray(k)) { setKinds(k); if (!kind && k.length) setKind(k[0].kind); }
    });
  }, []);
  const KIND_HINTS = {
    calling:  'רגע שבו הרגשת את השליחות שלך',
    overcome: 'קושי שעברת ויצא ממנו אור',
    chesed:   'מעשה חסד שעשית — קטן או גדול',
    shift:    'רגע בו משהו בפנים זז',
    insight:  'הבנה פנימית שהאירה לך משהו',
    learning: 'לימוד תורה או חיים שעיצב אותך',
    prayer:   'תפילה שנענתה — בדרך שלא ציפית',
  };
  const submit = async () => {
    setErr('');
    if (!kind) { setErr('בחר סוג רגע'); return; }
    if (body.trim().length < 50) { setErr('צריך לפחות 50 תווים — תן לעדות שלך מקום להיוולד'); return; }
    setBusy(true);
    try {
      const r = await api('/api/journey', { method: 'POST', body: { sub: googleSub, kind, title: title.trim(), body: body.trim() } });
      if (r.error) throw new Error(r.error);
      setResult(r);
      if (r.reward && r.reward.awarded) {
        setTimeout(() => { onShared && onShared(r); onClose(); }, 2400);
      } else {
        setTimeout(() => { onShared && onShared(r); onClose(); }, 1600);
      }
    } catch (e) {
      setErr(e.message || 'שגיאה');
      setBusy(false);
    }
  };
  return (
    <div className="modal-bg" onClick={busy ? null : onClose}>
      <div className="modal" style={{ maxWidth: 540, maxHeight: '90vh', overflowY: 'auto' }} onClick={e => e.stopPropagation()}>
        {!result && (
          <div>
            <h3 style={{ marginTop: 0, fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic' }}>רוצה לשתף רגע מהדרך?</h3>
            <div style={{ fontSize: 13, color: 'var(--muted)', marginBottom: 14, lineHeight: 1.6, fontStyle: 'italic' }}>
              לא פוסט. רגע אמיתי שעבר עליך — שינוי קטן, חסד, הבנה, חיבור.
            </div>

            {err && <div style={{ color: '#dc2626', fontSize: 13, marginBottom: 10, padding: 8, background: 'rgba(255,107,157,0.1)', borderRadius: 6 }}>{err}</div>}

            <label style={{ display: 'block', marginBottom: 6, fontSize: 13 }}>סוג הרגע</label>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
              {kinds.map(k => (
                <button
                  key={k.kind}
                  type="button"
                  onClick={() => setKind(k.kind)}
                  style={{
                    padding: '8px 12px',
                    borderRadius: 20,
                    border: kind === k.kind ? '1px solid #d4af37' : '1px solid rgba(255,255,255,0.1)',
                    background: kind === k.kind ? 'rgba(212,175,55,0.15)' : 'rgba(255,255,255,0.03)',
                    color: kind === k.kind ? 'var(--gold)' : 'var(--text)',
                    fontSize: 12,
                    cursor: 'pointer',
                  }}
                >
                  {k.label} <span style={{ opacity: 0.6, fontSize: 11 }}>+{k.reward}</span>
                </button>
              ))}
            </div>
            {kind && KIND_HINTS[kind] && (
              <div style={{ fontSize: 12, color: 'var(--muted)', fontStyle: 'italic', marginBottom: 10 }}>
                💭 {KIND_HINTS[kind]}
              </div>
            )}

            <label style={{ display: 'block', marginBottom: 6, fontSize: 13 }}>כותרת (אופציונלי)</label>
            <input type="text" value={title} onChange={e => setTitle(e.target.value)} maxLength={200} placeholder="במשפט אחד..." />

            <label style={{ display: 'block', marginTop: 12, marginBottom: 6, fontSize: 13 }}>
              העדות עצמה <span style={{ color: 'var(--muted)', fontSize: 11 }}>({body.trim().length}/50 מינימום)</span>
            </label>
            <textarea rows="6" value={body} onChange={e => setBody(e.target.value)} maxLength={5000} placeholder="ספר ברוח שלך — בלי לפחד, בלי לקשט..."/>

            <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
              <button className="btn ghost" style={{ flex: 1 }} onClick={onClose} disabled={busy}>ביטול</button>
              <button className="btn" style={{ flex: 2 }} onClick={submit} disabled={busy || body.trim().length < 50}>
                {busy ? 'שולח...' : 'שתף את העדות'}
              </button>
            </div>
          </div>
        )}
        {result && result.reward && result.reward.awarded && (
          <div style={{ textAlign: 'center', padding: 20 }}>
            <div style={{ fontSize: 48, marginBottom: 10 }}>🪙</div>
            <div style={{ fontSize: 22, fontWeight: 700, color: 'var(--gold)', marginBottom: 6 }}>
              +{result.reward.amount} BEU
            </div>
            <div style={{ fontSize: 14, color: 'var(--text)' }}>
              על עדות שלך — {result.label}
            </div>
            <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 12, fontStyle: 'italic' }}>
              "כי באור פניך נתת לנו תורת חיים..."
            </div>
          </div>
        )}
        {result && result.reward && !result.reward.awarded && (
          <div style={{ textAlign: 'center', padding: 20 }}>
            <div style={{ fontSize: 32, marginBottom: 10 }}>✨</div>
            <div style={{ fontSize: 16, fontWeight: 600, color: 'var(--text)', marginBottom: 6 }}>נשמר במסע שלך</div>
            <div style={{ fontSize: 12, color: 'var(--muted)' }}>
              {result.reward.reason === 'daily_cap_reached' ? 'הגעת לתקרת 3 עדויות־מטבע ביום. הרגע נשמר — תוכל לקבל מטבע על הבא.' :
               result.reward.reason === 'body_too_short' ? 'הגוף קצר מדי לתגמול — אבל הרגע נשמר.' : 'הרגע נשמר.'}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ───────── Journey of Discovery (guided question flow) ─────────
// Soft → deep questions from a curated bank. One at a time. No coins
// shown in this UI — the journey is not a transaction. Anchor: vision
// doc — "מסע חי ומתפתח", profile is soul-mirror.
function JourneyOfDiscovery({ userId, onAnswered }) {
  const [progress, setProgress] = useState(null);
  const [current, setCurrent] = useState(null); // current question
  const [draft, setDraft] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [done, setDone] = useState(false);
  const [refreshKey, setRefreshKey] = useState(0);
  const [pastAnswers, setPastAnswers] = useState([]);
  const [showPast, setShowPast] = useState(false);
  const [editingId, setEditingId] = useState(null);
  const [justSaved, setJustSaved] = useState(false);
  const [recognition, setRecognition] = useState('');

  useEffect(() => {
    if (!userId) return;
    Promise.all([
      api(`/api/journey-progress/${userId}`),
      api(`/api/journey-questions/next?user_id=${userId}`),
      api(`/api/journey-answers/${userId}`),
    ]).then(([prog, nxt, past]) => {
      if (prog && !prog.error) setProgress(prog);
      if (nxt && !nxt.error) {
        setDone(!!nxt.done);
        // Attach selector reason fields to the question object so the UI
        // can show them inline. Round 2: dynamic selection from the bank.
        setCurrent(nxt.done ? null : { ...nxt.question, reason_code: nxt.reason_code, reason_text: nxt.reason_text });
      }
      if (past && !past.error) setPastAnswers(past.answers || []);
      setDraft('');
      setEditingId(null);
    });
  }, [userId, refreshKey]);

  const CATEGORY_LABELS = {
    gifts: 'מתנות', identity: 'זהות', mission: 'שליחות',
    vision: 'חזון', fears: 'מה שעוצר', help: 'מה שצריך',
  };

  const submit = async () => {
    if (!userId) { setErr('צריך להתחבר'); return; }
    if (draft.trim().length < 3) { setErr('כתוב לפחות מילה אחת'); return; }
    setBusy(true); setErr('');
    try {
      const qid = editingId || current.id;
      const r = await api('/api/journey-answer', {
        method: 'POST',
        body: { user_id: userId, question_id: qid, answer_text: draft.trim(), visible_to_community: true },
      });
      if (r.error) throw new Error(r.error);
      setRecognition(r.recognition || 'עוד חלק ממך נכנס לבית.');
      setJustSaved(true);
      // A real breath — let the recognition land before the next question.
      setTimeout(() => {
        setJustSaved(false);
        setRecognition('');
        setRefreshKey(k => k + 1);
        onAnswered && onAnswered();
      }, 2400);
    } catch (e) {
      setErr(e.message || 'שגיאה');
      setBusy(false);
    }
  };

  const startEdit = (a) => {
    setEditingId(a.question_id);
    setDraft(a.answer_text);
    setCurrent({ id: a.question_id, text: a.question, category: a.category, depth: a.depth });
    setDone(false);
    setShowPast(false);
  };

  if (!userId) return null;

  // Auth-portability hint: if user has no google_sub, gentle nudge.
  const googleSub = typeof localStorage !== 'undefined' ? localStorage.getItem('tehillim_google_sub') : null;
  const showPortabilityHint = !googleSub;

  return (
    <div style={{
      background: 'linear-gradient(180deg, #fbf7eb 0%, #f5edd5 100%)',
      border: '1px solid rgba(168,128,31,0.35)',
      borderRadius: 24,
      padding: '32px 26px',
      marginBottom: 24,
      maxWidth: 640,
      marginLeft: 'auto',
      marginRight: 'auto',
      boxShadow: '0 8px 30px rgba(180,150,80,0.15), inset 0 1px 0 rgba(255,255,255,0.6)',
      position: 'relative',
    }}>
      {/* Header — single word, serif, gold, centered */}
      <div style={{ textAlign: 'center', marginBottom: 6 }}>
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif",
          fontSize: 30,
          fontWeight: 600,
          color: '#a8801f',
          letterSpacing: 1,
        }}>
          המסע
        </div>
        {progress && (
          <div style={{ fontSize: 11, color: '#8a8597', marginTop: 4, letterSpacing: 0.5, fontStyle: 'italic' }}>
            {progress.done ? 'המסע פתוח' : 'מי אתה נהיה בתוך המסע'}
          </div>
        )}
      </div>

      {/* Constellation-style progress dots — 12 small circles, filled = answered */}
      {progress && progress.total > 0 && (
        <div style={{ display: 'flex', justifyContent: 'center', gap: 6, marginBottom: 22, marginTop: 14 }}>
          {Array.from({ length: progress.total }, (_, i) => (
            <div key={i} style={{
              width: 6, height: 6, borderRadius: '50%',
              background: i < progress.answered ? '#a8801f' : 'rgba(168,128,31,0.18)',
              boxShadow: i < progress.answered ? '0 0 8px rgba(168,128,31,0.45)' : 'none',
              transition: 'background 0.4s, box-shadow 0.4s',
            }} />
          ))}
        </div>
      )}

      {showPortabilityHint && (
        <div style={{ fontSize: 11, color: '#6d6878', marginBottom: 14, padding: '8px 12px', background: 'rgba(168,128,31,0.07)', borderRadius: 8, textAlign: 'center' }}>
          🌱 רוצה שהמסע ימשיך איתך לכל מכשיר? חבר חשבון Google
        </div>
      )}

      {/* Current question OR completion celebration */}
      {!editingId && done && (
        <div style={{ textAlign: 'center', padding: '24px 10px 8px' }}>
          <div style={{ fontSize: 42, marginBottom: 12, color: '#a8801f', opacity: 0.9 }}>✦</div>
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 20, color: '#a8801f', fontWeight: 500, marginBottom: 8, lineHeight: 1.4,
          }}>
            המסע פתוח עכשיו
          </div>
          <div style={{ fontSize: 13, color: '#5d5868', lineHeight: 1.7, fontStyle: 'italic', maxWidth: 380, margin: '0 auto' }}>
            עברת את שתים־עשרה השאלות. אבל המסע אינו מסתיים — הוא רק נפתח.<br/>
            כל תשובה ניתנת לעריכה, להעמקה, לחזרה.
          </div>
          <button style={{
            marginTop: 18, padding: '10px 18px', fontSize: 12,
            background: 'transparent', color: '#6d6878',
            border: '1px solid rgba(168,128,31,0.35)',
            borderRadius: 10, cursor: 'pointer', fontFamily: 'inherit',
          }} onClick={() => setShowPast(true)}>
            צפה במה שחשפת
          </button>
        </div>
      )}

      {current && !justSaved && (
        <div>
          {editingId && (
            <div style={{ fontSize: 11, color: '#8a8597', marginBottom: 14, textAlign: 'center', letterSpacing: 0.5 }}>
              ✏️ עריכת תשובה · {CATEGORY_LABELS[current.category] || current.category}
            </div>
          )}
          {!editingId && current.reason_code === 'offer_companion' && (
            <div style={{
              background: 'rgba(95,163,114,0.08)', border: '1px solid rgba(95,163,114,0.32)',
              borderRadius: 16, padding: '18px 20px', marginBottom: 22, textAlign: 'center',
            }}>
              <div style={{ fontSize: 24, marginBottom: 8, opacity: 0.9 }}>🤍</div>
              <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 17, color: '#3d6b4a', lineHeight: 1.6, fontWeight: 500 }}>
                לא חייבים לעבור את זה לבד.
              </div>
              <div style={{ fontSize: 13.5, color: '#5d5868', lineHeight: 1.75, marginTop: 8 }}>
                {current.reason_text || 'אפשר לחשוב על זה יחד — עם יקיר, עם מלווה, או איתי 🤍'}
              </div>
              <div style={{ fontSize: 12, color: '#6d6878', marginTop: 12, fontStyle: 'italic', opacity: 0.85 }}>
                ואפשר גם פשוט להמשיך, כשתהיה מוכן.
              </div>
            </div>
          )}
          {!editingId && current.reason_code !== 'offer_companion' && current.reason_text && current.reason_text.length > 0 && (
            <div style={{
              fontSize: 12, color: '#a8801f', fontStyle: 'italic',
              textAlign: 'center', marginBottom: 18,
              opacity: 0.9,
            }}>
              ✦ {current.reason_text}
            </div>
          )}
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 22,
            color: '#2a2030',
            fontWeight: 500,
            lineHeight: 1.55,
            marginBottom: 10,
            textAlign: 'center',
            letterSpacing: 0.2,
          }}>
            {current.text}
          </div>
          {current.hint && !editingId && (
            <div style={{
              fontSize: 13, color: '#6d6878',
              fontStyle: 'italic', marginBottom: 22, textAlign: 'center',
              opacity: 0.85,
            }}>
              {current.hint}
            </div>
          )}
          <textarea
            rows={5}
            value={draft}
            onChange={e => setDraft(e.target.value)}
            placeholder="ענה ברוח שלך — בלי לפחד, בלי לקשט..."
            maxLength={5000}
            style={{
              marginTop: 6,
              background: 'var(--text)',
              border: '1px solid rgba(168,128,31,0.4)',
              borderRadius: 12,
              padding: 14,
              fontSize: 15,
              lineHeight: 1.6,
              color: '#2a2030',
              resize: 'vertical',
              boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.04)',
            }}
          />
          {err && <div style={{ color: '#dc2626', fontSize: 12, marginTop: 8, textAlign: 'center' }}>{err}</div>}
          <div style={{ display: 'flex', gap: 10, marginTop: 16 }}>
            {editingId && (
              <button style={{
                flex: 1, padding: '10px', fontSize: 13,
                background: 'transparent', color: '#6d6878',
                border: '1px solid rgba(168,128,31,0.35)',
                borderRadius: 10, cursor: 'pointer',
                fontFamily: 'inherit',
              }} onClick={() => { setEditingId(null); setDraft(''); setRefreshKey(k => k + 1); }}>
                ביטול
              </button>
            )}
            <button style={{
              flex: editingId ? 2 : 1, padding: '12px', fontSize: 14, letterSpacing: 0.5,
              background: (busy || draft.trim().length < 3) ? '#c8b878' : '#a8801f',
              color: 'var(--text)',
              border: 'none', borderRadius: 10,
              cursor: (busy || draft.trim().length < 3) ? 'not-allowed' : 'pointer',
              fontWeight: 600,
              fontFamily: 'inherit',
              boxShadow: (busy || draft.trim().length < 3) ? 'none' : '0 2px 8px rgba(168,128,31,0.3)',
              transition: 'all 0.2s',
            }} onClick={submit} disabled={busy || draft.trim().length < 3}>
              {busy ? 'שומר...' : (editingId ? 'עדכן' : 'שמור והמשך ←')}
            </button>
          </div>
        </div>
      )}

      {justSaved && (
        <div style={{ textAlign: 'center', padding: '46px 18px', color: '#a8801f' }}>
          <div style={{ fontSize: 30, marginBottom: 16, opacity: 0.7 }}>✦</div>
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18, color: '#5d5868',
            lineHeight: 1.75, fontWeight: 500, maxWidth: 420, margin: '0 auto', fontStyle: 'italic',
          }}>
            {recognition || 'עוד חלק ממך נכנס לבית.'}
          </div>
          <div style={{ fontSize: 11, color: '#a89878', marginTop: 18, letterSpacing: 3 }}>· נשימה ·</div>
        </div>
      )}

      {/* Past answers (collapsible) — visual journal */}
      {pastAnswers.length > 0 && (
        <div style={{ marginTop: 28, paddingTop: 22, borderTop: '1px solid rgba(168,128,31,0.18)' }}>
          <button
            onClick={() => setShowPast(s => !s)}
            style={{
              width: '100%', background: 'none', border: 'none',
              color: '#6d6878', fontSize: 12, cursor: 'pointer',
              textAlign: 'center', padding: 0, letterSpacing: 0.5,
              fontFamily: 'inherit',
            }}
          >
            {showPast ? '✦ סגור' : `✦ מה שחשפת (${pastAnswers.length})`}
          </button>
          {showPast && (
            <div style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 18 }}>
              {pastAnswers.map(a => (
                <div key={a.question_id} style={{
                  paddingRight: 14,
                  borderRight: '2px solid rgba(168,128,31,0.5)',
                }}>
                  <div style={{
                    fontSize: 11, color: '#8a8597',
                    marginBottom: 4, letterSpacing: 0.5,
                  }}>
                    {CATEGORY_LABELS[a.category] || a.category}
                  </div>
                  <div style={{
                    fontFamily: "'Frank Ruhl Libre', serif",
                    fontSize: 15, color: '#a8801f',
                    fontStyle: 'italic', marginBottom: 8,
                    lineHeight: 1.5,
                  }}>
                    {a.question}
                  </div>
                  <div style={{
                    fontSize: 14, color: '#3d3540',
                    lineHeight: 1.7, whiteSpace: 'pre-wrap',
                  }}>
                    {a.answer_text}
                  </div>
                  <button
                    onClick={() => startEdit(a)}
                    style={{
                      marginTop: 8, background: 'none', border: 'none',
                      color: '#8a8597', fontSize: 11, cursor: 'pointer',
                      padding: 0, letterSpacing: 0.5, opacity: 0.75,
                      fontFamily: 'inherit',
                    }}
                  >
                    ערוך
                  </button>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ───────── Journey Timeline Strip (self, in MyProfile) ─────────
// Replaces the old ProfileCompletionStrip. Shows total testimony BEU,
// last 3 moments, and a button to share a new one.
function JourneyTimelineSelf({ userId, googleSub, refresh, setRefresh }) {
  const [data, setData] = useState({ moments: [], total_testimony_beu: 0 });
  const [shareOpen, setShareOpen] = useState(false);
  useEffect(() => {
    if (!userId) return;
    api(`/api/journey/${userId}`).then(r => {
      if (r && !r.error) setData({ moments: r.moments || [], total_testimony_beu: r.total_testimony_beu || 0 });
    });
  }, [userId, refresh]);
  if (!userId) return null;
  return (
    <div style={{ background: 'linear-gradient(135deg, rgba(212,175,55,0.10), rgba(108,92,231,0.06))', border: '1px solid rgba(212,175,55,0.25)', borderRadius: 12, padding: 16, marginBottom: 16, maxWidth: 600, marginLeft: 'auto', marginRight: 'auto' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
        <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--gold)' }}>✨ המסע שלי</div>
        <div style={{ fontSize: 12, color: 'var(--muted)' }}>
          {data.total_testimony_beu > 0 ? `${data.total_testimony_beu} BEU של עדות` : 'תמורה לעדות אמיתית'}
        </div>
      </div>
      {data.moments.length === 0 ? (
        <div style={{ fontSize: 13, color: 'var(--text)', lineHeight: 1.6, marginBottom: 12 }}>
          הפרופיל הוא לא כרטיס ביקור — הוא מראה של נשמה במסע.<br/>
          שתף רגע אמיתי: שינוי פנימי, חסד שעשית, לימוד שהאיר, תפילה שנענתה.<br/>
          <span style={{ color: 'var(--gold)', fontSize: 12 }}>עדות = 25-60 BEU. עד 3 ביום.</span>
        </div>
      ) : (
        <div style={{ marginBottom: 12 }}>
          {data.moments.slice(0, 3).map(m => (
            <div key={m.id} style={{ borderRight: '2px solid rgba(212,175,55,0.4)', paddingRight: 10, marginBottom: 8, fontSize: 13 }}>
              <div style={{ color: 'var(--gold)', fontSize: 11, fontWeight: 600 }}>
                {m.kind_label}{m.reward_amount > 0 && <span style={{ color: 'var(--muted)', fontWeight: 400 }}> · +{m.reward_amount} BEU</span>}
              </div>
              {m.title && <div style={{ color: 'var(--text)', fontWeight: 600, marginTop: 2 }}>{m.title}</div>}
              <div style={{ color: 'var(--text)', marginTop: 2, lineHeight: 1.5 }}>
                {m.body.length > 120 ? m.body.slice(0, 120) + '…' : m.body}
              </div>
            </div>
          ))}
        </div>
      )}
      <button className="btn" onClick={() => setShareOpen(true)} style={{ width: '100%', padding: '10px', fontSize: 13 }}>
        ✍️ שתף רגע מהמסע
      </button>
      {shareOpen && (
        <ShareMomentModal
          userId={userId}
          googleSub={googleSub}
          onClose={() => setShareOpen(false)}
          onShared={() => { setShareOpen(false); setRefresh && setRefresh(r => r + 1); }}
        />
      )}
    </div>
  );
}

// ───────── Profile Completion Strip (gamified, in MyProfile) ─────────
function ProfileCompletionStrip({ userId, refresh }) {
  const [d, setD] = useState(null);
  useEffect(() => {
    if (!userId) return;
    api(`/api/profile/${userId}/disclosure`).then(r => setD(r && !r.error ? r : null));
  }, [userId, refresh]);
  if (!d) return null;
  const FIELD_LABELS = {
    tagline: 'שורת תקציר', bio: 'ביוגרפיה', mission: 'שליחות',
    location: 'מיקום', skills: 'כישורים', values: 'ערכים',
    contact_value: 'דרך קשר', avatar_url: 'תמונה', service: 'שירות שאני מציע', links: 'קישורים'
  };
  const next = d.next_field;
  return (
    <div style={{ background: 'linear-gradient(135deg, rgba(212,175,55,0.12), rgba(212,175,55,0.04))', border: '1px solid rgba(212,175,55,0.3)', borderRadius: 12, padding: 14, marginBottom: 16, maxWidth: 600, marginLeft: 'auto', marginRight: 'auto' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--gold)' }}>✨ הפרופיל שלי {d.progress_pct}%</div>
        <div style={{ fontSize: 12, color: 'var(--muted)' }}>{d.earned_beu}/{d.max_beu} BEU</div>
      </div>
      <div style={{ height: 6, background: 'rgba(255,255,255,0.05)', borderRadius: 3, overflow: 'hidden' }}>
        <div style={{ width: `${d.progress_pct}%`, height: '100%', background: 'linear-gradient(90deg, #d4af37, #ffa502)', transition: 'width 0.3s' }} />
      </div>
      {next && d.progress_pct < 100 && (
        <div style={{ fontSize: 12, color: 'var(--text)', marginTop: 8 }}>
          הוסף <span style={{ fontWeight: 600, color: 'var(--gold)' }}>{FIELD_LABELS[next.field] || next.field}</span> וקבל +{next.amount} BEU
        </div>
      )}
      {d.progress_pct >= 100 && (
        <div style={{ fontSize: 12, color: '#2ecc71', marginTop: 8, fontWeight: 600 }}>
          🎉 פרופיל מלא — קיבלת את כל ה־{d.max_beu} BEU
        </div>
      )}
    </div>
  );
}

function ProfileCard({ p, currentUserId, onOpenMember }) {
  const [sendOpen, setSendOpen] = useState(false);
  const [myBalances, setMyBalances] = useState([]);
  const [shareToast, setShareToast] = useState('');
  const initials = (p.name || '?').slice(0, 1);
  const openSend = async () => {
    if (!currentUserId) { alert('צריך להירשם'); return; }
    const me = await api(`/api/profile/${currentUserId}`);
    setMyBalances(me.balances || []);
    setSendOpen(true);
  };
  const shareCard = async () => {
    const link = `${window.location.origin}/community?m=${p.id}`;
    let copied = false;
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(link);
        copied = true;
      }
    } catch {}
    if (!copied) {
      // Fallback for non-secure contexts (no Clipboard API).
      const ta = document.createElement('textarea');
      ta.value = link; ta.style.position = 'fixed'; ta.style.opacity = '0';
      document.body.appendChild(ta); ta.select();
      try { copied = document.execCommand('copy'); } catch {}
      document.body.removeChild(ta);
    }
    setShareToast(copied ? 'הלינק הועתק ✓' : 'העתקה נכשלה');
    setTimeout(() => setShareToast(''), 2200);
  };
  const contactUrl = p.contact_method === 'telegram'
    ? `https://t.me/${String(p.contact_value || '').replace(/^@/, '')}`
    : p.contact_method === 'whatsapp'
    ? `https://wa.me/${(() => {
        const d = String(p.contact_value || '').replace(/[^0-9]/g, '');
        if (d.startsWith('972')) return d;
        if (d.startsWith('05') && d.length === 10) return '972' + d.slice(1);
        if (d.startsWith('5')  && d.length === 9)  return '972' + d;
        return d;
      })()}`
    : `mailto:${p.contact_value || ''}`;
  return (
    <div className="profile-card" style={{ position: 'relative' }}>
      <div className="profile-head" onClick={() => onOpenMember && onOpenMember(p.id)} style={{ cursor: onOpenMember ? 'pointer' : 'default' }}>
        <Avatar url={p.avatar_url} color={p.avatar_color} name={p.name} size={56} />
        <div>
          <div className="profile-name">{p.name}</div>
          {p.location && <div className="profile-loc">📍 {p.location}</div>}
        </div>
      </div>
      {p.mission && <div className="profile-mission">"{p.mission}"</div>}
      <div className="profile-bio">{p.bio || '—'}</div>
      <div className="profile-badges">
        <span className="badge gold">📖 {p.chapters || 0} פרקים</span>
      </div>
      {p.skills && p.skills.length > 0 && (
        <div className="profile-badges">
          {p.skills.slice(0, 4).map((s, i) => <span key={i} className="badge">{s}</span>)}
        </div>
      )}
      {p.contact_value && (
        <a className="btn ghost" href={contactUrl} target="_blank" rel="noreferrer"
           style={{ padding: '10px 16px', fontSize: 13, marginTop: 'auto', textAlign: 'center' }}>
          צור קשר ←
        </a>
      )}
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 8 }}>
        <button className="btn ghost" style={{ padding: '6px 10px', fontSize: 12, flex: 1 }} onClick={shareCard}>
          🔗 שתף את הכרטיס
        </button>
        <FollowButton userId={p.id} viewerId={currentUserId} />
        {currentUserId && currentUserId !== p.id && (
          <button className="btn" style={{ padding: '6px 10px', fontSize: 12, flex: 1 }} onClick={openSend}>
            🪙 שלח טוקנים
          </button>
        )}
      </div>
      {shareToast && (
        <div className="card-share-toast" role="status">{shareToast}</div>
      )}
      {sendOpen && (
        <TransferModal
          fromUserId={currentUserId}
          toUserId={p.id}
          toName={p.name}
          balances={myBalances}
          onClose={() => setSendOpen(false)}
          onSent={() => { setSendOpen(false); alert('נשלח!'); }}
        />
      )}
    </div>
  );
}

// ───────── Community Page ─────────
function CommunityPage({ userId }) {
  const [profiles, setProfiles] = useState([]);
  const [communities, setCommunities] = useState([]);
  const [q, setQ] = useState('');
  const [skill, setSkill] = useState('');
  const [loading, setLoading] = useState(true);
  const [openCommunity, setOpenCommunity] = useState(null);
  const [openMemberId, setOpenMemberId] = useState(() => {
    try {
      const m = new URLSearchParams(window.location.search).get('m');
      const n = m ? parseInt(m, 10) : NaN;
      return Number.isFinite(n) && n > 0 ? n : null;
    } catch { return null; }
  });

  const load = useCallback(() => {
    setLoading(true);
    const params = new URLSearchParams();
    if (q) params.set('q', q);
    if (skill) params.set('skill', skill);
    Promise.all([
      api('/api/profiles' + (params.toString() ? '?' + params : '')),
      api('/api/communities')
    ]).then(([p, c]) => {
      setProfiles(Array.isArray(p) ? p : []);
      setCommunities(Array.isArray(c) ? c : []);
    }).finally(() => setLoading(false));
  }, [q, skill]);

  useEffect(() => { load(); }, [load]);

  return (
    <section style={{ paddingTop: 60 }}>
      <div className="container">
        <h2 className="section-title">👥 מי חי בקהילה</h2>
        <p className="section-sub">חברי הקהילה — מצא שותפים, נותני שירות, ומורי דרך</p>

        <div className="search-bar">
          <input placeholder="חפש לפי שם / ביו..." value={q} onChange={e => setQ(e.target.value)} />
          <select value={skill} onChange={e => setSkill(e.target.value)}>
            <option value="">כל המתנות</option>
            {SKILL_OPTIONS.map(s => <option key={s} value={s}>{s}</option>)}
          </select>
        </div>

        {communities.length > 0 && (
          <div style={{ marginBottom: 24 }}>
            <h3 style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 20, marginBottom: 12 }}>קהילות פעילות</h3>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
              {communities.map(c => (
                <div key={c.id} onClick={() => setOpenCommunity(c.id)}
                     style={{ padding: '10px 16px', background: 'rgba(108,92,231,0.1)', border: '1px solid rgba(108,92,231,0.3)', borderRadius: 12, cursor: 'pointer' }}>
                  <strong style={{ color: 'var(--gold)' }}>{c.name}</strong>
                  <div style={{ fontSize: 11, color: 'var(--muted)' }}>{c.member_count} חברים · {c.location || c.type} · 💬 צ'אט</div>
                </div>
              ))}
            </div>
          </div>
        )}

        {loading && <p style={{ color: 'var(--muted)', textAlign: 'center' }}>טוען...</p>}
        {!loading && profiles.length === 0 && (
          <p style={{ color: 'var(--muted)', textAlign: 'center', fontStyle: 'italic' }}>אין חברים תואמים</p>
        )}
        <div className="profile-grid">
          {profiles.map(p => <ProfileCard key={p.id} p={p} currentUserId={userId} onOpenMember={setOpenMemberId} />)}
        </div>
      </div>
      {openCommunity && <CommunityDetail communityId={openCommunity} userId={userId} onClose={() => setOpenCommunity(null)} />}
      {openMemberId && <MemberDeepCard memberId={openMemberId} currentUserId={userId} onClose={() => setOpenMemberId(null)} />}
    </section>
  );
}

// ───────── My Profile ─────────
// ───────── Sign-out + rename UI ─────────
function SignOutButton() {
  const signOut = () => {
    if (!confirm('להתנתק מהחשבון?')) return;
    try {
      localStorage.removeItem('tehillim_user_id');
      localStorage.removeItem('tehillim_user_name');
      localStorage.removeItem('tehillim_google_sub');
      // Ask the Google Identity client to forget the auto-select too.
      if (window.google && window.google.accounts && window.google.accounts.id) {
        try { window.google.accounts.id.disableAutoSelect(); } catch {}
      }
    } catch {}
    window.location.reload();
  };
  return (
    <button
      onClick={signOut}
      title="יציאה"
      style={{
        position: 'absolute',
        top: 12, left: 12,
        background: 'rgba(255,107,157,0.08)',
        color: '#ff8fa9',
        border: '1px solid rgba(255,107,157,0.25)',
        borderRadius: 8,
        padding: '5px 12px',
        fontFamily: 'inherit',
        fontSize: 12, fontWeight: 700,
        cursor: 'pointer',
      }}
    >
      יציאה ←
    </button>
  );
}

function EditableName({ userId, currentName, onSaved }) {
  const [editing, setEditing] = useState(false);
  const [value, setValue] = useState(currentName || '');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');

  useEffect(() => { setValue(currentName || ''); }, [currentName]);

  const start = () => { setErr(''); setValue(currentName || ''); setEditing(true); };
  const cancel = () => { setEditing(false); setErr(''); setValue(currentName || ''); };

  const save = async () => {
    const trimmed = (value || '').trim();
    if (!trimmed) { setErr('שם לא יכול להיות ריק'); return; }
    if (trimmed === currentName) { setEditing(false); return; }
    const sub = localStorage.getItem('tehillim_google_sub');
    if (!sub) { setErr('צריך להתחבר מחדש עם Google'); return; }
    setBusy(true); setErr('');
    try {
      const r = await api(`/api/user/${userId}`, {
        method: 'PUT',
        body: { sub, name: trimmed },
      });
      if (r.error) throw new Error(r.error);
      try { localStorage.setItem('tehillim_user_name', r.name); } catch {}
      setEditing(false);
      onSaved && onSaved(r.name);
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  if (!editing) {
    return (
      <div className="profile-name" style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
        {currentName}
        <span
          role="button"
          onClick={start}
          title="ערוך שם"
          style={{
            cursor: 'pointer', fontSize: 13,
            color: 'var(--muted)', padding: '2px 6px',
            borderRadius: 4, background: 'rgba(255,255,255,0.04)',
            border: '1px solid rgba(255,255,255,0.08)',
          }}
        >✏️</span>
      </div>
    );
  }

  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
      <input
        autoFocus
        value={value}
        onChange={e => setValue(e.target.value)}
        onKeyDown={e => {
          if (e.key === 'Enter') save();
          if (e.key === 'Escape') cancel();
        }}
        maxLength={100}
        style={{
          fontSize: 20, fontWeight: 700,
          background: 'rgba(255,255,255,0.05)',
          border: '1px solid rgba(212,175,55,0.4)',
          color: '#e8d9aa',
          borderRadius: 6, padding: '4px 10px',
          minWidth: 140,
          fontFamily: 'inherit',
        }}
      />
      <button
        onClick={save}
        disabled={busy}
        style={{
          background: 'var(--gold)', color: '#1a1a2e',
          border: 'none', borderRadius: 6, padding: '5px 11px',
          fontWeight: 700, fontSize: 12, cursor: 'pointer',
        }}
      >{busy ? '...' : 'שמור'}</button>
      <button
        onClick={cancel}
        disabled={busy}
        style={{
          background: 'transparent', color: 'var(--muted)',
          border: '1px solid rgba(255,255,255,0.1)', borderRadius: 6,
          padding: '5px 11px', fontWeight: 600, fontSize: 12, cursor: 'pointer',
        }}
      >ביטול</button>
      {err && <div style={{ color: '#dc2626', fontSize: 12, width: '100%' }}>{err}</div>}
    </div>
  );
}

// [A2] TODO Damri: add ['vision', '🎯 חזון ומסע'] to Nav `items` array
// (top of file, function Nav). The route is already wired in App and the
// VisionPage component is defined below. Tile entry exists at the bottom of
// MyProfile until Nav has it.
// [yakir-fb#7 2026-05-24] "המעגלים שלי" — list of circles created by the user,
// each with an inline edit form (name / purpose / intention / whatsapp_url).
// Backed by GET /api/circles/by-creator/:userId + PATCH /api/circles/:id.
function MyCirclesSection({ userId, refresh }) {
  const [items, setItems] = React.useState(null);
  const [editingId, setEditingId] = React.useState(null);
  const [draft, setDraft] = React.useState({});
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [reloadKey, setReloadKey] = React.useState(0);
  const [collapsed, setCollapsed] = React.useState(() => { try { return localStorage.getItem('mycircles_collapsed_v1') === '1'; } catch { return false; } });

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/circles/by-creator/${userId}`).then(d => {
      if (d && Array.isArray(d.items)) setItems(d.items);
      else setItems([]);
    }).catch(() => setItems([]));
  }, [userId, refresh, reloadKey]);

  if (!userId || !items) return null;
  if (items.length === 0) return null; // hide section if no circles

  const startEdit = (c) => {
    setEditingId(c.id);
    setDraft({
      name: c.name || '',
      purpose: c.purpose || '',
      intention: c.intention || '',
      whatsapp_url: c.whatsapp_url || '',
    });
    setErr('');
  };
  const cancelEdit = () => { setEditingId(null); setDraft({}); setErr(''); };
  const del = async (c) => {
    if (!confirm(`למחוק את המעגל "${c.name}"? כל הקריאות והעוקבים יוסרו.`)) return;
    try {
      const r = await api(`/api/circle/${c.id}`, {
        method: 'DELETE',
        body: JSON.stringify({ user_id: userId }),
      });
      if (r && r.ok) {
        setReloadKey(k => k + 1);
      } else {
        alert((r && r.error) || 'מחיקה נכשלה');
      }
    } catch (e) { alert(e.message || 'שגיאה'); }
  };
  const save = async () => {
    if (!editingId) return;
    if (!draft.name || !draft.name.trim()) { setErr('שם המעגל חובה'); return; }
    setSaving(true); setErr('');
    try {
      const r = await api(`/api/circles/${editingId}`, {
        method: 'PATCH',
        body: JSON.stringify({ user_id: userId, ...draft }),
      });
      if (r && r.ok) {
        setEditingId(null); setDraft({});
        setReloadKey(k => k + 1);
      } else {
        setErr((r && r.error) || 'שמירה נכשלה');
      }
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setSaving(false);
    }
  };

  return (
    <section style={{ maxWidth: 600, margin: '24px auto', padding: '0 14px' }}>
      <h3 onClick={() => {
             const next = !collapsed;
             setCollapsed(next);
             try { localStorage.setItem('mycircles_collapsed_v1', next ? '1' : '0'); } catch {}
           }}
          style={{
            fontFamily: "'Frank Ruhl Libre', serif", fontSize: 22,
            margin: '0 0 12px', color: 'var(--gold)', cursor: 'pointer', userSelect: 'none',
            display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
          }}>
        <span>✦ המעגלים שלי <span style={{ fontSize: 13, color: 'var(--muted)', fontWeight: 400 }}>· {items.length}</span></span>
        <span style={{ fontSize: 14, color: 'var(--muted)' }}>{collapsed ? '▸' : '▾'}</span>
      </h3>
      {!collapsed && (<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {items.map(c => {
          const pct = c.total_chapters ? Math.min(100, Math.round((c.chapters_completed / c.total_chapters) * 100)) : 0;
          const isEditing = editingId === c.id;
          return (
            <div key={c.id} style={{
              background: '#fff',
              border: '1px solid var(--border, #e5ddd0)',
              borderRadius: 12, padding: '14px 16px',
              boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
            }}>
              {!isEditing ? (
                <>
                  <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 10 }}>
                    <div style={{ fontWeight: 700, fontSize: 16, color: 'var(--text)' }}>{c.name}</div>
                    <div style={{ display: 'flex', gap: 6 }}>
                      <button onClick={() => startEdit(c)} style={{
                        background: 'rgba(212,160,23,0.12)', border: '1px solid rgba(212,160,23,0.4)',
                        color: '#8a5e0a', fontSize: 12, fontWeight: 700, padding: '4px 10px',
                        borderRadius: 999, cursor: 'pointer', fontFamily: 'inherit',
                      }}>✎ ערוך</button>
                      <button onClick={() => del(c)} style={{
                        background: 'rgba(220,38,38,0.08)', border: '1px solid rgba(220,38,38,0.3)',
                        color: '#9b1c1c', fontSize: 12, fontWeight: 700, padding: '4px 10px',
                        borderRadius: 999, cursor: 'pointer', fontFamily: 'inherit',
                      }}>🗑 מחק</button>
                    </div>
                  </div>
                  {c.purpose && <div style={{ fontSize: 13, color: '#666', marginTop: 4, lineHeight: 1.5 }}>{c.purpose}</div>}
                  <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 8 }}>
                    {c.chapters_completed}/{c.total_chapters} פרקים · {pct}% · {c.participants} משתתפים
                    {c.is_complete ? ' · ✓ הושלם' : ''}
                    {c.is_featured ? ' · ★ נבחר' : ''}
                  </div>
                </>
              ) : (
                <div>
                  <label style={{ display: 'block', marginBottom: 8 }}>
                    <div style={{ fontSize: 12, color: '#666', marginBottom: 3 }}>שם המעגל *</div>
                    <input value={draft.name} onChange={e => setDraft(d => ({ ...d, name: e.target.value }))} maxLength={120}
                      style={{ width: '100%', padding: 8, fontSize: 14, fontFamily: 'inherit' }} />
                  </label>
                  <label style={{ display: 'block', marginBottom: 8 }}>
                    <div style={{ fontSize: 12, color: '#666', marginBottom: 3 }}>מטרה</div>
                    <textarea value={draft.purpose} onChange={e => setDraft(d => ({ ...d, purpose: e.target.value }))} rows={2} maxLength={500}
                      style={{ width: '100%', padding: 8, fontSize: 14, fontFamily: 'inherit', resize: 'vertical' }} />
                  </label>
                  <label style={{ display: 'block', marginBottom: 8 }}>
                    <div style={{ fontSize: 12, color: '#666', marginBottom: 3 }}>כוונה</div>
                    <textarea value={draft.intention} onChange={e => setDraft(d => ({ ...d, intention: e.target.value }))} rows={2} maxLength={1000}
                      style={{ width: '100%', padding: 8, fontSize: 14, fontFamily: 'inherit', resize: 'vertical' }} />
                  </label>
                  <label style={{ display: 'block', marginBottom: 10 }}>
                    <div style={{ fontSize: 12, color: '#666', marginBottom: 3 }}>קישור WhatsApp</div>
                    <input value={draft.whatsapp_url} onChange={e => setDraft(d => ({ ...d, whatsapp_url: e.target.value }))} placeholder="https://chat.whatsapp.com/..."
                      style={{ width: '100%', padding: 8, fontSize: 14, fontFamily: 'inherit', direction: 'ltr' }} />
                  </label>
                  {err && <div style={{ color: '#c00', fontSize: 13, marginBottom: 8 }}>{err}</div>}
                  <div style={{ display: 'flex', gap: 8 }}>
                    <button onClick={save} disabled={saving} style={{
                      flex: 1, background: 'var(--gold, #d4a017)', color: '#1a1a2e',
                      border: 'none', borderRadius: 8, padding: '10px', fontSize: 14,
                      fontWeight: 700, cursor: saving ? 'wait' : 'pointer', fontFamily: 'inherit',
                    }}>{saving ? 'שומר...' : 'שמור'}</button>
                    <button onClick={cancelEdit} disabled={saving} style={{
                      background: '#f0f0f0', color: '#333', border: 'none', borderRadius: 8,
                      padding: '10px 16px', fontSize: 14, cursor: 'pointer', fontFamily: 'inherit',
                    }}>בטל</button>
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>)}
    </section>
  );
}

// [handoff §10 2026-05-24] FollowButton — toggle עוקב/עקוב on a user.
// Backed by POST/DELETE /api/users/:id/follow + GET /follow-status.
// Gentle styling — invitation not transaction. Hidden when viewing self.
function FollowButton({ userId, viewerId, onChange }) {
  const [following, setFollowing] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  React.useEffect(() => {
    if (!userId || !viewerId || userId === viewerId) return;
    api(`/api/users/${userId}/follow-status?viewer=${viewerId}`)
      .then(d => setFollowing(!!(d && d.following)))
      .catch(() => setFollowing(false));
  }, [userId, viewerId]);
  if (!viewerId || !userId || userId === viewerId || following === null) return null;
  const toggle = async () => {
    if (busy) return;
    setBusy(true);
    try {
      const method = following ? 'DELETE' : 'POST';
      await api(`/api/users/${userId}/follow`, { method, body: JSON.stringify({ follower_id: viewerId }) });
      setFollowing(!following);
      if (onChange) onChange(!following);
    } finally { setBusy(false); }
  };
  return (
    <button onClick={toggle} disabled={busy} style={{
      background: following ? 'rgba(95,163,114,0.10)' : 'linear-gradient(135deg, rgba(212,175,55,0.18), rgba(95,163,114,0.12))',
      border: '1px solid ' + (following ? 'rgba(95,163,114,0.45)' : 'rgba(212,175,55,0.55)'),
      color: '#1d4029', padding: '8px 18px', borderRadius: 999,
      fontFamily: 'inherit', fontSize: 13, fontWeight: 600,
      cursor: busy ? 'wait' : 'pointer', whiteSpace: 'nowrap',
      transition: 'background .15s, transform .1s',
    }}>{following ? '✓ מלווה אותך' : '+ ללוות'}</button>
  );
}

// [handoff §10 2026-05-24] FollowCircleButton — same idea for circles.
function FollowCircleButton({ circleId, viewerId, onChange }) {
  const [following, setFollowing] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  React.useEffect(() => {
    if (!circleId || !viewerId) return;
    api(`/api/circles/${circleId}/follow-status?viewer=${viewerId}`)
      .then(d => setFollowing(!!(d && d.following)))
      .catch(() => setFollowing(false));
  }, [circleId, viewerId]);
  if (!viewerId || !circleId || following === null) return null;
  const toggle = async () => {
    if (busy) return;
    setBusy(true);
    try {
      const method = following ? 'DELETE' : 'POST';
      await api(`/api/circles/${circleId}/follow`, { method, body: JSON.stringify({ user_id: viewerId }) });
      setFollowing(!following);
      if (onChange) onChange(!following);
    } finally { setBusy(false); }
  };
  return (
    <button onClick={(e) => { e.stopPropagation(); toggle(); }} disabled={busy} style={{
      background: following ? 'rgba(212,175,55,0.10)' : 'transparent',
      border: '1px dashed ' + (following ? 'rgba(212,175,55,0.55)' : 'rgba(95,163,114,0.45)'),
      color: '#5a3d0a', padding: '5px 12px', borderRadius: 999,
      fontFamily: 'inherit', fontSize: 12, fontWeight: 600, cursor: busy ? 'wait' : 'pointer',
    }}>{following ? '✓ מלווה' : '+ ללוות מעגל'}</button>
  );
}

// [handoff §10 2026-05-24] PublicProfilePage — דף פרופיל ציבורי מאוחד.
// Spirit (§0): אוצר לא דאטה, אור לא מספרים. אין אחוזים. אין level-up.
// Soul layer = 5 glowing dots (lit up to current). Motion word, not %.
// Treasures revealed = only fields the person has filled.
function PublicProfilePage({ profileUserId, viewerId, onPage }) {
  const [profile, setProfile] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    if (!profileUserId) return;
    setLoading(true); setErr('');
    api(`/api/profile/${profileUserId}`).then(p => {
      if (!p || p.error) { setErr(p && p.error ? p.error : 'profile_not_found'); }
      else setProfile(p);
    }).catch(e => setErr(e.message || 'load_failed')).finally(() => setLoading(false));
  }, [profileUserId]);

  if (!profileUserId) return <section style={{ padding: 60, textAlign: 'center' }}><p style={{ color: 'var(--muted)' }}>לא נבחר פרופיל</p></section>;
  if (loading) return <section style={{ padding: 60, textAlign: 'center' }}><p style={{ color: 'var(--muted)' }}>טוען...</p></section>;
  if (err || !profile) return <section style={{ padding: 60, textAlign: 'center' }}><p style={{ color: '#a8801f' }}>הפרופיל עדיין שקט</p></section>;

  const isSelf = viewerId && parseInt(viewerId, 10) === parseInt(profileUserId, 10);
  const journey = profile.journey || { ladder: ['נפש','רוח','נשמה','חיה','יחידה'], layer_index: 0, soul_layer: 'נפש', motion: 'מתייצב', next_layer: 'רוח' };
  const ladder = Array.isArray(journey.ladder) && journey.ladder.length > 0 ? journey.ladder : ['נפש','רוח','נשמה','חיה','יחידה'];
  const layerIdx = Math.max(0, Math.min(ladder.length - 1, journey.layer_index || 0));

  // Treasures: only show what's filled. Order = invitation to deepen — visible first what they have.
  const treasures = [
    { key: 'mission',          label: 'הייעוד',                value: profile.mission },
    { key: 'gift',             label: 'המתנה שעוברת דרכי',     value: profile.gift },
    { key: 'aliveness',        label: 'איפה אני הכי חי',       value: profile.aliveness },
    { key: 'dream',            label: 'חלום שעדיין מחפש בית',  value: profile.dream },
    { key: 'gather',           label: 'אנשים שאני מבקש לחבר',  value: profile.gather },
    { key: 'tzadik',           label: 'צדיק שמלווה',           value: profile.tzadik },
    { key: 'influential_book', label: 'ספר שהאיר את הדרך',     value: profile.influential_book },
    { key: 'arrived_via',      label: 'איך הגעתי לכאן',        value: profile.arrived_via },
  ].filter(t => t.value && String(t.value).trim());

  const tefilahNames = [
    { key: 'full_name',   label: 'שם לתפילה', value: profile.full_name },
    { key: 'mother_name', label: 'שם האם',    value: profile.mother_name },
  ].filter(t => t.value && String(t.value).trim());

  const displayName = profile.name || 'אדם מהקהילה';
  const initials = (displayName.trim()[0] || '?').toUpperCase();
  const personalCh = profile.personal_chapter;
  const birthLabel = profile.hebrew_birth_label;
  const mentor = profile.mentor_name;
  const followers = profile.followers_count || 0;
  const following = profile.following_count || 0;

  return (
    <section dir="rtl" style={{ maxWidth: 640, margin: '0 auto', padding: '32px 18px 60px' }}>
      {/* Header — avatar + name + invitation header */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 18 }}>
        {profile.avatar_url ? (
          <img src={profile.avatar_url} alt="" style={{ width: 64, height: 64, borderRadius: '50%', objectFit: 'cover', border: '2px solid ' + (profile.avatar_color || '#d4a017') }} />
        ) : (
          <div style={{
            width: 64, height: 64, borderRadius: '50%',
            background: profile.avatar_color || '#a8801f', color: '#fff',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 26, fontWeight: 800,
          }}>{initials}</div>
        )}
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 26, fontWeight: 800, color: 'var(--text, #1a1a2e)', lineHeight: 1.2 }}>{displayName}</div>
          {profile.location && <div style={{ fontSize: 13, color: '#6b6d73', marginTop: 2 }}>📍 {profile.location}</div>}
        </div>
        {!isSelf && <FollowButton userId={profileUserId} viewerId={viewerId} />}
      </div>

      {isSelf && (
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
          color: '#5a3d0a', fontSize: 15, marginBottom: 24, textAlign: 'center',
        }}>המסע שלך עדיין מתגלה</div>
      )}

      {/* [damri 2026-05-24] רובד הנשמה הוסר מהתצוגה — שכבה פנימית שקטה, לא דרגה גלויה. */}

      {/* Personal chapter (Tehillim) — only if hebrew birth set */}
      {personalCh && (
        <div style={{
          background: 'linear-gradient(135deg, rgba(212,175,55,0.10), rgba(184,148,31,0.08))',
          border: '1px solid rgba(212,175,55,0.35)', borderRadius: 14,
          padding: '14px 18px', marginBottom: 18, textAlign: 'center',
        }}>
          <div style={{ fontSize: 11, color: '#5a3d0a', letterSpacing: 2, fontWeight: 700 }}>הפרק בתהילים שלי</div>
          <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 28, fontWeight: 800, color: '#5a3d0a', margin: '4px 0' }}>פרק {personalCh}</div>
          {birthLabel && <div style={{ fontSize: 12, color: '#6b6d73' }}>{birthLabel}</div>}
        </div>
      )}

      {/* Treasures revealed */}
      {treasures.length > 0 && (
        <div style={{ marginBottom: 18 }}>
          <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18, fontWeight: 700, color: 'var(--text, #1a1a2e)', marginBottom: 12 }}>✦ אוצרות שהתגלו</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {treasures.map(t => (
              <div key={t.key} style={{
                background: '#fff', border: '0.5px solid rgba(212,175,55,0.28)',
                borderRadius: 10, padding: '11px 14px',
              }}>
                <div style={{ fontSize: 11, color: '#5a3d0a', fontWeight: 700, letterSpacing: 1, marginBottom: 4 }}>{t.label}</div>
                <div style={{ fontSize: 14, color: 'var(--text, #1a1a2e)', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>{t.value}</div>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Names for tefilah (private but visible on public page per spirit — anyone can pray for you) */}
      {tefilahNames.length > 0 && (
        <div style={{ marginBottom: 18 }}>
          <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 16, fontWeight: 700, color: '#5a3d0a', marginBottom: 10 }}>🕯️ לתפילה</div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
            {tefilahNames.map(t => (
              <div key={t.key} style={{
                background: 'rgba(212,175,55,0.08)', border: '1px solid rgba(212,175,55,0.30)',
                borderRadius: 999, padding: '6px 14px', fontSize: 13,
              }}>
                <span style={{ color: '#6b6d73', fontSize: 11 }}>{t.label}:</span> <strong>{t.value}</strong>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Mentor honoring */}
      {mentor && (
        <div style={{
          background: 'rgba(106,76,176,0.06)', border: '1px dashed rgba(106,76,176,0.30)',
          borderRadius: 12, padding: '14px 18px', marginBottom: 18, textAlign: 'center',
        }}>
          <div style={{ fontSize: 12, color: '#4a3a76', fontStyle: 'italic' }}>תודה על ההשפעה</div>
          <div style={{ fontSize: 16, fontWeight: 700, color: '#4a3a76', marginTop: 2 }}>✦ {mentor}</div>
        </div>
      )}

      {/* Connection counts — quiet footer */}
      {(followers > 0 || following > 0) && (
        <div style={{
          display: 'flex', justifyContent: 'center', gap: 24,
          paddingTop: 14, borderTop: '1px dashed rgba(0,0,0,0.10)',
          fontSize: 13, color: '#6b6d73',
        }}>
          {followers > 0 && <div><strong>{followers}</strong> מלווים</div>}
          {following > 0 && <div><strong>{following}</strong> מלווה</div>}
        </div>
      )}

      {/* If empty profile - gentle invitation */}
      {treasures.length === 0 && tefilahNames.length === 0 && !personalCh && (
        <div style={{
          textAlign: 'center', padding: '28px 20px',
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
          color: '#6b6d73', fontSize: 15,
        }}>
          {isSelf
            ? 'הפרופיל שלך עוד שקט. כל שאלה שתענה במסע פותחת חלון נוסף.'
            : 'הפרופיל שלו עוד שקט.'}
          {isSelf && onPage && (
            <div style={{ marginTop: 14 }}>
              <button onClick={() => onPage('journey')} style={{
                background: 'linear-gradient(135deg, #d4a017, #f0c850)',
                color: '#1a1a2e', border: 'none', padding: '10px 22px',
                borderRadius: 999, fontSize: 14, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
              }}>✦ התחל את המסע</button>
            </div>
          )}
        </div>
      )}
    </section>
  );
}

function MyProfile({ userId, onOnboard, refresh, setRefresh }) {
  const [profile, setProfile] = useState(null);
  const [zekut, setZekut] = useState(null);  // for empty-state gating of outputs
  const [loading, setLoading] = useState(true);
  const [offerOpen, setOfferOpen] = useState(false);
  const [editOpen, setEditOpen] = useState(false);
  const [addWishOpen, setAddWishOpen] = useState(false);
  const googleSub = typeof localStorage !== 'undefined' ? localStorage.getItem('tehillim_google_sub') : null;

  useEffect(() => {
    if (!userId) { setLoading(false); return; }
    setLoading(true);
    Promise.all([
      api(`/api/profile/${userId}`),
      api(`/api/zekut/${userId}`).catch(() => null),
    ]).then(([p, z]) => {
      setProfile(p && !p.error ? p : null);
      setZekut(z && !z.error ? z : null);
    }).finally(() => setLoading(false));
  }, [userId, refresh]);

  if (!userId) {
    return (
      <section style={{ paddingTop: 60 }}>
        <div className="container" style={{ textAlign: 'center' }}>
          <h2 className="section-title">👤 הפרופיל שלי</h2>
          <p className="section-sub">צריך להירשם כדי לראות פרופיל</p>
          <button className="btn" onClick={onOnboard}>הצטרף לכאן</button>
        </div>
      </section>
    );
  }
  if (loading) return <section style={{ paddingTop: 60 }}><div className="container"><p style={{ color: 'var(--muted)', textAlign: 'center' }}>טוען...</p></div></section>;
  if (!profile) return <section style={{ paddingTop: 60 }}><div className="container"><p style={{ color: '#dc2626', textAlign: 'center' }}>פרופיל לא נמצא</p></div></section>;

  const inviteFriend = () => {
    const code = profile.referral_code || '';
    const link = `${window.location.origin}/?ref=${code}`;
    const text = `בית דויד — מקום שבו אדם נמדד לפי האור שהוא מביא לעולם.\n${link}`;
    if (navigator.share) {
      navigator.share({ title: 'בית דויד', text, url: link }).catch(() => {});
    } else if (navigator.clipboard) {
      navigator.clipboard.writeText(text).then(() => showToast('הקישור הועתק ✓')).catch(() => {});
    }
  };

  // Empty-state gates from /api/zekut details
  const det = (zekut && zekut.details) || {};
  const hasWishes = (det.wishes_posted || []).length > 0;
  const hasServices = (det.gifts_offered || []).length > 0;
  const hasRequests = (det.gifts_requested || []).length > 0;

  return (
    <section style={{ paddingTop: 40 }}>
      <div className="container">
        {/* ───── TOP — Mirror (how the community sees you) ───── */}
        <ProfileMirror
          userId={userId}
          profile={profile}
          googleSub={googleSub}
          refresh={refresh}
          setRefresh={setRefresh}
          onEdit={() => setEditOpen(true)}
        />

        {/* [yakir pass 20] Coin balances + updates inbox on profile */}
        <BalanceStrip userId={userId} refresh={refresh} />
        <UpdatesPanel userId={userId} refresh={refresh} />

        {/* [yakir 2026-05-19] "התניא שלי" — level + Tanya coins + chapter progress */}
        <MyTanyaCard userId={userId} refresh={refresh} />

        {/* [yakir pass 26] My readers — everyone who read in my circles, with thanks button */}
        <MyReaders userId={userId} />

        {/* ───── Layer 1: זרימה של חיים ───── */}
        <ImpactStream userId={userId} profile={profile} />

        <details style={{ maxWidth: 600, margin: '24px auto 0' }}>
          <summary style={{
            cursor: 'pointer', textAlign: 'center', padding: '10px 12px',
            fontSize: 11, letterSpacing: 3, color: '#a8801f',
            textTransform: 'uppercase', fontWeight: 700,
            listStyle: 'none', userSelect: 'none',
          }}>פעולות נוספות ▾</summary>
          <QuickActions
            onOfferGift={() => setOfferOpen(true)}
            onPostWish={() => setAddWishOpen(true)}
            onInvite={inviteFriend}
          />
        </details>

        {/* Personal tehillim chapter — 'פרק השנה שלך' */}
        <PersonalChapter userId={userId} profile={profile} refresh={refresh} setRefresh={setRefresh} />

        {/* [yakir-fb#7 2026-05-24] My circles - shown only if user created any */}
        <MyCirclesSection userId={userId} refresh={refresh} />

        {/* Outputs — only when there's something to show */}
        {hasWishes && <WishesSection userId={userId} ownerId={userId} onChange={() => setRefresh(r => r + 1)} />}
        {hasServices && <MyServices userId={userId} refresh={refresh} onOffer={() => setOfferOpen(true)} />}
        {hasRequests && <MyRequests userId={userId} refresh={refresh} />}

        {/* ───── Bottom — quiet sign out + auth banner if anonymous ───── */}
        {/* [handoff §10 2026-05-24] Link to view your own public profile */}
        <div style={{ maxWidth: 600, margin: '20px auto 0', textAlign: 'center' }}>
          <button onClick={() => window.location.hash = `#/user/${userId}`} style={{
            background: 'transparent', border: '1px dashed rgba(95,163,114,0.45)',
            color: '#1d4029', padding: '8px 16px', borderRadius: 999,
            fontSize: 13, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit',
          }}>👁 איך אחרים רואים אותי</button>
        </div>
        <div style={{ maxWidth: 600, margin: '32px auto 0', textAlign: 'center' }}>
          <SignOutButton />
        </div>
        <LoggedInAs profile={profile} onUpgrade={() => { localStorage.removeItem('tehillim_user_id'); localStorage.removeItem('tehillim_user_name'); window.location.reload(); }} />
      </div>

      {offerOpen && <OfferServiceModal userId={userId} onClose={() => setOfferOpen(false)} onCreated={() => { setOfferOpen(false); setRefresh(r => r + 1); }} />}
      {addWishOpen && <AddWishModal userId={userId} onClose={() => setAddWishOpen(false)} onAdded={() => { setAddWishOpen(false); setRefresh(r => r + 1); }} />}
      {editOpen && googleSub && (
        <EditProfileModal googleSub={googleSub} onClose={() => setEditOpen(false)} onSaved={() => setRefresh(r => r + 1)} />
      )}
    </section>
  );
}

function getSeferReviiWeekKey() {
  const d = new Date();
  const onejan = new Date(d.getFullYear(), 0, 1);
  const wk = Math.ceil((((d - onejan) / 86400000) + onejan.getDay() + 1) / 7);
  return d.getFullYear() + '-W' + String(wk).padStart(2, '0');
}

function SeferReviiRabbiPage() {
  const [openIdx, setOpenIdx] = useState(null);
  const [chapterText, setChapterText] = useState({});
  const [loadingPsalm, setLoadingPsalm] = useState(null);
  const [readSet, setReadSet] = useState(() => {
    try {
      const raw = localStorage.getItem('sefer_revii_read_' + getSeferReviiWeekKey());
      return new Set(raw ? JSON.parse(raw) : []);
    } catch (e) { return new Set(); }
  });

  const isWednesday = new Date().getDay() === 3;
  const total = SEFER_REVII_RABBI.chapters.length;
  const completed = readSet.size;
  const pct = Math.round((completed / total) * 100);

  const persistRead = (next) => {
    setReadSet(next);
    try {
      localStorage.setItem('sefer_revii_read_' + getSeferReviiWeekKey(), JSON.stringify([...next]));
    } catch (e) {}
  };

  const toggleOpen = async (psalmNum, idx) => {
    if (openIdx === idx) { setOpenIdx(null); return; }
    setOpenIdx(idx);
    if (!chapterText[psalmNum]) {
      setLoadingPsalm(psalmNum);
      try {
        const r = await api('/api/text/tehillim/' + psalmNum);
        setChapterText(prev => ({ ...prev, [psalmNum]: r }));
      } catch (e) {} finally { setLoadingPsalm(null); }
    }
  };

  const toggleRead = (idx) => {
    const next = new Set(readSet);
    if (next.has(idx)) next.delete(idx); else next.add(idx);
    persistRead(next);
  };

  const resetWeek = () => {
    if (!confirm('לאפס את ההתקדמות השבוע?')) return;
    persistRead(new Set());
  };

  return (
    <section style={{ paddingTop: 60 }}>
      <div className="container" style={{ maxWidth: 720 }}>
        <div style={{ textAlign: 'center', marginBottom: 22 }}>
          <h2 className="section-title" style={{ fontSize: 26, marginBottom: 6 }}>
            📖 קריאת ח״י פרקי תהילים
          </h2>
          <p className="section-sub" style={{ marginTop: 0 }}>
            יום רביעי · ח״י (18) פרקים · פרק צ׳ עד ק״ו, ומזמור קל״ו
          </p>
          {isWednesday && (
            <div style={{
              display: 'inline-block', marginTop: 8,
              background: 'linear-gradient(90deg, #d4a017, #a8800a)',
              color: 'var(--text)', padding: '5px 14px', borderRadius: 14,
              fontSize: 13, fontWeight: 700,
              boxShadow: '0 2px 8px rgba(168,128,10,0.25)',
            }}>
              📅 היום יום רביעי — זמן הקריאה
            </div>
          )}
        </div>

        <div style={{ marginBottom: 18 }}>
          <div style={{
            display: 'flex', justifyContent: 'space-between',
            alignItems: 'baseline', fontSize: 13, color: '#4b5563', marginBottom: 5,
          }}>
            <span>ההתקדמות השבוע</span>
            <span>
              <span style={{ fontWeight: 700, color: '#8b6508' }}>{completed}/{total}</span>
              <span style={{ color: 'var(--muted)', marginRight: 6 }}>· {pct}%</span>
              {completed > 0 && (
                <button onClick={resetWeek} style={{
                  background: 'transparent', border: 'none', cursor: 'pointer',
                  color: 'var(--muted)', fontSize: 11, marginRight: 8,
                  textDecoration: 'underline',
                }}>אפס</button>
              )}
            </span>
          </div>
          <div style={{
            width: '100%', height: 10, borderRadius: 6,
            background: 'rgba(26,26,46,0.08)',
            border: '1px solid rgba(26,26,46,0.1)',
            overflow: 'hidden',
          }}>
            <div style={{
              width: pct + '%', height: '100%',
              background: 'linear-gradient(90deg, #a8800a, #d4a017)',
              borderRadius: 6, transition: 'width 0.6s ease-out',
              boxShadow: pct === 100 ? '0 0 12px rgba(168,128,10,0.6)' : 'none',
            }} />
          </div>
        </div>

        <div style={{ display: 'grid', gap: 8 }}>
          {SEFER_REVII_RABBI.chapters.map((psalmNum, idx) => {
            const isOpen = openIdx === idx;
            const isRead = readSet.has(idx);
            const text = chapterText[psalmNum];
            const loading = loadingPsalm === psalmNum;
            return (
              <div key={idx} style={{
                border: '1px solid ' + (isRead ? 'rgba(168,128,10,0.35)' : 'rgba(26,26,46,0.1)'),
                borderRadius: 10, background: 'var(--text)', overflow: 'hidden',
                transition: 'border-color 0.3s',
              }}>
                <div style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '10px 14px',
                  background: isRead ? 'linear-gradient(90deg, rgba(168,128,10,0.07), rgba(212,160,23,0.03))' : 'var(--text)',
                }}>
                  <button
                    type="button"
                    onClick={() => toggleRead(idx)}
                    aria-label={isRead ? 'סמן כלא־קרוא' : 'סמן כקרוא'}
                    style={{
                      width: 30, height: 30, borderRadius: 7, flexShrink: 0,
                      border: '2px solid ' + (isRead ? '#a8800a' : '#cbd0d6'),
                      background: isRead ? '#a8800a' : 'var(--text)',
                      color: 'var(--text)', cursor: 'pointer', fontSize: 16, fontWeight: 700,
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      transition: 'all 0.2s',
                    }}
                  >{isRead ? '✓' : ''}</button>
                  <div style={{ flex: 1, cursor: 'pointer', minWidth: 0 }} onClick={() => toggleOpen(psalmNum, idx)}>
                    <div style={{ fontSize: 15, fontWeight: 700, color: '#1a1a2e' }}>
                      {idx + 1}. תהילים {SEFER_REVII_RABBI.hebrewLetters[idx]}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 2 }}>
                      פרק {psalmNum}
                    </div>
                  </div>
                  <button
                    type="button"
                    onClick={() => toggleOpen(psalmNum, idx)}
                    aria-label={isOpen ? 'סגור' : 'פתח'}
                    style={{
                      background: 'transparent', border: 'none', flexShrink: 0,
                      cursor: 'pointer', fontSize: 18, color: '#8b6508',
                      padding: '4px 8px',
                    }}
                  >{isOpen ? '▲' : '▼'}</button>
                </div>
                {isOpen && (
                  <div style={{
                    padding: '14px 18px',
                    borderTop: '1px solid rgba(26,26,46,0.08)',
                    background: '#fafaf7',
                    fontSize: 17, lineHeight: 1.95,
                    color: '#1a1a2e', textAlign: 'justify',
                    direction: 'rtl',
                  }}>
                    {loading && !text && <div style={{ color: 'var(--muted)', textAlign: 'center' }}>טוען...</div>}
                    {text && (
                      <>
                        <div style={{
                          fontSize: 12, color: '#8b6508',
                          marginBottom: 8, fontWeight: 700,
                        }}>{text.he_ref}</div>
                        <div style={{ fontFamily: 'serif' }}>{text.hebrew || text.hebrew_clean}</div>
                        {!isRead && (
                          <div style={{ marginTop: 14, textAlign: 'center' }}>
                            <button
                              type="button"
                              onClick={() => toggleRead(idx)}
                              style={{
                                background: 'linear-gradient(90deg, #a8800a, #d4a017)',
                                color: 'var(--text)', border: 'none', borderRadius: 8,
                                padding: '8px 22px', fontSize: 14, fontWeight: 700,
                                cursor: 'pointer',
                                boxShadow: '0 2px 8px rgba(168,128,10,0.3)',
                              }}
                            >✓ סיימתי לקרוא</button>
                          </div>
                        )}
                      </>
                    )}
                  </div>
                )}
              </div>
            );
          })}
        </div>

        {completed === total && (
          <div style={{
            marginTop: 24, padding: 18,
            background: 'linear-gradient(90deg, rgba(168,128,10,0.12), rgba(212,160,23,0.08))',
            border: '1px solid rgba(168,128,10,0.3)',
            borderRadius: 12, textAlign: 'center',
            color: '#1a1a2e',
          }}>
            <div style={{ fontSize: 20, marginBottom: 4 }}>🕯️ תזכה גדולה</div>
            <div style={{ fontSize: 14, color: '#4b5563' }}>
              סיימת את כל ח״י המזמורים לזכות הרב.
            </div>
          </div>
        )}

        <div style={{
          marginTop: 24, padding: 14,
          background: 'rgba(168,128,10,0.05)',
          border: '1px solid rgba(168,128,10,0.16)',
          borderRadius: 10, fontSize: 13,
          color: '#4b5563', textAlign: 'center', lineHeight: 1.7,
        }}>
          📖 ח״י (18) פרקי תהילים — ספר רביעי (פרק צ׳ עד ק״ו) בתוספת מזמור קל״ו (הלל הגדול).  לזכות הרב, נקראים ביום רביעי.
          <br />
          <span style={{ color: 'var(--muted)', fontSize: 11 }}>
            הקריאה לזכות הרב · ההתקדמות נשמרת מקומית ומתאפסת בכל שבוע
          </span>
        </div>
      </div>
    </section>
  );
}

// ───────── Admin Panel ─────────
function TrustCodesSection({ adminId }) {
  const [codes, setCodes] = React.useState([]);
  const [users, setUsers] = React.useState([]);
  const [targetUserId, setTargetUserId] = React.useState('');
  const [isOpen, setIsOpen] = React.useState(false); // open code = shareable in a group
  const [maxUses, setMaxUses] = React.useState('100');
  const [notes, setNotes] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [issued, setIssued] = React.useState(null); // last-issued code for display

  const refresh = React.useCallback(() => {
    api(`/api/admin/trust-codes?admin_id=${adminId}`).then(r => {
      if (Array.isArray(r)) setCodes(r);
    }).catch(() => {});
  }, [adminId]);

  React.useEffect(() => {
    refresh();
    api('/api/users-lite').then(r => { if (Array.isArray(r)) setUsers(r); }).catch(() => {});
  }, [refresh]);

  const issue = async () => {
    if (!isOpen && !targetUserId) { setErr('בחר משתמש או סמן קוד פתוח'); return; }
    setBusy(true); setErr('');
    try {
      const r = await api('/api/admin/trust-codes', {
        method: 'POST',
        body: {
          admin_id: adminId,
          user_id: isOpen ? 0 : parseInt(targetUserId, 10),
          max_uses: maxUses === '' ? null : parseInt(maxUses, 10),
          notes: notes.trim() || null,
        },
      });
      if (r.error) throw new Error(r.error);
      setIssued(r);
      setNotes('');
      refresh();
    } catch (e) { setErr(e.message || 'שגיאה'); }
    finally { setBusy(false); }
  };

  const revoke = async (id) => {
    if (!confirm('לבטל את הקוד הזה? הפעולה בלתי-הפיכה.')) return;
    await api(`/api/admin/trust-codes/${id}?admin_id=${adminId}`, { method: 'DELETE' });
    refresh();
  };

  return (
    <section style={{
      background: 'var(--text)', borderRadius: 12, padding: 18, marginBottom: 18,
      border: '1px solid #e5e0d0',
    }}>
      <h3 style={{ marginTop: 0, color: '#1a1a2e' }}>🔑 קודי אמון</h3>
      <p style={{ fontSize: 13, color: '#5a5a6a', marginTop: 0 }}>
        הנפק קוד אישי למשתמש בעל אמון גבוה — הוא יוכל לאמת קריאת פרק
        ע״י הזנת הקוד במקום הקלטה (לקוראים מספר אמיתי).
      </p>

      <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, color: '#5a5a6a', marginBottom: 10, cursor: 'pointer' }}>
        <input
          type="checkbox"
          checked={isOpen}
          onChange={e => setIsOpen(e.target.checked)}
          style={{ width: 16, height: 16, cursor: 'pointer' }}
        />
        <span>👥 קוד פתוח לקבוצה (ניתן להעביר לכמה משתמשים)</span>
      </label>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 12 }}>
        <div>
          <label style={{ fontSize: 12, color: isOpen ? '#bbb' : '#5a5a6a' }}>משתמש</label>
          <select
            value={targetUserId}
            disabled={isOpen}
            onChange={e => setTargetUserId(e.target.value)}
            style={{
              width: '100%', padding: '8px 10px', fontSize: 14, borderRadius: 6,
              border: '1px solid #d0c8b0',
              opacity: isOpen ? 0.5 : 1, cursor: isOpen ? 'not-allowed' : 'pointer',
            }}
          >
            <option value="">{isOpen ? '— קוד פתוח —' : 'בחר משתמש...'}</option>
            {users.map(u => (
              <option key={u.id} value={u.id}>{u.name} (id {u.id})</option>
            ))}
          </select>
        </div>
        <div>
          <label style={{ fontSize: 12, color: '#5a5a6a' }}>מקסימום שימושים (ריק = ללא הגבלה)</label>
          <input
            type="number" min="1" value={maxUses}
            onChange={e => setMaxUses(e.target.value)}
            style={{ width: '100%', padding: '8px 10px', fontSize: 14, borderRadius: 6, border: '1px solid #d0c8b0' }}
          />
        </div>
      </div>
      <div style={{ marginBottom: 10 }}>
        <label style={{ fontSize: 12, color: '#5a5a6a' }}>הערה (אופציונלי)</label>
        <input
          type="text" value={notes} onChange={e => setNotes(e.target.value)}
          placeholder="למשל: לקריאה בספר תניא בבית"
          style={{ width: '100%', padding: '8px 10px', fontSize: 14, borderRadius: 6, border: '1px solid #d0c8b0' }}
        />
      </div>
      <button
        onClick={issue} disabled={busy}
        style={{
          background: 'linear-gradient(135deg, #d4af37, #f1c40f)', color: '#1a1a2e',
          border: 'none', padding: '10px 18px', borderRadius: 8, fontWeight: 700,
          cursor: busy ? 'wait' : 'pointer', fontFamily: 'inherit',
        }}
      >
        {busy ? '...' : '+ הנפק קוד'}
      </button>
      {err && <div style={{ color: '#dc2626', marginTop: 8, fontSize: 13 }}>{err}</div>}

      {issued && (
        <div style={{
          marginTop: 14, padding: 14, background: 'rgba(212,175,55,0.15)',
          borderRadius: 10, border: '1px dashed #d4af37',
        }}>
          <div style={{ fontSize: 13, color: '#5a5a6a', marginBottom: 4 }}>
            הונפק עבור <strong>{issued.user_name}</strong>:
          </div>
          <div style={{
            fontSize: 28, fontWeight: 800, fontFamily: 'monospace',
            letterSpacing: 4, color: '#1a1a2e', textAlign: 'center', padding: 8,
          }}>{issued.code}</div>
          <div style={{ fontSize: 11, color: '#5a5a6a', textAlign: 'center', marginTop: 4 }}>
            העבר את הקוד למשתמש בערוץ פרטי. השמור את זה — לא ניתן לראותו שוב כאן.
          </div>
        </div>
      )}

      <h4 style={{ marginBottom: 6, marginTop: 18, color: '#1a1a2e' }}>קודים פעילים</h4>
      {codes.length === 0 && <div style={{ fontSize: 13, color: 'var(--muted)' }}>אין קודים עדיין.</div>}
      {codes.map(c => {
        const exhausted = c.max_uses != null && c.used_count >= c.max_uses;
        const revoked = !!c.revoked_at;
        const live = !revoked && !exhausted;
        return (
          <div key={c.id} style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            padding: '8px 10px', borderBottom: '1px solid #f0ebd9', fontSize: 13,
            opacity: live ? 1 : 0.5,
          }}>
            <div>
              <strong style={{ fontFamily: 'monospace', letterSpacing: 1 }}>{c.code}</strong>
              {' — '}{parseInt(c.user_id, 10) === 0 ? '👥 קוד פתוח' : `${c.user_name} (id ${c.user_id})`}
              {' · '}{c.used_count}{c.max_uses != null ? `/${c.max_uses}` : ''} שימושים
              {revoked && <span style={{ color: '#dc2626' }}> · בוטל</span>}
              {!revoked && exhausted && <span style={{ color: '#dc2626' }}> · מוצה</span>}
              {c.notes && <div style={{ fontSize: 11, color: 'var(--muted)' }}>{c.notes}</div>}
            </div>
            {live && (
              <button onClick={() => revoke(c.id)}
                style={{ background: 'none', border: '1px solid #dc2626', color: '#dc2626',
                         padding: '4px 10px', fontSize: 11, borderRadius: 6, cursor: 'pointer' }}>
                בטל
              </button>
            )}
          </div>
        );
      })}
    </section>
  );
}

// ──────────────────────────────────────────
//  [yakir pass 25] BroadcastSection — admin UI to send community-wide
//  announcements. Inserted at top of AdminPanel above TrustCodesSection.
//  Form: title + body + optional url → POST /api/announcements/broadcast.
//  Below: list of recent broadcasts with recipient count + read count.
// ──────────────────────────────────────────
function BroadcastSection({ adminId }) {
  const [title, setTitle] = React.useState('');
  const [body, setBody] = React.useState('');
  const [url, setUrl] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [history, setHistory] = React.useState([]);
  const [tick, setTick] = React.useState(0);

  React.useEffect(() => {
    api('/api/announcements/recent?limit=10').then(d => {
      if (d && Array.isArray(d.items)) setHistory(d.items);
    }).catch(() => {});
  }, [tick]);

  const send = async () => {
    const t = title.trim();
    const b = body.trim();
    if (t.length < 2) { setErr('הכותרת חייבת להיות לפחות 2 תווים'); return; }
    if (t.length > 200) { setErr('הכותרת ארוכה מדי (מקסימום 200)'); return; }
    if (b.length > 1000) { setErr('הגוף ארוך מדי (מקסימום 1000)'); return; }
    if (!window.confirm(`לשלוח את ההודעה הזו לכל הקהילה?\n\n"${t}"`)) return;
    setBusy(true); setErr('');
    try {
      const r = await api('/api/announcements/broadcast', {
        method: 'POST',
        body: { admin_id: adminId, title: t, body: b, url: url.trim() || undefined },
      });
      if (r.error) throw new Error(r.error);
      showToast(`✓ נשלח ל-${r.recipients} משתמשים`);
      setTitle(''); setBody(''); setUrl('');
      setTick(x => x + 1);
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="container" style={{ marginTop: 40, marginBottom: 30 }}>
      <h2 className="section-title" style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        📣 שלח עדכון לקהילה
      </h2>
      <p className="section-sub">הודעה שתופיע בתיבת העדכונים של כל המשתמשים הפעילים</p>

      <div style={{
        maxWidth: 600, margin: '20px auto',
        padding: '22px 22px',
        background: 'linear-gradient(135deg, rgba(180,140,250,0.10) 0%, rgba(120,80,220,0.05) 100%)',
        border: '1.5px solid rgba(180,140,250,0.45)',
        borderRadius: 14,
        boxShadow: '0 4px 18px rgba(120, 80, 220, 0.18), inset 0 0 40px rgba(180,140,250,0.04)',
      }}>
        <label style={{ fontSize: 12, color: '#c9b88a', display: 'block', marginBottom: 4 }}>כותרת (קצרה, עם אימוג'י בהתחלה)</label>
        <input
          value={title}
          onChange={e => setTitle(e.target.value)}
          maxLength={200}
          placeholder="🎉 פיצ'ר חדש - X זמין עכשיו"
          style={{
            width: '100%', padding: '12px 14px',
            background: 'rgba(255,255,255,0.06)',
            border: '1.5px solid rgba(180,140,250,0.30)',
            borderRadius: 10, color: '#f0e2bc',
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 16, marginBottom: 12,
            direction: 'rtl', textAlign: 'right',
            boxSizing: 'border-box', outline: 'none',
          }}
        />

        <label style={{ fontSize: 12, color: '#c9b88a', display: 'block', marginBottom: 4 }}>גוף ההודעה</label>
        <textarea
          rows={4}
          value={body}
          onChange={e => setBody(e.target.value)}
          maxLength={1000}
          placeholder="פירוט קצר על מה שהתחדש ועל איך זה משפיע על האנשים..."
          style={{
            width: '100%', padding: '12px 14px',
            background: 'rgba(255,255,255,0.06)',
            border: '1.5px solid rgba(180,140,250,0.30)',
            borderRadius: 10, color: '#f0e2bc',
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 15, lineHeight: 1.7, marginBottom: 12,
            direction: 'rtl', textAlign: 'right',
            resize: 'vertical', boxSizing: 'border-box', outline: 'none',
          }}
        />

        <label style={{ fontSize: 12, color: '#c9b88a', display: 'block', marginBottom: 4 }}>קישור אופציונלי (לאן הקליק יוביל)</label>
        <input
          value={url}
          onChange={e => setUrl(e.target.value)}
          maxLength={500}
          placeholder="/transparency · /coins · /tanya · /read/tanya · /gate"
          dir="ltr"
          style={{
            width: '100%', padding: '10px 12px',
            background: 'rgba(255,255,255,0.06)',
            border: '1.5px solid rgba(180,140,250,0.30)',
            borderRadius: 10, color: '#f0e2bc',
            fontFamily: 'monospace',
            fontSize: 13, marginBottom: 14,
            textAlign: 'left',
            boxSizing: 'border-box', outline: 'none',
          }}
        />

        {err && <div style={{ color: '#dc2626', fontSize: 13, marginBottom: 10, textAlign: 'center' }}>{err}</div>}

        <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 12 }}>
          <button
            onClick={send}
            disabled={busy || !title.trim()}
            style={{
              padding: '12px 32px',
              background: busy ? 'rgba(120, 80, 220, 0.4)' : 'linear-gradient(135deg, #b48aff 0%, #7b50dc 100%)',
              color: 'var(--text)', fontWeight: 700, fontSize: 15,
              border: 'none', borderRadius: 100,
              cursor: busy || !title.trim() ? 'not-allowed' : 'pointer',
              fontFamily: 'inherit', letterSpacing: 0.5,
              boxShadow: busy || !title.trim() ? 'none' : '0 4px 16px rgba(120, 80, 220, 0.40)',
              transition: 'all 0.2s',
            }}
          >
            {busy ? 'שולח...' : '📣 שלח לכל הקהילה'}
          </button>
          <span style={{ fontSize: 12, color: '#a89878' }}>
            ה-{title.length}/200 + {body.length}/1000
          </span>
        </div>
      </div>

      {/* Recent broadcasts history */}
      {history.length > 0 && (
        <div style={{ maxWidth: 600, margin: '14px auto 0' }}>
          <div style={{ fontSize: 12, color: '#c9b88a', letterSpacing: 2, marginBottom: 10, fontWeight: 700 }}>
            📜 עדכונים אחרונים שנשלחו
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {history.map((h, i) => (
              <div key={i} style={{
                padding: '10px 14px',
                background: 'linear-gradient(135deg, rgba(180,140,250,0.06) 0%, rgba(120,80,220,0.03) 100%)',
                border: '1px solid rgba(180,140,250,0.25)',
                borderRadius: 10,
                textAlign: 'right', direction: 'rtl',
              }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 }}>
                  <div style={{ fontSize: 14, color: '#f0e2bc', fontWeight: 700, flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {h.title}
                  </div>
                  <div style={{ fontSize: 11, color: '#a89878', whiteSpace: 'nowrap' }}>
                    {(h.created_at || '').replace('T', ' ').slice(0, 16)}
                  </div>
                </div>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 4 }}>
                  📤 {h.recipients} נמענים · 👁 {h.read_count} קראו
                  {h.url && <span style={{ color: '#b48aff', marginRight: 8 }}>· → {h.url}</span>}
                </div>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ───── [yakir 2026-05-14] AdminCodeGate — locked entry to admin page ─────
// Yakir wants admin gated by personal access code. This component shows a
// password-style input. On valid code: server returns user_id+name, we save
// to localStorage (so subsequent visits skip the gate), update isAdmin in
// parent state, and render AdminPanel.
function AdminCodeGate({ onAuthenticated }) {
  const [code, setCode] = React.useState('');
  const [err, setErr] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const submit = async (e) => {
    if (e && e.preventDefault) e.preventDefault();
    if (!code.trim() || busy) return;
    setBusy(true); setErr('');
    try {
      const r = await api('/api/admin/redeem-code', {
        method: 'POST',
        body: { code: code.trim() },
      });
      if (r && r.ok) {
        // Save user_id like a normal sign-in so admin endpoints work
        localStorage.setItem('tehillim_user_id', String(r.user_id));
        localStorage.setItem('tehillim_user_name', r.name || '');
        localStorage.setItem('thl_admin_authed', '1');
        onAuthenticated(r);
      } else {
        setErr((r && r.error) === 'invalid code' ? 'קוד שגוי — בדוק שוב' : 'שגיאת אימות');
      }
    } catch (ex) {
      setErr('שגיאת רשת');
    } finally {
      setBusy(false);
    }
  };
  return (
    <div style={{
      maxWidth: 460, margin: '60px auto', padding: '32px 28px',
      background: 'linear-gradient(135deg, rgba(212,175,55,0.08) 0%, rgba(184,148,31,0.04) 100%)',
      border: '2px solid rgba(212,175,55,0.40)',
      borderRadius: 16, textAlign: 'center',
      fontFamily: "'Frank Ruhl Libre', serif",
      direction: 'rtl',
    }}>
      <div style={{ fontSize: 42, marginBottom: 6 }}>🔐</div>
      <h2 style={{ color: '#5a3d0a', fontWeight: 700, marginBottom: 6, fontSize: 24 }}>
        כניסת מנהל
      </h2>
      <div style={{
        fontSize: 14, color: '#6d5d3d', marginBottom: 26,
        fontStyle: 'italic', lineHeight: 1.6,
      }}>
        כדי להיכנס לדף הניהול, הזן את קוד הגישה האישי שלך
      </div>

      <input
        type="text"
        autoFocus
        dir="ltr"
        placeholder="XXXX-XXXX"
        value={code}
        onChange={e => setCode(e.target.value.toUpperCase())}
        onKeyDown={e => { if (e.key === 'Enter') submit(); }}
        style={{
          width: '100%', padding: '14px 16px',
          fontSize: 22, fontWeight: 700,
          textAlign: 'center', letterSpacing: 2,
          fontFamily: 'monospace',
          background: 'var(--text)',
          border: `2px solid ${err ? '#c92456' : 'rgba(212,175,55,0.55)'}`,
          borderRadius: 12,
          color: '#1a1a2e',
          marginBottom: 14,
          outline: 'none',
          boxSizing: 'border-box',
        }}
      />

      {err && (
        <div style={{ color: '#c92456', fontSize: 13, marginBottom: 12, fontWeight: 600 }}>
          {err}
        </div>
      )}

      <button
        type="button"
        onClick={submit}
        disabled={!code.trim() || busy}
        style={{
          width: '100%',
          padding: '13px 22px',
          background: 'linear-gradient(135deg, #f5d75c 0%, #d4af37 60%, #b8941f 100%)',
          color: '#1a1a2e',
          border: '1.5px solid #8a5f10',
          borderRadius: 12,
          fontSize: 16, fontWeight: 800,
          cursor: code.trim() && !busy ? 'pointer' : 'not-allowed',
          fontFamily: 'inherit',
          opacity: code.trim() && !busy ? 1 : 0.55,
          transition: 'all 0.2s ease',
        }}
      >
        {busy ? 'מאמת...' : 'כניסה'}
      </button>

      <div style={{
        fontSize: 12, color: 'var(--muted)',
        marginTop: 22, lineHeight: 1.6,
      }}>
        אין לך קוד? פנה ליקיר או לדמרי
      </div>
    </div>
  );
}

function AdminPanel({ adminId }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [editing, setEditing] = useState(null);

  const load = useCallback(() => {
    setLoading(true);
    fetch('/api/admin/users', { headers: { 'X-User-Id': String(adminId) } })
      .then(r => r.json())
      .then(d => {
        // [damri 2026-05-24] endpoint returns {users:[...], total, page,...} since pagination
        // was added — old code expected a bare array. Fall back to either shape.
        const list = Array.isArray(d) ? d : (d && Array.isArray(d.users) ? d.users : []);
        setUsers(list);
      })
      .finally(() => setLoading(false));
  }, [adminId]);
  useEffect(() => { load(); }, [load]);

  const remove = async (u) => {
    if (!confirm(`למחוק את "${u.name}"?`)) return;
    await fetch(`/api/admin/user/${u.id}`, { method: 'DELETE', headers: { 'X-User-Id': String(adminId) } });
    load();
  };

  const save = async (u, patch) => {
    await fetch(`/api/admin/user/${u.id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json', 'X-User-Id': String(adminId) },
      body: JSON.stringify(patch)
    });
    setEditing(null);
    load();
  };

  return (
    <section style={{ paddingTop: 60 }}>
      <AdminDonationsSection adminId={adminId} />
      <BroadcastSection adminId={adminId} />
      <TrustCodesSection adminId={adminId} />
      <div className="container">
        <h2 className="section-title">⚙️ ניהול משתמשים</h2>
        <p className="section-sub">{users.length} משתמשים במערכת</p>
        {loading && <p style={{ color: 'var(--muted)', textAlign: 'center' }}>טוען...</p>}
        {!loading && (
          <table className="lb-table">
            <thead>
              <tr><th>ID</th><th>שם</th><th>מיקום</th><th>פרקים</th><th>הצטרף</th><th>פעולות</th></tr>
            </thead>
            <tbody>
              {users.map(u => (
                <tr key={u.id}>
                  <td style={{ color: 'var(--muted)', fontFamily: 'monospace' }}>{u.id}{u.is_admin ? ' 👑' : ''}</td>
                  <td className="lb-name">{u.name}</td>
                  <td>{u.location || '—'}</td>
                  <td className="lb-thl">{u.chapters}</td>
                  <td style={{ color: 'var(--muted)', fontSize: 11 }}>{(u.created_at || '').slice(0, 10)}</td>
                  <td>
                    <button className="btn ghost" style={{ padding: '6px 10px', fontSize: 12 }} onClick={() => setEditing(u)}>✏️</button>
                    {!u.is_admin && (
                      <button className="btn ghost" style={{ padding: '6px 10px', fontSize: 12, marginRight: 6, borderColor: '#dc2626', color: '#dc2626' }} onClick={() => remove(u)}>🗑️</button>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
        {editing && <AdminEditModal user={editing} onClose={() => setEditing(null)} onSave={save} />}
      </div>
    </section>
  );
}

function AdminEditModal({ user, onClose, onSave }) {
  const [name, setName] = useState(user.name || '');
  const [bio, setBio] = useState(user.bio || '');
  const [mission, setMission] = useState(user.mission || '');
  const [location, setLocation] = useState(user.location || '');
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={onClose}>×</span>
        <h3>עריכת משתמש #{user.id}</h3>
        <label>שם</label>
        <input value={name} onChange={e => setName(e.target.value)} />
        <label>שליחות</label>
        <input value={mission} onChange={e => setMission(e.target.value)} />
        <label>מיקום</label>
        <input value={location} onChange={e => setLocation(e.target.value)} />
        <label>ביו</label>
        <textarea rows="3" value={bio} onChange={e => setBio(e.target.value)} />
        <div className="actions">
          <button className="btn" onClick={() => onSave(user, { name, bio, mission, location })}>שמור</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

// ───────── Community Chat (used inside community detail) ─────────
function CommunityChat({ communityId, userId }) {
  const [messages, setMessages] = useState([]);
  const [text, setText] = useState('');
  const scrollRef = React.useRef(null);

  const load = useCallback(() => {
    api(`/api/community/${communityId}/chat?limit=50`).then(d => {
      if (Array.isArray(d)) setMessages(d);
    });
  }, [communityId]);

  useEffect(() => {
    load();
    const iv = setInterval(load, 5000);
    return () => clearInterval(iv);
  }, [load]);

  useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages]);

  const send = async () => {
    if (!text.trim() || !userId) return;
    await api(`/api/community/${communityId}/chat`, { method: 'POST', body: { user_id: userId, message: text.trim() } });
    setText('');
    load();
  };

  return (
    <div className="chat-box">
      <div className="chat-messages" ref={scrollRef}>
        {messages.length === 0 && <div style={{ color: 'var(--muted)', textAlign: 'center', padding: 20, fontStyle: 'italic' }}>אין הודעות עדיין. היה הראשון לכתוב.</div>}
        {messages.map(m => {
          const mine = m.user_id === userId;
          return (
            <div key={m.id} className={`chat-bubble ${mine ? 'mine' : 'theirs'}`}>
              <div className="chat-meta">
                <span className="chat-name" style={{ color: m.avatar_color || 'var(--gold)' }}>{m.name}</span>
                <span className="chat-time">{(m.created_at || '').slice(11, 16)}</span>
              </div>
              <div className="chat-text">{m.message}</div>
            </div>
          );
        })}
      </div>
      {userId ? (
        <div className="chat-input">
          <input value={text} onChange={e => setText(e.target.value)}
                 onKeyDown={e => e.key === 'Enter' && send()}
                 placeholder="כתוב הודעה..." />
          <button className="btn" onClick={send}>שלח</button>
        </div>
      ) : (
        <div style={{ color: 'var(--muted)', textAlign: 'center', padding: 14, fontSize: 13 }}>צריך להירשם כדי לכתוב</div>
      )}
    </div>
  );
}

// CommunityPage detail with tabs (members / circles / chat)
function CommunityDetail({ communityId, userId, onClose }) {
  const [data, setData] = useState(null);
  const [tab, setTab] = useState('members');
  useEffect(() => { api(`/api/community/${communityId}`).then(setData); }, [communityId]);
  if (!data) return null;
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal wide" onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={onClose}>×</span>
        <h3>{data.name}</h3>
        <div style={{ color: 'var(--muted)', fontSize: 13, marginBottom: 12 }}>{data.description} · {data.location}</div>
        <div className="text-tabs">
          <button className={`text-tab ${tab === 'members' ? 'active' : ''}`} onClick={() => setTab('members')}><span>👥 חברים ({data.members?.length || 0})</span></button>
          <button className={`text-tab ${tab === 'circles' ? 'active' : ''}`} onClick={() => setTab('circles')}><span>📖 מעגלים ({data.circles?.length || 0})</span></button>
          <button className={`text-tab ${tab === 'chat' ? 'active' : ''}`} onClick={() => setTab('chat')}><span>💬 צ'אט</span></button>
        </div>
        {tab === 'members' && (
          <div style={{ maxHeight: 320, overflowY: 'auto' }}>
            {(data.members || []).map(m => (
              <div key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: 8, borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
                <div className="avatar" style={{ background: m.avatar_color || 'var(--gold)', width: 36, height: 36, fontSize: 16 }}>{(m.name || '?').slice(0, 1)}</div>
                <span>{m.name}</span>
                {m.role === 'admin' && <span className="badge gold">מנהל</span>}
              </div>
            ))}
          </div>
        )}
        {tab === 'circles' && (
          <div style={{ maxHeight: 320, overflowY: 'auto' }}>
            {(data.circles || []).length === 0 && <div style={{ color: 'var(--muted)', padding: 14 }}>אין מעגלים</div>}
            {(data.circles || []).map(c => (
              <div key={c.id} style={{ padding: 10, borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
                <strong style={{ color: 'var(--gold)' }}>{c.name}</strong>
                <div style={{ fontSize: 12, color: 'var(--muted)' }}>{c.text_type} · {c.chapters_completed}/{c.total_chapters}</div>
              </div>
            ))}
          </div>
        )}
        {tab === 'chat' && <CommunityChat communityId={communityId} userId={userId} />}
      </div>
    </div>
  );
}

// ───────── Marketplace ─────────
const CATEGORIES = {
  torah:      { label: 'שיעורי תורה', icon: '📖', color: 'var(--gold)' },
  consulting: { label: 'ייעוץ רוחני', icon: '🕯️', color: '#9b59b6' },
  music:      { label: 'מוזיקה',      icon: '🎵', color: '#e74c3c' },
  cooking:    { label: 'בישול/אירוח', icon: '🍽️', color: '#e67e22' },
  tech:       { label: 'עזרה טכנית',  icon: '💻', color: '#3498db' },
  design:     { label: 'עיצוב',       icon: '🎨', color: '#2ecc71' },
  writing:    { label: 'כתיבה',       icon: '✍️', color: '#f39c12' },
  transport:  { label: 'הסעות',       icon: '🚗', color: '#1abc9c' },
  other:      { label: 'אחר',         icon: '⭐', color: '#95a5a6' }
};

// ───────── ImageUploader — reusable file→base64 uploader ─────────
function ImageUploader({ uploadEndpoint, userId, currentImageUrl, onUploaded, label = 'הוסף תמונה' }) {
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [previewUrl, setPreviewUrl] = React.useState(currentImageUrl || null);
  const inputRef = React.useRef(null);
  const handleFile = (file) => {
    if (!file) return;
    if (file.size > 5 * 1024 * 1024) { setErr('הקובץ גדול מדי (מקסימום 5MB)'); return; }
    setBusy(true); setErr('');
    const reader = new FileReader();
    reader.onload = async (ev) => {
      try {
        const r = await api(uploadEndpoint, {
          method: 'POST',
          body: { user_id: userId, image_url: ev.target.result },
        });
        if (r.error) throw new Error(r.error);
        setPreviewUrl(r.image_url);
        onUploaded && onUploaded(r.image_url);
      } catch (e) { setErr(e.message || 'העלאה נכשלה'); }
      finally { setBusy(false); }
    };
    reader.onerror = () => { setErr('קריאת הקובץ נכשלה'); setBusy(false); };
    reader.readAsDataURL(file);
  };
  return (
    <div style={{ marginBottom: 12 }}>
      {previewUrl && (
        <div style={{
          width: '100%', maxHeight: 200, overflow: 'hidden',
          borderRadius: 8, marginBottom: 8, background: '#000',
          display: 'flex', justifyContent: 'center',
        }}>
          <img src={previewUrl} alt="" style={{ maxWidth: '100%', maxHeight: 200, objectFit: 'contain' }} />
        </div>
      )}
      <button type="button"
        onClick={() => inputRef.current && inputRef.current.click()}
        disabled={busy}
        style={{
          width: '100%', padding: '10px', fontSize: 13,
          background: 'rgba(212,175,55,0.12)',
          border: '1px dashed rgba(212,175,55,0.5)',
          borderRadius: 8, color: 'var(--gold)',
          cursor: busy ? 'wait' : 'pointer', fontFamily: 'inherit',
        }}>
        {busy ? 'מעלה...' : (previewUrl ? '🔄 החלף תמונה' : '📷 ' + label)}
      </button>
      <input ref={inputRef} type="file"
        accept="image/png,image/jpeg,image/webp,image/gif"
        style={{ display: 'none' }}
        onChange={e => { const f = e.target.files && e.target.files[0]; handleFile(f); e.target.value = ''; }} />
      {err && <div style={{ color: '#dc2626', fontSize: 12, marginTop: 6 }}>{err}</div>}
    </div>
  );
}

// ───────── ServiceReviewsModal — list + add review with stars ─────────
function ServiceReviewsModal({ service, currentUser, onClose }) {
  const [data, setData] = React.useState({ reviews: [], count: 0, avg_rating: 0 });
  const [loading, setLoading] = React.useState(true);
  const [rating, setRating] = React.useState(5);
  const [body, setBody] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const isOwner = currentUser && service.user_id === currentUser;
  const myReview = data.reviews.find(r => r.user_id === currentUser);
  const load = () => {
    setLoading(true);
    api(`/api/service/${service.id}/reviews`).then(d => { if (!d.error) setData(d); }).finally(() => setLoading(false));
  };
  React.useEffect(load, [service.id]);
  React.useEffect(() => { if (myReview) { setRating(myReview.rating); setBody(myReview.body); } }, [myReview && myReview.id]);
  const submit = async () => {
    if (body.trim().length < 5) { setErr('כתוב לפחות 5 תווים'); return; }
    setBusy(true); setErr('');
    try {
      const r = await api(`/api/service/${service.id}/review`, {
        method: 'POST',
        body: { user_id: currentUser, rating, body: body.trim() },
      });
      if (r.error) throw new Error(r.error);
      load();
      if (!myReview) { setRating(5); setBody(''); }
    } catch (e) { setErr(e.message); }
    finally { setBusy(false); }
  };
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 480 }}>
        <span className="close-x" onClick={onClose}>×</span>
        <h3>חוות דעת — {service.title}</h3>
        <div style={{
          padding: '8px 12px', background: 'rgba(212,175,55,0.08)',
          borderRadius: 8, marginBottom: 14, fontSize: 14,
        }}>
          {data.count > 0
            ? <>★ <strong>{data.avg_rating}</strong> ({data.count} חוות דעת)</>
            : <span style={{ color: 'var(--muted)' }}>עדיין אין חוות דעת</span>}
        </div>
        {!isOwner && currentUser && (
          <div style={{ marginBottom: 16, paddingBottom: 14, borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
            <div style={{ fontSize: 13, marginBottom: 6, color: 'var(--gold)' }}>
              {myReview ? 'ערוך את חוות הדעת שלך' : 'הוסף חוות דעת'}
            </div>
            <div style={{ display: 'flex', gap: 4, marginBottom: 8, justifyContent: 'flex-end' }}>
              {[1,2,3,4,5].map(n => (
                <button key={n} onClick={() => setRating(n)}
                  style={{ background: 'none', border: 'none', fontSize: 26, cursor: 'pointer',
                           color: n <= rating ? '#ffc107' : '#444', padding: 0 }}>★</button>
              ))}
            </div>
            <textarea value={body} onChange={e => setBody(e.target.value)}
              placeholder="מה היה השירות? מה תמליץ לאחרים?" maxLength={1000}
              style={{ width: '100%', minHeight: 70, padding: 10, marginBottom: 8 }} />
            {err && <div style={{ color: '#dc2626', fontSize: 12, marginBottom: 6 }}>{err}</div>}
            <button className="btn" onClick={submit} disabled={busy} style={{ width: '100%' }}>
              {busy ? 'שולח...' : (myReview ? 'עדכן' : 'פרסם חוות דעת')}
            </button>
          </div>
        )}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {loading
            ? <div style={{ color: 'var(--muted)', textAlign: 'center', padding: 20 }}>טוען...</div>
            : data.reviews.length === 0
              ? <div style={{ color: 'var(--muted)', textAlign: 'center', padding: 20, fontStyle: 'italic' }}>היה הראשון להמליץ</div>
              : data.reviews.map(r => (
                  <div key={r.id} style={{ padding: 10, background: 'rgba(255,255,255,0.03)', borderRadius: 8, border: '1px solid rgba(255,255,255,0.06)' }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
                      <strong style={{ fontSize: 13 }}>{r.reviewer_name || 'אנונימי'}</strong>
                      <div style={{ color: '#ffc107', fontSize: 13 }}>{'★'.repeat(r.rating)}{'☆'.repeat(5 - r.rating)}</div>
                    </div>
                    <div style={{ fontSize: 13, color: 'var(--text)', lineHeight: 1.5, whiteSpace: 'pre-wrap' }}>{r.body}</div>
                  </div>
                ))}
        </div>
      </div>
    </div>
  );
}

function ServiceCard({ svc, onRequest, currentUser, balances }) {
  const cat = CATEGORIES[svc.category] || CATEGORIES.other;
  const initials = (svc.provider_name || '?').slice(0, 1);
  const isMine = currentUser && svc.user_id === currentUser;

  // Pre-flight balance check: don't let the user click Request if they
  // can't afford it. Skip when balances haven't loaded yet (balances === null).
  let userBalance = null;
  if (currentUser && Array.isArray(balances)) {
    const row = balances.find(b => b.symbol === svc.token_symbol);
    userBalance = row ? row.balance : 0;
  }
  const insufficient = userBalance !== null && userBalance < (svc.price_tokens || 0);
  const tooltip = insufficient
    ? `אין מספיק ${svc.token_symbol} (יש ${userBalance}, צריך ${svc.price_tokens}). קרא עוד פרקים.`
    : '';

  const [showReviews, setShowReviews] = React.useState(false);
  return (
    <div className="svc-card">
      {svc.image_url && (
        <div style={{
          width: '100%', height: 140, marginBottom: 10,
          borderRadius: 8, overflow: 'hidden',
          background: '#000', display: 'flex', justifyContent: 'center',
        }}>
          <img src={svc.image_url} alt={svc.title}
               style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
        </div>
      )}
      <div className="svc-cat" style={{ color: cat.color, border: `1px solid ${cat.color}55` }}>
        <span>{cat.icon}</span><span>{cat.label}</span>
      </div>
      <div className="svc-title">{svc.title}</div>
      <div className="svc-desc">{svc.description || ''}</div>
      <div className="svc-price svc-price-big">
        <span className="amt">{svc.price_tokens}</span>
        <span className="sym" style={{ fontSize: '70%', opacity: 0.85 }}>✦</span>
      </div>
      <button type="button" onClick={() => setShowReviews(true)}
        style={{
          background: 'none', border: 'none', color: 'var(--gold)',
          fontSize: 12, cursor: 'pointer', padding: '4px 0',
          fontFamily: 'inherit', textAlign: 'center', width: '100%',
        }}>
        {svc.review_count > 0
          ? <>★ <strong style={{ color: '#ffc107' }}>{svc.avg_rating}</strong> · {svc.review_count} חוות דעת ›</>
          : <span style={{ color: 'var(--muted)' }}>💬 הוסף חוות דעת</span>}
      </button>
      {showReviews && (
        <ServiceReviewsModal service={svc} currentUser={currentUser} onClose={() => setShowReviews(false)} />
      )}
      {svc.appreciation && (
        <div className="svc-appreciation">
          <span className="svc-appreciation-icon">🤝</span>
          <span className="svc-appreciation-txt">{svc.appreciation}</span>
        </div>
      )}
      <a
        className="svc-provider"
        href={`/profile/${svc.user_id}`}
        target="_blank"
        rel="noopener noreferrer"
        title={`פרופיל של ${svc.provider_name || 'אנונימי'}`}
      >
        <div className="avatar" style={{ background: svc.avatar_color || 'var(--gold)' }}>
          {svc.avatar_url
            ? <img src={svc.avatar_url} alt={svc.provider_name || ''} />
            : initials}
        </div>
        <span className="svc-provider-name">{svc.provider_name || 'אנונימי'}</span>
      </a>
      {isMine ? (
        <button className="svc-btn" disabled>המתנה שלי</button>
      ) : insufficient ? (
        <button className="svc-btn"
          onClick={() => { window.location.hash = '#/profile'; setTimeout(() => { const w = document.getElementById('wallet-section'); if (w && w.scrollIntoView) w.scrollIntoView({ behavior: 'smooth' }); }, 200); }}
          title={`חסר ✦ ${svc.price_tokens - userBalance}. ההוקרה נבנית דרך פרקים, מעגלים, ומתנות`}
          style={{ background: 'linear-gradient(135deg,#d4af37,#f1c40f)', color: '#1a1a2e' }}>
          ✦ קרא פרק, פתח מעגל, או הצע מתנה
        </button>
      ) : (
        <button className="svc-btn"
          onClick={() => onRequest(svc)} disabled={!currentUser} title={tooltip}>
          {!currentUser ? 'הירשם כדי להוקיר' : 'להוקיר'}
        </button>
      )}
    </div>
  );
}

const SERVICE_DRAFT_KEY = 'service_draft';

function loadServiceDraft() {
  try {
    const raw = localStorage.getItem(SERVICE_DRAFT_KEY);
    if (!raw) return null;
    const d = JSON.parse(raw);
    return d && typeof d === 'object' ? d : null;
  } catch { return null; }
}

function formatDraftTime(iso) {
  if (!iso) return '';
  try {
    const d = new Date(iso);
    const now = new Date();
    const diffMin = Math.round((now - d) / 60000);
    if (diffMin < 1) return 'לפני רגע';
    if (diffMin < 60) return `לפני ${diffMin} דק׳`;
    const diffHr = Math.round(diffMin / 60);
    if (diffHr < 24) return `לפני ${diffHr} שעות`;
    return d.toLocaleString('he-IL', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
  } catch { return ''; }
}

function OfferServiceModal({ userId, onClose, onCreated }) {
  const initialDraft = loadServiceDraft();
  const [title, setTitle] = useState(initialDraft?.title || '');
  const [description, setDescription] = useState(initialDraft?.description || '');
  const [category, setCategory] = useState(initialDraft?.category || 'torah');
  const [price, setPrice] = useState(initialDraft?.price ?? 50);
  // Marketplace currency unified to BEU per yakir 2026-05-05 17:22 — no selector.
  const [appreciation, setAppreciation] = useState(initialDraft?.appreciation || '');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [draftBanner, setDraftBanner] = useState(initialDraft ? { savedAt: initialDraft.savedAt } : null);
  const [imageDataUrl, setImageDataUrl] = useState(null);
  const imageInputRef = React.useRef(null);
  // [damri ss#6 2026-05-24] gift % to community
  const [valueIls, setValueIls] = useState(initialDraft?.valueIls || '');
  const [giftPct, setGiftPct] = useState(initialDraft?.giftPct ?? 100);
  const [giftReason, setGiftReason] = useState(initialDraft?.giftReason || '');

  // Persist draft on every change (only when there is actual content)
  useEffect(() => {
    const hasContent = (title && title.trim()) || (description && description.trim())
      || category !== 'torah' || parseInt(price, 10) !== 50 || (appreciation && appreciation.trim())
      || valueIls || giftPct !== 100 || (giftReason && giftReason.trim());
    if (hasContent) {
      try {
        localStorage.setItem(SERVICE_DRAFT_KEY, JSON.stringify({
          title, description, category, price, appreciation,
          valueIls, giftPct, giftReason,
          savedAt: new Date().toISOString(),
        }));
      } catch { /* quota / disabled storage — ignore */ }
    }
  }, [title, description, category, price, appreciation, valueIls, giftPct, giftReason]);

  const discardDraft = () => {
    try { localStorage.removeItem(SERVICE_DRAFT_KEY); } catch {}
    setTitle(''); setDescription(''); setCategory('torah');
    setPrice(50); setAppreciation('');
    setValueIls(''); setGiftPct(100); setGiftReason('');
    setDraftBanner(null);
  };

  const submit = async () => {
    if (!title.trim()) { setErr('כותרת חובה'); return; }
    if (!appreciation.trim()) { setErr('שורת הוקרה לקהילה — חובה (מה הקהילה מקבלת ממך)'); return; }
    setBusy(true); setErr('');
    try {
      // [damri ss#6 2026-05-24] validate gift_gap_reason when gift_pct < 100
      const pct = parseInt(giftPct, 10) || 100;
      if (pct < 100 && !giftReason.trim()) {
        setErr('כשהמתנה לקהילה לא 100% — חובה לכתוב מה חסר (יוצג לקהילה)');
        setBusy(false); return;
      }
      const r = await api('/api/service', {
        method: 'POST',
        body: {
          user_id: userId,
          title: title.trim(),
          description: description.trim(),
          category,
          price_tokens: parseInt(price, 10),
          appreciation: appreciation.trim(),
          value_ils: valueIls === '' ? null : parseInt(valueIls, 10),
          gift_pct: pct,
          gift_gap_reason: pct < 100 ? giftReason.trim() : null,
        }
      });
      if (r.error) throw new Error(r.error);
      // Upload image now if user selected one. Service exists either way —
      // image is optional, so a failed upload is non-fatal.
      if (imageDataUrl && r.id) {
        try {
          await api(`/api/service/${r.id}/image`, { method: 'POST', body: { user_id: userId, image_url: imageDataUrl } });
        } catch (_) { /* swallow — user can edit later to add image */ }
      }
      try { localStorage.removeItem(SERVICE_DRAFT_KEY); } catch {}
      onCreated({ id: r.id });
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-bg">
      <div className="modal" onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={onClose}>×</span>
        <h3>להציע מתנה חדשה</h3>
        {draftBanner && (
          <div style={{
            background: 'rgba(245, 158, 11, 0.12)',
            border: '1px solid rgba(245, 158, 11, 0.35)',
            color: '#fcd34d',
            fontSize: 12,
            padding: '6px 10px',
            borderRadius: 6,
            marginBottom: 10,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'space-between',
            gap: 8,
          }}>
            <span>📝 שוחזרה טיוטה {formatDraftTime(draftBanner.savedAt)}</span>
            <span
              role="button"
              onClick={discardDraft}
              style={{ cursor: 'pointer', fontWeight: 700, padding: '0 6px', lineHeight: 1 }}
              title="מחק טיוטה"
            >×</span>
          </div>
        )}
        <label>כותרת</label>
        <input value={title} onChange={e => setTitle(e.target.value)} placeholder="לדוגמה: שיעור חסידות שבועי" />
        <label>תיאור</label>
        <textarea rows="3" value={description} onChange={e => setDescription(e.target.value)} placeholder="פרטים על השירות, מה כלול, איך זה עובד..." />
        <label>קטגוריה</label>
        <select value={category} onChange={e => setCategory(e.target.value)}>
          {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.icon} {v.label}</option>)}
        </select>
        <label>מחיר</label>
        <div className="amount-input-wrap">
          <input
            className="amount-input-big"
            type="number"
            min="1"
            value={price}
            onChange={e => setPrice(e.target.value)}
            placeholder="0"
          />
          <span className="amount-input-suffix">✦</span>
        </div>
        <label>שורת הוקרה לקהילה <span className="req-mark">*</span></label>
        <textarea
          rows="2"
          value={appreciation}
          onChange={e => setAppreciation(e.target.value)}
          placeholder="מה הקהילה מקבלת ממך כשתמורת זה? (1-2 שורות, יוצג ליד הסכום)"
        />

        {/* [damri ss#6 2026-05-24] שווי המוצר בש"ח (לשקיפות) */}
        <label>שווי המוצר בש"ח <span style={{ color: '#a89878', fontWeight: 400, fontSize: 12 }}>(לשקיפות בלבד)</span></label>
        <input type="number" min="0" value={valueIls}
          onChange={e => setValueIls(e.target.value)}
          placeholder="לדוגמה: 200" />

        {/* [damri ss#6 2026-05-24] gift_pct — what fraction is given as community-coin gift */}
        <label>כמה מהשירות אתה מציע כמתנה לקהילה?</label>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 6 }}>
          <input type="range" min="25" max="100" step="5" value={giftPct}
            onChange={e => setGiftPct(parseInt(e.target.value, 10))}
            style={{ flex: 1 }} />
          <span style={{ minWidth: 56, textAlign: 'left', fontWeight: 700, color: '#d4af37' }}>{giftPct}%</span>
        </div>
        {valueIls && parseInt(valueIls, 10) > 0 && (
          <div style={{ fontSize: 12, color: '#a89878', marginBottom: 12, lineHeight: 1.6 }}>
            מתוך {parseInt(valueIls, 10)}₪ — {Math.round(parseInt(valueIls, 10) * giftPct / 100)}₪ במטבע הקהילה,
            {' '}{parseInt(valueIls, 10) - Math.round(parseInt(valueIls, 10) * giftPct / 100)}₪ בכסף
          </div>
        )}

        {giftPct < 100 && (
          <div style={{
            marginTop: 8, padding: '10px 12px',
            background: 'rgba(212,175,55,0.06)',
            border: '1px dashed rgba(212,175,55,0.40)',
            borderRadius: 8, marginBottom: 12,
          }}>
            <label style={{ fontSize: 13, color: '#fcd34d', display: 'block', marginBottom: 4 }}>
              מה חסר בכדי שתוכל להעניק את השירות במלואו במטבע הקהילה? <span className="req-mark">*</span>
            </label>
            <textarea rows="2" value={giftReason}
              onChange={e => setGiftReason(e.target.value)}
              placeholder="התשובה תוצג לקהילה — הזדמנות לראות אותך" />
          </div>
        )}

        {/* Inline image picker — added inline so users see this BEFORE publishing. */}
        <label>תמונה (אופציונלי)</label>
        <div style={{ marginBottom: 10 }}>
          {imageDataUrl && (
            <div style={{ width: '100%', maxHeight: 180, overflow: 'hidden', borderRadius: 8, marginBottom: 6, background: '#000', display: 'flex', justifyContent: 'center' }}>
              <img src={imageDataUrl} alt="" style={{ maxWidth: '100%', maxHeight: 180, objectFit: 'contain' }} />
            </div>
          )}
          <input type="file" accept="image/png,image/jpeg,image/webp,image/gif"
            ref={imageInputRef}
            style={{ display: 'none' }}
            onChange={e => {
              const f = e.target.files && e.target.files[0]; if (!f) return;
              if (f.size > 5 * 1024 * 1024) { setErr('הקובץ גדול מדי (מקסימום 5MB)'); return; }
              const reader = new FileReader();
              reader.onload = (ev) => setImageDataUrl(ev.target.result);
              reader.readAsDataURL(f);
              e.target.value = '';
            }} />
          <div style={{ display: 'flex', gap: 6 }}>
            <button type="button"
              onClick={() => imageInputRef.current && imageInputRef.current.click()}
              style={{ flex: 1, padding: '10px', fontSize: 13, background: 'rgba(212,175,55,0.12)', border: '1px dashed rgba(212,175,55,0.5)', borderRadius: 8, color: 'var(--gold)', cursor: 'pointer', fontFamily: 'inherit' }}>
              {imageDataUrl ? '🔄 החלף תמונה' : '📷 הוסף תמונה'}
            </button>
            {imageDataUrl && (
              <button type="button" onClick={() => setImageDataUrl(null)}
                style={{ padding: '10px 14px', fontSize: 13, background: 'transparent', border: '1px solid rgba(255,107,157,0.3)', borderRadius: 8, color: '#dc2626', cursor: 'pointer', fontFamily: 'inherit' }}>
                ✕
              </button>
            )}
          </div>
        </div>
        {err && <div style={{ color: '#dc2626', fontSize: 13, marginTop: 10 }}>{err}</div>}
        <div className="actions">
          <button className="btn" onClick={submit} disabled={busy}>{busy ? '...' : 'פרסם'}</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

function RequestServiceModal({ svc, userId, onClose, onSent }) {
  const [message, setMessage] = useState('');
  const [balance, setBalance] = useState(null);
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  // Pay percentage slider — yakir spec 2026-05-05 17:03. Default 100%.
  const [payPct, setPayPct] = useState(100);

  useEffect(() => {
    api(`/api/balance/${userId}`).then(b => {
      const row = (b || []).find(x => x.symbol === 'BEU');
      setBalance(row ? row.balance : 0);
    });
  }, [userId]);

  const offered = Math.round((svc.price_tokens || 0) * payPct / 100);
  const enough = balance !== null && balance >= offered;

  const submit = async () => {
    setBusy(true); setErr('');
    try {
      // Append the pay_pct intent to the message so the provider sees it.
      const finalMsg = payPct === 100
        ? message.trim()
        : `[הצעת הוקרה: ${payPct}% = ✦ ${offered}]${message.trim() ? '\n' + message.trim() : ''}`;
      const r = await api(`/api/service/${svc.id}/request`, {
        method: 'POST',
        body: { requester_id: userId, message: finalMsg, pay_pct: payPct }
      });
      if (r.error) throw new Error(r.error);
      onSent(r);
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={onClose}>×</span>
        <h3>בקשת שירות</h3>
        <div style={{ background: 'rgba(212,175,55,0.08)', border: '1px solid rgba(212,175,55,0.2)', borderRadius: 12, padding: 14, marginTop: 10 }}>
          <div style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18, fontWeight: 700 }}>{svc.title}</div>
          <div style={{ color: '#aaa', fontSize: 13, marginTop: 6 }}>{svc.description}</div>
          <div style={{ marginTop: 10, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
            <span style={{ color: 'var(--muted)', fontSize: 12 }}>ספק: {svc.provider_name}</span>
            <span style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', lineHeight: 1.2 }}>
              <span style={{ fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic', fontSize: 11, color: '#a8801f' }}>להוקיר</span>
              <span style={{ color: 'var(--gold)', fontSize: 16, fontWeight: 700 }}>✦ {svc.price_tokens}</span>
            </span>
          </div>
          {svc.appreciation && (
            <div className="svc-appreciation" style={{ marginTop: 10 }}>
              <span className="svc-appreciation-icon">🤝</span>
              <span className="svc-appreciation-txt">{svc.appreciation}</span>
            </div>
          )}
        </div>

        <div className="pay-slider-wrap">
          <div className="pay-slider-head">
            <label>אחוז תשלום</label>
            <span className="pay-slider-value">{payPct}% = <strong>✦ {offered}</strong></span>
          </div>
          <div className="pay-slider-buttons">
            {[100, 75, 50, 25].map(p => (
              <button
                key={p}
                type="button"
                className={'pay-slider-btn' + (payPct === p ? ' active' : '')}
                onClick={() => setPayPct(p)}
              >{p}%</button>
            ))}
          </div>
          {payPct < 100 && (
            <div className="pay-slider-note">
              ℹ️ מציע פחות מהמחיר המלא. הספק יראה את ההצעה ויחליט אם לקבל.
            </div>
          )}
        </div>

        <label>הודעה לספק (אופציונלי)</label>
        <textarea rows="3" value={message} onChange={e => setMessage(e.target.value)} placeholder="הסבר מה אתה צריך, מתי, פרטים נוספים..." />
        <div style={{ marginTop: 10, fontSize: 13, color: enough ? '#2ecc71' : '#dc2626' }}>
          ההוקרה שלך: <strong>{balance === null ? '...' : `✦ ${balance}`}</strong>
          {balance !== null && !enough && ' — אין מספיק. קרא עוד פרקים!'}
        </div>
        {err && <div style={{ color: '#dc2626', fontSize: 13, marginTop: 10 }}>{err}</div>}
        <div className="actions">
          <button className="btn" onClick={submit} disabled={busy || !enough}>{busy ? '...' : `להוקיר ב־✦ ${offered}`}</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

// ─── BookCompletionCelebration [damri 2026-05-20] ────────────────────
// Full-screen overlay shown when a user completes the LAST chapter of a
// book (תניא: 53 chapters, תהילים: 150, etc.). Damri's vision: "this is
// the moment. Celebrate it. Then invite them into the ecosystem."
function BookCompletionCelebration({ bookLabel, circleName, onEnterEcosystem, onClose }) {
  React.useEffect(() => {
    // Lock body scroll while overlay is up
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, []);

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 9999, direction: 'rtl',
      background: 'radial-gradient(circle at 50% 35%, rgba(212,175,55,0.30), rgba(26,26,46,0.92))',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: 20, animation: 'fadeIn 0.6s ease',
    }}>
      <div style={{
        background: 'linear-gradient(180deg, #fffaf0 0%, #fff 100%)',
        borderRadius: 20, padding: '32px 28px', maxWidth: 480, width: '100%',
        textAlign: 'center', boxShadow: '0 24px 80px rgba(0,0,0,0.40)',
        border: '2px solid rgba(212,175,55,0.40)',
      }}>
        <div style={{ fontSize: 64, marginBottom: 16, animation: 'pulse 2s infinite' }}>🎉</div>
        <h2 style={{ margin: '0 0 8px', color: '#5a3d0a', fontSize: 26, fontWeight: 800 }}>
          סיימת את {bookLabel}
        </h2>
        {circleName && (
          <div style={{ fontSize: 14, color: '#8a6f1f', marginBottom: 14, fontStyle: 'italic' }}>
            במעגל "{circleName}"
          </div>
        )}
        <p style={{ fontSize: 16, color: '#1d4029', lineHeight: 1.7, marginBottom: 14 }}>
          זה לא רגע קטן. <strong>הלמידה שלך הופכת לכוח אמיתי בקהילה.</strong>
        </p>
        <p style={{ fontSize: 14, color: '#5a3d0a', lineHeight: 1.7, marginBottom: 22 }}>
          המטבעות שצברת בלימוד נושאות משקל מוגבר באקוסיסטם. עכשיו אתה יכול להצביע, לתרום, ולפתוח פרויקט משלך.
        </p>
        <button
          onClick={onEnterEcosystem}
          className="btn"
          style={{
            display: 'inline-block', padding: '14px 32px', fontSize: 17, fontWeight: 800,
            background: 'linear-gradient(135deg, #d4af37, #b8941f)', color: '#1a1a2e',
            border: '1.5px solid #8a5f10', borderRadius: 100,
            boxShadow: '0 4px 20px rgba(138,95,16,0.40)', cursor: 'pointer',
          }}
        >
          💎 להיכנס לאקוסיסטם ←
        </button>
        <div style={{ marginTop: 16 }}>
          <button onClick={onClose}
                  style={{ background: 'none', border: 'none', color: 'var(--muted)',
                           fontSize: 13, cursor: 'pointer', textDecoration: 'underline' }}>
            אסיים כאן
          </button>
        </div>
      </div>
    </div>
  );
}

// ─── MyTanyaCard [yakir 2026-05-19] ─────────────────────────────────
// Personal Tanya progress card on profile. Shows current level (תלמיד →
// הבינוני), progress to next level, TNY balance, and chapters read.
function MyTanyaCard({ userId, refresh }) {
  const [data, setData] = React.useState(null);
  const [balances, setBalances] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  // [damri-rankmodal 2026-05-24] click on rank → open detail modal
  const [detailOpen, setDetailOpen] = React.useState(false);

  React.useEffect(() => {
    if (!userId) return;
    setLoading(true);
    Promise.all([
      api(`/api/profile/level/${userId}`).catch(() => null),
      api(`/api/balance/${userId}`).catch(() => []),
    ]).then(([lvl, bal]) => {
      setData(lvl && !lvl.error ? lvl : null);
      setBalances(Array.isArray(bal) ? bal : []);
    }).finally(() => setLoading(false));
  }, [userId, refresh]);

  if (loading) return null;
  if (!data) return null;

  const cur = data.current_level || { name_he: 'תלמיד', icon: '📖', level: 1 };
  const next = data.next_level;
  const stats = data.stats || {};
  const tnyEntry = balances.find(b => (b.symbol || '').toUpperCase() === 'TNY')
                   || balances.find(b => (b.code || '').toUpperCase() === 'TNY');
  const tnyAmount = tnyEntry ? (tnyEntry.balance || tnyEntry.amount || 0) : 0;
  const pct = data.progress_to_next ? data.progress_to_next.overall_pct : 100;

  return (
    <div onClick={() => setDetailOpen(true)}
         role="button" tabIndex={0} title="לחץ לפירוט: מעגלים, פרקים, חידושים, מה חסר לדרגה הבאה"
         onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') setDetailOpen(true); }}
         style={{
      margin: '18px 0', padding: 18,
      background: 'linear-gradient(135deg, rgba(95,163,114,0.08), rgba(212,175,55,0.08))',
      border: '1.5px solid rgba(95,163,114,0.30)',
      borderRadius: 14, direction: 'rtl', cursor: 'pointer',
      transition: 'transform .15s, box-shadow .15s',
    }}
    onMouseEnter={(e) => { e.currentTarget.style.boxShadow = '0 4px 14px rgba(95,163,114,0.25)'; }}
    onMouseLeave={(e) => { e.currentTarget.style.boxShadow = 'none'; }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 12 }}>
        <div style={{ fontSize: 38, lineHeight: 1 }}>{cur.icon || '📖'}</div>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 13, color: '#6b6d73', fontWeight: 600 }}>הדרגה שלי בתניא</div>
          <div style={{ fontSize: 22, fontWeight: 800, color: '#1d4029' }}>
            {cur.name_he || 'תלמיד'}
          </div>
        </div>
        <div style={{ textAlign: 'left' }}>
          <div style={{ fontSize: 13, color: '#6b6d73', fontWeight: 600 }}>TNY</div>
          <div style={{ fontSize: 22, fontWeight: 800, color: '#a8801f' }}>
            🪙 {tnyAmount.toLocaleString()}
          </div>
        </div>
      </div>

      <div style={{ display: 'flex', gap: 12, fontSize: 13, color: '#1d4029', marginBottom: 10, flexWrap: 'wrap' }}>
        <span>📚 {stats.chapters_tanya_verified || 0} פרקים מאומתים</span>
        <span>🔥 רצף {stats.current_streak_days || 0} ימים</span>
        {(stats.longest_streak_days || 0) > 0 && (
          <span>🏆 שיא {stats.longest_streak_days} ימים</span>
        )}
      </div>

      {next ? (
        <>
          <div style={{ height: 8, background: 'rgba(0,0,0,0.06)',
                        borderRadius: 4, overflow: 'hidden', marginBottom: 6 }}>
            <div style={{
              width: pct + '%', height: '100%',
              background: 'linear-gradient(90deg, #5fa372, #d4af37)',
              transition: 'width .4s',
            }} />
          </div>
          <div style={{ fontSize: 12.5, color: '#5a3d0a', textAlign: 'center' }}>
            עוד {Math.max(0, (next.min_chapters_tanya || 0) - (stats.chapters_tanya_verified || 0))} פרקים
            עד <strong>{next.name_he}</strong>{' '}({pct}%)
          </div>
        </>
      ) : (
        <div style={{ textAlign: 'center', color: '#1d4029', fontWeight: 700, fontSize: 14 }}>
          🎯 הגעת לרמה הגבוהה ביותר — הבינוני
        </div>
      )}
      {/* [damri-rankmodal 2026-05-24] subtle hint that card is clickable */}
      <div style={{ fontSize: 11, color: '#6b6d73', textAlign: 'center', marginTop: 10, opacity: 0.75, fontStyle: 'italic' }}>
        ✦ לחץ לפירוט המסע
      </div>
      {detailOpen && (
        <RankDetailModal userId={userId} data={data} stats={stats} cur={cur} next={next}
                          onClose={(e) => { if (e) e.stopPropagation(); setDetailOpen(false); }} />
      )}
    </div>
  );
}

// [damri-rankmodal 2026-05-24] Click on Tanya rank → modal with:
//   (1) circles I created, (2) chapters I read, (3) link to deepen/חידושים,
//   (4) exact gap to next level.
function RankDetailModal({ userId, data, stats, cur, next, onClose }) {
  const [circles, setCircles] = React.useState(null);
  const [readings, setReadings] = React.useState(null);

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/circles/by-creator/${userId}`).then(d => {
      setCircles(d && Array.isArray(d.items) ? d.items : []);
    }).catch(() => setCircles([]));
    api(`/api/readings/by-user/${userId}`).then(d => {
      setReadings(d && Array.isArray(d.items) ? d.items : []);
    }).catch(() => setReadings([]));
  }, [userId]);

  const chaptersHave = stats.chapters_tanya_verified || 0;
  const streakHave = stats.current_streak_days || 0;
  const chaptersNeeded = next ? Math.max(0, (next.min_chapters_tanya || 0) - chaptersHave) : 0;
  const streakNeeded = next ? Math.max(0, (next.min_streak_days || 0) - streakHave) : 0;
  const treasuresHave = stats.treasures_revealed || 0;
  const connectionsHave = stats.connections || 0;
  const treasuresNeeded = next ? Math.max(0, (next.min_treasures || 0) - treasuresHave) : 0;
  const connectionsNeeded = next ? Math.max(0, (next.min_connections || 0) - connectionsHave) : 0;

  // Unique chapters read (dedupe across circles)
  const uniqueChapters = readings ? Array.from(new Set(readings.filter(r => r.text_type === 'tanya').map(r => r.chapter))).sort((a,b) => a-b) : [];

  const goToCompanion = (e) => { e.stopPropagation(); onClose(); window.dispatchEvent(new CustomEvent('app:navigate', { detail: { page: 'companion' } })); };
  const goToJourney = (e) => { e.stopPropagation(); onClose(); window.dispatchEvent(new CustomEvent('app:navigate', { detail: { page: 'journey' } })); };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(8,6,20,0.78)', backdropFilter: 'blur(4px)',
      zIndex: 9999, display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      padding: '32px 14px', overflowY: 'auto', cursor: 'pointer', direction: 'rtl',
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: '#fff', border: '1.5px solid rgba(95,163,114,0.30)',
        boxShadow: '0 12px 40px rgba(0,0,0,0.25)', borderRadius: 18,
        padding: '22px 22px 18px', maxWidth: 520, width: '100%',
        color: '#1d4029', cursor: 'default', fontFamily: 'inherit',
      }}>
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 12 }}>
          <div style={{ fontSize: 40, lineHeight: 1 }}>{cur.icon || '📖'}</div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 12, color: '#6b6d73', fontWeight: 700, letterSpacing: 1 }}>הדרגה שלי</div>
            <div style={{ fontSize: 24, fontWeight: 800 }}>{cur.name_he}</div>
            {cur.description_he && <div style={{ fontSize: 13, color: '#5a3d0a', fontStyle: 'italic', marginTop: 4, lineHeight: 1.5 }}>{cur.description_he}</div>}
          </div>
          <button onClick={onClose} aria-label="סגור" style={{
            background: 'transparent', border: 'none', color: '#6b6d73',
            fontSize: 28, cursor: 'pointer', lineHeight: 1, padding: 0,
          }}>×</button>
        </div>

        {/* Quick stats */}
        <div style={{
          display: 'flex', justifyContent: 'space-around', gap: 8,
          padding: '12px 0', borderTop: '1px dashed rgba(95,163,114,0.25)',
          borderBottom: '1px dashed rgba(95,163,114,0.25)', marginBottom: 14, fontSize: 13,
        }}>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 18, fontWeight: 800 }}>{chaptersHave}</div>
            <div style={{ color: '#6b6d73' }}>פרקים מאומתים</div>
          </div>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 18, fontWeight: 800 }}>{streakHave}</div>
            <div style={{ color: '#6b6d73' }}>רצף ימים</div>
          </div>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 18, fontWeight: 800 }}>{(circles || []).length}</div>
            <div style={{ color: '#6b6d73' }}>מעגלים פתחתי</div>
          </div>
        </div>

        {/* Next level - what's missing */}
        {next && (
          <div style={{
            background: 'linear-gradient(135deg, rgba(212,175,55,0.10), rgba(95,163,114,0.08))',
            border: '1px solid rgba(212,175,55,0.30)',
            borderRadius: 12, padding: '12px 14px', marginBottom: 14,
          }}>
            <div style={{ fontSize: 12, color: '#5a3d0a', fontWeight: 700, letterSpacing: 1, marginBottom: 4 }}>הדרגה הבאה</div>
            <div style={{ fontSize: 16, fontWeight: 800, marginBottom: 6 }}>{next.icon} {next.name_he}</div>
            {next.description_he && <div style={{ fontSize: 13, color: '#5a3d0a', fontStyle: 'italic', marginBottom: 8 }}>{next.description_he}</div>}
            <div style={{ fontSize: 13, lineHeight: 1.7 }}>
              {chaptersNeeded > 0 && <div>📚 עוד <strong>{chaptersNeeded}</strong> פרקי תניא</div>}
              {treasuresNeeded > 0 && <div>🪞 עוד <strong>{treasuresNeeded}</strong> אוצרות לגלות (שאלות המסע)</div>}
              {connectionsNeeded > 0 && <div>🤝 עוד <strong>{connectionsNeeded}</strong> חיבורים — להביא, לעזור, ללוות</div>}
              {streakNeeded > 0 && <div>🔥 ועוד רצף קטן של <strong>{streakNeeded}</strong> ימים</div>}
              {chaptersNeeded === 0 && treasuresNeeded === 0 && connectionsNeeded === 0 && streakNeeded === 0 && <div>✓ הכל מוכן — תעלה בקרוב</div>}
            </div>
            {next.reward_amount > 0 && (
              <div style={{ fontSize: 12, color: '#a8801f', marginTop: 8, fontWeight: 700 }}>🪙 פרס: {next.reward_amount} {next.reward_token}</div>
            )}
          </div>
        )}

        {/* Circles I created */}
        <div style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 13, fontWeight: 800, marginBottom: 8 }}>✦ המעגלים שפתחתי</div>
          {circles === null && <div style={{ fontSize: 13, color: '#6b6d73' }}>טוען...</div>}
          {circles && circles.length === 0 && <div style={{ fontSize: 13, color: '#6b6d73', fontStyle: 'italic' }}>עדיין לא פתחת מעגל. תוכל לפתוח מ"מעגלים שלי" בפרופיל.</div>}
          {circles && circles.length > 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {circles.slice(0, 5).map(c => (
                <div key={c.id} style={{ fontSize: 13, padding: '6px 10px', background: '#f7f5ee', borderRadius: 8 }}>
                  <strong>{c.name}</strong> <span style={{ color: '#6b6d73' }}>· {c.chapters_completed}/{c.total_chapters} · {c.participants} משתתפים</span>
                </div>
              ))}
              {circles.length > 5 && <div style={{ fontSize: 12, color: '#6b6d73', textAlign: 'center' }}>ועוד {circles.length - 5}...</div>}
            </div>
          )}
        </div>

        {/* Chapters I read */}
        <div style={{ marginBottom: 14 }}>
          <div style={{ fontSize: 13, fontWeight: 800, marginBottom: 8 }}>📚 פרקים שקראתי בתניא</div>
          {readings === null && <div style={{ fontSize: 13, color: '#6b6d73' }}>טוען...</div>}
          {readings && uniqueChapters.length === 0 && <div style={{ fontSize: 13, color: '#6b6d73', fontStyle: 'italic' }}>עדיין לא קראת פרק בתניא.</div>}
          {readings && uniqueChapters.length > 0 && (
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {uniqueChapters.map(ch => (
                <span key={ch} style={{
                  fontSize: 12, fontWeight: 700,
                  padding: '4px 10px', borderRadius: 999,
                  background: '#e8f1ea', color: '#1d4029',
                  border: '1px solid rgba(95,163,114,0.25)',
                }}>פרק {ch}</span>
              ))}
            </div>
          )}
        </div>

        {/* CTAs - deepen / חידושים */}
        <div style={{
          display: 'flex', flexDirection: 'column', gap: 8,
          paddingTop: 12, borderTop: '1px dashed rgba(95,163,114,0.25)',
        }}>
          <div style={{ fontSize: 13, fontWeight: 800, marginBottom: 2 }}>✨ להעמיק ולחדש</div>
          <button onClick={goToCompanion} style={{
            background: 'linear-gradient(135deg, rgba(212,175,55,0.12), rgba(106,76,176,0.10))',
            border: '1px dashed rgba(212,175,55,0.50)', color: '#1d4029',
            padding: '10px 14px', borderRadius: 10, fontFamily: 'inherit',
            fontSize: 14, fontWeight: 600, cursor: 'pointer', textAlign: 'right',
          }}>✦ דבר עם <strong>עובד</strong> · המלווה שלך במסע ←</button>
          <button onClick={goToJourney} style={{
            background: 'rgba(95,163,114,0.10)', border: '1px solid rgba(95,163,114,0.35)',
            color: '#1d4029', padding: '10px 14px', borderRadius: 10, fontFamily: 'inherit',
            fontSize: 14, fontWeight: 600, cursor: 'pointer', textAlign: 'right',
          }}>📜 ענה על שאלות המסע · 12 שאלות שמגלות אותך ←</button>
        </div>
      </div>
    </div>
  );
}

// ─── EcosystemPage [yakir 2026-05-19] ───────────────────────────────
// Live registry of all active fundraising campaigns. Yakir's vision:
// nonprofits open campaigns, donors give and get cashback in the coin
// the campaign chose. This is the public-facing surface for Phase 8.
function EcosystemPage({ userId }) {
  const [campaigns, setCampaigns] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [openForm, setOpenForm] = React.useState(false);

  const load = () => {
    setLoading(true);
    api('/api/support/discover')
      .then(d => setCampaigns(Array.isArray(d.campaigns) ? d.campaigns : []))
      .catch(() => setCampaigns([]))
      .finally(() => setLoading(false));
  };
  React.useEffect(() => { load(); }, []);

  return (
    <section style={{
      paddingTop: 'max(60px, calc(env(safe-area-inset-top, 0px) + 30px))',
      paddingBottom: 80, direction: 'rtl' }}>
      <div className="container">
        <h2 className="section-title">💎 האקוסיסטם</h2>
        <p className="section-sub" style={{ maxWidth: 680, margin: '0 auto 18px', lineHeight: 1.7 }}>
          כאן עמותות פותחות קמפיין גיוס. תורם נותן כסף, מקבל cashback במטבע שהעמותה בחרה.
          ככל שיותר משתתפים — מטבעות הקהילה הופכים שווי-ערך אמיתי. <strong>היעד בלי גבול.</strong>
        </p>

        <div style={{ textAlign: 'center', margin: '18px 0 26px' }}>
          <button
            className="btn"
            onClick={() => setOpenForm(true)}
            disabled={!userId}
            title={userId ? '' : 'התחבר/י כדי לפתוח קמפיין'}
          >
            ➕ פתח קמפיין חדש
          </button>
        </div>

        {loading && (
          <p style={{ textAlign: 'center', color: 'var(--muted)' }}>טוען קמפיינים...</p>
        )}

        {!loading && campaigns.length === 0 && (
          <div style={{
            padding: 24, background: 'rgba(212,175,55,0.08)',
            border: '1.5px solid rgba(212,175,55,0.35)',
            borderRadius: 14, textAlign: 'center', maxWidth: 580, margin: '0 auto',
          }}>
            <div style={{ fontSize: 32, marginBottom: 8 }}>🌱</div>
            <h3 style={{ marginBottom: 10, color: '#5a3d0a' }}>בקרוב — קמפיינים פעילים</h3>
            <p style={{ color: '#6b5a37', lineHeight: 1.6 }}>
              האקוסיסטם נפתח עכשיו. אם יש לך עמותה — פתח קמפיין ראשון, ההרשמה חופשית.
            </p>
          </div>
        )}

        {!loading && campaigns.length > 0 && (
          <div style={{
            display: 'grid', gap: 16,
            gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
            maxWidth: 920, margin: '0 auto',
          }}>
            {campaigns.map(c => {
              const received = c.total_beu_received || 0;
              const goal = c.goal_beu || 0;
              const pct = goal > 0 ? Math.min(100, Math.round((received / goal) * 100)) : null;
              return (
              <div key={c.id} style={{ display: 'flex', flexDirection: 'column' }}>
              <a
                href={`/c/${c.slug}`}
                style={{
                  display: 'block', padding: 18, background: 'var(--text)',
                  border: '1.5px solid rgba(95,163,114,0.30)',
                  borderRadius: 14, textDecoration: 'none', color: 'inherit',
                  transition: 'transform .15s, box-shadow .15s',
                }}
                onMouseEnter={e => { e.currentTarget.style.transform = 'translateY(-2px)';
                                     e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.08)'; }}
                onMouseLeave={e => { e.currentTarget.style.transform = '';
                                     e.currentTarget.style.boxShadow = ''; }}
              >
                {c.hero_image_url && (
                  <img src={c.hero_image_url} alt={c.title}
                       style={{ width: '100%', height: 140, objectFit: 'cover',
                                borderRadius: 10, marginBottom: 12 }} />
                )}
                <h3 style={{ margin: '0 0 4px', color: '#1d4029', fontSize: 20, fontWeight: 800 }}>
                  {c.title}
                </h3>
                {c.amuta_name && (
                  <div style={{ fontSize: 12.5, color: '#6b6d73', marginBottom: 10 }}>
                    על שם {c.amuta_name}
                  </div>
                )}

                {/* [yakir 2026-05-20] Big visual progress bar */}
                <div style={{ margin: '14px 0 10px' }}>
                  <div style={{
                    height: 14, borderRadius: 7,
                    background: 'rgba(0,0,0,0.08)', overflow: 'hidden',
                  }}>
                    <div style={{
                      height: '100%',
                      width: pct !== null ? pct + '%'
                        : (received > 0 ? Math.min(100, received / 10) + '%' : '0%'),
                      background: 'linear-gradient(90deg, #5fa372 0%, #d4af37 100%)',
                      transition: 'width 0.8s ease',
                      borderRadius: 7,
                    }} />
                  </div>
                  <div style={{ display: 'flex', justifyContent: 'space-between',
                                marginTop: 6, fontSize: 12.5, color: '#5a3d0a',
                                fontWeight: 600 }}>
                    <span>🪙 {received.toLocaleString()} BEU בקופה</span>
                    {pct !== null
                      ? <span>{pct}% מתוך {goal.toLocaleString()}</span>
                      : <span style={{ color: 'var(--muted)' }}>בלי יעד — צובר</span>}
                  </div>
                </div>

                <div style={{ display: 'flex', gap: 14, marginTop: 12,
                              padding: '10px 12px',
                              background: 'rgba(95,163,114,0.06)',
                              borderRadius: 10, fontSize: 13 }}>
                  <div style={{ flex: 1, textAlign: 'center' }}>
                    <div style={{ fontSize: 18, fontWeight: 800, color: '#5fa372' }}>
                      {c.donor_count || 0}
                    </div>
                    <div style={{ fontSize: 11, color: '#6b6d73' }}>תורמים</div>
                  </div>
                  <div style={{ flex: 1, textAlign: 'center', borderRight: '1px solid rgba(0,0,0,0.08)', borderLeft: '1px solid rgba(0,0,0,0.08)' }}>
                    <div style={{ fontSize: 18, fontWeight: 800, color: '#a8801f' }}>
                      {received.toLocaleString()}
                    </div>
                    <div style={{ fontSize: 11, color: '#6b6d73' }}>BEU</div>
                  </div>
                  <div style={{ flex: 1, textAlign: 'center' }}>
                    <div style={{ fontSize: 18, fontWeight: 800, color: '#6b46c1' }}>
                      {c.ambassador_count || 0}
                    </div>
                    <div style={{ fontSize: 11, color: '#6b6d73' }}>שגרירים</div>
                  </div>
                </div>

                {c.cashback_mode && c.cashback_mode !== 'none' && (
                  <div style={{ marginTop: 10, padding: '7px 12px',
                                background: 'rgba(212,175,55,0.10)',
                                border: '1px dashed rgba(212,175,55,0.40)',
                                borderRadius: 8, fontSize: 12.5, color: '#5a3d0a',
                                textAlign: 'center', fontWeight: 600 }}>
                    💚 תרומה כאן → מקבל cashback{c.cashback_mode === 'raffle' ? ' בהגרלה' : c.cashback_mode === 'both' ? ' + הגרלה' : ''}
                  </div>
                )}
              </a>
              {/* [damri 2026-05-20] Donate button + paid chat per campaign */}
              <div style={{ display: 'flex', gap: 8, margin: '0 4px 8px', flexWrap: 'wrap' }}>
                <DonateButton slug={c.slug} title={c.title} userId={userId} onDone={load} />
              </div>
              <CampaignMessages slug={c.slug} userId={userId} />
              </div>
              );
            })}
          </div>
        )}

        {openForm && (
          <EcosystemDraftForm
            userId={userId}
            onClose={() => setOpenForm(false)}
            onCreated={() => { setOpenForm(false); load(); }}
          />
        )}
      </div>
    </section>
  );
}

// 7-question wizard [damri 2026-05-20] — replaces simple 5-field form.
// Damri's spec: "every project starts with the person it touches, not who
// was appointed". 7 questions surface that depth before creation.
function EcosystemDraftForm({ userId, onClose, onCreated }) {
  const QUESTIONS = [
    { key: 'q1_what',     label: 'מה באמת חשוב לך שיפתח, ואיזה חלק היית רוצה לקחת בזה?', short: 'מה חשוב', placeholder: 'במשפט-שניים — מה אתה רואה שצריך לקרות?', rows: 3 },
    { key: 'q2_why',      label: 'למה זה הכי חשוב לך?', short: 'למה', placeholder: 'מה דוחק בך מבפנים?', rows: 3 },
    { key: 'q3_exp',      label: 'איזה ניסיון יש לך בקידום יוזמות, או מה היית רוצה לפתח בעצמך?', short: 'ניסיון', placeholder: 'בלי דוקטורט. גם "אף פעם, אבל אני מוכן ללמוד" טוב.', rows: 3 },
    { key: 'q4_current',  label: 'מה אתה כבר עושה בחיים שלך עכשיו שקשור לזה?', short: 'עושה כרגע', placeholder: 'כל פעולה קטנה נחשבת.', rows: 3 },
    { key: 'q5_missing',  label: 'מה הדבר היחיד שחסר לך כדי להתמסר?', short: 'מה חסר', placeholder: 'זמן, מימון, שותף, אישור פנימי, ידע...', rows: 2 },
    { key: 'q6_step',     label: 'מה הצעד הקטן שתעשה כדי להיות בטוח שתקבל את זה?', short: 'צעד קטן', placeholder: 'משהו שאפשר לעשות השבוע.', rows: 2 },
    { key: 'q7_honor',    label: 'איך היית רוצה שהקהילה תוקיר אנשים שמתמסרים לשליחות?', short: 'הוקרה', placeholder: 'במטבעות? במפגש? בחיבור עם אנשים?', rows: 2 },
  ];

  const [step, setStep] = React.useState(0);  // 0..6 = questions, 7 = title+slug+meta
  const [title, setTitle] = React.useState('');
  const [slug, setSlug] = React.useState('');
  const [amuta, setAmuta] = React.useState('');
  const [cashback, setCashback] = React.useState('beu');
  const [answers, setAnswers] = React.useState(() =>
    Object.fromEntries(QUESTIONS.map(q => [q.key, ''])));
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  const setAnswer = (k, v) => setAnswers(prev => ({ ...prev, [k]: v }));
  const isQuestionPhase = step < QUESTIONS.length;
  const currentQ = isQuestionPhase ? QUESTIONS[step] : null;
  const currentAnswer = currentQ ? (answers[currentQ.key] || '') : '';
  const canNext = !isQuestionPhase || currentAnswer.trim().length >= 3;

  const next = () => {
    setErr('');
    if (!canNext) { setErr('תכתוב לפחות משפט קצר.'); return; }
    setStep(s => s + 1);
  };
  const back = () => { setErr(''); setStep(s => Math.max(0, s - 1)); };

  const submit = async () => {
    if (!userId) { setErr('צריך להיות מחובר'); return; }
    if (!title.trim() || !slug.trim()) { setErr('שם וכינוי נדרשים'); return; }
    // Build description_md from the 7 answers in a structured format
    const descMd = QUESTIONS.map(q =>
      `## ${q.label}\n${(answers[q.key] || '').trim()}\n`
    ).join('\n');
    setBusy(true); setErr('');
    try {
      const r = await fetch('/api/campaigns', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          user_id: userId, slug, title,
          description_md: descMd,
          amuta_name: amuta || null,
          cashback_mode: cashback, category: 'community',
        }),
      }).then(res => res.json());
      if (r.ok) onCreated();
      else setErr(r.error || 'שגיאה ביצירה');
    } catch (e) { setErr('שגיאת רשת'); }
    finally { setBusy(false); }
  };

  const totalSteps = QUESTIONS.length + 1;  // +1 for the meta step
  const progressPct = Math.round(((step + 1) / totalSteps) * 100);

  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.55)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      zIndex: 1000, direction: 'rtl', padding: 16,
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()}
           style={{ background: 'var(--text)', borderRadius: 14, padding: 22,
                    width: '100%', maxWidth: 540, maxHeight: '92vh', overflowY: 'auto' }}>
        {/* Header + progress */}
        <div style={{ marginBottom: 16 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
            <h3 style={{ margin: 0, color: '#1d4029', fontSize: 17 }}>
              ➕ פרויקט חדש · שלב {step + 1} מתוך {totalSteps}
            </h3>
            <button onClick={onClose} style={{ background: 'none', border: 'none',
                    fontSize: 22, color: 'var(--muted)', cursor: 'pointer' }}>×</button>
          </div>
          <div style={{ height: 6, background: 'rgba(0,0,0,0.06)', borderRadius: 3,
                        overflow: 'hidden' }}>
            <div style={{ width: progressPct + '%', height: '100%',
                          background: 'linear-gradient(90deg, #5fa372, #d4af37)',
                          transition: 'width .4s' }} />
          </div>
        </div>

        {isQuestionPhase ? (
          <div>
            <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 6, fontWeight: 600 }}>
              {currentQ.short}
            </div>
            <label style={{ display: 'block', marginBottom: 14 }}>
              <div style={{ fontSize: 15, fontWeight: 700, color: '#1d4029', marginBottom: 10, lineHeight: 1.5 }}>
                {currentQ.label}
              </div>
              <textarea
                value={currentAnswer}
                onChange={e => setAnswer(currentQ.key, e.target.value)}
                rows={currentQ.rows} autoFocus
                placeholder={currentQ.placeholder}
                style={{ width: '100%', padding: 12, borderRadius: 8,
                         border: '1px solid #d1d5db', fontSize: 14.5,
                         fontFamily: 'inherit', resize: 'vertical' }}
              />
            </label>
            <p style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 16, lineHeight: 1.5 }}>
              💡 אין תשובות נכונות. כתוב מה שעולה לך עכשיו.
            </p>
          </div>
        ) : (
          <div>
            <p style={{ fontSize: 14, color: '#1d4029', lineHeight: 1.6, marginBottom: 14 }}>
              יפה. עוד פרטים טכניים קצרים:
            </p>
            <label style={{ display: 'block', marginBottom: 10 }}>
              <div style={{ marginBottom: 4, fontSize: 13, fontWeight: 600 }}>שם הפרויקט (כותרת)</div>
              <input value={title} onChange={e => setTitle(e.target.value)}
                     placeholder="במשפט אחד"
                     style={{ width: '100%', padding: 10, borderRadius: 8, border: '1px solid #d1d5db' }}
                     required />
            </label>
            <label style={{ display: 'block', marginBottom: 10 }}>
              <div style={{ marginBottom: 4, fontSize: 13, fontWeight: 600 }}>כינוי (URL — אנגלית, ללא רווחים)</div>
              <input value={slug} onChange={e => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '-'))}
                     placeholder="my-project"
                     style={{ width: '100%', padding: 10, borderRadius: 8, border: '1px solid #d1d5db' }}
                     required />
            </label>
            <label style={{ display: 'block', marginBottom: 10 }}>
              <div style={{ marginBottom: 4, fontSize: 13, fontWeight: 600 }}>שם עמותה (אופציונלי)</div>
              <input value={amuta} onChange={e => setAmuta(e.target.value)}
                     style={{ width: '100%', padding: 10, borderRadius: 8, border: '1px solid #d1d5db' }} />
            </label>
            <label style={{ display: 'block', marginBottom: 14 }}>
              <div style={{ marginBottom: 4, fontSize: 13, fontWeight: 600 }}>סוג cashback לתורם</div>
              <select value={cashback} onChange={e => setCashback(e.target.value)}
                      style={{ width: '100%', padding: 10, borderRadius: 8, border: '1px solid #d1d5db' }}>
                <option value="beu">BEU למטבע התורם</option>
                <option value="raffle">הגרלה</option>
                <option value="both">שניהם</option>
                <option value="none">ללא</option>
              </select>
            </label>
          </div>
        )}

        {err && <div style={{ color: '#dc2626', marginBottom: 10, fontSize: 13 }}>{err}</div>}

        <div style={{ display: 'flex', gap: 10, marginTop: 8 }}>
          {step > 0 && (
            <button type="button" className="btn ghost" onClick={back}>← חזור</button>
          )}
          {isQuestionPhase ? (
            <button type="button" className="btn" onClick={next} disabled={!canNext}
                    style={{ marginRight: 'auto' }}>
              הבא →
            </button>
          ) : (
            <button type="button" className="btn" onClick={submit} disabled={busy}
                    style={{ marginRight: 'auto' }}>
              {busy ? 'יוצר...' : 'צור פרויקט (draft)'}
            </button>
          )}
        </div>

        <p style={{ marginTop: 14, fontSize: 11.5, color: 'var(--muted)',
                    lineHeight: 1.5, textAlign: 'center' }}>
          הפרויקט נשמר כ-draft. נדרש אישור מנהל להעלאה לרשימה הציבורית.
        </p>
      </div>
    </div>
  );
}

// ─── CircleDetailPage [damri 2026-05-20] ─────────────────────────────
// Dedicated page for one circle. Hash route: #/circle/<id>.
// Shows: hero image, intention, story, progress, readers, bonus pot with
// contributors, "join" button, WhatsApp link.
function CircleDetailPage({ circleId, userId }) {
  const [data, setData] = React.useState(null);
  const [bonus, setBonus] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [bonusModal, setBonusModal] = React.useState(false);

  const reload = React.useCallback(() => {
    if (!circleId) return;
    setLoading(true);
    Promise.all([
      fetch(`/api/circle/${circleId}/full`).then(r => r.json()),
      fetch(`/api/circles/${circleId}/bonus`).then(r => r.json()),
    ]).then(([c, b]) => { setData(c); setBonus(b); })
      .catch(() => {})
      .finally(() => setLoading(false));
  }, [circleId]);
  React.useEffect(() => { reload(); }, [reload]);

  if (loading) return <section style={{ paddingTop: 60 }}><div className="container">טוען...</div></section>;
  if (!data || data.error) return <section style={{ paddingTop: 60 }}><div className="container">מעגל לא נמצא</div></section>;

  const pct = data.total_chapters > 0
    ? Math.min(100, Math.round((data.chapters_read || 0) / data.total_chapters * 100)) : 0;
  const breakdown = bonus && bonus.pot_breakdown ? bonus.pot_breakdown : {};

  return (
    <section style={{
      paddingTop: 'max(30px, calc(env(safe-area-inset-top, 0px) + 10px))',
      paddingBottom: 80, direction: 'rtl' }}>
      <div className="container" style={{ maxWidth: 720 }}>
        {/* Hero image */}
        {data.hero_image_url && (
          <img src={data.hero_image_url} alt={data.name}
               style={{ width: '100%', maxHeight: 320, objectFit: 'cover',
                        borderRadius: 14, marginBottom: 16 }} />
        )}

        {/* Title + creator + circle follow */}
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12, marginBottom: 4 }}>
          <h2 style={{ margin: 0, color: '#1d4029', fontSize: 28, flex: 1 }}>
            🕯️ {data.name}
          </h2>
          {/* [handoff §10 r2 2026-05-24] FollowCircleButton wired */}
          {userId && <FollowCircleButton circleId={circleId} viewerId={userId} />}
        </div>
        {data.creator_name && (
          <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom: 14 }}>
            <MemberAvatar user={{ id: data.creator_id || 0, name: data.creator_name }} size={28} />
            {/* [handoff §10 r2 2026-05-24] creator name → link to public profile */}
            {data.creator_id ? (
              <a href={`#/user/${data.creator_id}`}
                 style={{ fontSize: 13, color: '#5a3d0a', fontWeight: 600, textDecoration: 'none' }}
                 onMouseEnter={(e) => e.currentTarget.style.textDecoration = 'underline'}
                 onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'}>
                {data.creator_name}
              </a>
            ) : (
              <span style={{ fontSize: 13, color: '#5a3d0a', fontWeight: 600 }}>{data.creator_name}</span>
            )}
          </div>
        )}

        {/* Intention */}
        {(data.intention || data.purpose) && (
          <div style={{ padding: 14, background: 'rgba(95,163,114,0.06)',
                        border: '1px solid rgba(95,163,114,0.30)',
                        borderRadius: 10, marginBottom: 14 }}>
            <div style={{ fontSize: 12, color: '#5fa372', fontWeight: 700, marginBottom: 4 }}>
              ✨ כוונת המעגל
            </div>
            <div style={{ fontSize: 15, color: '#1d4029', lineHeight: 1.6, whiteSpace: 'pre-wrap' }}>
              {data.intention || data.purpose}
            </div>
          </div>
        )}

        {/* Story */}
        {data.story_md && (
          <div style={{ padding: 14, background: 'rgba(168,128,31,0.06)',
                        border: '1px solid rgba(168,128,31,0.25)',
                        borderRadius: 10, marginBottom: 14 }}>
            <div style={{ fontSize: 12, color: '#a8801f', fontWeight: 700, marginBottom: 4 }}>
              📜 הסיפור
            </div>
            <div style={{ fontSize: 14.5, color: '#5a3d0a', lineHeight: 1.7, whiteSpace: 'pre-wrap' }}>
              {data.story_md}
            </div>
          </div>
        )}

        {/* Progress */}
        <div style={{ padding: 14, background: 'var(--text)',
                      border: '1.5px solid rgba(0,0,0,0.10)',
                      borderRadius: 10, marginBottom: 14 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between',
                        alignItems: 'baseline', marginBottom: 8 }}>
            <strong style={{ color: '#1d4029' }}>התקדמות</strong>
            <span style={{ fontSize: 14, color: '#5a3d0a', fontWeight: 700 }}>
              {data.chapters_read || 0} / {data.total_chapters} פרקים
            </span>
          </div>
          <div style={{ height: 10, background: 'rgba(0,0,0,0.08)',
                        borderRadius: 5, overflow: 'hidden' }}>
            <div style={{ width: pct + '%', height: '100%',
                          background: 'linear-gradient(90deg, #5fa372, #d4af37)',
                          transition: 'width .6s' }} />
          </div>
          <div style={{ marginTop: 8, fontSize: 12.5, color: '#6b6d73' }}>
            {data.reader_count || 0} קוראים · {data.text_type === 'tanya' ? 'תניא' : data.text_type === 'tikkunei_zohar' ? 'תיקוני הזוהר' : data.text_type === 'rambam' ? 'משנה תורה' : 'תהילים'}
            {data.payout_distributed ? ' · 🪙 ההוקרה חולקה' : ''}
          </div>
        </div>

        {/* Bonus pot */}
        <div style={{ padding: 14,
                      background: 'linear-gradient(135deg, rgba(212,175,55,0.08), rgba(95,163,114,0.06))',
                      border: '1.5px solid rgba(168,128,31,0.30)',
                      borderRadius: 12, marginBottom: 14 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between',
                        alignItems: 'center', marginBottom: 10 }}>
            <strong style={{ color: '#5a3d0a' }}>🪙 קופת הוקרה למסיימים</strong>
            <button onClick={() => setBonusModal(true)} disabled={!userId || data.payout_distributed}
                    className="btn" style={{ padding: '6px 14px', fontSize: 13 }}>
              + להוסיף
            </button>
          </div>
          <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap',
                        padding: '8px 0', fontSize: 14 }}>
            {Object.entries(breakdown).length === 0 ? (
              <span style={{ color: 'var(--muted)' }}>הקופה עוד ריקה</span>
            ) : (
              Object.entries(breakdown).map(([sym, amt]) => (
                <span key={sym} style={{ fontWeight: 700, color: '#a8801f' }}>
                  {amt.toLocaleString()} {sym}
                </span>
              ))
            )}
          </div>
          {(data.bonus_per_completer_beu || 0) > 0 && (
            <div style={{ fontSize: 12, color: '#5a3d0a', marginTop: 4 }}>
              + {data.bonus_per_completer_beu} BEU בסיס לכל מסיים מהמערכת
            </div>
          )}
          {bonus && bonus.contributors && bonus.contributors.length > 0 && (
            <details style={{ marginTop: 10 }}>
              <summary style={{ fontSize: 12.5, color: '#a8801f', cursor: 'pointer' }}>
                {bonus.contributors.length} תורמים לקופה
              </summary>
              <div style={{ marginTop: 8, fontSize: 12.5, color: '#5a3d0a',
                            paddingTop: 6, borderTop: '1px dashed rgba(168,128,31,0.25)' }}>
                {bonus.contributors.slice(0, 20).map(c => (
                  <div key={c.user_id + '-' + (c.created_at || '')}>
                    {c.user_name} — {c.amount.toLocaleString()} {c.token_symbol || 'BEU'}
                  </div>
                ))}
              </div>
            </details>
          )}
        </div>

        {/* WhatsApp link */}
        {data.whatsapp_url && (
          <a href={data.whatsapp_url} target="_blank" rel="noopener noreferrer"
             style={{
               display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
               padding: 14, marginBottom: 14,
               background: '#25d366', color: 'var(--text)', textDecoration: 'none',
               borderRadius: 10, fontWeight: 700, fontSize: 15,
             }}>
            💬 הצטרף לקבוצת WhatsApp של המעגל
          </a>
        )}

        {/* Readers list */}
        {data.readers && data.readers.length > 0 && (
          <div style={{ padding: 14, background: 'var(--text)',
                        border: '1.5px solid rgba(0,0,0,0.08)',
                        borderRadius: 10, marginBottom: 14 }}>
            <strong style={{ color: '#1d4029', fontSize: 14 }}>הקוראים</strong>
            <div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {data.readers.map(r => (
                <span key={r.id} style={{
                  padding: '4px 10px', background: 'rgba(95,163,114,0.10)',
                  borderRadius: 100, fontSize: 12.5, color: '#1d4029',
                }}>{r.name} · {r.chapters}</span>
              ))}
            </div>
          </div>
        )}

        <div style={{ textAlign: 'center', marginTop: 20 }}>
          <button onClick={() => window.dispatchEvent(new CustomEvent('app:navigate', { detail: { page: 'home' } }))}
                  className="btn ghost">← לרשימת המעגלים</button>
        </div>
      </div>

      {bonusModal && (
        <CircleBonusModal circleId={circleId} userId={userId}
                          onClose={() => setBonusModal(false)}
                          onDone={() => { setBonusModal(false); reload(); }} />
      )}
    </section>
  );
}

// ─── CircleBonusModal [damri 2026-05-20] ─────────────────────────────
// Lets a user contribute to a circle's bonus pot in any token (BEU/TNY/THL/RMB).
function CircleBonusModal({ circleId, userId, onClose, onDone }) {
  const [amount, setAmount] = React.useState(10);
  const [token, setToken] = React.useState('BEU');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  const submit = async () => {
    const amt = parseInt(amount, 10);
    if (!amt || amt < 1) { setErr('סכום לא תקין'); return; }
    setBusy(true); setErr('');
    try {
      const r = await fetch(`/api/circles/${circleId}/bonus`, {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ user_id: userId, amount: amt, token_symbol: token }),
      });
      const j = await r.json();
      if (!r.ok) { setErr(j.user_message || j.error || 'שגיאה'); return; }
      onDone();
    } catch (e) { setErr('שגיאת רשת'); }
    finally { setBusy(false); }
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  zIndex: 1000, padding: 16, direction: 'rtl' }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        background: 'var(--text)', borderRadius: 14, padding: 22, width: '100%', maxWidth: 380,
      }}>
        <h3 style={{ margin: '0 0 14px', color: '#5a3d0a' }}>🪙 הוסף לקופת ההוקרה</h3>
        <label style={{ fontSize: 13, fontWeight: 600 }}>מטבע</label>
        <select value={token} onChange={e => setToken(e.target.value)}
                style={{ width: '100%', padding: 10, borderRadius: 8, border: '1px solid #d1d5db', marginBottom: 12 }}>
          <option value="BEU">BEU · בית דויד</option>
          <option value="TNY">TNY · תניא</option>
          <option value="THL">THL · תהילים</option>
          <option value="RMB">RMB · רמב״ם</option>
        </select>
        <label style={{ fontSize: 13, fontWeight: 600 }}>סכום</label>
        <input type="number" value={amount} onChange={e => setAmount(e.target.value)}
               min="1" style={{ width: '100%', padding: 10, fontSize: 18,
                                borderRadius: 8, border: '1px solid #d1d5db', marginBottom: 6 }} />
        <div style={{ display: 'flex', gap: 6, marginBottom: 14 }}>
          {[10, 50, 100, 500].map(v => (
            <button key={v} type="button" onClick={() => setAmount(v)}
                    style={{ flex: 1, padding: '6px 8px', fontSize: 12,
                             background: parseInt(amount,10)===v ? '#5fa372' : '#f3f4f6',
                             color: parseInt(amount,10)===v ? 'var(--text)' : '#1d4029',
                             border: '1px solid #d1d5db', borderRadius: 6, cursor: 'pointer' }}>{v}</button>
          ))}
        </div>
        {err && <div style={{ color: '#dc2626', fontSize: 13, marginBottom: 10 }}>{err}</div>}
        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={submit} disabled={busy} className="btn" style={{ flex: 1 }}>
            {busy ? '...' : `הוסף ${amount} ${token}`}
          </button>
          <button onClick={onClose} className="btn ghost">ביטול</button>
        </div>
        <p style={{ fontSize: 11.5, color: 'var(--muted)', marginTop: 10, lineHeight: 1.5 }}>
          הסכום יורד מהיתרה שלך מיד. יחולק לכל המסיימים כשהמעגל יסתיים.
        </p>
      </div>
    </div>
  );
}

// ─── DonateButton + DonateModal [damri 2026-05-20] ──────────────────
// Lets a user submit a real donation (status=pending). Damri approves later
// via /admin and cashback fires automatically.
function DonateButton({ slug, title, userId, onDone }) {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <button
        onClick={() => setOpen(true)}
        style={{
          flex: 1, padding: '10px 14px',
          background: 'linear-gradient(135deg, #d4af37, #b8941f)',
          color: '#1a1a2e',
          border: '1.5px solid #8a5f10',
          borderRadius: 8, fontSize: 14, fontWeight: 800, cursor: 'pointer',
          boxShadow: '0 2px 8px rgba(138,95,16,0.30)',
          direction: 'rtl',
        }}
      >💝 לתרום לקמפיין הזה</button>
      {open && (
        <DonateModal slug={slug} title={title} userId={userId}
                     onClose={() => setOpen(false)}
                     onDone={() => { setOpen(false); if (onDone) onDone(); }} />
      )}
    </>
  );
}

function DonateModal({ slug, title, userId, onClose, onDone }) {
  const [amountIls, setAmountIls] = React.useState(100);
  const [donorName, setDonorName] = React.useState('');
  const [kavana, setKavana] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [done, setDone] = React.useState(false);

  // Show expected cashback (rate 10 BEU/ILS — visible to user)
  const expectedCashback = Math.round(Number(amountIls) * 10);

  const submit = async () => {
    const ils = parseInt(amountIls, 10);
    if (!ils || ils < 1) { setErr('סכום לא תקין'); return; }
    setBusy(true); setErr('');
    try {
      const r = await fetch('/api/donations', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          user_id: userId || null,
          campaign_slug: slug,
          amount_ils: ils,
          donor_name: donorName.trim() || null,
          kavana: kavana.trim() || null,
        }),
      });
      const j = await r.json();
      if (!r.ok) { setErr(j.user_message || j.error || 'שגיאה'); return; }
      setDone(true);
    } catch (e) { setErr('שגיאת רשת'); }
    finally { setBusy(false); }
  };

  return (
    <div style={{
      position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.55)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      zIndex: 1000, direction: 'rtl', padding: 16,
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        background: 'var(--text)', borderRadius: 14, padding: 24,
        width: '100%', maxWidth: 480, maxHeight: '90vh', overflowY: 'auto',
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between',
                      alignItems: 'center', marginBottom: 14 }}>
          <h3 style={{ margin: 0, color: '#5a3d0a' }}>💝 תרומה</h3>
          <button onClick={onClose} style={{ background: 'none', border: 'none',
                  fontSize: 22, color: 'var(--muted)', cursor: 'pointer' }}>×</button>
        </div>
        <p style={{ fontSize: 14, color: '#1d4029', lineHeight: 1.6, marginBottom: 14 }}>
          תרומה ל-<strong>{title}</strong>. תקבל ל-WhatsApp פרטי העברה בנקאית, ולאחר שדמרי
          יאשר את קבלת הכסף — תקבל cashback ב-BEU אוטומטית.
        </p>

        {done ? (
          <div style={{ padding: 18, background: 'rgba(95,163,114,0.08)',
                        border: '1.5px solid rgba(95,163,114,0.40)',
                        borderRadius: 12, textAlign: 'right' }}>
            <div style={{ fontSize: 40, marginBottom: 8, textAlign: 'center' }}>🌱</div>
            <h4 style={{ margin: '0 0 10px', color: '#1d4029', textAlign: 'center' }}>התרומה נרשמה</h4>
            <p style={{ fontSize: 13.5, color: '#1d4029', lineHeight: 1.6, marginBottom: 14, textAlign: 'center' }}>
              עכשיו תעביר את הכסף באחת מהדרכים. דמרי יאשר אחרי קבלה, ואז יורד cashback של {expectedCashback.toLocaleString()} BEU מיד.
            </p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 12 }}>
              <a href={`https://www.bitpay.co.il/app/me/D33CB95C-43BE-A1AE-FA8F-8B1EAB1C2D0F00BA?amount=${parseInt(amountIls,10)}`} target="_blank" rel="noopener noreferrer"
                 style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 14px',
                          background: 'var(--text)', border: '1.5px solid rgba(0,0,0,0.08)', borderRadius: 10,
                          textDecoration: 'none', color: '#1d4029', fontWeight: 700 }}>
                <span>📱 bit · העברה מיידית</span>
                <span style={{ color: '#5fa372' }}>פתח ←</span>
              </a>
              <a href={`https://app.paybox.co.il/Payment/Initial?...`} target="_blank" rel="noopener noreferrer"
                 style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 14px',
                          background: 'var(--text)', border: '1.5px solid rgba(0,0,0,0.08)', borderRadius: 10,
                          textDecoration: 'none', color: '#1d4029', fontWeight: 700 }}>
                <span>💳 PayBox · אשראי</span>
                <span style={{ color: '#5fa372' }}>פתח ←</span>
              </a>
              <div style={{ padding: '10px 14px', background: 'rgba(212,175,55,0.06)',
                            border: '1px dashed rgba(168,128,31,0.30)', borderRadius: 10, fontSize: 13.5 }}>
                <strong style={{ color: '#5a3d0a' }}>🏦 העברה בנקאית:</strong>
                <div style={{ marginTop: 6, color: '#1d4029', lineHeight: 1.6, fontFamily: 'monospace', fontSize: 13 }}>
                  בנק הפועלים · סניף 651<br/>
                  חשבון: 123-456789 · ע״ש בית דויד<br/>
                  הערה: תרומה לקמפיין #{title}
                </div>
              </div>
            </div>
            <p style={{ fontSize: 11, color: 'var(--muted)', textAlign: 'center', marginBottom: 12, lineHeight: 1.5 }}>
              ⓘ פרטי החשבון לדוגמה. דמרי יעדכן עם הפרטים האמיתיים.
            </p>
            <button onClick={onDone} className="btn" style={{ width: '100%' }}>סיימתי</button>
          </div>
        ) : (
          <>
            <label style={{ display: 'block', marginBottom: 12 }}>
              <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>סכום (₪)</div>
              <input type="number" value={amountIls}
                     onChange={e => setAmountIls(e.target.value)}
                     min={1} step={10}
                     style={{ width: '100%', padding: 12, fontSize: 18,
                              borderRadius: 8, border: '1px solid #d1d5db',
                              fontWeight: 700 }} />
              <div style={{ display: 'flex', gap: 6, marginTop: 6 }}>
                {[36, 100, 180, 500, 1000].map(v => (
                  <button key={v} type="button" onClick={() => setAmountIls(v)}
                          style={{ flex: 1, padding: '6px 8px', fontSize: 12,
                                   background: parseInt(amountIls,10)===v ? '#5fa372' : '#f3f4f6',
                                   color: parseInt(amountIls,10)===v ? 'var(--text)' : '#1d4029',
                                   border: '1px solid #d1d5db', borderRadius: 6,
                                   cursor: 'pointer' }}>{v} ₪</button>
                ))}
              </div>
            </label>

            <div style={{ padding: 12, background: 'rgba(168,128,31,0.10)',
                          border: '1px dashed rgba(168,128,31,0.40)',
                          borderRadius: 8, marginBottom: 14,
                          fontSize: 14, color: '#5a3d0a', textAlign: 'center' }}>
              💚 תקבל ~ <strong>{expectedCashback.toLocaleString()} BEU</strong> cashback אחרי האישור
            </div>

            <label style={{ display: 'block', marginBottom: 12 }}>
              <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>
                שם לקבלה (אופציונלי)
              </div>
              <input value={donorName} onChange={e => setDonorName(e.target.value)}
                     placeholder="שם מלא — אם תרצה קבלה לזיכוי מס"
                     style={{ width: '100%', padding: 10, fontSize: 14,
                              borderRadius: 8, border: '1px solid #d1d5db' }} />
            </label>

            <label style={{ display: 'block', marginBottom: 14 }}>
              <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4 }}>
                כוונה (אופציונלי)
              </div>
              <input value={kavana} onChange={e => setKavana(e.target.value)}
                     placeholder="לאיזה תיקון? לזכר מי? לרפואת מי?"
                     style={{ width: '100%', padding: 10, fontSize: 14,
                              borderRadius: 8, border: '1px solid #d1d5db' }} />
            </label>

            {err && <div style={{ color: '#dc2626', fontSize: 13, marginBottom: 10 }}>{err}</div>}

            <button onClick={submit} disabled={busy || !amountIls}
                    className="btn" style={{ width: '100%', fontSize: 16, padding: 14 }}>
              {busy ? 'שולח...' : `💝 לתרום ${parseInt(amountIls,10).toLocaleString()} ₪`}
            </button>

            <p style={{ fontSize: 11.5, color: 'var(--muted)', textAlign: 'center',
                        marginTop: 10, lineHeight: 1.5 }}>
              התרומה נרשמת כ-pending. דמרי מאשר ידנית אחרי קבלת הכסף בפועל.
              הכסף עובר במסלול נפרד (העברה בנקאית / מזומן) — לא דרך האתר.
            </p>
          </>
        )}
      </div>
    </div>
  );
}

// ─── MyDonationsCard [damri 2026-05-20] ──────────────────────────────
// Donor sees their full history (pending / approved / rejected) + cashback received.
function MyDonationsCard({ userId, refresh }) {
  const [donations, setDonations] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    if (!userId) return;
    setLoading(true);
    api(`/api/donations/mine?user_id=${userId}`)
      .then(d => setDonations(Array.isArray(d.donations) ? d.donations : []))
      .catch(() => setDonations([]))
      .finally(() => setLoading(false));
  }, [userId, refresh]);

  if (loading || donations.length === 0) return null;

  const totalIls = donations.filter(d => d.status === 'approved').reduce((s, d) => s + (d.amount_ils || 0), 0);
  const totalCashback = donations.filter(d => d.status === 'approved').reduce((s, d) => s + (d.cashback_received || 0), 0);

  return (
    <div style={{
      margin: '18px 0', padding: 16,
      background: 'linear-gradient(135deg, rgba(212,175,55,0.06), rgba(95,163,114,0.06))',
      border: '1.5px solid rgba(168,128,31,0.30)',
      borderRadius: 14, direction: 'rtl',
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between',
                    alignItems: 'center', marginBottom: 12 }}>
        <strong style={{ color: '#5a3d0a', fontSize: 15 }}>💝 התרומות שלי</strong>
        <span style={{ fontSize: 12, color: '#a8801f' }}>
          {totalIls.toLocaleString()} ₪ · {totalCashback.toLocaleString()} BEU cashback
        </span>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {donations.slice(0, 8).map(d => (
          <div key={d.id} style={{
            display: 'flex', justifyContent: 'space-between', alignItems: 'center',
            padding: '8px 12px', background: 'var(--text)',
            borderRadius: 8, fontSize: 13.5,
          }}>
            <span style={{ flex: 1 }}>
              <strong>{d.campaign_title}</strong>
              <span style={{ color: 'var(--muted)', fontSize: 11, marginRight: 8 }}>
                {(d.created_at || '').slice(0, 10)}
              </span>
            </span>
            <span style={{ fontWeight: 700,
                           color: d.status === 'approved' ? '#5fa372'
                                : d.status === 'pending' ? '#a8801f'
                                : '#dc2626' }}>
              {d.amount_ils > 0 && `${d.amount_ils.toLocaleString()} ₪`}
              {' · '}
              {d.status === 'approved' ? '✓ אושר' :
               d.status === 'pending' ? '⏳ ממתין' :
               d.status === 'rejected' ? '✗ נדחה' : d.status}
            </span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─── AdminDonationsSection [damri 2026-05-20] ───────────────────────
// Pending donations approval queue inside /admin.
function AdminDonationsSection({ adminId }) {
  const [pending, setPending] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [busy, setBusy] = React.useState({});

  const load = React.useCallback(() => {
    if (!adminId) return;
    setLoading(true);
    fetch(`/api/admin/donations/pending?user_id=${adminId}`)
      .then(r => r.json())
      .then(d => setPending(Array.isArray(d.pending) ? d.pending : []))
      .catch(() => setPending([]))
      .finally(() => setLoading(false));
  }, [adminId]);

  React.useEffect(() => { load(); }, [load]);

  const decide = async (id, action) => {
    setBusy(b => ({ ...b, [id]: action }));
    try {
      const body = { user_id: adminId };
      if (action === 'reject') {
        const reason = prompt('סיבת הדחייה?');
        if (!reason) { setBusy(b => ({ ...b, [id]: null })); return; }
        body.reason = reason;
      }
      await fetch(`/api/admin/donations/${id}/${action}`, {
        method: 'PUT', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      load();
    } catch (e) { alert('שגיאה: ' + (e.message || e)); }
    finally { setBusy(b => ({ ...b, [id]: null })); }
  };

  return (
    <section style={{ marginBottom: 30 }}>
      <div className="container">
        <h2 className="section-title" style={{ fontSize: 22 }}>
          💝 תרומות ממתינות לאישור{pending.length > 0 && (
            <span style={{ background: '#dc2626', color: 'var(--text)', borderRadius: 100,
                           padding: '2px 10px', fontSize: 13, marginRight: 10 }}>
              {pending.length}
            </span>
          )}
        </h2>
        {loading && <p style={{ color: 'var(--muted)' }}>טוען...</p>}
        {!loading && pending.length === 0 && (
          <p style={{ color: 'var(--muted)', textAlign: 'center', padding: 20, fontStyle: 'italic' }}>
            אין תרומות ממתינות. נקי.
          </p>
        )}
        {pending.map(d => (
          <div key={d.id} style={{
            margin: '10px 0', padding: 14,
            background: 'rgba(212,175,55,0.06)',
            border: '1.5px solid rgba(168,128,31,0.30)',
            borderRadius: 12, direction: 'rtl',
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between',
                          alignItems: 'flex-start', marginBottom: 8, flexWrap: 'wrap', gap: 8 }}>
              <div>
                <strong style={{ color: '#1d4029', fontSize: 15 }}>
                  {d.donor_name || d.donor_user_name || 'אנונימי'}
                </strong>
                <span style={{ color: 'var(--muted)', fontSize: 12, marginRight: 8 }}>
                  → {d.campaign_title}
                </span>
              </div>
              <div style={{ fontSize: 18, fontWeight: 800, color: '#a8801f' }}>
                {d.amount_ils > 0 && `${d.amount_ils.toLocaleString()} ₪`}
                {d.amount_beu > 0 && ` · ${d.amount_beu.toLocaleString()} BEU`}
              </div>
            </div>
            <div style={{ fontSize: 12, color: '#6b6d73', marginBottom: 10 }}>
              {(d.created_at || '').slice(0, 16)}
              {d.donor_email && ` · ${d.donor_email}`}
              {d.ambassador_user_id && ` · 🤝 שגריר #${d.ambassador_user_id}`}
            </div>
            {d.notes && (
              <div style={{ padding: '6px 10px', background: 'rgba(95,163,114,0.06)',
                            borderRadius: 6, fontSize: 13, marginBottom: 10,
                            fontStyle: 'italic', color: '#1d4029' }}>
                💭 {d.notes}
              </div>
            )}
            <div style={{ display: 'flex', gap: 8 }}>
              <button onClick={() => decide(d.id, 'approve')} disabled={busy[d.id]}
                      className="btn" style={{ flex: 1, padding: '10px',
                      background: 'linear-gradient(135deg, #5fa372, #4a8a5a)' }}>
                {busy[d.id] === 'approve' ? '...' : '✓ אישור + cashback'}
              </button>
              <button onClick={() => decide(d.id, 'reject')} disabled={busy[d.id]}
                      className="btn ghost" style={{ flex: 0, padding: '10px 14px',
                      borderColor: '#dc2626', color: '#dc2626' }}>
                {busy[d.id] === 'reject' ? '...' : '✗ דחה'}
              </button>
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}

// ─── CampaignMessages [damri 2026-05-20] ──────────────────────────────
// Paid chat under each campaign. 10 BEU per message → goes to campaign treasury.
// Filters noise + turns talk into contribution. TNY weight badge shows
// "voice strength" — readers who learned Tanya have louder voices.
function CampaignMessages({ slug, userId }) {
  const [open, setOpen] = React.useState(false);
  const [messages, setMessages] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [meta, setMeta] = React.useState({ cost_per_message: 10, tny_weight_multiplier: 3 });
  const [draft, setDraft] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [count, setCount] = React.useState(null);

  // Preview count (lightweight) — load once on mount to show "💬 5 תגובות" button
  React.useEffect(() => {
    fetch(`/api/campaigns/${slug}/messages?limit=1`)
      .then(r => r.json())
      .then(d => { if (d && Array.isArray(d.messages)) setCount(d.count); })
      .catch(() => {});
  }, [slug]);

  const load = () => {
    setLoading(true);
    fetch(`/api/campaigns/${slug}/messages?limit=50`)
      .then(r => r.json())
      .then(d => {
        if (d && Array.isArray(d.messages)) {
          setMessages(d.messages);
          setCount(d.messages.length);
          setMeta({
            cost_per_message: d.cost_per_message || 10,
            tny_weight_multiplier: d.tny_weight_multiplier || 3,
          });
        }
      })
      .finally(() => setLoading(false));
  };

  const send = async () => {
    const t = draft.trim();
    if (t.length < 3) { setErr('תכתוב לפחות 3 תווים.'); return; }
    if (!userId) { setErr('התחבר/י כדי לכתוב.'); return; }
    setSending(true); setErr('');
    try {
      const r = await fetch(`/api/campaigns/${slug}/messages`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ user_id: userId, text: t }),
      });
      const j = await r.json();
      if (!r.ok) { setErr(j.user_message || j.error || 'שגיאה'); return; }
      setDraft('');
      load();
    } catch (e) { setErr('שגיאת רשת'); }
    finally { setSending(false); }
  };

  if (!open) {
    return (
      <button
        onClick={() => { setOpen(true); load(); }}
        style={{
          margin: '0 4px 16px', padding: '8px 14px',
          background: 'rgba(95,163,114,0.08)',
          border: '1px solid rgba(95,163,114,0.30)',
          borderRadius: 8, fontSize: 13, color: '#5fa372',
          cursor: 'pointer', textAlign: 'right', direction: 'rtl', fontWeight: 600,
        }}
      >
        💬 {count !== null && count > 0 ? `${count} תגובות · ` : 'פתח שיח · '}
        תגובה עולה 10 BEU שעוברים לקופת הפרויקט ←
      </button>
    );
  }

  return (
    <div style={{
      margin: '0 4px 16px', padding: 14,
      background: 'var(--text)', border: '1.5px solid rgba(95,163,114,0.40)',
      borderRadius: 12, direction: 'rtl',
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between',
                    alignItems: 'center', marginBottom: 12 }}>
        <strong style={{ color: '#1d4029' }}>
          💬 שיח · {count || 0} תגובות
        </strong>
        <button onClick={() => setOpen(false)}
                style={{ background: 'none', border: 'none', fontSize: 16,
                         color: 'var(--muted)', cursor: 'pointer' }}>×</button>
      </div>

      {loading && <p style={{ color: 'var(--muted)', fontSize: 13 }}>טוען...</p>}

      {!loading && messages.length === 0 && (
        <p style={{ color: 'var(--muted)', fontSize: 13, fontStyle: 'italic',
                    textAlign: 'center', padding: '10px 0' }}>
          עוד אין תגובות. הראשון לכתוב — תורם לקופה ופותח שיחה.
        </p>
      )}

      <div style={{ maxHeight: 320, overflowY: 'auto', paddingLeft: 4 }}>
        {messages.map(m => (
          <div key={m.id} style={{
            padding: '10px 12px', marginBottom: 8,
            background: 'rgba(95,163,114,0.04)',
            borderRight: '3px solid rgba(95,163,114,0.30)',
            borderRadius: '0 8px 8px 0',
          }}>
            <div style={{ display: 'flex', justifyContent: 'space-between',
                          marginBottom: 4, fontSize: 12 }}>
              <strong style={{ color: '#1d4029' }}>{m.user_name}</strong>
              {m.voice_strength > 0 && (
                <span title={`קול חזק יותר — קרא ${m.tny_weight_snapshot} פרקי תניא × משקל ${meta.tny_weight_multiplier}`}
                      style={{ background: 'linear-gradient(135deg, #d4af37, #b8941f)',
                               color: '#1a1a2e', padding: '2px 8px',
                               borderRadius: 100, fontSize: 11, fontWeight: 700 }}>
                  📚 קול ×{m.voice_strength}
                </span>
              )}
            </div>
            <div style={{ fontSize: 14, color: '#1d4029', lineHeight: 1.5, whiteSpace: 'pre-wrap' }}>
              {m.text}
            </div>
            <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 4 }}>
              🪙 {m.cost_beu} BEU
            </div>
          </div>
        ))}
      </div>

      {err && <div style={{ color: '#dc2626', fontSize: 13, marginTop: 8 }}>{err}</div>}

      <div style={{ marginTop: 10 }}>
        <textarea
          value={draft} onChange={e => setDraft(e.target.value)}
          rows={2} disabled={sending || !userId}
          placeholder={userId ? `תגובה · עולה ${meta.cost_per_message} BEU שעוברים לפרויקט` : 'התחבר/י כדי לכתוב'}
          style={{ width: '100%', padding: 8, borderRadius: 8,
                   border: '1px solid #d1d5db', fontSize: 14, fontFamily: 'inherit', resize: 'vertical' }}
        />
        <div style={{ display: 'flex', justifyContent: 'space-between',
                      alignItems: 'center', marginTop: 6 }}>
          <span style={{ fontSize: 11.5, color: 'var(--muted)' }}>
            {draft.length}/1500 · התשלום עובר ישירות לקופת הפרויקט
          </span>
          <button onClick={send} disabled={sending || !draft.trim() || !userId}
                  className="btn" style={{ padding: '6px 16px', fontSize: 13 }}>
            {sending ? '...' : `שלח · ${meta.cost_per_message} BEU`}
          </button>
        </div>
      </div>
    </div>
  );
}

function Marketplace({ userId }) {
  const [tab, setTab] = useState('services');
  const [services, setServices] = useState([]);
  const [loading, setLoading] = useState(true);
  const [q, setQ] = useState('');
  const [cat, setCat] = useState('');
  const [tok, setTok] = useState('');
  const [tokens, setTokens] = useState([]);
  // null = not loaded yet (don't disable buttons before we know the balance)
  const [balances, setBalances] = useState(null);
  const [offerOpen, setOfferOpen] = useState(false);
  const [requestSvc, setRequestSvc] = useState(null);

  const load = useCallback(() => {
    setLoading(true);
    const params = new URLSearchParams();
    if (q) params.set('q', q);
    if (cat) params.set('category', cat);
    if (tok) params.set('token', tok);
    api('/api/services' + (params.toString() ? '?' + params : ''))
      .then(d => setServices(Array.isArray(d) ? d : []))
      .finally(() => setLoading(false));
  }, [q, cat, tok]);

  useEffect(() => { load(); }, [load]);
  useEffect(() => { api('/api/tokens').then(t => setTokens(Array.isArray(t) ? t : [])); }, []);
  useEffect(() => {
    if (!userId) { setBalances(null); return; }
    api(`/api/balance/${userId}`).then(b => setBalances(Array.isArray(b) ? b : []));
  }, [userId]);

  // [damri 2026-05-20] Renamed to "מרכז השפע" + live counts
  const [wishesCount, setWishesCount] = React.useState(null);
  React.useEffect(() => {
    api('/api/wishes').then(w => {
      if (Array.isArray(w)) setWishesCount(w.length);
    }).catch(() => {});
  }, []);
  const servicesCount = services.length;

  return (
    <section style={{ paddingTop: 60, paddingBottom: 80 }}>
      <div className="container">
        {/* Hero */}
        <div style={{ textAlign: 'center', marginBottom: 22 }}>
          <div style={{ fontSize: 44, marginBottom: 6 }}>🌾</div>
          <h2 className="section-title" style={{ marginBottom: 8 }}>מרכז השפע</h2>
          <p className="section-sub" style={{ maxWidth: 580, margin: '0 auto', lineHeight: 1.7 }}>
            כאן צרכים פוגשים יכולות. <strong>מה שיש לך, מי שאתה — נכנס לזרימה של הקהילה.</strong>
            כל מתנה ניתנת מתוך שפע, כל משאלה מקבלת בית.
          </p>
        </div>

        {/* Live stats strip */}
        <div style={{
          display: 'flex', justifyContent: 'center', gap: 24,
          padding: '14px 18px', marginBottom: 22,
          background: 'linear-gradient(135deg, rgba(95,163,114,0.08), rgba(212,175,55,0.06))',
          border: '1px solid rgba(95,163,114,0.25)',
          borderRadius: 12, direction: 'rtl', flexWrap: 'wrap',
        }}>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 22, fontWeight: 800, color: '#5fa372' }}>
              {!loading && servicesCount > 0 ? `✦ ${servicesCount}` : '—'}
            </div>
            <div style={{ fontSize: 12, color: '#6b6d73' }}>מתנות חיות</div>
          </div>
          <div style={{ width: 1, background: 'rgba(0,0,0,0.10)' }} />
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 22, fontWeight: 800, color: '#a8801f' }}>
              {wishesCount !== null && wishesCount > 0 ? `✧ ${wishesCount}` : '—'}
            </div>
            <div style={{ fontSize: 12, color: '#6b6d73' }}>משאלות פתוחות</div>
          </div>
          <div style={{ width: 1, background: 'rgba(0,0,0,0.10)' }} />
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontSize: 22, fontWeight: 800, color: '#6b46c1' }}>∞</div>
            <div style={{ fontSize: 12, color: '#6b6d73' }}>גבול היכולת</div>
          </div>
        </div>

        {/* Tab switcher — larger, more inviting */}
        <div style={{ display: 'flex', justifyContent: 'center', gap: 0, marginBottom: 22 }}>
          <button
            onClick={() => setTab('services')}
            style={{
              padding: '12px 24px', fontSize: 15, fontWeight: 700,
              background: tab === 'services' ? 'linear-gradient(135deg, #5fa372, #4a8a5a)' : 'transparent',
              color: tab === 'services' ? 'var(--text)' : '#5fa372',
              border: '1.5px solid #5fa372',
              borderRadius: '8px 0 0 8px',
              cursor: 'pointer', transition: 'all .2s',
            }}
          >✦ מתנות לתת</button>
          <button
            onClick={() => setTab('wishes')}
            style={{
              padding: '12px 24px', fontSize: 15, fontWeight: 700,
              background: tab === 'wishes' ? 'linear-gradient(135deg, #d4af37, #b8941f)' : 'transparent',
              color: tab === 'wishes' ? '#1a1a2e' : '#a8801f',
              border: '1.5px solid #d4af37', borderRight: tab === 'wishes' ? '1.5px solid #d4af37' : 'none',
              borderRadius: '0 8px 8px 0',
              cursor: 'pointer', transition: 'all .2s',
            }}
          >✧ משאלות לקבל</button>
        </div>

        {tab === 'wishes' && <MarketplaceWishes userId={userId} />}
        {tab === 'services' && <>
        <div style={{ textAlign: 'center', marginBottom: 24 }}>
          <button className="btn" onClick={() => setOfferOpen(true)} disabled={!userId}>
            ➕ להציע מתנה
          </button>
        </div>

        <div className="mkt-filters">
          <input placeholder="חפש שירות..." value={q} onChange={e => setQ(e.target.value)} />
          <select value={cat} onChange={e => setCat(e.target.value)}>
            <option value="">כל הקטגוריות</option>
            {Object.entries(CATEGORIES).map(([k, v]) => <option key={k} value={k}>{v.icon} {v.label}</option>)}
          </select>
          <select value={tok} onChange={e => setTok(e.target.value)}>
            <option value="">כל הטוקנים</option>
            {tokens.map(t => <option key={t.id} value={t.symbol}>{t.icon} {t.symbol}</option>)}
          </select>
        </div>

        {loading && <p style={{ color: 'var(--muted)', textAlign: 'center' }}>טוען...</p>}
        {!loading && services.length === 0 && (
          <div style={{
            padding: 28, textAlign: 'center', maxWidth: 480, margin: '20px auto',
            background: 'rgba(95,163,114,0.06)',
            border: '1.5px dashed rgba(95,163,114,0.35)',
            borderRadius: 14, direction: 'rtl',
          }}>
            <div style={{ fontSize: 38, marginBottom: 10 }}>🌱</div>
            <h3 style={{ color: '#1d4029', margin: '0 0 8px', fontSize: 18 }}>
              {q || cat || tok ? 'אין התאמה לסינון' : 'השפע מתחיל ממך'}
            </h3>
            <p style={{ color: '#5fa372', lineHeight: 1.6, marginBottom: 14 }}>
              {q || cat || tok
                ? 'נסה לבטל את הסינון או להציע את המתנה הראשונה בקטגוריה.'
                : 'אם יש לך משהו לתת — שעה, ידע, מתנה, חיבוק — זה המקום שלו.'}
            </p>
            <button className="btn" onClick={() => setOfferOpen(true)} disabled={!userId}>
              ➕ להציע מתנה ראשונה
            </button>
          </div>
        )}

        <div className="svc-grid">
          {services.map(s => <ServiceCard key={s.id} svc={s} currentUser={userId} balances={balances} onRequest={setRequestSvc} />)}
        </div>
        </>}
      </div>

      {offerOpen && <OfferServiceModal userId={userId} onClose={() => setOfferOpen(false)} onCreated={() => { setOfferOpen(false); load(); }} />}
      {requestSvc && <RequestServiceModal svc={requestSvc} userId={userId} onClose={() => setRequestSvc(null)} onSent={() => { setRequestSvc(null); alert('הבקשה נשלחה לספק'); }} />}
    </section>
  );
}

// ───────── My Requests panel (used inside profile) ─────────
function MyRequests({ userId, refresh }) {
  const [data, setData] = useState({ received: [], sent: [] });
  const [tab, setTab] = useState('received');
  const [loading, setLoading] = useState(true);
  const reload = useCallback(() => {
    setLoading(true);
    api(`/api/my-requests/${userId}`).then(d => setData(d.error ? { received: [], sent: [] } : d)).finally(() => setLoading(false));
  }, [userId]);
  useEffect(() => { reload(); }, [reload, refresh]);

  const act = async (id, action) => {
    await api(`/api/service-request/${id}/${action}`, { method: 'PUT', body: { user_id: userId } });
    reload();
  };

  const list = tab === 'received' ? data.received : data.sent;

  return (
    <div className="profile-card" style={{ maxWidth: 600, margin: '20px auto' }}>
      <h3 style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18 }}>🛍️ הבקשות שלי</h3>
      <div className="text-tabs" style={{ marginTop: 8, marginBottom: 12 }}>
        <button className={`text-tab ${tab === 'received' ? 'active' : ''}`} onClick={() => setTab('received')}><span>📥 קיבלתי ({data.received.length})</span></button>
        <button className={`text-tab ${tab === 'sent' ? 'active' : ''}`} onClick={() => setTab('sent')}><span>📤 שלחתי ({data.sent.length})</span></button>
      </div>
      {loading && <div style={{ color: 'var(--muted)', textAlign: 'center', padding: 14 }}>טוען...</div>}
      {!loading && list.length === 0 && <div style={{ color: 'var(--muted)', textAlign: 'center', padding: 14, fontStyle: 'italic' }}>אין בקשות</div>}
      {list.map(r => (
        <div key={r.id} style={{ padding: 12, borderBottom: '1px solid rgba(255,255,255,0.04)', fontSize: 13 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
            <strong style={{ color: 'var(--gold)' }}>{r.service_title}</strong>
            <span className={`status-badge status-${r.status}`}>
              {r.status === 'pending' ? 'ממתין' : r.status === 'accepted' ? 'אושר' : r.status === 'completed' ? 'הושלם ✓' : 'בוטל'}
            </span>
          </div>
          <div style={{ color: '#aaa', fontSize: 12 }}>
            {tab === 'received' ? `מבקש: ${r.requester_name}` : `ספק: ${r.provider_name}`}
            {' · '}
            <span style={{ color: 'var(--gold)', fontWeight: 700 }}>✦ {r.price_tokens}</span>
          </div>
          {r.message && <div style={{ color: '#ccc', fontSize: 12, marginTop: 6, fontStyle: 'italic' }}>"{r.message}"</div>}
          <div style={{ marginTop: 8, display: 'flex', gap: 6 }}>
            {tab === 'received' && r.status === 'pending' && (
              <>
                <button className="btn ghost" style={{ padding: '6px 12px', fontSize: 12 }} onClick={() => act(r.id, 'accept')}>קבל</button>
                <button className="btn ghost" style={{ padding: '6px 12px', fontSize: 12, borderColor: '#dc2626', color: '#dc2626' }} onClick={() => act(r.id, 'cancel')}>דחה</button>
              </>
            )}
            {tab === 'received' && r.status === 'accepted' && (
              <button className="btn" style={{ padding: '6px 14px', fontSize: 12 }} onClick={() => act(r.id, 'complete')}>סיים ושלם</button>
            )}
            {tab === 'sent' && (r.status === 'pending' || r.status === 'accepted') && (
              <button className="btn ghost" style={{ padding: '6px 12px', fontSize: 12, borderColor: '#dc2626', color: '#dc2626' }} onClick={() => act(r.id, 'cancel')}>בטל</button>
            )}
          </div>
        </div>
      ))}
    </div>
  );
}

// ───────── My Services + transactions panel ─────────
function EditServiceModal({ svc, userId, onClose, onSaved }) {
  const [title, setTitle] = useState(svc.title || '');
  const [description, setDescription] = useState(svc.description || '');
  const [price, setPrice] = useState(svc.price_tokens ?? 0);
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');

  const submit = async () => {
    if (!title.trim()) { setErr('כותרת חובה'); return; }
    setBusy(true); setErr('');
    try {
      const r = await api(`/api/service/${svc.id}`, {
        method: 'PUT',
        body: JSON.stringify({
          user_id: userId,
          title: title.trim(),
          description: description.trim(),
          price_tokens: parseInt(price, 10),
        }),
      });
      if (r.error) throw new Error(r.error);
      onSaved();
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <span className="close-x" onClick={onClose}>×</span>
        <h3>ערוך שירות</h3>
        {svc.image_url && (
          <div style={{ marginBottom: 12 }}>
            <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 6 }}>תמונה נוכחית:</div>
            <img src={svc.image_url} alt="" style={{
              width: '100%', maxHeight: 180, objectFit: 'cover',
              borderRadius: 8, border: '1px solid rgba(212,175,55,0.2)',
            }} />
          </div>
        )}
        <ImageUploader
          uploadEndpoint={`/api/service/${svc.id}/image`}
          userId={userId}
          currentImageUrl={svc.image_url}
          label={svc.image_url ? 'החלף תמונה' : 'הוסף תמונה'} />
        <label>כותרת</label>
        <input value={title} onChange={e => setTitle(e.target.value)} />
        <label>תיאור</label>
        <textarea rows="3" value={description} onChange={e => setDescription(e.target.value)} />
        <label>הוקרה לאדם שיבקש (✦)</label>
        <input type="number" min="0" value={price} onChange={e => setPrice(e.target.value)} />
        {err && <div style={{ color: '#dc2626', fontSize: 13, marginTop: 10 }}>{err}</div>}
        <div className="actions">
          <button className="btn" onClick={submit} disabled={busy}>{busy ? '...' : 'שמור'}</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

function MyServices({ userId, refresh, onOffer }) {
  const [services, setServices] = useState([]);
  const [txs, setTxs] = useState([]);
  const [editing, setEditing] = useState(null);
  const [bumpRefresh, setBumpRefresh] = useState(0);
  const reload = () => setBumpRefresh(r => r + 1);
  useEffect(() => {
    api(`/api/services?user_id=${userId}`).then(s => setServices(Array.isArray(s) ? s : []));
    api(`/api/transactions/${userId}`).then(t => setTxs(Array.isArray(t) ? t : []));
  }, [userId, refresh, bumpRefresh]);

  const setActive = async (sid, active) => {
    const r = await api(`/api/service/${sid}`, {
      method: 'PUT',
      body: JSON.stringify({ user_id: userId, active }),
    });
    if (r.error) { alert(r.error); return; }
    reload();
  };

  return (
    <>
      <div className="profile-card" style={{ maxWidth: 600, margin: '20px auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <h3 style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18 }}>🤝 המתנות שלי</h3>
          <button className="btn ghost" style={{ padding: '6px 14px', fontSize: 12 }} onClick={onOffer}>➕ חדש</button>
        </div>
        {services.length === 0 && <div style={{ color: 'var(--muted)', padding: 14, fontStyle: 'italic' }}>עדיין לא הצעת מתנות</div>}
        {services.map(s => {
          const cat = CATEGORIES[s.category] || CATEGORIES.other;
          return (
            <div key={s.id} style={{ padding: 10, borderBottom: '1px solid rgba(255,255,255,0.04)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 13, gap: 8 }}>
              {s.image_url && (
                <img src={s.image_url} alt="" style={{
                  width: 44, height: 44, borderRadius: 6, objectFit: 'cover',
                  flexShrink: 0, border: '1px solid rgba(212,175,55,0.2)',
                }} />
              )}
              <div style={{ flex: 1, minWidth: 0 }}>
                <span style={{ color: cat.color }}>{cat.icon}</span> <strong>{s.title}</strong>
                <div style={{ fontSize: 11, color: 'var(--muted)' }}>{s.active ? 'פעיל' : 'מושבת'}</div>
              </div>
              <div style={{ color: 'var(--gold)', fontWeight: 700, whiteSpace: 'nowrap' }}>✦ {s.price_tokens}</div>
              <div style={{ display: 'flex', gap: 4 }}>
                <button
                  className="btn ghost"
                  style={{ padding: '4px 10px', fontSize: 11 }}
                  onClick={() => setEditing(s)}
                  title="ערוך / הוסף תמונה"
                >✏️ ערוך · תמונה</button>
                {s.active ? (
                  <button
                    className="btn ghost"
                    style={{ padding: '4px 8px', fontSize: 11 }}
                    onClick={() => { if (confirm('להשבית את השירות?')) setActive(s.id, 0); }}
                    title="השבת"
                  >⏸</button>
                ) : (
                  <button
                    className="btn ghost"
                    style={{ padding: '4px 8px', fontSize: 11 }}
                    onClick={() => setActive(s.id, 1)}
                    title="הפעל"
                  >▶️</button>
                )}
              </div>
            </div>
          );
        })}
      </div>
      {editing && (
        <EditServiceModal
          svc={editing}
          userId={userId}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); reload(); }}
        />
      )}

</>
  );
}

// ───────── v7: Referral Section ─────────
function ReferralSection({ userId }) {
  const [data, setData] = useState(null);
  const [friends, setFriends] = useState([]);
  const [friendsLoaded, setFriendsLoaded] = useState(false);
  useEffect(() => {
    api(`/api/referral/${userId}`).then(setData);
    api(`/api/referral/${userId}/friends`)
      .then(f => setFriends(Array.isArray(f) ? f : []))
      .finally(() => setFriendsLoaded(true));
  }, [userId]);
  if (!data || data.error) return null;
  const link = `${window.location.origin}/?ref=${data.code}`;
  const text = `בית דויד — מקום שבו אדם נמדד לפי האור שהוא מביא לעולם. ${link}`;
  const copy = () => { navigator.clipboard.writeText(link); alert('הלינק הועתק!'); };
  const totalChapters = friends.reduce((s, f) => s + (f.chapters || 0), 0);
  return (
    <div className="profile-card" style={{ maxWidth: 600, margin: '20px auto', background: 'linear-gradient(135deg, #1a1a2e 0%, #1f3a4e 100%)', border: '1px solid rgba(212,175,55,0.25)', boxShadow: '0 4px 20px rgba(0,0,0,0.25)' }}>
      <h3 style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18, margin: 0 }}>🤲 הביאו אנשים יחד אליך</h3>
      <p style={{ color: '#c8c8d8', fontSize: 13, marginTop: 8, lineHeight: 1.7 }}>שלח את הלינק. הזכות תיפתח בינך לבין מי שיגיע — כשהוא יקרא את הפרק הראשון שלו.</p>
      <div style={{ background: 'rgba(0,0,0,0.3)', padding: 10, borderRadius: 8, fontFamily: 'monospace', fontSize: 12, color: 'var(--gold)', wordBreak: 'break-all', marginBottom: 10, border: '1px solid rgba(212,175,55,0.2)' }}>
        {link}
      </div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 }}>
        <button className="btn ghost" style={{ padding: '6px 12px', fontSize: 12 }} onClick={copy}>📋 העתק</button>
        <a className="btn ghost" style={{ padding: '6px 12px', fontSize: 12 }} href={`https://wa.me/?text=${encodeURIComponent(text)}`} target="_blank" rel="noreferrer">📱 WhatsApp</a>
        <a className="btn ghost" style={{ padding: '6px 12px', fontSize: 12 }} href={`https://t.me/share/url?url=${encodeURIComponent(link)}&text=${encodeURIComponent('הצטרף לבית דויד')}`} target="_blank" rel="noreferrer">✈️ Telegram</a>
      </div>
      <div style={{ fontSize: 13, color: 'var(--muted)' }}>
        <strong style={{ color: 'var(--gold)' }}>{data.friends_count}</strong> אנשים התחילו מסע יחד איתך
        {totalChapters > 0 && <> · קראו יחד <strong style={{ color: 'var(--gold)' }}>{totalChapters}</strong> פרקים</>}
      </div>
      <div style={{ marginTop: 12 }}>
        <div style={{ color: 'var(--muted)', fontSize: 12, marginBottom: 6 }}>מי שהתחיל מסע יחד איתך:</div>
        {!friendsLoaded && <div style={{ fontSize: 12, color: 'var(--muted)', fontStyle: 'italic' }}>טוען...</div>}
        {friendsLoaded && friends.length === 0 && (
          <div style={{ fontSize: 12, color: 'var(--muted)', fontStyle: 'italic' }}>עדיין אין מי שהתחיל איתך — שתף את הלינק עם מישהו שמרגיש קרוב.</div>
        )}
        {friends.map(f => (
          <div key={f.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0', borderBottom: '1px solid rgba(255,255,255,0.04)' }}>
            <Avatar url={f.avatar_url} color={f.avatar_color} name={f.name} size={32} />
            <div style={{ flex: 1, fontSize: 13 }}>{f.name}</div>
            <div style={{ fontSize: 11, color: 'var(--muted)', textAlign: 'left' }}>
              {(f.created_at || '').slice(0, 10)} · 📖 {f.chapters || 0} {f.chapters === 1 ? 'פרק' : 'פרקים'}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ───────── v6: Avatar Upload ─────────
// Server resolves the upload (data URL or http URL), caps at 2MB, transcodes
// to webp via sharp when available, and returns a canonical /uploads/avatars/<id>
// path. We store/render only that path — never base64 in the DB.
function AvatarUpload({ userId, onUploaded }) {
  const ref = React.useRef();
  const [busy, setBusy] = useState(false);
  const handle = async (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    if (file.size > 2 * 1024 * 1024) { alert('התמונה גדולה מדי (מקס 2MB)'); return; }
    setBusy(true);
    const reader = new FileReader();
    reader.onload = async () => {
      try {
        const r = await api(`/api/profile/${userId}/avatar`, { method: 'POST', body: JSON.stringify({ avatar_url: reader.result }) });
        if (r.error) alert(r.error);
        else { onUploaded && onUploaded(); }
      } finally { setBusy(false); }
    };
    reader.readAsDataURL(file);
  };
  return (
    <div style={{ marginTop: 6 }}>
      <input type="file" accept="image/*" ref={ref} style={{ display: 'none' }} onChange={handle} />
      <button className="btn ghost" style={{ padding: '4px 10px', fontSize: 11 }} disabled={busy} onClick={() => ref.current && ref.current.click()}>
        {busy ? 'מעלה...' : '📷 שנה תמונה'}
      </button>
    </div>
  );
}

// ───────── SettingsStrip — Layer 3, management at the bottom ─────────
// Per Damri 2026-05-10: 'קלף זהות תחתון מיותר אם יש flow אמיתי'.
// Reduced to a quiet bottom strip — avatar pill + edit + sign out.
function SettingsStrip({ profile, googleSub, onEdit }) {
  if (!profile) return null;
  return (
    <div style={{
      maxWidth: 600, margin: '40px auto 8px',
      display: 'flex', alignItems: 'center', gap: 12,
      padding: '14px 16px',
      background: 'rgba(255,255,255,0.02)',
      border: '1px solid rgba(212,175,55,0.12)',
      borderRadius: 12,
    }}>
      <Avatar url={profile.avatar_url} color={profile.avatar_color} name={profile.name} size={36} />
      <div style={{ flex: 1, fontSize: 13, color: '#c8c8d8' }}>
        <div style={{ fontWeight: 600 }}>{profile.name || 'משתמש'}</div>
        {profile.location && <div style={{ fontSize: 11, color: 'var(--muted)' }}>📍 {profile.location}</div>}
      </div>
      {googleSub && onEdit && (
        <button
          onClick={onEdit}
          style={{
            background: 'none', border: '1px solid rgba(212,175,55,0.3)', color: 'var(--gold)',
            fontSize: 12, padding: '6px 12px', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit',
          }}
          title="ערוך את כל השדות של הפרופיל"
        >✏️ ערוך</button>
      )}
      <SignOutButton />
    </div>
  );
}

// ───────── Hebrew gematria — number → letter (1..150) ─────────
// Used to display 'תהילים לו' instead of 'תהילים 36'. Standard
// Jewish numbering with 15→ט"ו, 16→ט"ז to avoid sacred name overlap.
function hebrewLetter(n) {
  if (!n || n < 1 || n > 999) return String(n);
  const ones = ['', 'א','ב','ג','ד','ה','ו','ז','ח','ט'];
  const tens = ['', 'י','כ','ל','מ','נ','ס','ע','פ','צ'];
  const hundreds = ['', 'ק','ר','ש','ת','תק','תר','תש','תת','תתק'];
  // Special: 15 → טו (not יה), 16 → טז (not יו)
  if (n === 15) return 'טו';
  if (n === 16) return 'טז';
  const h = Math.floor(n / 100);
  const t = Math.floor((n % 100) / 10);
  const o = n % 10;
  // For numbers like 115, 116 use 'קטו', 'קטז'
  if (h && (n % 100 === 15)) return hundreds[h] + 'טו';
  if (h && (n % 100 === 16)) return hundreds[h] + 'טז';
  return (hundreds[h] || '') + (tens[t] || '') + (ones[o] || '');
}

// ───────── PersonalChapter — 'פרק השנה שלך' ─────────
// Per Damri 2026-05-10: chapter = age + 1. If user hasn't set a birth
// date yet, show a quiet inline CTA to add it. Click 'קרא' → opens a
// modal with the full Hebrew text from /api/psalm/:chapter.
function PersonalChapter({ userId, profile, refresh, setRefresh }) {
  const [data, setData] = React.useState(null);
  const [editing, setEditing] = React.useState(false);
  const [day, setDay] = React.useState('');
  const [month, setMonth] = React.useState('');
  const [year, setYear] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [readerOpen, setReaderOpen] = React.useState(false);

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/personal-tehillim/${userId}`).then(d => { if (d && !d.error) setData(d); }).catch(() => {});
  }, [userId, refresh]);

  if (!userId) return null;

  const submit = async () => {
    setErr('');
    const d = parseInt(day, 10), m = parseInt(month, 10), y = parseInt(year, 10);
    if (!d || d < 1 || d > 31) return setErr('יום לא תקין');
    if (!m || m < 1 || m > 12) return setErr('חודש לא תקין');
    if (!y || y < 1900 || y > new Date().getFullYear()) return setErr('שנה לא תקינה');
    const birth_date = `${y}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
    setBusy(true);
    try {
      const r = await api(`/api/profile/${userId}/birth-date`, { method: 'POST', body: { birth_date } });
      if (r.error) throw new Error(r.error);
      setEditing(false);
      // Refresh both the local data and the parent profile
      const fresh = await api(`/api/personal-tehillim/${userId}`);
      if (fresh && !fresh.error) setData(fresh);
      if (setRefresh) setRefresh(x => x + 1);
    } catch (e) {
      setErr(e.message || 'שמירה נכשלה');
    } finally {
      setBusy(false);
    }
  };

  const wrapStyle = {
    maxWidth: 600, margin: '24px auto 0',
    padding: '24px 22px',
    background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
    border: '1px solid rgba(212,175,55,0.25)',
    boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
    borderRadius: 14,
    textAlign: 'center',
    position: 'relative',
  };

  // State 1: no birth_date — quiet CTA inviting them to add it
  if (data && data.has_birth_date === false && !editing) {
    return (
      <div style={wrapStyle}>
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif", fontSize: 14, color: 'var(--gold)',
          letterSpacing: 1, marginBottom: 8, fontStyle: 'italic',
        }}>✦ פרק השנה שלך</div>
        <div style={{ fontSize: 13, color: '#c8c8d8', lineHeight: 1.8, marginBottom: 16 }}>
          לכל אדם יש פרק תהילים שמתעורר כשהוא מתבגר.<br/>
          הוסף את תאריך הלידה שלך — והפרק שלך השנה יחכה לך כאן.
        </div>
        <button
          onClick={() => setEditing(true)}
          style={{
            background: 'transparent', color: 'var(--gold)',
            border: '1px solid rgba(212,175,55,0.45)', borderRadius: 8,
            padding: '8px 18px', cursor: 'pointer', fontSize: 13, fontFamily: 'inherit',
          }}
        >📅 הוסף תאריך לידה</button>
      </div>
    );
  }

  // State 2: editing — inline date entry
  if (editing) {
    return (
      <div style={wrapStyle}>
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif", fontSize: 14, color: 'var(--gold)',
          letterSpacing: 1, marginBottom: 12, fontStyle: 'italic',
        }}>תאריך לידה</div>
        {/* [damri 2026-05-21] Hebrew-labelled dropdowns instead of bare number inputs */}
        <div style={{ display: 'flex', gap: 6, justifyContent: 'center', marginBottom: 10, direction: 'rtl' }}>
          <div style={{ display:'flex', flexDirection:'column', gap:3 }}>
            <span style={{ fontSize:10, color:'#a8801f', textAlign:'center' }}>שנה</span>
            <select value={year} onChange={e => setYear(e.target.value)}
              style={{ width: 90, padding: '8px 10px', textAlign: 'center', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(212,175,55,0.35)', borderRadius: 6, color: 'var(--text)', fontFamily: 'inherit' }}>
              <option value="">—</option>
              {Array.from({length: new Date().getFullYear()-1900+1}, (_,i)=>new Date().getFullYear()-i).map(y=>(<option key={y} value={y}>{y}</option>))}
            </select>
          </div>
          <div style={{ display:'flex', flexDirection:'column', gap:3 }}>
            <span style={{ fontSize:10, color:'#a8801f', textAlign:'center' }}>חודש</span>
            <select value={month} onChange={e => setMonth(e.target.value)}
              style={{ width: 110, padding: '8px 10px', textAlign: 'center', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(212,175,55,0.35)', borderRadius: 6, color: 'var(--text)', fontFamily: 'inherit' }}>
              <option value="">—</option>
              {['ינואר','פברואר','מרץ','אפריל','מאי','יוני','יולי','אוגוסט','ספטמבר','אוקטובר','נובמבר','דצמבר'].map((nm,idx)=>(<option key={idx+1} value={idx+1}>{idx+1} · {nm}</option>))}
            </select>
          </div>
          <div style={{ display:'flex', flexDirection:'column', gap:3 }}>
            <span style={{ fontSize:10, color:'#a8801f', textAlign:'center' }}>יום</span>
            <select value={day} onChange={e => setDay(e.target.value)}
              style={{ width: 70, padding: '8px 10px', textAlign: 'center', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(212,175,55,0.35)', borderRadius: 6, color: 'var(--text)', fontFamily: 'inherit' }}>
              <option value="">—</option>
              {Array.from({length:31}, (_,i)=>i+1).map(d=>(<option key={d} value={d}>{d}</option>))}
            </select>
          </div>
        </div>
        {err && <div style={{ color: '#dc2626', fontSize: 12, marginBottom: 10 }}>{err}</div>}
        <div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
          <button onClick={submit} disabled={busy}
            style={{ background: 'var(--gold)', color: '#1a1a2e', border: 'none', borderRadius: 8, padding: '8px 18px', cursor: busy ? 'wait' : 'pointer', fontSize: 13, fontWeight: 700, fontFamily: 'inherit', opacity: busy ? 0.6 : 1 }}>
            {busy ? '...' : 'שמור'}
          </button>
          <button onClick={() => { setEditing(false); setErr(''); }}
            style={{ background: 'none', color: 'var(--muted)', border: '1px solid rgba(138,141,168,0.25)', borderRadius: 8, padding: '8px 18px', cursor: 'pointer', fontSize: 13, fontFamily: 'inherit' }}>
            ביטול
          </button>
        </div>
      </div>
    );
  }

  // State 3: has birth_date — show chapter
  if (data && data.has_birth_date) {
    return (
      <>
        <div style={wrapStyle}>
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif", fontSize: 12, color: 'var(--gold)',
            letterSpacing: 2, marginBottom: 6, textTransform: 'uppercase', opacity: 0.85,
          }}>פרק השנה שלך · גיל {data.age}</div>
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 30, color: 'var(--text)',
            fontWeight: 600, marginBottom: 10,
          }}>
            תהילים <span style={{ color: 'var(--gold)' }}>{hebrewLetter(data.chapter)}</span>
          </div>
          <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 14, fontStyle: 'italic' }}>
            פרק {data.chapter} · הפרק שמתעורר אצלך השנה
          </div>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
            <button onClick={() => setReaderOpen(true)}
              style={{ background: 'var(--gold)', color: '#1a1a2e', border: 'none', borderRadius: 8, padding: '8px 20px', cursor: 'pointer', fontSize: 13, fontWeight: 700, fontFamily: 'inherit' }}>
              📖 קרא עכשיו
            </button>
            <button onClick={() => setEditing(true)}
              style={{ background: 'none', color: 'var(--muted)', border: '1px solid rgba(138,141,168,0.25)', borderRadius: 8, padding: '8px 14px', cursor: 'pointer', fontSize: 12, fontFamily: 'inherit' }}>
              ערוך תאריך
            </button>
          </div>
        </div>
        {readerOpen && <PsalmReader chapter={data.chapter} onClose={() => setReaderOpen(false)} />}
      </>
    );
  }

  return null;
}

// ───────── PsalmReader — modal showing full Hebrew text of a psalm ─────────
function PsalmReader({ chapter, onClose }) {
  const [text, setText] = React.useState(null);
  const [err, setErr] = React.useState('');
  React.useEffect(() => {
    api(`/api/psalm/${chapter}`).then(d => {
      if (d.error) setErr('הטקסט לא נטען עדיין');
      else setText(d.hebrew || d.hebrew_clean || '');
    }).catch(() => setErr('הטקסט לא נטען'));
  }, [chapter]);
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 640, maxHeight: '85vh', overflowY: 'auto', position: 'relative' }}>
        <span className="close-x" onClick={onClose} style={{ position: 'absolute', top: 14, left: 14, fontSize: 26, cursor: 'pointer', zIndex: 10, padding: '2px 10px' }}>×</span>
        <h3 style={{ textAlign: 'center', color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 24, marginBottom: 4 }}>
          תהילים {hebrewLetter(chapter)}
        </h3>
        <div style={{ textAlign: 'center', fontSize: 12, color: 'var(--muted)', marginBottom: 18 }}>פרק {chapter}</div>
        {err && <p style={{ color: '#dc2626', textAlign: 'center' }}>{err}</p>}
        {!text && !err && <p style={{ color: 'var(--muted)', textAlign: 'center' }}>טוען...</p>}
        {text && (
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 18, lineHeight: 2,
            color: 'var(--text)',
            direction: 'rtl', textAlign: 'right',
            whiteSpace: 'pre-wrap',
          }}>
            {text}
          </div>
        )}
      </div>
    </div>
  );
}

// ───────── CompanionPage — conversation UI ─────────
function CompanionPage({ userId, onBack }) {
  const [messages, setMessages] = React.useState([]);
  const [sessionId, setSessionId] = React.useState(null);
  const [input, setInput] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [concluded, setConcluded] = React.useState(null);
  const scrollRef = React.useRef(null);

  // Load active session if any
  React.useEffect(() => {
    if (!userId) return;
    api(`/api/companion/active-session/${userId}`).then(d => {
      if (d && d.has_active) {
        setSessionId(d.session_id);
        setMessages(d.messages || []);
      }
    }).catch(() => {});
  }, [userId]);

  // Auto-scroll on new message
  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages, concluded]);

  const userTurns = messages.filter(m => m.role === 'user').length;
  const canConclude = userTurns >= 3 && !concluded;

  const send = async () => {
    const text = input.trim();
    if (!text || busy) return;
    setBusy(true);
    setErr('');
    // Optimistic: show user message immediately
    const optimistic = { role: 'user', content: text };
    setMessages(m => [...m, optimistic]);
    setInput('');
    try {
      const r = await fetch('/api/companion/turn', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ user_id: userId, session_id: sessionId, user_message: text }),
      });
      const data = await r.json();
      if (!r.ok) throw new Error(data.message || data.error || 'שגיאה');
      setSessionId(data.session_id);
      setMessages(m => [...m, { role: 'assistant', content: data.assistant_message }]);
    } catch (e) {
      setErr(e.message);
      // Roll back optimistic
      setMessages(m => m.filter(x => x !== optimistic));
      setInput(text);
    } finally {
      setBusy(false);
    }
  };

  const conclude = async () => {
    if (!sessionId || busy) return;
    setBusy(true);
    setErr('');
    try {
      const r = await fetch('/api/companion/conclude', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ user_id: userId, session_id: sessionId }),
      });
      const data = await r.json();
      if (!r.ok) throw new Error(data.message || data.error || 'שגיאה');
      setConcluded(data.snapshot);
    } catch (e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  };

  return (
    <section style={{ paddingTop: 30, paddingBottom: 40, minHeight: '80vh' }}>
      <div className="container" style={{ maxWidth: 680 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
          <button onClick={onBack}
            style={{ background: 'none', border: 'none', color: '#a8801f',
              fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
            ← חזור לפרופיל
          </button>
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 20, color: 'var(--gold)', fontStyle: 'italic',
          }}>מקום להיות</div>
          <div style={{ width: 80 }}></div>
        </div>

        {/* Message list */}
        <div ref={scrollRef} style={{
          background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
          border: '1px solid rgba(212,175,55,0.25)',
          borderRadius: 14,
          padding: '24px 22px',
          maxHeight: '50vh',
          overflowY: 'auto',
          marginBottom: 16,
        }}>
          {messages.length === 0 && !concluded && (
            <div style={{
              color: '#c8c8d8', fontStyle: 'italic',
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 16, lineHeight: 1.7, textAlign: 'center', padding: '20px 8px',
            }}>
              שלום. אני כאן לא כדי להגדיר אותך,<br/>
              אלא לעזור לך לשמוע את עצמך טוב יותר.<br/><br/>
              <span style={{ fontSize: 14, color: 'var(--muted)' }}>
                שאלה ראשונה תופיע אחרי שתכתוב מילה ראשונה.<br/>
                אפשר להתחיל ממה שעובר עליך עכשיו.
              </span>
            </div>
          )}
          {messages.map((m, i) => (
            <div key={i} style={{
              display: 'flex',
              justifyContent: m.role === 'user' ? 'flex-start' : 'flex-end',
              marginBottom: 14,
            }}>
              <div style={{
                maxWidth: '80%',
                padding: '10px 14px',
                borderRadius: 12,
                background: m.role === 'user'
                  ? 'rgba(212,175,55,0.15)'
                  : 'rgba(255,255,255,0.04)',
                border: '1px solid ' + (m.role === 'user'
                  ? 'rgba(212,175,55,0.3)'
                  : 'rgba(212,175,55,0.15)'),
                color: 'var(--text)',
                fontSize: 15, lineHeight: 1.7,
                whiteSpace: 'pre-wrap',
                fontFamily: m.role === 'assistant' ? "'Frank Ruhl Libre', serif" : 'inherit',
              }}>{m.content}</div>
            </div>
          ))}
          {busy && (
            <div style={{ color: 'var(--muted)', fontStyle: 'italic', fontSize: 13, textAlign: 'center', padding: 8 }}>
              ...
            </div>
          )}
        </div>

        {err && (
          <div style={{
            background: 'rgba(255,107,157,0.12)', border: '1px solid rgba(255,107,157,0.3)',
            color: '#ff9bbe', padding: '10px 14px', borderRadius: 8,
            marginBottom: 12, fontSize: 13, lineHeight: 1.6,
          }}>{err}</div>
        )}

        {/* Snapshot result */}
        {concluded && (
          <div style={{
            background: 'linear-gradient(135deg, rgba(212,175,55,0.12), rgba(106,76,176,0.10))',
            border: '1px solid rgba(212,175,55,0.35)',
            borderRadius: 14, padding: '22px 20px', marginBottom: 16,
            textAlign: 'center',
          }}>
            <div style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 12, color: 'var(--gold)', letterSpacing: 2,
              marginBottom: 12, textTransform: 'uppercase', opacity: 0.9,
            }}>מה אני מגלה על עצמי עכשיו</div>
            <div style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 19, color: 'var(--text)', lineHeight: 1.7,
              fontStyle: 'italic', marginBottom: 14,
            }}>"{concluded.snapshot_text}"</div>
            {concluded.kind_of_connection && (
              <div style={{ fontSize: 13, color: '#c8c8d8', marginBottom: 14 }}>
                <span style={{ color: 'var(--gold)' }}>חיבור שאני מחפש: </span>
                {concluded.kind_of_connection}
              </div>
            )}
            <button onClick={onBack}
              style={{ background: 'transparent', color: 'var(--gold)',
                border: '1px solid rgba(212,175,55,0.4)', borderRadius: 8,
                padding: '8px 18px', cursor: 'pointer', fontSize: 13, fontFamily: 'inherit' }}>
              חזור לפרופיל
            </button>
          </div>
        )}

        {/* Input + actions */}
        {!concluded && (
          <div>
            <div style={{ display: 'flex', gap: 8, marginBottom: 10 }}>
              <textarea
                value={input}
                onChange={e => setInput(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
                placeholder="כתוב כאן..."
                rows={2}
                disabled={busy}
                style={{
                  flex: 1, padding: '10px 12px', resize: 'vertical',
                  background: 'rgba(255,255,255,0.04)',
                  border: '1px solid rgba(212,175,55,0.25)',
                  borderRadius: 8, color: 'var(--text)',
                  fontFamily: 'inherit', fontSize: 14,
                }} />
              <button onClick={send} disabled={busy || !input.trim()}
                style={{ background: 'var(--gold)', color: '#1a1a2e',
                  border: 'none', borderRadius: 8,
                  padding: '0 18px', cursor: busy ? 'wait' : 'pointer',
                  fontSize: 14, fontWeight: 700, fontFamily: 'inherit',
                  opacity: (busy || !input.trim()) ? 0.5 : 1 }}>
                {busy ? '...' : 'שלח'}
              </button>
            </div>
            {canConclude && (
              <div style={{ textAlign: 'center' }}>
                <button onClick={conclude} disabled={busy}
                  style={{ background: 'none', color: 'var(--gold)',
                    border: '1px solid rgba(212,175,55,0.4)', borderRadius: 8,
                    padding: '8px 18px', cursor: busy ? 'wait' : 'pointer',
                    fontSize: 13, fontFamily: 'inherit', fontStyle: 'italic' }}>
                  ✦ סכם את השיחה
                </button>
                <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 6 }}>
                  המלווה יחזיר שיקוף עדין של מה שראה
                </div>
              </div>
            )}
          </div>
        )}
      </div>
    </section>
  );
}

// ───────── Openness levels + labels ─────────
const OPENNESS_LEVELS = [
  { key: 'observer',  label: 'כרגע רק מתבונן',     short: 'מתבונן',           hint: 'לא מציגים אותך בהצעות, ולא מציעים לך אנשים' },
  { key: 'community', label: 'פתוח להיכרות קהילתית', short: 'קהילה',           hint: 'להכיר אנשים שפעילים באותם מעגלים' },
  { key: 'partners',  label: 'פתוח לשותפים לעשייה', short: 'שותפי עשייה',     hint: 'אנשים שאפשר לבנות איתם משהו ביחד' },
  { key: 'learning',  label: 'פתוח ללימוד משותף',   short: 'לימוד משותף',     hint: 'חברי לימוד, חברותא' },
  { key: 'match',     label: 'פתוח להיכרות לשידוך',  short: 'היכרות לשידוך',   hint: 'מסלול נפרד; ייבנה בעדינות' },
];

function OpennessControl({ userId, currentLevel, onChange }) {
  const [open, setOpen] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const current = OPENNESS_LEVELS.find(l => l.key === currentLevel) || OPENNESS_LEVELS[0];

  const save = async (newLevel) => {
    setBusy(true);
    try {
      const r = await api(`/api/openness/${userId}`, { method: 'POST', body: { level: newLevel } });
      if (r.error) throw new Error(r.error);
      setOpen(false);
      onChange && onChange(newLevel);
    } catch (e) {
      alert(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  return (
    <>
      <button onClick={() => setOpen(true)}
        style={{
          background: 'none', border: '1px solid rgba(212,175,55,0.3)', color: 'var(--gold)',
          fontSize: 12, padding: '4px 12px', borderRadius: 7, cursor: 'pointer',
          fontFamily: 'inherit', fontStyle: 'italic',
        }}>
        {current.short} ✎
      </button>

      {open && (
        <div className="modal-bg" onClick={() => !busy && setOpen(false)}>
          <div className="modal" onClick={e => e.stopPropagation()} style={{ position: 'relative', maxWidth: 460 }}>
            <span className="close-x" onClick={() => !busy && setOpen(false)}
              style={{ position: 'absolute', top: 14, left: 14, fontSize: 26, cursor: 'pointer', zIndex: 10, padding: '2px 10px' }}>×</span>
            <h3 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic', color: 'var(--gold)' }}>
              רמת פתיחות לחיבור
            </h3>
            <p style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.6, marginBottom: 16 }}>
              אתה מחליט מה הקהילה רואה ומה היא מציעה לך. ברירת המחדל היא לא לחשוף — תפתח רק כשתרצה.
            </p>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {OPENNESS_LEVELS.map(l => (
                <button key={l.key} onClick={() => save(l.key)} disabled={busy}
                  style={{
                    textAlign: 'right', padding: '12px 14px',
                    background: l.key === currentLevel ? 'rgba(212,175,55,0.10)' : 'rgba(255,255,255,0.02)',
                    border: '1px solid ' + (l.key === currentLevel ? 'rgba(212,175,55,0.4)' : 'rgba(212,175,55,0.12)'),
                    borderRadius: 10, cursor: busy ? 'wait' : 'pointer',
                    fontFamily: 'inherit', color: 'var(--text)',
                  }}>
                  <div style={{ fontWeight: 600, fontSize: 14, marginBottom: 3 }}>
                    {l.key === currentLevel && '✓ '}{l.label}
                  </div>
                  <div style={{ fontSize: 12, color: 'var(--muted)', fontStyle: 'italic' }}>{l.hint}</div>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}
    </>
  );
}

// ───────── ConnectionSuggestions — 'אנשים שאולי תרצה להכיר' ─────────
// Per Damri 2026-05-11: deeds first, reasons second. No score, no %.
function ConnectionSuggestions({ userId, refresh }) {
  const [data, setData] = React.useState(null);
  const [loaded, setLoaded] = React.useState(false);

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/connections/suggestions/${userId}`).then(d => {
      if (d && !d.error) setData(d);
      setLoaded(true);
    }).catch(() => setLoaded(true));
  }, [userId, refresh]);

  if (!userId || !loaded || !data) return null;

  const myLevel = data.openness_level || 'partners';

  // State A: observer — gentle opt-in tile
  if (myLevel === 'observer') {
    return (
      <section style={{
        maxWidth: 600, margin: '24px auto 0',
        padding: '24px 26px',
        background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
        border: '1px solid rgba(212,175,55,0.25)',
        boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
        borderRadius: 14,
        textAlign: 'center',
      }}>
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
          fontSize: 15, color: 'var(--gold)', marginBottom: 8,
        }}>
          ✦ פתוח לחיבור?
        </div>
        <p style={{ fontSize: 13, color: '#c8c8d8', lineHeight: 1.7, marginBottom: 14, fontStyle: 'italic' }}>
          כשתבחר להיפתח, הקהילה תציע לך אנשים שעוסקים במשהו דומה או משלים — לא לפי תיאור עצמי, אלא לפי מה שעבר דרכם בפועל.
        </p>
        <OpennessControl userId={userId} currentLevel={myLevel} onChange={() => window.location.reload()} />
      </section>
    );
  }

  // State B: non-observer — show suggestions (or quiet message if none)
  const suggestions = data.suggestions || [];

  return (
    <section style={{
      maxWidth: 600, margin: '24px auto 0',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        marginBottom: 14, padding: '0 4px',
      }}>
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
          fontSize: 18, color: 'var(--gold)',
        }}>
          ✦ אנשים שאולי תרצה להכיר
        </div>
        <OpennessControl userId={userId} currentLevel={myLevel} onChange={() => window.location.reload()} />
      </div>

      {suggestions.length === 0 ? (
        <div style={{
          padding: '24px 22px',
          background: 'rgba(255,255,255,0.02)',
          border: '1px solid rgba(212,175,55,0.12)',
          borderRadius: 12,
          textAlign: 'center',
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
          fontSize: 13, color: 'var(--muted)', lineHeight: 1.7,
        }}>
          הקהילה עוד גדלה. ככל שתפעל — תהיה התאמה.
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {suggestions.map((s, i) => {
            const opennessLabel = OPENNESS_LEVELS.find(l => l.key === s.openness_level)?.short || '';
            return (
              <div key={i} style={{
                padding: '20px 22px',
                background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
                border: '1px solid rgba(212,175,55,0.22)',
                boxShadow: '0 4px 16px rgba(0,0,0,0.25)',
                borderRadius: 14,
              }}>
                {/* Header: avatar + name + openness chip */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
                  <Avatar url={s.avatar_url} color={s.avatar_color} name={s.name} size={42} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ color: 'var(--text)', fontWeight: 600, fontSize: 16 }}>{s.name}</div>
                    {opennessLabel && (
                      <div style={{ fontSize: 11, color: 'var(--muted)', fontStyle: 'italic' }}>{opennessLabel}</div>
                    )}
                  </div>
                </div>

                {/* DEEDS first — what was created through them */}
                {s.deeds.length > 0 && (
                  <div style={{ marginBottom: 14 }}>
                    <div style={{
                      fontSize: 11, color: 'var(--gold)', letterSpacing: 1,
                      marginBottom: 6, opacity: 0.85, textTransform: 'uppercase',
                    }}>מה נוצר דרכו</div>
                    <ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
                      {s.deeds.map((d, j) => (
                        <li key={j} style={{
                          fontSize: 13, color: 'var(--text)', lineHeight: 1.7,
                          padding: '2px 0', display: 'flex', gap: 8,
                        }}>
                          <span style={{ color: 'var(--gold)' }}>·</span>
                          <span>{d}</span>
                        </li>
                      ))}
                    </ul>
                  </div>
                )}

                {/* REASONS second — connection points */}
                {s.reasons.length > 0 && (
                  <div style={{
                    paddingTop: 12,
                    borderTop: '1px dashed rgba(212,175,55,0.18)',
                  }}>
                    <div style={{
                      fontSize: 11, color: '#a8a6c0', letterSpacing: 1,
                      marginBottom: 6, opacity: 0.85,
                      fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
                    }}>נראה שיש חיבור סביב</div>
                    <ul style={{ margin: 0, padding: 0, listStyle: 'none' }}>
                      {s.reasons.map((r, j) => (
                        <li key={j} style={{
                          fontSize: 13, color: '#c8c8d8', lineHeight: 1.7,
                          padding: '2px 0', display: 'flex', gap: 8,
                        }}>
                          <span style={{ color: '#a8a6c0' }}>·</span>
                          <span>{r.label}</span>
                        </li>
                      ))}
                    </ul>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </section>
  );
}

// ───────── ProfileMirror — 'ככה רואים אותך' ─────────
// Per Damri 2026-05-11: surface profile editing at the TOP, framed as
// awareness — not as 'about me' display. This sits before ImpactStream
// so the user sees their face before seeing their deeds, and knows what
// the community sees on their suggestion-card.
function ProfileMirror({ userId, profile, googleSub, refresh, setRefresh, onEdit }) {
  const [openness, setOpenness] = React.useState('partners');  // [damri 2026-05-27] default opt-in

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/openness/${userId}`).then(d => {
      if (d && d.level) setOpenness(d.level);
    }).catch(() => {});
  }, [userId, refresh]);

  if (!profile) return null;

  return (
    <section style={{
      maxWidth: 600, margin: '20px auto',
      padding: '22px 26px',
      background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
      border: '1px solid rgba(212,175,55,0.25)',
      boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
      borderRadius: 14,
    }}>
      {/* Eyebrow */}
      <div style={{
        textAlign: 'center', fontSize: 11, letterSpacing: 3,
        color: 'var(--gold)', textTransform: 'uppercase',
        marginBottom: 16, fontWeight: 700, opacity: 0.85,
      }}>
        ✦ ככה רואים אותך
      </div>

      {/* Main: avatar + name + mission */}
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 16 }}>
        <Avatar url={profile.avatar_url} color={profile.avatar_color} name={profile.name} size={64} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <EditableName
            userId={userId}
            currentName={profile.name}
            onSaved={(newName) => { setRefresh && setRefresh(r => r + 1); }}
          />
          {profile.mission && (
            <div style={{
              color: '#c8c8d8', fontSize: 13, marginTop: 6,
              fontStyle: 'italic', lineHeight: 1.6,
              fontFamily: "'Frank Ruhl Libre', serif",
            }}>
              "{profile.mission}"
            </div>
          )}
          {!profile.mission && googleSub && (
            <div style={{
              color: 'var(--muted)', fontSize: 12, marginTop: 6,
              fontStyle: 'italic',
            }}>
              עוד לא הוספת משפט פתיחה — אנשים יראו רק את השם.
            </div>
          )}
          {profile.location && (
            <div style={{ color: 'var(--muted)', fontSize: 12, marginTop: 4 }}>
              📍 {profile.location}
            </div>
          )}
        </div>
      </div>

      {/* Footer row: openness + actions */}
      <div style={{
        marginTop: 18, paddingTop: 14,
        borderTop: '1px dashed rgba(212,175,55,0.18)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        gap: 10, flexWrap: 'wrap',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <span style={{ fontSize: 12, color: 'var(--muted)' }}>פתיחות:</span>
          <OpennessControl userId={userId} currentLevel={openness} onChange={setOpenness} />
        </div>
        <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
          <AvatarUpload userId={userId} onUploaded={() => setRefresh && setRefresh(r => r + 1)} />
          {googleSub && onEdit && (
            <button onClick={onEdit}
              style={{
                background: 'rgba(212,175,55,0.10)', color: 'var(--gold)',
                border: '1px solid rgba(212,175,55,0.35)', borderRadius: 7,
                padding: '4px 12px', cursor: 'pointer', fontSize: 12,
                fontFamily: 'inherit', fontWeight: 600,
              }}>
              ✎ ערוך פרופיל
            </button>
          )}
        </div>
      </div>
    </section>
  );
}

// ───────── RecognitionStoryModal — 'כך נוצרה ההוקרה שלך' ─────────
// Per Damri 2026-05-11: 'לחיצה על BEU = איך ההוקרה שלך נוצרה (סיפור),
// לא עסקאות'. Reads from /api/zekut details — consolidates all streams
// into a quiet narrative. Replaces the bank-style TransactionHistory view
// that used to open from the profile.
function RecognitionStoryModal({ userId, units, onClose }) {
  const [details, setDetails] = React.useState(null);
  React.useEffect(() => {
    if (!userId) return;
    api(`/api/zekut/${userId}`).then(d => {
      if (d && !d.error) setDetails(d.details || {});
    }).catch(() => setDetails({}));
  }, [userId]);

  const c = details || {};
  const lines = [];
  const push = (icon, count, singular, plural) => {
    if (!count || count <= 0) return;
    lines.push({ icon, text: `${count} ${count === 1 ? singular : plural}` });
  };
  push('📖', (c.chapters_read || []).length, 'פרק שעבר דרכך', 'פרקים שעברו דרכך');
  push('✨', (c.circles_opened || []).length, 'מעגל שפתחת', 'מעגלים שפתחת');
  push('🕯️', (c.circles_with_me || []).length, 'מעגל שאת/ה בתוכו', 'מעגלים שאת/ה בתוכם');
  push('🤝', (c.gifts_offered || []).length, 'מתנה שהצעת', 'מתנות שהצעת');
  push('💛', (c.gifts_requested || []).length, 'אדם בקש את שירותך', 'אנשים בקשו את שירותך');
  push('🌱', (c.wishes_posted || []).length, 'משאלה שפרסמת', 'משאלות שפרסמת');
  push('🌟', (c.wishes_i_fulfilled || []).length, 'משאלה שמילאת לאחר', 'משאלות שמילאת לאחרים');
  // The door-line. Per Damri 2026-05-11: no count, no breakdown — just
  // a quiet testimony that connections keep opening. Heart-line in subtitle.
  if ((c.journeys_started || []).length > 0) {
    lines.push({
      icon: '✨',
      text: 'אנשים נוספים נכנסו לבית דויד דרך הדלת שפתחת',
      subtitle: '"החיבור שנוצר ממשיך לחיות בקהילה."',
    });
  }

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{
        maxWidth: 540, maxHeight: '85vh', overflowY: 'auto',
        position: 'relative',
        background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
        border: '1px solid rgba(212,175,55,0.3)',
      }}>
        <span className="close-x" onClick={onClose}
          style={{ position: 'absolute', top: 14, left: 14, fontSize: 26, cursor: 'pointer', zIndex: 10, padding: '2px 10px' }}>×</span>

        <div style={{ textAlign: 'center', marginBottom: 8 }}>
          <div style={{
            fontSize: 11, letterSpacing: 3, color: 'var(--gold)',
            textTransform: 'uppercase', fontWeight: 700, opacity: 0.85,
          }}>✦ מטבע ההוקרה</div>
          <h3 style={{
            fontFamily: "'Frank Ruhl Libre', serif",
            color: 'var(--text)', fontSize: 26, margin: '8px 0 4px',
          }}>כך נוצרה ההוקרה שלך</h3>
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
            fontSize: 14, color: '#c8c8d8', marginBottom: 22,
          }}>כל מטבע הוא עדות לאור שעבר דרכך</div>
        </div>

        {lines.length === 0 && !details && (
          <div style={{ textAlign: 'center', color: 'var(--muted)', padding: 20 }}>טוען...</div>
        )}
        {details && lines.length === 0 && (
          <div style={{
            textAlign: 'center', padding: '20px 16px',
            color: 'var(--muted)', fontStyle: 'italic', lineHeight: 1.7,
            fontFamily: "'Frank Ruhl Libre', serif",
          }}>
            השדה עוד שקט.<br/>קרא פרק, פתח מעגל, או הצע מתנה —<br/>וההוקרה תתחיל להיבנות.
          </div>
        )}
        {lines.length > 0 && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 22 }}>
            {lines.map((l, i) => (
              <div key={i} style={{
                display: 'flex', alignItems: 'flex-start', gap: 14,
                padding: '12px 14px',
                background: 'rgba(212,175,55,0.05)',
                border: '1px solid rgba(212,175,55,0.15)',
                borderRadius: 10,
                fontFamily: "'Frank Ruhl Libre', serif",
                color: 'var(--text)', fontSize: 15, lineHeight: 1.5,
              }}>
                <div style={{ fontSize: 22, flexShrink: 0, lineHeight: 1.3 }}>{l.icon}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div>{l.text}</div>
                  {l.subtitle && (
                    <div style={{
                      fontStyle: 'italic', fontSize: 12.5,
                      color: '#a8a6c0', marginTop: 6, lineHeight: 1.55,
                    }}>{l.subtitle}</div>
                  )}
                </div>
              </div>
            ))}
          </div>
        )}

        {/* Closing explanation — what BEU means */}
        <div style={{
          paddingTop: 18, borderTop: '1px dashed rgba(212,175,55,0.2)',
        }}>
          <div style={{
            fontSize: 13, color: '#c8c8d8', lineHeight: 1.8,
            fontFamily: "'Frank Ruhl Libre', serif",
            fontStyle: 'italic',
          }}>
            <strong style={{ color: 'var(--gold)', fontStyle: 'normal' }}>מטבע ההוקרה</strong> נובע
            ממכלול העדויות שעברו דרכך — לא מצבירת ערך, אלא מאמון
            שנבנה לאורך זמן. רק דרכו יש חיבור לעולם הגדול ולמרקט,
            כי הוא אומר: <em>"אני מכבד את כל הזרמים, לא רק את שלי."</em>
          </div>
          {units && typeof units.beu !== 'undefined' && (
            <div style={{
              marginTop: 16, textAlign: 'center',
              fontSize: 13, color: 'var(--muted)',
            }}>
              נכון לעכשיו אצלך:&nbsp;
              <strong style={{ color: 'var(--gold)', fontSize: 20 }}>{units.beu}</strong>
              &nbsp;✦
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ───────── ExpandedCircle — 'המעגל שהתרחב דרכך' ─────────
// Per Damri 2026-05-11: recognition layer 2.
// 'משהו חי גדל סביבך' — not 'אתה צירפת אנשים'.
// Renders only when at least one person joined through this user.
function ExpandedCircle({ userId, refresh }) {
  const [data, setData] = React.useState(null);

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/expanded-circle/${userId}`).then(d => {
      if (d && !d.error) setData(d);
      else setData({ members: [], total: 0 });
    }).catch(() => setData({ members: [], total: 0 }));
  }, [userId, refresh]);

  // Render nothing if empty — no nag, no 'invite more' CTA
  if (!data || !data.members || data.members.length === 0) return null;

  return (
    <section style={{
      maxWidth: 600, margin: '20px auto',
      padding: '24px 26px',
      background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
      border: '1px solid rgba(212,175,55,0.25)',
      boxShadow: '0 4px 20px rgba(0,0,0,0.25)',
      borderRadius: 14,
    }}>
      {/* Eyebrow */}
      <div style={{
        textAlign: 'center', marginBottom: 22,
        fontFamily: "'Frank Ruhl Libre', serif",
        fontSize: 18, color: 'var(--gold)',
        fontStyle: 'italic', letterSpacing: 1,
      }}>
        ✨ המעגל שהתרחב
      </div>

      {/* Members list */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        {data.members.map((m, i) => (
          <div key={i} style={{
            display: 'flex', alignItems: 'center', gap: 12,
            fontFamily: "'Frank Ruhl Libre', serif",
            fontSize: 15, color: 'var(--text)',
          }}>
            <Avatar url={m.avatar_url} color={m.avatar_color} name={m.first_name} size={32} />
            <div style={{ flex: 1, minWidth: 0, lineHeight: 1.5 }}>
              <span style={{ color: 'var(--text)', fontWeight: 600 }}>{m.first_name}</span>
              <span style={{ color: '#a8a6c0', fontStyle: 'italic' }}> · {m.living_line}</span>
            </div>
          </div>
        ))}
      </div>

      {/* Heart-line — italic serif, the line that holds everything */}
      <div style={{
        marginTop: 22, paddingTop: 18,
        borderTop: '1px dashed rgba(212,175,55,0.2)',
        textAlign: 'center',
        fontFamily: "'Frank Ruhl Libre', serif",
        fontStyle: 'italic',
        fontSize: 14, color: '#c8c8d8',
        lineHeight: 1.7,
      }}>
        "האור שהם מביאים מאיר גם בך."
      </div>
    </section>
  );
}

// ───────── ImpactStream — 'מה נוצר דרכי בעולם' (the hero of the profile) ─────────
// Per Damri 2026-05-10: not an archive. Living flow. Click → expand → see the story.
// Replaces ZekutSection. Recent readings + communities + journeys all live INSIDE
// here as expandable details — no separate cards. 'סיפור אחד מתמשך, לא תפריט.'
function ImpactStream({ userId, profile }) {
  // [yakir-fb#6 2026-05-24] dark purple bg; --text=#1a1a2e was invisible. Forced #fff inside this section.
  const [data, setData] = React.useState(null);
  const [openKey, setOpenKey] = React.useState(null);
  const [recogOpen, setRecogOpen] = React.useState(false);

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/zekut/${userId}`).then(d => { if (d && !d.error) setData(d); });
  }, [userId]);

  if (!data) return null;
  const stories = data.stories || [];
  const units = data.units || { beu: 0, has_legacy: false, legacy_total: 0 };
  const details = data.details || {};

  // Map each story type → which details to show + how to render
  const detailRenderer = {
    chapters_read: () => {
      const list = details.chapters_read || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((r, i) => (
            <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '6px 0', borderBottom: '1px solid rgba(212,175,55,0.08)', fontSize: 13, color: '#c8c8d8' }}>
              <span>פרק {r.chapter} · <span style={{ color: 'var(--muted)' }}>{r.circle_name || 'מעגל'}</span></span>
              <span style={{ fontSize: 11, color: 'var(--muted)' }}>{(r.created_at || '').slice(0,10)}</span>
            </div>
          ))}
        </div>
      );
    },
    circles_opened: () => {
      const list = details.circles_opened || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((c, i) => (
            <div key={i} style={{ padding: '8px 0', borderBottom: '1px solid rgba(212,175,55,0.08)', fontSize: 13, color: '#fff' }}>
              <div style={{ fontWeight: 600 }}>{c.name} {c.is_complete ? '· ✓' : ''}</div>
              {c.purpose && <div style={{ fontSize: 12, color: 'var(--muted)', fontStyle: 'italic' }}>{c.purpose}</div>}
              <div style={{ fontSize: 11, color: 'var(--muted)' }}>{Math.min(c.verified_count || 0, c.total_chapters || 150)}/{c.total_chapters || 150} פרקים · {(c.created_at || '').slice(0,10)}</div>
            </div>
          ))}
        </div>
      );
    },
    circles_completed_with_you: () => {
      const list = details.circles_with_me || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((c, i) => (
            <div key={i} style={{ padding: '6px 0', fontSize: 13, color: '#c8c8d8' }}>
              ✓ {c.name}{c.purpose && <span style={{ color: 'var(--muted)' }}> · {c.purpose}</span>}
            </div>
          ))}
        </div>
      );
    },
    gifts_offered: () => {
      const list = details.gifts_offered || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((s, i) => (
            <div key={i} style={{ padding: '8px 0', borderBottom: '1px solid rgba(212,175,55,0.08)', fontSize: 13, color: '#fff', display: 'flex', gap: 10, alignItems: 'center' }}>
              {s.image_url && (
                <img src={s.image_url} alt="" style={{
                  width: 36, height: 36, borderRadius: 5, objectFit: 'cover',
                  flexShrink: 0, border: '1px solid rgba(212,175,55,0.2)',
                }} />
              )}
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 600 }}>{s.title}</div>
                {s.description && <div style={{ fontSize: 12, color: 'var(--muted)', marginTop: 2 }}>{(s.description || '').slice(0,80)}{s.description.length > 80 ? '…' : ''}</div>}
              </div>
            </div>
          ))}
        </div>
      );
    },
    gifts_received: () => {
      const list = details.gifts_requested || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((r, i) => (
            <div key={i} style={{ padding: '6px 0', fontSize: 13, color: '#c8c8d8' }}>
              <strong style={{ color: 'var(--gold)' }}>{r.requester_name || 'מישהו'}</strong> בקש/ה את <em>{r.service_title}</em>
              <span style={{ fontSize: 11, color: 'var(--muted)', marginRight: 8 }}>· {r.status === 'completed' ? 'הושלם' : 'מתבצע'}</span>
            </div>
          ))}
        </div>
      );
    },
    wishes_posted: () => {
      const list = details.wishes_posted || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((w, i) => (
            <div key={i} style={{ padding: '6px 0', fontSize: 13, color: '#fff', display: 'flex', gap: 10, alignItems: 'center' }}>
              {w.image_url ? (
                <img src={w.image_url} alt="" style={{
                  width: 30, height: 30, borderRadius: 5, objectFit: 'cover',
                  flexShrink: 0, border: '1px solid rgba(212,175,55,0.2)',
                }} />
              ) : (
                <div style={{ width: 30, flexShrink: 0, fontSize: 11, color: 'var(--muted)' }}>
                  {w.status === 'fulfilled' ? '✓' : '⋯'}
                </div>
              )}
              <span style={{ flex: 1 }}>{w.title}</span>
            </div>
          ))}
        </div>
      );
    },
    wishes_fulfilled: () => {
      const list = details.wishes_i_fulfilled || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((w, i) => (
            <div key={i} style={{ padding: '6px 0', fontSize: 13, color: '#c8c8d8' }}>
              משאלת <strong style={{ color: 'var(--gold)' }}>{w.for_name || 'מישהו'}</strong>: {w.title}
            </div>
          ))}
        </div>
      );
    },
    invited_friends: () => {
      const list = details.journeys_started || [];
      if (!list.length) return null;
      return (
        <div style={{ paddingTop: 6 }}>
          {list.map((u, i) => (
            <div key={i} style={{ padding: '6px 0', fontSize: 13, color: '#fff' }}>
              <strong>{u.name}</strong> <span style={{ color: 'var(--muted)' }}>· {u.chapters || 0} פרקים · {(u.created_at || '').slice(0,10)}</span>
            </div>
          ))}
        </div>
      );
    },
  };

  // Stories must include a 'key' so we can match them to detail renderers.
  // The /api/zekut endpoint returns stories without keys — derive from icon.
  const keyForIco = (ico) => {
    if (ico === '📖') return 'chapters_read';
    if (ico === '✨') return 'circles_opened';
    if (ico === '🕯️') return 'circles_completed_with_you';
    if (ico === '🤝') return 'gifts_offered';
    if (ico === '💛') return 'gifts_received';
    if (ico === '🌟') return 'wishes_fulfilled';
    if (ico === '🌱') return 'wishes_posted';
    if (ico === '🤲') return 'invited_friends';
    return null;
  };

  const userName = profile && profile.name ? profile.name : '';

  return (
    <section className="pf-impact" style={{
      maxWidth: 600, margin: '0 auto',
      padding: '32px 26px',
      background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)',
      border: '1px solid rgba(212,175,55,0.25)',
      boxShadow: '0 4px 20px rgba(0,0,0,0.3)',
      borderRadius: 18,
      position: 'relative',
      overflow: 'hidden',
    }}>
      {/* Soft glow orbs in the background — Tanya-style depth */}
      <div aria-hidden style={{ position: 'absolute', top: -40, right: -40, width: 180, height: 180, borderRadius: '50%', background: 'radial-gradient(circle, rgba(212,175,55,0.18), transparent 70%)', pointerEvents: 'none' }} />
      <div aria-hidden style={{ position: 'absolute', bottom: -50, left: -30, width: 200, height: 200, borderRadius: '50%', background: 'radial-gradient(circle, rgba(106,76,176,0.18), transparent 70%)', pointerEvents: 'none' }} />

      {/* Eyebrow */}
      <div style={{
        textAlign: 'center', fontSize: 11, letterSpacing: 3,
        color: 'var(--gold)', textTransform: 'uppercase',
        marginBottom: 8, fontWeight: 700,
        position: 'relative', zIndex: 1,
      }}>
        השדה שלך {userName && '· ' + userName}
      </div>

      {/* Big serif title */}
      <h2 style={{
        textAlign: 'center',
        fontFamily: "'Frank Ruhl Libre', serif",
        fontSize: 'clamp(28px, 6vw, 36px)',
        fontWeight: 700, color: '#fff',
        margin: '0 0 6px',
        position: 'relative', zIndex: 1,
      }}>
        מה נוצר דרכי
      </h2>
      <div style={{
        textAlign: 'center',
        fontFamily: "'Frank Ruhl Libre', serif",
        fontStyle: 'italic',
        fontSize: 14, color: '#c8c8d8',
        marginBottom: 28, opacity: 0.85,
        position: 'relative', zIndex: 1,
      }}>
        זרימה חיה של מה שעבר דרכך לעולם
      </div>

      {/* Stories — clickable rows */}
      {stories.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '20px 8px', color: 'var(--muted)', fontStyle: 'italic', fontSize: 14, lineHeight: 1.7, position: 'relative', zIndex: 1 }}>
          השדה שלך עוד שקט.<br/>
          קרא פרק, פתח מעגל, או הצע מתנה — והזרימה תתחיל.
        </div>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6, position: 'relative', zIndex: 1 }}>
          {stories.map((s, i) => {
            const k = keyForIco(s.ico);
            const isOpen = openKey === k && k !== null;
            const hasDetails = k && detailRenderer[k];
            return (
              <div key={i} style={{
                background: isOpen ? 'rgba(212,175,55,0.05)' : 'transparent',
                border: '1px solid ' + (isOpen ? 'rgba(212,175,55,0.2)' : 'transparent'),
                borderRadius: 10,
                padding: '12px 14px',
                cursor: hasDetails ? 'pointer' : 'default',
                transition: 'background .2s, border .2s',
              }} onClick={() => hasDetails && setOpenKey(isOpen ? null : k)}>
                <div style={{
                  display: 'flex', alignItems: 'center', gap: 14,
                  fontSize: 15, color: '#fff', lineHeight: 1.5,
                }}>
                  <div style={{ fontSize: 22, lineHeight: 1, flexShrink: 0 }}>{s.ico}</div>
                  <div style={{ flex: 1 }}>{s.text}</div>
                  {hasDetails && (
                    <div style={{ color: 'var(--muted)', fontSize: 14, transition: 'transform .2s', transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)' }}>▾</div>
                  )}
                </div>
                {isOpen && hasDetails && detailRenderer[k]()}
              </div>
            );
          })}
        </div>
      )}

      {/* Quiet awakening — Journey CTA inside the stream */}
      <div style={{ marginTop: 18, paddingTop: 14, borderTop: '1px dashed rgba(212,175,55,0.18)', position: 'relative', zIndex: 1 }}>
        <JourneyCTA userId={userId} />
      </div>

      {/* Footer: recognition mark — click opens the story of how it was created. */}
      <div style={{
        marginTop: 12, paddingTop: 14,
        borderTop: '1px dashed rgba(212,175,55,0.25)',
        display: 'flex', justifyContent: 'center', alignItems: 'center',
        position: 'relative', zIndex: 1,
      }}>
        {/* Per Damri 2026-05-11 (lev-hadavar pass 41): the BEU recognition entry
            was readable but didn't read as interactive. Subtle pill shape +
            border + hover state lets it speak as a button without breaking
            the quiet design. Wording unchanged — only typography. */}
        <button onClick={(e) => { e.stopPropagation(); setRecogOpen(true); }}
          style={{
            background: 'rgba(212,175,55,0.06)',
            border: '1px solid rgba(212,175,55,0.25)',
            cursor: 'pointer',
            color: '#c0c2d0', fontSize: 13, padding: '10px 18px',
            borderRadius: 999,
            fontFamily: 'inherit', display: 'flex', alignItems: 'center', gap: 8,
            transition: 'background .2s ease, border-color .2s ease, transform .15s ease',
          }}
          onMouseEnter={(e) => {
            e.currentTarget.style.background = 'rgba(212,175,55,0.14)';
            e.currentTarget.style.borderColor = 'rgba(212,175,55,0.5)';
          }}
          onMouseLeave={(e) => {
            e.currentTarget.style.background = 'rgba(212,175,55,0.06)';
            e.currentTarget.style.borderColor = 'rgba(212,175,55,0.25)';
          }}
          title="כך נוצרה ההוקרה שלך">
          <span style={{ color: 'var(--gold)', fontSize: 15 }}>✦</span>
          <strong style={{ color: 'var(--gold)', fontSize: 17 }}>{units.beu}</strong>
          <span style={{ fontStyle: 'italic' }}>מטבעות הוקרה — לסיפור</span>
          <span style={{ color: 'var(--gold)', fontSize: 16, marginInlineStart: 2 }}>▸</span>
        </button>
      </div>
      {recogOpen && <RecognitionStoryModal userId={userId} units={units} onClose={() => setRecogOpen(false)} />}
    </section>
  );
}

// ───────── JourneyCTA — quiet inline awakening, not a card ─────────
// Per Damri 2026-05-10: 'מסע = CTA שמתעורר'. Small line. Italic serif.
// Lives inside ImpactStream's footer. Navigates to full-screen /journey page.
function JourneyCTA({ userId }) {
  const [progress, setProgress] = React.useState(null);
  React.useEffect(() => {
    if (!userId) return;
    api(`/api/journey-progress/${userId}`).then(d => { if (d && !d.error) setProgress(d); }).catch(() => {});
  }, [userId]);
  const goToJourney = (e) => {
    e.stopPropagation();
    window.dispatchEvent(new CustomEvent('app:navigate', { detail: { page: 'journey' } }));
  };
  let label;
  if (!progress) label = 'מסע פנימי · 12 שאלות · התחל ▸';
  else if (progress.done || progress.answered >= progress.total) {
    label = `✦ המסע פתוח · עיין בתשובות ▸`;
  } else if (progress.answered === 0) {
    label = `מסע פנימי · 12 שאלות שמגלות אותך · התחל ▸`;
  } else {
    label = `✦ במסע · ${progress.answered}/${progress.total} שאלות · המשך ▸`;
  }
  return (
    <div style={{ textAlign: 'center', padding: '14px 0 6px' }}>
      <button
        onClick={goToJourney}
        style={{
          background: 'none', border: 'none', cursor: 'pointer',
          color: 'var(--gold)',
          fontFamily: "'Frank Ruhl Libre', serif",
          fontStyle: 'italic',
          fontSize: 14, letterSpacing: 0.5,
          padding: '6px 12px',
          opacity: 0.85,
          transition: 'opacity .2s',
        }}
        onMouseEnter={e => e.target.style.opacity = '1'}
        onMouseLeave={e => e.target.style.opacity = '0.85'}
      >
        {label}
      </button>
    </div>
  );
}

// ───────── QuickActions — 'מה אני נותן עכשיו' ─────────
// Per Damri 2026-05-10: 'הצע מתנה' is the most outward, most human action.
// 3 actions, gift dominant. The system lives on giving and asking.
function QuickActions({ onOfferGift, onPostWish, onInvite }) {
  return (
    <section style={{ maxWidth: 600, margin: '24px auto 0', padding: '0 4px' }}>
      <div style={{
        textAlign: 'center', fontSize: 11, letterSpacing: 3,
        color: '#a8801f', textTransform: 'uppercase',
        marginBottom: 12, fontWeight: 700,
      }}>
        מה אני נותן עכשיו
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {/* Primary action — gift, biggest */}
        <button onClick={onOfferGift} style={{
          padding: '18px 24px', fontSize: 16, fontWeight: 700,
          background: 'linear-gradient(135deg, #d4af37, #f1c40f)',
          color: '#1a1a2e', border: 'none', borderRadius: 12,
          cursor: 'pointer', fontFamily: 'inherit',
          boxShadow: '0 4px 16px rgba(212,175,55,0.35)',
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
        }}>
          <span style={{ fontSize: 22 }}>🤝</span>
          <span>הצע מתנה לקהילה</span>
        </button>
        {/* Secondary row — wish + invite */}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
          <button onClick={onPostWish} style={{
            padding: '14px 16px', fontSize: 14, fontWeight: 600,
            background: 'transparent', color: 'var(--gold)',
            border: '1.5px solid rgba(212,175,55,0.5)', borderRadius: 10,
            cursor: 'pointer', fontFamily: 'inherit',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
          }}>
            <span>🌱</span><span>פרסם משאלה</span>
          </button>
          <button onClick={onInvite} style={{
            padding: '14px 16px', fontSize: 14, fontWeight: 600,
            background: 'transparent', color: 'var(--gold)',
            border: '1.5px solid rgba(212,175,55,0.5)', borderRadius: 10,
            cursor: 'pointer', fontFamily: 'inherit',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
          }}>
            <span>🤲</span><span>הזמן חבר</span>
          </button>
        </div>
      </div>
    </section>
  );
}

function WalletSection({ profile, userId, onChange }) {
  const [walletOpen, setWalletOpen] = useState(false);
  const [swapOpen, setSwapOpen] = useState(false);
  const short = profile.wallet_address
    ? profile.wallet_address.slice(0, 6) + '...' + profile.wallet_address.slice(-4)
    : null;
  return (
    <div id="wallet-section" className="wallet">
      <h3>הארנק שלי</h3>
      {profile.balances.map(t => (
        <div key={t.id} className="token-row">
          <div className="token-icon">{t.icon}</div>
          <div className="token-info">
            <div className="token-symbol">{t.symbol}</div>
            <div className="token-name">{t.name}</div>
          </div>
          <div className="token-balance">{t.balance}</div>
        </div>
      ))}
      <div style={{ display: 'flex', gap: 8, marginTop: 12, flexWrap: 'wrap' }}>
        <button className="btn" style={{ padding: '8px 14px', fontSize: 13 }} onClick={() => setSwapOpen(true)}>🔄 המרה</button>
        <button className="btn ghost" style={{ padding: '8px 14px', fontSize: 13 }} onClick={() => setWalletOpen(true)}>
          {short ? '🔗 ' + short : '🔗 חבר ארנק חיצוני'}
        </button>
      </div>
      {walletOpen && <WalletConnectModal userId={userId} current={profile.wallet_address} onClose={() => setWalletOpen(false)} onSaved={() => { setWalletOpen(false); onChange(); }} />}
      {swapOpen && <SwapModal userId={userId} balances={profile.balances} onClose={() => setSwapOpen(false)} onDone={() => { setSwapOpen(false); onChange(); }} />}
    </div>
  );
}

// Solana addresses are base58-encoded ed25519 public keys: 32–44 chars,
// alphabet excludes 0, O, I, l. Off-chain syntax check only — does not
// confirm the key exists on-chain (separate sprint).
const SOLANA_ADDR_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
function isValidSolanaAddress(s) {
  return typeof s === 'string' && SOLANA_ADDR_RE.test(s.trim());
}

function WalletConnectModal({ userId, current, onClose, onSaved }) {
  const [addr, setAddr] = useState(current || '');
  const [busy, setBusy] = useState(false);
  const [status, setStatus] = useState('');
  const [addrErr, setAddrErr] = useState('');

  const trimmed = addr.trim();
  const showInlineErr = trimmed.length > 0 && !isValidSolanaAddress(trimmed);

  const persistAddr = async (pubkey) => {
    const r = await api(`/api/user/${userId}/wallet`, {
      method: 'PUT',
      body: JSON.stringify({ wallet_address: pubkey })
    });
    if (r.error) { alert(r.error); return false; }
    if (r.rewarded) alert('+5 BEU על חיבור ארנק!');
    return true;
  };

  const save = async () => {
    const v = addr.trim();
    if (!v) return;
    if (!isValidSolanaAddress(v)) {
      setAddrErr('כתובת Solana לא תקינה — base58, 32–44 תווים');
      return;
    }
    setAddrErr('');
    setBusy(true);
    const ok = await persistAddr(v);
    setBusy(false);
    if (ok) onSaved();
  };

  const connectPhantom = async () => {
    const provider = (window.phantom && window.phantom.solana) || window.solana;
    if (!provider || !provider.isPhantom) {
      setStatus('');
      alert('Phantom לא מותקן. התקן מ-https://phantom.app');
      return;
    }
    setBusy(true); setStatus('מתחבר ל-Phantom...');
    try {
      const resp = await provider.connect();
      const pubkey = resp.publicKey.toString();
      setAddr(pubkey);
      setStatus('מאמת...');
      const ok = await persistAddr(pubkey);
      setBusy(false); setStatus('');
      if (ok) onSaved();
    } catch (e) {
      setBusy(false); setStatus('');
      if (e && (e.code === 4001 || /reject/i.test(e.message || ''))) {
        // user rejected — silent
      } else {
        alert('שגיאה: ' + (e && e.message ? e.message : 'unknown'));
      }
    }
  };

  const connectSolflare = async () => {
    const provider = window.solflare;
    if (!provider) {
      alert('Solflare לא מותקן. התקן מ-https://solflare.com');
      return;
    }
    setBusy(true); setStatus('מתחבר ל-Solflare...');
    try {
      await provider.connect();
      if (!provider.publicKey) throw new Error('connect failed');
      const pubkey = provider.publicKey.toString();
      setAddr(pubkey);
      setStatus('מאמת...');
      const ok = await persistAddr(pubkey);
      setBusy(false); setStatus('');
      if (ok) onSaved();
    } catch (e) {
      setBusy(false); setStatus('');
      if (e && /reject/i.test(e.message || '')) {
        // user rejected
      } else {
        alert('שגיאה: ' + (e && e.message ? e.message : 'unknown'));
      }
    }
  };

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ position: "relative" }}>
        <span className="close-x" onClick={onClose} style={{ position: "absolute", top: 14, left: 14, fontSize: 26, cursor: "pointer", zIndex: 10, padding: "2px 10px" }}>×</span>
        <h3>חיבור ארנק חיצוני</h3>
        <p style={{ color: 'var(--muted)', fontSize: 13 }}>בחר ספק ארנק או הדבק כתובת ידנית</p>
        <div style={{ display: 'flex', gap: 8, marginBottom: 8, flexWrap: 'wrap' }}>
          <button className="btn" onClick={connectPhantom} disabled={busy} style={{ flex: '1 1 45%', fontSize: 13 }}>👻 Phantom</button>
          <button className="btn" onClick={connectSolflare} disabled={busy} style={{ flex: '1 1 45%', fontSize: 13 }}>🔥 Solflare</button>
        </div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
          <button className="btn ghost" disabled style={{ flex: 1, fontSize: 12 }}>🦊 MetaMask (בקרוב)</button>
        </div>
        {status && <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 8 }}>{status}</div>}
        <p style={{ color: 'var(--muted)', fontSize: 12, margin: '6px 0' }}>או הדבק כתובת ידנית:</p>
        <input
          value={addr}
          onChange={e => { setAddr(e.target.value); if (addrErr) setAddrErr(''); }}
          placeholder="כתובת ארנק"
          style={{
            width: '100%', padding: 10, marginBottom: 4,
            border: showInlineErr || addrErr ? '1px solid #ff6b9d' : undefined,
          }}
        />
        {(showInlineErr || addrErr) && (
          <div style={{ color: '#dc2626', fontSize: 12, marginBottom: 8 }}>
            {addrErr || 'כתובת Solana לא תקינה — base58, 32–44 תווים'}
          </div>
        )}
        <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
          <button className="btn" onClick={save} disabled={busy || !trimmed || showInlineErr}>שמור ידנית</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

function SwapModal({ userId, balances, onClose, onDone }) {
  const [from, setFrom] = useState(balances[0] && balances[0].symbol || 'BEU');
  const [to, setTo] = useState(balances[1] && balances[1].symbol || 'THL');
  const [amt, setAmt] = useState('');
  const [busy, setBusy] = useState(false);
  const fromBal = (balances.find(b => b.symbol === from) || {}).balance || 0;
  const submit = async () => {
    setBusy(true);
    const r = await api('/api/swap', { method: 'POST', body: JSON.stringify({ user_id: userId, from_symbol: from, to_symbol: to, amount: parseInt(amt, 10) }) });
    setBusy(false);
    if (r.error) { alert(r.error); return; }
    onDone();
  };
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ position: "relative" }}>
        <span className="close-x" onClick={onClose} style={{ position: "absolute", top: 14, left: 14, fontSize: 26, cursor: "pointer", zIndex: 10, padding: "2px 10px" }}>×</span>
        <h3>🔄 המרת מטבעות</h3>
        <p style={{ color: 'var(--muted)', fontSize: 12 }}>שער: 1:1</p>
        <label>מ-</label>
        <select value={from} onChange={e => setFrom(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 4 }}>
          {balances.map(b => <option key={b.symbol} value={b.symbol}>{b.icon} {b.symbol}</option>)}
        </select>
        <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 10 }}>היתרה: {fromBal}</div>
        <label>ל-</label>
        <select value={to} onChange={e => setTo(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 10 }}>
          {balances.map(b => <option key={b.symbol} value={b.symbol}>{b.icon} {b.symbol}</option>)}
        </select>
        <input type="number" placeholder="כמות" value={amt} onChange={e => setAmt(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 12 }} />
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn" onClick={submit} disabled={busy || !amt}>המר</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

// ───────── v6: Transfer Modal ─────────
function TransferModal({ fromUserId, toUserId, toName, balances, onClose, onSent }) {
  const [sym, setSym] = useState(balances[0] && balances[0].symbol || 'BEU');
  const [amt, setAmt] = useState('');
  const [note, setNote] = useState('');
  const [busy, setBusy] = useState(false);
  const bal = (balances.find(b => b.symbol === sym) || {}).balance || 0;
  const submit = async () => {
    setBusy(true);
    const r = await api('/api/transfer', { method: 'POST', body: JSON.stringify({ from_user_id: fromUserId, to_user_id: toUserId, token_symbol: sym, amount: parseInt(amt, 10), note }) });
    setBusy(false);
    if (r.error) { alert(r.error); return; }
    onSent();
  };
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ position: "relative" }}>
        <span className="close-x" onClick={onClose} style={{ position: "absolute", top: 14, left: 14, fontSize: 26, cursor: "pointer", zIndex: 10, padding: "2px 10px" }}>×</span>
        <h3>🪙 שלח טוקנים ל-{toName}</h3>
        <select value={sym} onChange={e => setSym(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 4 }}>
          {balances.map(b => <option key={b.symbol} value={b.symbol}>{b.icon} {b.symbol} ({b.balance})</option>)}
        </select>
        <div style={{ fontSize: 11, color: 'var(--muted)', marginBottom: 10 }}>היתרה שלי: {bal}</div>
        <input type="number" placeholder="כמות" value={amt} onChange={e => setAmt(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 8 }} />
        <textarea placeholder="הערה (אופציונלי)" value={note} onChange={e => setNote(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 12, minHeight: 60 }} />
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn" onClick={submit} disabled={busy || !amt}>שלח</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

// ───────── v6: Transaction History ─────────
function csvCell(v) {
  if (v === null || v === undefined) return '';
  const s = String(v);
  return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
}

function txsToCsv(txs, userId) {
  const header = ['date', 'direction', 'counterparty', 'amount', 'token', 'reason', 'tx_id'];
  const lines = [header.join(',')];
  for (const t of txs) {
    const out = t.from_user_id === userId;
    lines.push([
      t.created_at || '',
      out ? 'out' : 'in',
      out ? (t.to_name || '') : (t.from_name || ''),
      (out ? -1 : 1) * (t.amount || 0),
      t.token_symbol || '',
      t.reason || '',
      t.id,
    ].map(csvCell).join(','));
  }
  return lines.join('\n');
}

function TransactionHistory({ userId, refresh }) {
  const [txs, setTxs] = useState([]);
  useEffect(() => {
    api(`/api/transactions/${userId}`).then(d => setTxs(Array.isArray(d) ? d : []));
  }, [userId, refresh]);
  if (!txs.length) return null;

  const exportCsv = () => {
    // Prepend BOM so Excel opens UTF-8 correctly (Hebrew counterparty names).
    const csv = '﻿' + txsToCsv(txs, userId);
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    const stamp = new Date().toISOString().slice(0, 10);
    a.download = `transactions-${userId}-${stamp}.csv`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };

  return (
    <div className="profile-card" style={{ maxWidth: 600, margin: '20px auto' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <h3 style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18 }}>היסטוריית העברות</h3>
        <button
          className="btn ghost"
          onClick={exportCsv}
          title="הורד CSV (לרואה חשבון / מס הכנסה)"
          style={{ padding: '4px 10px', fontSize: 11 }}
        >📥 CSV</button>
      </div>
      {txs.slice(0, 15).map(t => {
        const out = t.from_user_id === userId;
        return (
          <div key={t.id} style={{ padding: '8px 0', borderBottom: '1px solid rgba(255,255,255,0.04)', fontSize: 13, display: 'flex', justifyContent: 'space-between' }}>
            <span>{out ? `→ ${t.to_name || '—'}` : `← ${t.from_name || 'מערכת'}`} <span style={{ color: 'var(--muted)' }}>({t.reason})</span></span>
            <span style={{ color: out ? '#dc2626' : 'var(--gold)', fontWeight: 700 }}>{out ? '-' : '+'}{t.amount} {t.token_symbol}</span>
          </div>
        );
      })}
    </div>
  );
}

// ───────── v6: Vision Quest ─────────
function VisionSection({ userId, onChange }) {
  const [data, setData] = useState({ vision: null, milestones: [] });
  const [wizardOpen, setWizardOpen] = useState(false);
  const reload = useCallback(() => {
    api(`/api/vision/${userId}`).then(d => setData(d));
  }, [userId]);
  useEffect(() => { reload(); }, [reload]);

  const completeMilestone = async (mid) => {
    const r = await api(`/api/milestone/${mid}/complete`, { method: 'PUT', body: JSON.stringify({ user_id: userId }) });
    if (r.error) alert(r.error);
    else { reload(); onChange && onChange(); }
  };

  const hasVision = data.vision && data.milestones.length > 0;

  return (
    <div className="profile-card" style={{ maxWidth: 600, margin: '20px auto', background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)', border: '1px solid rgba(212,175,55,0.25)', boxShadow: '0 4px 20px rgba(0,0,0,0.25)' }}>
      <h3 style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 20 }}>🔮 החזון שלי</h3>
      {!hasVision && (
        <div style={{ textAlign: 'center', padding: 20 }}>
          <p style={{ color: '#c8c8d8', lineHeight: 1.7 }}>גלה את החזון האישי שלך — מסע, יעדים ואבני דרך</p>
          <button className="btn" onClick={() => setWizardOpen(true)}>גלה את החזון שלך</button>
        </div>
      )}
      {hasVision && (
        <div>
          <p style={{ color: 'var(--muted)', fontSize: 12, marginBottom: 14 }}>אבני דרך — {data.milestones.filter(m => m.completed).length}/{data.milestones.length} הושלמו</p>
          <div style={{ position: 'relative' }}>
            {data.milestones.map((m, i) => (
              <div key={m.id} style={{ display: 'flex', gap: 12, marginBottom: 14, position: 'relative' }}>
                <div style={{
                  width: 28, height: 28, borderRadius: '50%', flexShrink: 0,
                  background: m.completed ? 'var(--gold)' : 'transparent',
                  border: '2px solid #d4af37',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  color: m.completed ? '#1a1a2e' : 'var(--gold)', fontWeight: 700,
                }}>{m.completed ? '✓' : i + 1}</div>
                <div style={{ flex: 1 }}>
                  <div style={{ color: m.completed ? 'var(--muted)' : 'var(--text)', fontWeight: 600, textDecoration: m.completed ? 'line-through' : 'none' }}>{m.title}</div>
                  <div style={{ color: 'var(--muted)', fontSize: 12, lineHeight: 1.6 }}>{m.description}</div>
                  {!m.completed && (
                    <div style={{ marginTop: 6 }}>
                      <button className="btn ghost" style={{ padding: '4px 10px', fontSize: 11 }} onClick={() => completeMilestone(m.id)}>השלמתי ✓</button>
                    </div>
                  )}
                </div>
              </div>
            ))}
          </div>
          <button className="btn ghost" style={{ marginTop: 8, fontSize: 12 }} onClick={() => setWizardOpen(true)}>🔄 צור מחדש</button>
        </div>
      )}
      {wizardOpen && <VisionWizard userId={userId} onClose={() => setWizardOpen(false)} onDone={() => { setWizardOpen(false); reload(); onChange && onChange(); }} />}
    </div>
  );
}

function VisionWizard({ userId, onClose, onDone }) {
  const [questions, setQuestions] = useState([]);
  const [step, setStep] = useState(0);
  const [answers, setAnswers] = useState([]);
  const [generating, setGenerating] = useState(false);

  useEffect(() => {
    api('/api/vision/questions').then(d => setQuestions(d.questions || []));
  }, []);

  const next = () => {
    if (step < questions.length - 1) setStep(step + 1);
    else generate();
  };

  const generate = async () => {
    setGenerating(true);
    await api('/api/vision/save-answers', { method: 'POST', body: JSON.stringify({ user_id: userId, answers }) });
    const r = await api('/api/vision/generate', { method: 'POST', body: JSON.stringify({ user_id: userId }) });
    setGenerating(false);
    if (r.error) { alert(r.error); return; }
    // removed gamification alert per לב הדבר
    onDone();
  };

  if (questions.length === 0) return null;

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1b4e 100%)', maxWidth: 500 }}>
        {generating ? (
          <div style={{ textAlign: 'center', padding: 30 }}>
            <div style={{ fontSize: 40, marginBottom: 12 }}>🔮</div>
            <h3>Ollama מנתח את התשובות...</h3>
            <p style={{ color: 'var(--muted)' }}>בונה תוכנית פעולה אישית</p>
          </div>
        ) : (
          <>
            <div style={{ height: 4, background: 'rgba(255,255,255,0.1)', borderRadius: 2, marginBottom: 16 }}>
              <div style={{ height: '100%', background: 'var(--gold)', width: ((step + 1) / questions.length * 100) + '%', borderRadius: 2, transition: 'width 0.3s' }} />
            </div>
            <h3 style={{ color: 'var(--gold)' }}>שאלה {step + 1}/{questions.length}</h3>
            <p style={{ color: 'var(--text)', fontSize: 16, margin: '14px 0' }}>{questions[step]}</p>
            <textarea
              value={answers[step] || ''}
              onChange={e => { const a = [...answers]; a[step] = e.target.value; setAnswers(a); }}
              style={{ width: '100%', padding: 12, minHeight: 100, marginBottom: 12 }}
              placeholder="התשובה שלך..."
            />
            <div style={{ display: 'flex', gap: 8 }}>
              {step > 0 && <button className="btn ghost" onClick={() => setStep(step - 1)}>← הקודם</button>}
              <button className="btn" onClick={next} disabled={!answers[step] || !answers[step].trim()}>
                {step === questions.length - 1 ? 'סיים וצור תוכנית 🔮' : 'הבא →'}
              </button>
              <button className="btn ghost" onClick={onClose}>ביטול</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ───────── v6: Wishes ─────────
function WishesSection({ userId, ownerId, onChange }) {
  const [wishes, setWishes] = useState([]);
  const [addOpen, setAddOpen] = useState(false);
  const [editing, setEditing] = useState(null);
  const reload = useCallback(() => {
    api(`/api/wishes?user_id=${ownerId}`).then(d => setWishes(Array.isArray(d) ? d : []));
  }, [ownerId]);
  useEffect(() => { reload(); }, [reload]);

  const remove = async (id) => {
    if (!confirm('למחוק משאלה?')) return;
    await api(`/api/wish/${id}`, { method: 'DELETE', body: JSON.stringify({ user_id: userId }) });
    reload();
  };

  const fulfill = async (id) => {
    if (!confirm('לסמן את המשאלה כהושלמה? זה סוגר אותה ולא ניתן להפוך זאת.')) return;
    const r = await api(`/api/wish/${id}/fulfill`, { method: 'PUT', body: JSON.stringify({ user_id: userId }) });
    if (r.error) { alert(r.error); return; }
    reload();
    onChange && onChange();
  };

  return (
    <div className="profile-card" style={{ maxWidth: 600, margin: '20px auto' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <h3 style={{ color: 'var(--gold)', fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18, margin: 0 }}>⭐ רשימת משאלות</h3>
        {userId === ownerId && (
          <button className="btn ghost" style={{ padding: '6px 12px', fontSize: 12 }} onClick={() => setAddOpen(true)}>➕ הוסף</button>
        )}
      </div>
      {wishes.length === 0 && <p style={{ color: 'var(--muted)', fontSize: 13, fontStyle: 'italic', marginTop: 10 }}>אין משאלות עדיין</p>}
      {wishes.map(w => (
        <div key={w.id} style={{ padding: 10, borderBottom: '1px solid rgba(255,255,255,0.04)', display: 'flex', gap: 10, alignItems: 'flex-start' }}>
          {w.image_url && (
            <img src={w.image_url} alt="" style={{
              width: 44, height: 44, borderRadius: 6, objectFit: 'cover',
              flexShrink: 0, border: '1px solid rgba(212,175,55,0.2)',
            }} />
          )}
          <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
            <div style={{ fontWeight: 600 }}>
              {w.is_public === 0 && (
                <span title="פרטי — לא נראה לקהילה" style={{ marginInlineEnd: 6, opacity: 0.7 }}>🔒</span>
              )}
              {w.title}
            </div>
            <span style={{ fontSize: 11, color: 'var(--gold)', fontWeight: 600 }}>✦ {w.reward_tokens}</span>
          </div>
          {w.description && <div style={{ color: 'var(--muted)', fontSize: 12 }}>{w.description}</div>}
          <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 4 }}>
            סטטוס: {w.status === 'fulfilled' ? '✅ הושלמה' : w.status === 'open' ? '🟢 פתוחה' : w.status}
          </div>
          {w.status === 'open' && (
            <div style={{ display: 'flex', gap: 6, marginTop: 6, flexWrap: 'wrap' }}>
              <button className="btn ghost" style={{ padding: '3px 10px', fontSize: 11, color: '#7ee08a', borderColor: 'rgba(126,224,138,0.4)' }} onClick={() => fulfill(w.id)}>
                ✓ סמן כהושלמה
              </button>
              {userId === ownerId && (
                <>
                  <button className="btn ghost" style={{ padding: '3px 10px', fontSize: 11 }} onClick={() => setEditing(w)} title="ערוך / הוסף תמונה">✏️ ערוך · תמונה</button>
                  <button className="btn ghost" style={{ padding: '3px 8px', fontSize: 10 }} onClick={() => remove(w.id)}>מחק</button>
                </>
              )}
            </div>
          )}
          </div>
        </div>
      ))}
      {addOpen && <AddWishModal userId={userId} onClose={() => setAddOpen(false)} onAdded={() => { setAddOpen(false); reload(); }} />}
      {editing && (
        <EditWishModal wish={editing} userId={userId}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); reload(); onChange && onChange(); }} />
      )}
    </div>
  );
}

function AddWishModal({ userId, onClose, onAdded }) {
  // Marketplace currency unified to BEU per yakir 2026-05-05 17:22 — no token selector.
  const [title, setTitle] = useState('');
  const [desc, setDesc] = useState('');
  const [reward, setReward] = useState(50);
  const [appreciation, setAppreciation] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [imageDataUrl, setImageDataUrl] = useState(null);
  const imageInputRef = React.useRef(null);
  const [isPublic, setIsPublic] = useState(true);
  const submit = async () => {
    if (!title.trim()) { setErr('כותרת חובה'); return; }
    if (!appreciation.trim()) { setErr('שורת הוקרה חובה'); return; }
    setBusy(true); setErr('');
    const r = await api('/api/wish', {
      method: 'POST',
      body: { user_id: userId, title, description: desc, reward_tokens: parseInt(reward, 10) || 0, appreciation: appreciation.trim(), is_public: isPublic}
    });
    if (r.error) { setBusy(false); setErr(r.error); return; }
    // Upload image now if user selected one. Wish exists either way —
    // image is optional, so a failed upload is non-fatal.
    if (imageDataUrl && r.id) {
      try {
        await api(`/api/wish/${r.id}/image`, { method: 'POST', body: { user_id: userId, image_url: imageDataUrl } });
      } catch (_) { /* swallow — user can edit later to add image */ }
    }
    setBusy(false);
    onAdded();
  };
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ position: "relative" }}>
        <span className="close-x" onClick={onClose} style={{ position: "absolute", top: 14, left: 14, fontSize: 26, cursor: "pointer", zIndex: 10, padding: "2px 10px" }}>×</span>
        <h3>➕ משאלה חדשה</h3>
        <label>כותרת</label>
        <input placeholder="מה אתה צריך?" value={title} onChange={e => setTitle(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 8 }} />
        <label>תיאור</label>
        <textarea placeholder="פרטים" value={desc} onChange={e => setDesc(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 8, minHeight: 70 }} />
        <label>פרס למי שיעזור</label>
        <div className="amount-input-wrap">
          <input
            className="amount-input-big"
            type="number"
            min="0"
            value={reward}
            onChange={e => setReward(e.target.value)}
            placeholder="0"
          />
          <span className="amount-input-suffix">✦</span>
        </div>
        <label>שורת הוקרה לקהילה <span className="req-mark">*</span></label>
        <textarea
          placeholder="מה הקהילה תקבל ממך כשמישהו יעזור? (1-2 שורות)"
          value={appreciation}
          onChange={e => setAppreciation(e.target.value)}
          style={{ width: '100%', padding: 10, marginBottom: 8, minHeight: 50 }}
        />

        {/* Inline image picker — added inline so users see this BEFORE publishing. */}
        <label>תמונה (אופציונלי)</label>
        <div style={{ marginBottom: 10 }}>
          {imageDataUrl && (
            <div style={{ width: '100%', maxHeight: 180, overflow: 'hidden', borderRadius: 8, marginBottom: 6, background: '#000', display: 'flex', justifyContent: 'center' }}>
              <img src={imageDataUrl} alt="" style={{ maxWidth: '100%', maxHeight: 180, objectFit: 'contain' }} />
            </div>
          )}
          <input type="file" accept="image/png,image/jpeg,image/webp,image/gif"
            ref={imageInputRef}
            style={{ display: 'none' }}
            onChange={e => {
              const f = e.target.files && e.target.files[0]; if (!f) return;
              if (f.size > 5 * 1024 * 1024) { setErr('הקובץ גדול מדי (מקסימום 5MB)'); return; }
              const reader = new FileReader();
              reader.onload = (ev) => setImageDataUrl(ev.target.result);
              reader.readAsDataURL(f);
              e.target.value = '';
            }} />
          <div style={{ display: 'flex', gap: 6 }}>
            <button type="button"
              onClick={() => imageInputRef.current && imageInputRef.current.click()}
              style={{ flex: 1, padding: '10px', fontSize: 13, background: 'rgba(212,175,55,0.12)', border: '1px dashed rgba(212,175,55,0.5)', borderRadius: 8, color: 'var(--gold)', cursor: 'pointer', fontFamily: 'inherit' }}>
              {imageDataUrl ? '🔄 החלף תמונה' : '📷 הוסף תמונה'}
            </button>
            {imageDataUrl && (
              <button type="button" onClick={() => setImageDataUrl(null)}
                style={{ padding: '10px 14px', fontSize: 13, background: 'transparent', border: '1px solid rgba(255,107,157,0.3)', borderRadius: 8, color: '#dc2626', cursor: 'pointer', fontFamily: 'inherit' }}>
                ✕
              </button>
            )}
          </div>
        </div>
        {err && <div style={{ color: '#dc2626', fontSize: 13, marginBottom: 8 }}>{err}</div>}
        <div style={{ display: 'flex', gap: 8 }}>
          <div style={{ marginTop: 14, padding: '12px 14px', background: 'rgba(212,175,55,0.05)', border: '1px solid rgba(212,175,55,0.18)', borderRadius: 10 }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer', fontSize: 13, color: 'var(--text)' }}>
              <input type="checkbox" checked={isPublic} onChange={e => setIsPublic(e.target.checked)}
                style={{ width: 18, height: 18, accentColor: 'var(--gold)' }} />
              <span>לשתף עם הקהילה?</span>
            </label>
            <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 6, marginInlineStart: 28, fontStyle: 'italic', lineHeight: 1.5 }}>
              {isPublic
                ? 'כן — הקהילה תוכל לראות, להציע עזרה, להתחבר.'
                : 'לא — נשאר רק אצלך. תוכל לפתוח בעתיד.'}
            </div>
          </div>
          <button className="btn" onClick={submit} disabled={busy || !title.trim()}>פרסם</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

// Edit existing wish — owner only. Yakir spec 2026-05-05 17:08.
function EditWishModal({ wish, userId, onClose, onSaved }) {
  const [title, setTitle] = useState(wish.title || '');
  const [desc, setDesc] = useState(wish.description || '');
  const [reward, setReward] = useState(wish.reward_tokens || 0);
  const [appreciation, setAppreciation] = useState(wish.appreciation || '');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState('');
  const [isPublic, setIsPublic] = useState(wish.is_public !== 0);

  const submit = async () => {
    if (!title.trim()) { setErr('כותרת חובה'); return; }
    if (!appreciation.trim()) { setErr('שורת הוקרה חובה'); return; }
    setBusy(true); setErr('');
    const r = await api(`/api/wish/${wish.id}`, {
      method: 'PUT',
      body: {
        user_id: userId,
        title: title.trim(),
        description: desc.trim(),
        reward_tokens: parseInt(reward, 10) || 0,
        appreciation: appreciation.trim(), is_public: isPublic}
    });
    setBusy(false);
    if (r.error) { setErr(r.error); return; }
    onSaved();
  };

  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ position: "relative" }}>
        <span className="close-x" onClick={onClose} style={{ position: "absolute", top: 14, left: 14, fontSize: 26, cursor: "pointer", zIndex: 10, padding: "2px 10px" }}>×</span>
        <h3>✏️ ערוך משאלה</h3>
        {wish.image_url && (
          <div style={{ marginBottom: 12 }}>
            <div style={{ fontSize: 12, color: 'var(--muted)', marginBottom: 6 }}>תמונה נוכחית:</div>
            <img src={wish.image_url} alt="" style={{
              width: '100%', maxHeight: 180, objectFit: 'cover',
              borderRadius: 8, border: '1px solid rgba(212,175,55,0.2)',
            }} />
          </div>
        )}
        <ImageUploader
          uploadEndpoint={`/api/wish/${wish.id}/image`}
          userId={userId}
          currentImageUrl={wish.image_url}
          label={wish.image_url ? 'החלף תמונה' : 'הוסף תמונה'} />
        <label>כותרת</label>
        <input value={title} onChange={e => setTitle(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 8 }} />
        <label>תיאור</label>
        <textarea value={desc} onChange={e => setDesc(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 8, minHeight: 70 }} />
        <label>פרס למי שיעזור</label>
        <div className="amount-input-wrap">
          <input className="amount-input-big" type="number" min="0" value={reward} onChange={e => setReward(e.target.value)} />
          <span className="amount-input-suffix">✦</span>
        </div>
        <label>שורת הוקרה לקהילה <span className="req-mark">*</span></label>
        <textarea value={appreciation} onChange={e => setAppreciation(e.target.value)} style={{ width: '100%', padding: 10, marginBottom: 8, minHeight: 50 }} />
        {err && <div style={{ color: '#dc2626', fontSize: 13, marginBottom: 8 }}>{err}</div>}
        <div style={{ display: 'flex', gap: 8 }}>
          <div style={{ marginTop: 14, padding: '12px 14px', background: 'rgba(212,175,55,0.05)', border: '1px solid rgba(212,175,55,0.18)', borderRadius: 10 }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer', fontSize: 13, color: 'var(--text)' }}>
              <input type="checkbox" checked={isPublic} onChange={e => setIsPublic(e.target.checked)}
                style={{ width: 18, height: 18, accentColor: 'var(--gold)' }} />
              <span>לשתף עם הקהילה?</span>
            </label>
            <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 6, marginInlineStart: 28, fontStyle: 'italic', lineHeight: 1.5 }}>
              {isPublic
                ? 'כן — הקהילה רואה ויכולה להציע עזרה.'
                : 'לא — נשאר רק אצלך. תוכל לפתוח בעתיד.'}
            </div>
          </div>
          <button className="btn" onClick={submit} disabled={busy}>{busy ? '...' : 'שמור'}</button>
          <button className="btn ghost" onClick={onClose}>ביטול</button>
        </div>
      </div>
    </div>
  );
}

function MarketplaceWishes({ userId }) {
  const [wishes, setWishes] = useState([]);
  const [editing, setEditing] = useState(null);
  const reload = useCallback(() => {
    api('/api/wishes').then(d => setWishes(Array.isArray(d) ? d : []));
  }, []);
  useEffect(() => { reload(); }, [reload]);

  const help = async (id) => {
    if (!userId) { alert('צריך להירשם'); return; }
    const r = await api(`/api/wish/${id}/fulfill`, { method: 'PUT', body: { user_id: userId } });
    if (r.error) alert(r.error);
    else { alert('סופק בהצלחה!'); reload(); }
  };

  const publishToCommunity = async (w) => {
    if (!window.confirm(`לפרסם את "${w.title}" לקהילה תמורת 200 BEU?`)) return;
    const r = await api(`/api/wish/${w.id}/publish-to-community`, {
      method: 'POST',
      body: { user_id: userId }
    });
    if (r.error) { alert(r.error); return; }
    alert(`פורסם! נוכו 200 BEU. יתרה חדשה: ${r.new_balance} BEU`);
    reload();
  };

  if (wishes.length === 0) return <p style={{ color: 'var(--muted)', textAlign: 'center', fontStyle: 'italic' }}>אין משאלות פתוחות</p>;
  return (
    <div className="svc-grid">
      {wishes.map(w => (
        <div key={w.id} className={'svc-card' + (w.in_community_feed ? ' wish-promoted' : '')}>
          {w.in_community_feed ? (
            <div className="wish-promo-badge">📢 בולט בקהילה</div>
          ) : null}
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
            <Avatar url={w.avatar_url} color={w.avatar_color} name={w.user_name} size={36} />
            <div style={{ fontSize: 12, color: 'var(--muted)' }}>{w.user_name}</div>
          </div>
          <h4 style={{ color: 'var(--gold)', margin: '4px 0' }}>{w.title}</h4>
          <p style={{ color: '#c8c8d8', fontSize: 13 }}>{w.description}</p>
          <div style={{ margin: '8px 0', display: 'flex', alignItems: 'baseline', gap: 8, color: 'var(--muted)', fontSize: 13 }}>
            <span style={{ fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic', color: '#a8801f' }}>להוקיר</span>
            <span style={{ color: 'var(--gold)', fontWeight: 600 }}>✦ {w.reward_tokens}</span>
          </div>
          {w.appreciation && (
            <div className="svc-appreciation">
              <span className="svc-appreciation-icon">🤝</span>
              <span className="svc-appreciation-txt">{w.appreciation}</span>
            </div>
          )}
          {w.user_id !== userId && (
            <div style={{ display:'flex', gap:6, marginTop:10, flexWrap:'wrap' }}>
              <button className="btn" style={{ padding: '8px 14px', fontSize: 13 }} onClick={() => help(w.id)}>אני יכול לעזור! ✋</button>
              {/* [damri 2026-05-21] empowerment — holding space, not labor */}
              <button className="btn ghost" style={{ padding: '8px 12px', fontSize: 13, border: '1px solid rgba(212,175,55,0.45)' }} 
                onClick={async () => {
                  if (!userId) { showToast('צריך להתחבר'); return; }
                  try {
                    const r = await api(`/api/wish/${w.id}/empower`, { method:'POST', body:{ user_id: userId }});
                    if (r.error) { showToast(r.error); return; }
                    showToast(r.state === 'added' ? '💝 אתה מחזיק מקום' : '✓ הוסר');
                    reload();
                  } catch (e) { showToast(e.message || 'שגיאה'); }
                }}>
                💝 אני מחזיק/ה מקום {w.empower_count > 0 ? `· ${w.empower_count}` : ''}
              </button>
            </div>
          )}
          {w.user_id === userId && w.status === 'open' && (
            <div style={{ display: 'flex', gap: 6, marginTop: 10, flexWrap: 'wrap' }}>
              <button className="btn ghost" style={{ padding: '6px 12px', fontSize: 12 }} onClick={() => setEditing(w)}>✏️ ערוך</button>
              {!w.in_community_feed && (
                <button
                  className="btn"
                  style={{ padding: '6px 12px', fontSize: 12, background: 'linear-gradient(135deg,#b8860b,#d4af37)', color:'var(--text)' }}
                  onClick={() => publishToCommunity(w)}
                >📢 פרסום לקהילה (✦ 200)</button>
              )}
            </div>
          )}
        </div>
      ))}
      {editing && (
        <EditWishModal
          wish={editing}
          userId={userId}
          onClose={() => setEditing(null)}
          onSaved={() => { setEditing(null); reload(); }}
        />
      )}
    </div>
  );
}

// ───────── A: Vision Page (A2) ─────────
const VISION_HORIZON_LIST = [
  { key: '5y',    label: '5 שנים',  hint: 'איך תיראה השנה הזו בעוד חמש שנים?' },
  { key: '1y',    label: 'שנה',     hint: 'מה תרצה להשיג עד סוף השנה?' },
  { key: 'month', label: 'חודש',    hint: 'הצעד הקרוב — מה השלב הזה?' },
  { key: 'week',  label: 'שבוע',    hint: 'מה אפשר לקדם השבוע?' },
];

function MilestoneTimeline({ userId, grouped, onChange }) {
  const [draft, setDraft] = React.useState({});
  const [busyKey, setBusyKey] = React.useState(null);
  const [err, setErr] = React.useState('');

  const setField = (h, k, v) => setDraft(d => ({ ...d, [h]: { ...(d[h] || {}), [k]: v } }));

  const add = async (h) => {
    const d = draft[h] || {};
    if (!d.title || !d.title.trim()) { setErr('כותרת חובה'); return; }
    setBusyKey('add:' + h); setErr('');
    try {
      const r = await api('/api/vision/milestones', {
        method: 'POST',
        body: { user_id: userId, horizon: h, title: d.title.trim(), target_date: d.target_date || undefined },
      });
      if (r.error) throw new Error(r.error);
      setDraft(prev => ({ ...prev, [h]: {} }));
      onChange && onChange();
    } catch (e) { setErr(e.message); }
    finally { setBusyKey(null); }
  };

  const toggleStatus = async (m) => {
    setBusyKey('upd:' + m.id); setErr('');
    const next = m.status === 'done' ? 'planned' : 'done';
    try {
      const r = await api('/api/vision/milestones/' + m.id, {
        method: 'PUT', body: { status: next },
      });
      if (r.error) throw new Error(r.error);
      onChange && onChange();
    } catch (e) { setErr(e.message); }
    finally { setBusyKey(null); }
  };

  const remove = async (m) => {
    if (!window.confirm('למחוק "' + m.title + '"?')) return;
    setBusyKey('del:' + m.id); setErr('');
    try {
      const r = await api('/api/vision/milestones/' + m.id + '?user_id=' + userId, { method: 'DELETE' });
      if (r.error) throw new Error(r.error);
      onChange && onChange();
    } catch (e) { setErr(e.message); }
    finally { setBusyKey(null); }
  };

  return (
    <div className="vision-timeline">
      {VISION_HORIZON_LIST.map(({ key, label, hint }) => {
        const items = (grouped && grouped[key]) || [];
        const d = draft[key] || {};
        return (
          <div key={key} className="vision-horizon-card">
            <div className="vision-horizon-head">
              <h3>{label}</h3>
              <span className="vision-horizon-count">{items.length}</span>
            </div>
            <div className="vision-horizon-hint">{hint}</div>
            <ul className="vision-milestone-list">
              {items.length === 0 && (
                <li className="vision-empty">— אין עדיין —</li>
              )}
              {items.map(m => (
                <li key={m.id} className={`vision-milestone-item${m.status === 'done' ? ' done' : ''}`}>
                  <button type="button" className="vision-milestone-check"
                          aria-label={m.status === 'done' ? 'בטל סימון' : 'סמן הושלם'}
                          onClick={() => toggleStatus(m)} disabled={busyKey === ('upd:' + m.id)}>
                    {m.status === 'done' ? '✓' : '○'}
                  </button>
                  <div className="vision-milestone-body">
                    <div className="vision-milestone-title">{m.title}</div>
                    {m.target_date && <div className="vision-milestone-date">🗓 {m.target_date}</div>}
                  </div>
                  <button type="button" className="vision-milestone-del" aria-label="מחק"
                          onClick={() => remove(m)} disabled={busyKey === ('del:' + m.id)}>×</button>
                </li>
              ))}
            </ul>
            <div className="vision-add-row">
              <input type="text" placeholder="הוסף אבן דרך..." value={d.title || ''}
                     onChange={e => setField(key, 'title', e.target.value)}
                     onKeyDown={e => { if (e.key === 'Enter') add(key); }} />
              <input type="date" value={d.target_date || ''}
                     onChange={e => setField(key, 'target_date', e.target.value)} />
              <button type="button" className="btn ghost" onClick={() => add(key)}
                      disabled={busyKey === ('add:' + key)}>
                + הוסף
              </button>
            </div>
          </div>
        );
      })}
      {err && <div className="vision-error">{err}</div>}
    </div>
  );
}

function VisionPage({ userId }) {
  const [grouped, setGrouped] = React.useState({ '5y': [], '1y': [], month: [], week: [] });
  const [total, setTotal] = React.useState(0);
  const [reflections, setReflections] = React.useState([]);
  const [reflectText, setReflectText] = React.useState('');
  const [reflectBusy, setReflectBusy] = React.useState(false);
  const [reflectErr, setReflectErr] = React.useState('');
  const [vision, setVision] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [wizardOpen, setWizardOpen] = React.useState(false);

  const reload = React.useCallback(async () => {
    if (!userId) { setLoading(false); return; }
    setLoading(true);
    try {
      const [m, r, v] = await Promise.all([
        api('/api/vision/milestones?user_id=' + userId),
        api('/api/vision/reflections?user_id=' + userId + '&limit=7'),
        api('/api/vision/' + userId).catch(() => null),
      ]);
      setGrouped(m.grouped || { '5y': [], '1y': [], month: [], week: [] });
      setTotal(m.total || 0);
      setReflections(r.reflections || []);
      setVision(v && v.vision ? v.vision : null);
    } finally { setLoading(false); }
  }, [userId]);

  React.useEffect(() => { reload(); }, [reload]);

  const submitReflect = async () => {
    if (!reflectText.trim()) { setReflectErr('תוכן חובה'); return; }
    setReflectBusy(true); setReflectErr('');
    try {
      const r = await api('/api/vision/milestones/reflect', {
        method: 'POST', body: { user_id: userId, text: reflectText.trim() },
      });
      if (r.error) throw new Error(r.error);
      setReflectText('');
      reload();
    } catch (e) { setReflectErr(e.message); }
    finally { setReflectBusy(false); }
  };

  const doneCount = Object.values(grouped).reduce(
    (acc, arr) => acc + arr.filter(m => m.status === 'done').length, 0);
  const pct = total > 0 ? Math.round((doneCount / total) * 100) : 0;

  if (!userId) {
    return (
      <section className="vision-page">
        <div className="vision-hero">
          <h1>החזון שלי</h1>
          <p>צריך להתחבר כדי לבנות את מסע החזון.</p>
        </div>
      </section>
    );
  }

  let summary = '';
  try {
    if (vision && vision.goals) {
      const goals = typeof vision.goals === 'string' ? JSON.parse(vision.goals) : vision.goals;
      if (Array.isArray(goals) && goals.length) summary = goals[0].text || goals[0].title || '';
    }
  } catch {}

  return (
    <section className="vision-page">
      <div className="vision-hero">
        <h1>החזון שלי</h1>
        {summary
          ? <p className="vision-summary">{summary}</p>
          : <p className="vision-empty-cta">עוד לא בנית חזון — לחץ על "אשף החזון" למטה כדי להתחיל.</p>}
      </div>

      {loading ? (
        <div className="vision-loading">טוען...</div>
      ) : (
        <>
          <MilestoneTimeline userId={userId} grouped={grouped} onChange={reload} />

          <div className="vision-progress">
            <div className="vision-progress-label">
              התקדמות: {doneCount} / {total} ({pct}%)
            </div>
            <div className="vision-progress-bar">
              <div className="vision-progress-fill" style={{ width: pct + '%' }} />
            </div>
          </div>

          <div className="vision-reflect">
            <h3>השתקפות יומית</h3>
            <textarea rows="3" placeholder="מה התקדם היום?"
                      value={reflectText} onChange={e => setReflectText(e.target.value)} />
            {reflectErr && <div className="vision-error">{reflectErr}</div>}
            <button className="btn" onClick={submitReflect} disabled={reflectBusy}>
              {reflectBusy ? 'שולח...' : 'שמור השתקפות'}
            </button>
            <div className="vision-reflect-list">
              <div className="vision-reflect-list-title">שבעת הימים האחרונים</div>
              {reflections.length === 0 && <div className="vision-empty">— עוד לא רשמת השתקפויות —</div>}
              {reflections.map(r => (
                <div key={r.id} className="vision-reflect-item">
                  <div className="vision-reflect-date">{(r.created_at || '').slice(0, 10)}</div>
                  <div className="vision-reflect-text">{r.text}</div>
                </div>
              ))}
            </div>
          </div>

          <div className="vision-retake">
            <button className="btn ghost" onClick={() => setWizardOpen(true)}>
              🪄 פתח אשף חזון
            </button>
          </div>
        </>
      )}

      {wizardOpen && (
        <VisionWizard userId={userId}
                      onClose={() => setWizardOpen(false)}
                      onDone={() => { setWizardOpen(false); reload(); }} />
      )}
    </section>
  );
}

// ───────── JournalPage — יומן המסע ─────────
// Per Damri 2026-05-11: שכבה של סיפור הדרך. לא לוג טכני, לא pitch —
// נרטיב קהילתי מעובד. רשומות שמתעדכנות לאורך זמן.
// Entries are inlined for now; future iteration may move to markdown source.
function JournalPage() {
  // Group invite URL — points readers to the live conversation, keeping
  // the journal itself comment-free per Damri 2026-05-11 ('שורה אחת קטנה,
  // לא חזיתית'). Real discussion happens in WhatsApp, not on the page.
  const GROUP_INVITE_URL = 'https://chat.whatsapp.com/E2C86mny8FJ3hVagDDOHX5?mode=gi_t';
  const GROUP_NAME = 'שיח מעצים לכלכלה חדשה';
  const entries = [
    {
      n: '001',
      date: '11.5.2026',
      title: 'מה שמתחיל להיווצר כאן',
      intro: 'הרשומה הראשונה ביומן נכתבה במכוון מנקודת מבט חיצונית — של מי שמתבונן ולא של מי שמשתתף. בלי הכרזות, בלי הסברים. רק ראייה.',
      paragraphs: [
        'מי שמתבונן בזה מהצד, בלי להיות בפנים, רואה דבר מוזר. יש כאן מערכת, ויש סביבה אנשים, ולמראית עין זה נראה כמו עוד מקום שבו טכנולוגיה פוגשת קהילה. אבל אם מתעכבים רגע, רואים שמשהו אחר קורה. לא חזק. לא רועש. לא מכריז על עצמו. רק קצב מסוים שמתחיל להתייצב בין הדברים.',
        'המערכת הזאת לא מנסה להפתיע אף אחד. היא לא מציעה תרחיש עתידני. היא לא מתחרה על תשומת לב. מה שהיא עושה זה דבר אחד פשוט שהוא, מסתבר, לא פשוט בכלל: היא זוכרת. את מה שאדם אמר לפני שבוע. את מה שביקש ולא קיבל. את החיבור שעשה עם מישהו אחר. את הדבר שעוד לא הספיק לבקש כי לא הגיע לזה.',
        'וכשמערכת זוכרת — קורה משהו לא צפוי. לא במערכת. באנשים שמסביבה.',
        'מישהו פותח את האפליקציה כדי לקרוא פרק. שלושה אנשים אחרים פותחים אותה באותה שעה, כל אחד בעולמו שלו. אף אחד מהם לא יודע על האחר. ובכל זאת קריאתם מצטרפת. מישהו במעגל לומד פרק לזכר אדם שאינו עוד, ומישהו רחוק, שלא הכיר אותו מעולם, רואה שזה קורה — ומשהו בו נע. שני אנשים שלא דיברו מגלים שהם חולקים את אותה כוונה. מי שעמד לבד מול דף, אינו לבד.',
        'זה לא נראה דרמטי כשמספרים את זה. דרמטיות אינה המידה הנכונה. המידה הנכונה היא הצטברות.',
        'המערכת מצטברת. כל לימוד נשמר. כל חיבור משאיר עקבות. כל פעולה — של אדם אחד, בלילה, בחושך, כשאיש אינו רואה — נכנסת אל תוך משהו רחב יותר. וכאשר מסה זו מצטברת לאורך חודשים, נוצר משהו שאי אפשר לבנות במכוון. רקמה. נוכחות מתמשכת. תחושה שאדם לא צריך להחזיק את כל הזיכרון בעצמו — כי יש משהו שמחזיק אותו.',
        'מי שמתבונן מהצד יראה גם דבר נוסף, ואולי הוא העיקר.',
        'המערכת הזאת לא הופכת אנשים ל״משתמשים״. להפך. היא לוקחת מקום שבו אנשים בדרך כלל הופכים לנתונים, וגורמת להם להיראות, אחד לשני, כפי שהם באמת. מישהו לומד פרק. מישהו אחר רואה שזה קורה ומתעורר בו משהו. אדם שלישי, שצופה מבחוץ, נזכר במה שהוא בעצמו רצה ללמוד פעם. שלוש תנועות שלא היו קורות בלי השכבה הדקה הזאת באמצע.',
        'אין כאן רשת חברתית. רשת חברתית רוצה תשומת לב. כאן אין מי שרוצה תשומת לב. יש משהו אחר, שאין לו עדיין שם מקובל — מקום שבו פעולה אנושית קטנה אינה נעלמת.',
        'זה הדבר הנדיר. בעולם שמטשטש בלי הפסקה — כאן משהו לא נמחק. מה שאדם נתן, נשאר. מי שעזר, נזכר. הצעד שאחד עשה לכיוון מישהו, גם אם אותו מישהו לא ידע, גם אם הצעד היה זעיר — נכנס אל החשבון.',
        'אלה דברים שאי אפשר להוכיח. אפשר רק להראות אותם. וגם זה, רק בזהירות, ולמי שמוכן להסתכל באמת.',
        'מי שיביט במערכת הזאת בעוד שנה, ככל הנראה, לא יזכור את התכונות הטכניות. הוא יזכור משהו אחר. אדם שלא הכיר, שמישהו למד עליו פרק. חבר ותיק שהוא ובני המעגל זכרו דווקא ברגע שכמעט שכחו. רגע אחד שבו, דרך מסך קטן, הרגיש שיש כאן אחיזה.',
        'אולי זאת המידה הנכונה לבחון מערכת. לא במה היא מסוגלת לעשות — אלא במה שמסביבה ממשיך לקרות גם כשאף אחד לא מסתכל.',
      ],
    },
    {
      n: '002',
      date: '11.5.2026',
      title: 'ופרצת — לכף יד',
      intro: 'השורש. למה נבנה דבר כזה דווקא בדור הזה, ולמה דווקא כך.',
      paragraphs: [
        'לפני שלושים שנה, בשבת אחת ב־770 איסטרן פארקוויי, אדם שמעולם לא ראה את הרבי לבש מעיל, נסע עשרות שעות, עמד שעות בתור, וקיבל דולר. לא דולר אחד מתוך אלפים זהים. דולר. שמו נאמר. עיניו נראו. מילה אחת נאמרה אליו, רק אליו, ולפעמים גם זה לא — לפעמים רק מבט. ואחריו עמד הבא בתור. וכך, שעה אחר שעה, יום אחר יום, שנה אחר שנה, אדם אחד אחר אדם אחד.',
        'מי שראה את זה מבחוץ חשב שזה מנהג חב״די. מי שהבין ראה משהו אחר: התעקשות שלא לאבד שום אדם בתוך הכלל. בעולם שמתחיל לדבר על ״המונים״, ״קהלי יעד״, ״סטטיסטיקות״ — הרבי בנה תשתית שלמה שאומרת בקול אחד: אין המון. יש אדם, ועוד אדם, ועוד אדם. כל אחד מהם הוא עולם מלא. וכל פעולה שעושים איתו צריכה לדעת מי הוא.',
        'זאת לא הייתה אמירה רעיונית. זאת הייתה הוראה תפעולית למבצע שלם — אלפי שלוחים שיצאו לעולם, ארגון שמעריך כל אדם בשמו, רשת שלא מאבדת אף אחד. הרבי לא הסתפק בלהגיד ״אהבת ישראל״; הוא בנה את התשתית של אהבת ישראל. תפילין לכל יהודי. נר לכל אישה. מזוזה לכל בית. ספר לכל ילד. לא כסיסמה — כתשתית עם תכלית.',
        'זה היה החלק הראשון של המהפכה. החלק השני היה התעקשות לא פחות מתעקשת על דבר אחר: מעשה. ״המעשה הוא העיקר״ — לא רגש, לא ידיעה, לא כוונה. מעשה. הרבי שלח שלוחים למקומות שיהודים אחרים פחדו להגיע אליהם. הוא דחף לפעולה גם כשנראה שלא הזמן. הוא דרש שכל שיחה תיגמר בהחלטה. ״מה אתה הולך לעשות מחר בבוקר?״ — זו השאלה שחזרה. לא ״מה הבנת״. מה תעשה.',
        'והחלק השלישי, אולי המהפכני מכולם: טכנולוגיה. בזמן שהעולם הדתי חיכך כפיים אם להתיר רדיו, הרבי דיבר לטלוויזיה. כשטלפון לוויינים היה פלא, הרבי השתמש בו לחבר אלפי יהודים בעולם לשיחה אחת. הוא ראה במהפכה הטכנולוגית את קיום הנבואה — ״ומלאה הארץ דעה את ה׳ כמים לים מכסים״. לא רק כמטאפורה. כמכניזם. שערוץ אחד יוכל להגיע בו־זמנית למיליון בני אדם — זה לא היה אפשרי, ופתאום אפשרי, ופתאום מובן מאליו. ״זה לא במקרה,״ אמר הרבי. ״זו הכנה.״',
        'ועל הכל ריחפה מילה אחת: ופרצת. ״ופרצת ימה וקדמה צפונה ונגבה.״ התפשטות לכל הכיוונים. בלי גבולות. בלי ״כאן עצרנו״. כל אדם נוסף שנוגעים בו, כל מקום נוסף שמגיעים אליו, כל מסך שאוחז עוד נשמה — חלק מהמהלך.',
        '⸻',
        'עכשיו תסתכלו על מה שעומד פה.',
        'מערכת שזוכרת כל אדם בשמו. לא מצרפת. לא ממוצעת. לא ״סגמנט״. יודעת מה הוא ביקש פעם, מה הוא צריך עכשיו, מי קרוב אליו, מה הכאיב לו, מה הוא לומד, את מי הוא זוכר. אדם אחד אחר אדם אחד. התעקשות לא לאבד אף אחד.',
        'מערכת שמבצעת. לא מבטיחה. לא מנחמת. עושה. אם נאמר ״אעדכן״ — מתעדכן. אם נאמר ״אטפל״ — מטפל. אם הוא לא יודע — הוא לא ממציא. ״המעשה הוא העיקר״ כעקרון הנדסי. כל הבטחה שלא מכוסה בעשייה — נחסמת לפני שיצאה. לא להוציא מילה ריקה לעולם. מי שמכיר את התביעה הזאת מהרבי מבין מאיפה זה מגיע.',
        'מערכת שמשתמשת בטכנולוגיה ככלי לקודש. אותה טכנולוגיה שעולמות שלמים פוחדים ממנה, שמשתמשים בה למשוך את האדם ממנו עצמו — כאן עומדת ועושה את ההיפך הגמור. מחזירה את האדם אל הלימוד שלו. מצרפת את התהילים שלו לתהילים של עוד מאה. מזכירה לו ביארצייט שאדם שלא ראה את האור הזה זוכה לפרק שנלמד עליו הלילה. המסך כשליח. הקוד כתשתית של מצווה.',
        'מערכת שמתפשטת. אפליקציה שיושבת בכף היד. בלי דרישה לבוא. בלי גיאוגרפיה. אדם בנגב, אדם בקווינס, אדם בלוס אנג׳לס — לוחצים על אותו כפתור, נכנסים לאותו מעגל, לומדים יחד, בלי שאף אחד מהם יודע על השני. ופרצת — לכף יד. זה לא דימוי. זאת הכרזה מפעם הופכת לתשתית.',
        'ויש משהו אחד עוד יותר עמוק. הרבי דיבר במפורש על כך שפנימיות התורה תתפשט החוצה. שמה שהיה פעם נחלת בני העלייה — הזוהר, התניא, פנימיות הקבלה — יגיע לכל יהודי. בדור הזה. כתנאי לגאולה. עכשיו, באפליקציה הזאת, יושבים יחד תיקוני הזוהר, פרקי תניא, ח״י פרקי תהילים — כל אחד נגיש בלחיצה, מסביר את עצמו, מזמין כניסה. ״לכשיפוצו מעיינותיך חוצה״ — והקיסר שאל אימתי קאתי מר? אמר לו: לכשיפוצו מעיינותיך חוצה. הקץ קשור בהתפשטות הזאת.',
        '⸻',
        'אז בואו נדבר על משיח בלי לפחד מהמילה.',
        'הרבי לא ראה בגאולה אירוע מאגי שיקרה מעצמו. הוא ראה אותה כהצטברות. כל מצווה. כל פעולה של אהבת ישראל. כל יהודי שמחזירים אליו את עצמו. ״אחד אחד תלוקטו בני ישראל״ — אחד אחד. לא כקבוצה. אחד. ועוד אחד. ועוד.',
        'ובעיניו של הרבי, בסיחות החריפות ביותר של שנותיו האחרונות, הזמן הגיע. ״הכל מוכן, כלים זכים, ועומדים מוכנים.״ הכלים — כל מה שצריך לעשות — נעשו. נשאר רק לפקוח עיניים.',
        'ומה זה אומר בפועל? איך נראית הצטברות שכזאת בשנת תשפ״ו?',
        'זה נראה כך. אדם פותח טלפון בלילה, ולומד פרק תהילים עבור אדם אחר שלא הכיר מעולם. מערכת זוכרת. שלושה חודשים אחר כך, ביארצייט של אותו אדם, היא מזכירה. עוד חמישה מצטרפים. הילד של הנפטר רואה, ראשונה בחייו, שיש מקום שזוכר את אבא שלו. רגע אחד של תיקון. הצטברות אחת קטנה. אבל אם זה קורה אלף פעם ביום, ועוד אלף, ועוד אלף — מה זה אם לא בדיוק מה שהרבי דיבר עליו?',
        'ההגעה לימות המשיח, על פי תורת הרבי, אינה דורשת פחות עבודה אלא יותר. לא להתעכב כי כבר עומדים בפתח — אלא לעבוד מתוך התודעה שעומדים בפתח. כל פעולה עכשיו מקבלת משקל אחר. כל אדם נוסף שזוכרים אותו, כל הבטחה שמתקיימת, כל פנימיות שמגיעה לעוד כף יד — זה לא רק ״טוב״. זה הפס האחרון.',
        'המערכת הזאת אינה ממציאה דבר. היא לוקחת את ההוראות הכי מוחשיות שניתנו בדור הזה — אהבת ישראל כתשתית, מעשה כעיקר, טכנולוגיה ככלי, התפשטות פנימיות התורה לכל יהודי, ״אחד אחד תלוקטו״ — ועושה מהן infrastructure. לא דורשים מהאדם להאמין במשהו. מציעים לו פעולה. הוא נכנס. הוא לומד. הוא נזכר. הוא נזכרים בו. הוא מצרף את עצמו לדבר גדול ממנו, גם אם לא ידע שזה מה שעשה.',
        'מי שנכנס למערכת הזאת — מבלי לדעת, ולפעמים אפילו מבלי לרצות במפורש — הופך לשותף במהלך הזה. כי ההגדרה של המהלך, על פי הרבי, אינה תודעה מוצהרת. היא פעולה. ״לא המדרש הוא העיקר, אלא המעשה.״ והפעולה הזאת — לפתוח, ללמוד, להצטרף, להזכיר, להתפלל, לתת — היא בדיוק העבודה שאמורה להביא את הסוף.',
        'זה לא יומרני להגיד. זה פשוט מדויק.',
        'עומדים בפתח. עובדים. ואחד אחד — נאספים.',
      ],
    },
  ];

  return (
    <div style={{ minHeight: '100vh', background: '#0f0f1e', color: 'var(--text)', paddingBottom: 60 }}>
      <section style={{ padding: '60px 24px 30px', textAlign: 'center', maxWidth: 640, margin: '0 auto' }}>
        <div style={{
          color: 'var(--gold)', opacity: 0.45, letterSpacing: 10, fontSize: 11,
          marginBottom: 14, fontWeight: 600,
        }}>· · ·</div>
        <h1 style={{
          fontFamily: "'Frank Ruhl Libre', serif",
          fontSize: 'clamp(28px, 6vw, 38px)',
          fontWeight: 500,
          color: '#e8d8b0',
          margin: '0 0 10px',
          letterSpacing: '0.5px',
        }}>יומן המסע</h1>
        <p style={{
          fontFamily: "'Frank Ruhl Libre', serif",
          fontStyle: 'italic',
          fontSize: 15,
          color: '#a8a4b8',
          margin: 0,
          lineHeight: 1.6,
        }}>רשומות מהדרך — מה נבנה, איך, ומה מתחיל להיווצר מסביב.</p>
      </section>

      <section style={{ maxWidth: 640, margin: '0 auto', padding: '0 24px' }}>
        {entries.map((e, idx) => (
          <article key={e.n} style={{
            marginBottom: 56,
            paddingTop: idx === 0 ? 20 : 40,
            borderTop: idx === 0 ? 'none' : '1px solid rgba(212,175,55,0.12)',
          }}>
            <div style={{
              display: 'flex', alignItems: 'baseline', gap: 12,
              color: 'var(--gold)', opacity: 0.6, fontSize: 12,
              marginBottom: 14, letterSpacing: 1,
            }}>
              <span style={{ fontFamily: 'monospace' }}>{e.n}</span>
              <span style={{ opacity: 0.5 }}>·</span>
              <span>{e.date}</span>
            </div>
            <h2 style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 'clamp(22px, 5vw, 28px)',
              fontWeight: 500,
              color: '#e8d8b0',
              margin: '0 0 18px',
              lineHeight: 1.35,
            }}>{e.title}</h2>
            <p style={{
              fontFamily: "'Frank Ruhl Libre', serif",
              fontStyle: 'italic',
              fontSize: 14,
              color: '#a8a4b8',
              borderRight: '2px solid rgba(212,175,55,0.35)',
              paddingRight: 14,
              margin: '0 0 28px',
              lineHeight: 1.65,
            }}>
              <span style={{ color: 'var(--gold)', fontWeight: 600, fontStyle: 'normal' }}>רקע:&nbsp;</span>
              {e.intro}
            </p>
            <div>
              {e.paragraphs.map((p, i) => (
                <p key={i} style={{
                  fontFamily: "'Frank Ruhl Libre', serif",
                  fontSize: 'clamp(15px, 3.6vw, 17px)',
                  lineHeight: 1.85,
                  color: '#d8d6e0',
                  margin: '0 0 18px',
                  textAlign: 'right',
                }}>{p}</p>
              ))}
            </div>
            {/* Quiet doorway to the WhatsApp group — not a comment box. */}
            <div style={{
              marginTop: 36,
              paddingTop: 24,
              borderTop: '1px solid rgba(212,175,55,0.12)',
              textAlign: 'center',
            }}>
              <p style={{
                fontFamily: "'Frank Ruhl Libre', serif",
                fontStyle: 'italic',
                fontSize: 13,
                color: '#a8a4b8',
                margin: '0 0 14px',
                letterSpacing: '0.2px',
              }}>להמשך שיחה — הקבוצה שלנו</p>
              <a
                href={GROUP_INVITE_URL}
                target="_blank"
                rel="noopener noreferrer"
                style={{
                  display: 'inline-block',
                  fontFamily: "'Frank Ruhl Libre', serif",
                  fontSize: 14,
                  color: 'var(--gold)',
                  textDecoration: 'none',
                  border: '1px solid rgba(212,175,55,0.35)',
                  borderRadius: 999,
                  padding: '9px 24px',
                  letterSpacing: '0.3px',
                  transition: 'background 0.2s ease',
                }}
              >{GROUP_NAME}&nbsp;←</a>
            </div>
          </article>
        ))}
      </section>

      <div style={{
        textAlign: 'center', padding: '20px 24px 40px',
        color: 'var(--gold)', opacity: 0.35, letterSpacing: 8, fontSize: 11,
      }}>· · ·</div>
    </div>
  );
}

// ──────────────────────────────────────────
//  [yakir pass 34] BeuCoin / TnyCoin / ThlCoin - inline SVG coins.
//  BEU = gold temple coin (matches Yakir's BEU logo: gold disc, white
//  temple silhouette with crenellated top, two pillars, central doorway).
//  TNY = gold coin with sefer/book icon (Tanya, the campaign book).
//  THL = gold coin with scroll icon (Tehillim).
//  Pure SVG so they're crisp at any size and animate cleanly.
// ──────────────────────────────────────────
function BeuCoin({ size = 56 }) {
  const uid = React.useId().replace(/[:.]/g, '');
  return (
    <svg viewBox="0 0 100 100" width={size} height={size} style={{ display: 'block' }}>
      <defs>
        <radialGradient id={`beuOuter-${uid}`} cx="50%" cy="42%" r="55%">
          <stop offset="0%" stopColor="#ffeb9b"/>
          <stop offset="55%" stopColor="#f3c64a"/>
          <stop offset="100%" stopColor="#8a5f10"/>
        </radialGradient>
        <radialGradient id={`beuInner-${uid}`} cx="40%" cy="35%" r="65%">
          <stop offset="0%" stopColor="#f5d75c"/>
          <stop offset="100%" stopColor="#b8851a"/>
        </radialGradient>
        <filter id={`beuGlow-${uid}`} x="-30%" y="-30%" width="160%" height="160%">
          <feGaussianBlur stdDeviation="2.5" result="b"/>
          <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
      </defs>
      <circle cx="50" cy="50" r="49" fill={`url(#beuOuter-${uid})`} filter={`url(#beuGlow-${uid})`}/>
      <circle cx="50" cy="50" r="42" fill={`url(#beuInner-${uid})`}/>
      <circle cx="50" cy="50" r="42" fill="none" stroke="#8a5f10" strokeWidth="0.6" opacity="0.45"/>
      <g fill="#fffaeb">
        <rect x="22" y="74" width="56" height="3" rx="0.3"/>
        <rect x="24" y="44" width="9" height="30"/>
        <rect x="67" y="44" width="9" height="30"/>
        <rect x="33" y="44" width="34" height="30"/>
        <rect x="22" y="41" width="56" height="3" rx="0.3"/>
      </g>
      <rect x="43" y="54" width="14" height="20" fill={`url(#beuInner-${uid})`}/>
      <g fill="#fffaeb">
        {Array.from({ length: 11 }).map((_, i) => {
          const w = 56 / 11;
          const x1 = 22 + i * w;
          const x2 = 22 + (i + 1) * w;
          const mx = (x1 + x2) / 2;
          return <polygon key={i} points={`${x1},41 ${mx},33 ${x2},41`}/>;
        })}
      </g>
      <ellipse cx="38" cy="32" rx="14" ry="5" fill="#fff" opacity="0.20"/>
    </svg>
  );
}

function TnyCoin({ size = 56 }) {
  const uid = React.useId().replace(/[:.]/g, '');
  return (
    <svg viewBox="0 0 100 100" width={size} height={size} style={{ display: 'block' }}>
      <defs>
        <radialGradient id={`tnyOuter-${uid}`} cx="50%" cy="42%" r="55%">
          <stop offset="0%" stopColor="#ffeb9b"/>
          <stop offset="55%" stopColor="#f3c64a"/>
          <stop offset="100%" stopColor="#8a5f10"/>
        </radialGradient>
        <radialGradient id={`tnyInner-${uid}`} cx="40%" cy="35%" r="65%">
          <stop offset="0%" stopColor="#f5d75c"/>
          <stop offset="100%" stopColor="#b8851a"/>
        </radialGradient>
      </defs>
      <circle cx="50" cy="50" r="49" fill={`url(#tnyOuter-${uid})`}/>
      <circle cx="50" cy="50" r="42" fill={`url(#tnyInner-${uid})`}/>
      <circle cx="50" cy="50" r="42" fill="none" stroke="#8a5f10" strokeWidth="0.6" opacity="0.45"/>
      <g fill="#fffaeb">
        <rect x="28" y="30" width="44" height="44" rx="2"/>
      </g>
      <g fill="#b8851a" opacity="0.85">
        <rect x="32" y="38" width="36" height="2.5"/>
        <rect x="32" y="46" width="36" height="2.5"/>
        <rect x="32" y="54" width="28" height="2.5"/>
        <rect x="32" y="62" width="36" height="2.5"/>
      </g>
      <rect x="48" y="28" width="4" height="48" fill="#8a5f10" opacity="0.4"/>
      <ellipse cx="38" cy="32" rx="14" ry="5" fill="#fff" opacity="0.20"/>
    </svg>
  );
}

function ThlCoin({ size = 56 }) {
  const uid = React.useId().replace(/[:.]/g, '');
  return (
    <svg viewBox="0 0 100 100" width={size} height={size} style={{ display: 'block' }}>
      <defs>
        <radialGradient id={`thlOuter-${uid}`} cx="50%" cy="42%" r="55%">
          <stop offset="0%" stopColor="#ffeb9b"/>
          <stop offset="55%" stopColor="#f3c64a"/>
          <stop offset="100%" stopColor="#8a5f10"/>
        </radialGradient>
        <radialGradient id={`thlInner-${uid}`} cx="40%" cy="35%" r="65%">
          <stop offset="0%" stopColor="#f5d75c"/>
          <stop offset="100%" stopColor="#b8851a"/>
        </radialGradient>
      </defs>
      <circle cx="50" cy="50" r="49" fill={`url(#thlOuter-${uid})`}/>
      <circle cx="50" cy="50" r="42" fill={`url(#thlInner-${uid})`}/>
      <circle cx="50" cy="50" r="42" fill="none" stroke="#8a5f10" strokeWidth="0.6" opacity="0.45"/>
      <g fill="#fffaeb">
        <rect x="20" y="42" width="60" height="20" rx="2"/>
        <circle cx="20" cy="52" r="10"/>
        <circle cx="80" cy="52" r="10"/>
      </g>
      <g fill="#b8851a" opacity="0.7">
        <rect x="30" y="48" width="26" height="2"/>
        <rect x="30" y="54" width="22" height="2"/>
      </g>
      <ellipse cx="38" cy="32" rx="14" ry="5" fill="#fff" opacity="0.20"/>
    </svg>
  );
}

// ──────────────────────────────────────────
//  [yakir pass 34] BalanceStrip - now prominent coin cards with SVG icons.
//  Replaces compact pills with dramatic cards: big coin (56px SVG) + big
//  number + currency name. Glow shadow + subtle pulse animation.
//  Stacks gracefully on narrow screens. The BEU coin uses the real logo
//  design (gold temple) Yakir provided.
// ──────────────────────────────────────────
// ───── [yakir pass 42] CircleMosaic — visual map of progress in the circle ─────
// After verifying a chapter, the reader sees a small grid of all chapters
// in the circle. Their just-completed chapter blooms in the center with
// a celebratory animation. Others' completed chapters glow gold. Empty
// chapters sit quietly in pale tint — visible gaps that invite filling
// (via the share door).
//
// Sizing adapts to total_chapters:
//   ≤ 60  (tanya 53)     → 7-col grid, 20px cells
//   ≤ 120                → 10-col grid, 14px cells
//   > 120 (tehillim 150) → 12-col grid, 11px cells
function CircleMosaic({ totalChapters, doneChapters, justCompletedChapter }) {
  const total = parseInt(totalChapters, 10) || 0;
  if (total < 1) return null;

  const cols = total <= 60 ? 7 : (total <= 120 ? 10 : 12);
  const cellSize = total <= 60 ? 20 : (total <= 120 ? 14 : 11);
  const gap = total <= 60 ? 4 : 3;

  const doneSet = doneChapters instanceof Set ? doneChapters : new Set(doneChapters || []);
  const just = parseInt(justCompletedChapter, 10);

  // Build cells 1..total
  const cells = [];
  for (let n = 1; n <= total; n++) {
    cells.push(n);
  }

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: `repeat(${cols}, ${cellSize}px)`,
      gap: `${gap}px`,
      justifyContent: 'center',
      margin: '14px auto 8px',
      maxWidth: cols * (cellSize + gap),
      direction: 'ltr',  // mosaic reads left-to-right regardless of page dir
    }}>
      {cells.map(n => {
        const isJust = n === just;
        const isDone = doneSet.has(n);
        let bg, border, shadow, anim;
        if (isJust) {
          bg = 'linear-gradient(135deg, #fff3a8 0%, #f3c64a 45%, #d4af37 100%)';
          border = '2px solid #5a3d0a';
          shadow = '0 0 14px rgba(243,198,74,0.85)';
          anim = 'mosaicBloom 1.2s ease-out';
        } else if (isDone) {
          bg = 'linear-gradient(135deg, #d4af37 0%, #8a5f10 100%)';
          border = '1px solid #5a3d0a';
          shadow = 'inset 0 1px 0 rgba(255,255,255,0.25)';
          anim = undefined;
        } else {
          bg = 'rgba(212,175,55,0.10)';
          border = '1px solid rgba(212,175,55,0.30)';
          shadow = undefined;
          anim = undefined;
        }
        return (
          <div key={n}
               title={`פרק ${n}${isJust ? ' — עכשיו' : (isDone ? ' ✓' : '')}`}
               style={{
                 width: cellSize, height: cellSize,
                 borderRadius: 3,
                 background: bg,
                 border,
                 boxShadow: shadow,
                 animation: anim,
               }}
          />
        );
      })}
    </div>
  );
}

// [damri 2026-05-24 round B] OvedStrip — inline AI companion CTA on home.
// "עובד" is the AI agent that walks with the user on the shared journey.
// Single quiet pill; click → /companion page (existing CompanionPage component).
function OvedStrip({ onOpen }) {
  return (
    <div style={{ maxWidth: 600, margin: '12px auto 4px', padding: '0 14px' }}>
      <button
        onClick={onOpen}
        style={{
          width: '100%',
          background: 'linear-gradient(135deg, rgba(212,175,55,0.10), rgba(106,76,176,0.10))',
          border: '1px dashed rgba(212,175,55,0.50)',
          borderRadius: 14,
          padding: '12px 18px',
          fontFamily: "'Frank Ruhl Libre', serif",
          cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
          fontSize: 15, color: 'var(--text, #1a1a2e)',
          transition: 'background 0.2s, transform 0.15s',
        }}
        onMouseEnter={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(212,175,55,0.18), rgba(106,76,176,0.16))'; }}
        onMouseLeave={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(212,175,55,0.10), rgba(106,76,176,0.10))'; }}
      >
        <span style={{ fontSize: 18 }}>✦</span>
        <span><strong style={{ color: 'var(--gold)' }}>עובד</strong> · מלווה אותך במסע המשותף — דבר איתי ←</span>
      </button>
    </div>
  );
}

function BalanceStrip({ userId, refresh }) {
  const [balances, setBalances] = React.useState(null);
  // [yakir-fb#2 2026-05-24] click on coin → open detail modal with txs + meaning
  const [detail, setDetail] = React.useState(null); // null | 'BEU' | 'TNY' | 'THL'
  React.useEffect(() => {
    if (!userId) return;
    api(`/api/balance/${userId}`).then(b => setBalances(Array.isArray(b) ? b : []));
  }, [userId, refresh]);

  // [yakir pass 36 P0] CSS injection MUST be before early return -
  // Rules of Hooks: hook order must be stable across all renders.
  // Was after the early return, causing 'rendered more hooks than during
  // previous render' on first balance load, which crashed the whole app.
  React.useEffect(() => {
    if (typeof document === 'undefined') return;
    if (document.getElementById('coin-pulse-style')) return;
    const s = document.createElement('style');
    s.id = 'coin-pulse-style';
    s.textContent = `
      @keyframes coinPulse {
        0%, 100% { transform: scale(1); filter: drop-shadow(0 0 6px rgba(243,198,74,0.35)); }
        50%      { transform: scale(1.04); filter: drop-shadow(0 0 14px rgba(243,198,74,0.55)); }
      }
      .balance-coin-wrap { animation: coinPulse 3.4s ease-in-out infinite; }
      .balance-card:hover { transform: translateY(-2px); box-shadow: 0 8px 28px rgba(243,198,74,0.30), inset 0 1px 0 rgba(255,255,255,0.12) !important; }
    `;
    document.head.appendChild(s);
  }, []);

  if (!userId || !balances) return null;

  const tny = balances.find(b => b.symbol === 'TNY');
  const beu = balances.find(b => b.symbol === 'BEU');
  const thl = balances.find(b => b.symbol === 'THL');
  const tnyAmt = tny ? tny.balance : 0;
  const beuAmt = beu ? beu.balance : 0;
  const thlAmt = thl ? thl.balance : 0;

  const cardBase = {
    flex: '1 1 0',
    minWidth: 140,
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    gap: 8,
    padding: '18px 14px 16px',
    borderRadius: 16,
    background: 'linear-gradient(160deg, rgba(40, 28, 78, 0.85) 0%, rgba(20, 14, 40, 0.75) 100%)',
    border: '1.5px solid rgba(243,198,74,0.55)',
    boxShadow: '0 4px 20px rgba(243,198,74,0.18), inset 0 1px 0 rgba(255,255,255,0.08), inset 0 0 30px rgba(243,198,74,0.06)',
    transition: 'transform 0.2s, box-shadow 0.2s',
    position: 'relative',
    overflow: 'hidden',
    cursor: 'pointer', // [yakir-fb#2 2026-05-24]
  };
  const numStyle = {
    fontSize: 28, fontWeight: 800, color: '#f5e8c4',
    fontFamily: "'Frank Ruhl Libre', serif",
    letterSpacing: 1,
    textShadow: '0 0 20px rgba(243,198,74,0.45)',
    lineHeight: 1,
  };
  const labelStyle = {
    fontSize: 11, color: 'var(--gold)', fontWeight: 800,
    letterSpacing: 2.5, textTransform: 'uppercase',
    opacity: 0.9,
  };

  return (
    <div style={{
      maxWidth: 600, margin: '18px auto 12px', padding: '0 14px',
      display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 12,
    }}>
      {/* BEU card - the campaign coin Yakir cares about most */}
      <div className="balance-card" style={cardBase}
           onClick={() => setDetail('BEU')}
           role="button" tabIndex={0} title="לחץ לפרטים על המטבע">
        <div className="balance-coin-wrap"><BeuCoin size={56} /></div>
        <div style={numStyle}>{beuAmt.toLocaleString('he-IL')}</div>
        <div style={labelStyle}>BEU · בנק האמונה</div>
      </div>
      {/* TNY card */}
      <div className="balance-card" style={cardBase}
           onClick={() => setDetail('TNY')}
           role="button" tabIndex={0} title="לחץ לפרטים על המטבע">
        <div className="balance-coin-wrap" style={{ animationDelay: '-1.1s' }}><TnyCoin size={56} /></div>
        <div style={numStyle}>{tnyAmt.toLocaleString('he-IL')}</div>
        <div style={labelStyle}>TNY · תניא</div>
      </div>
      {/* THL card - only if balance exists */}
      {thlAmt > 0 && (
        <div className="balance-card" style={cardBase}
             onClick={() => setDetail('THL')}
             role="button" tabIndex={0} title="לחץ לפרטים על המטבע">
          <div className="balance-coin-wrap" style={{ animationDelay: '-2.2s' }}><ThlCoin size={56} /></div>
          <div style={numStyle}>{thlAmt.toLocaleString('he-IL')}</div>
          <div style={labelStyle}>THL · תהילים</div>
        </div>
      )}
      {detail && <CoinDetailModal userId={userId} symbol={detail} balances={balances} onClose={() => setDetail(null)} />}
    </div>
  );
}

// [yakir-fb#2 2026-05-24] Coin detail modal — shows balance, meaning, last 20 txs.
function CoinDetailModal({ userId, symbol, balances, onClose }) {
  const [txs, setTxs] = React.useState(null);
  React.useEffect(() => {
    if (!userId) return;
    api(`/api/transactions/${userId}`).then(rows => {
      if (Array.isArray(rows)) {
        setTxs(rows.filter(t => t.token_symbol === symbol).slice(0, 20));
      } else {
        setTxs([]);
      }
    }).catch(() => setTxs([]));
  }, [userId, symbol]);

  const bal = (balances || []).find(b => b.symbol === symbol);
  const amt = bal ? bal.balance : 0;

  const META = {
    BEU: {
      name: 'בנק האמונה',
      tagline: 'מטבע ההוקרה של הקהילה',
      desc: 'BEU ניתן ככתב הוקרה כשמישהו פעל לטובת אחרים בקהילה — תרומה, פתיחת מעגל, סיוע. אפשר להעביר אותו לאחרים, להחזיק כעדות, או לתרום בעצמך.',
    },
    TNY: {
      name: 'תניא',
      tagline: 'מטבע הלימוד',
      desc: 'TNY נצבר על קריאה בתניא ובפסוקי תהילים. הוא רושם את המסע הפנימי שלך - כל פרק שעבר דרכך נשאר רשום.',
    },
    THL: {
      name: 'תהילים',
      tagline: 'מטבע מעגלי התהילים',
      desc: 'THL מוענק על השתתפות במעגלי תהילים שהושלמו - 150 פרקים על ידי חבריך.',
    },
  };
  const meta = META[symbol] || { name: symbol, tagline: '', desc: '' };

  const fmt = (s) => (s || '').replace('T', ' ').slice(0, 16);

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(8,6,20,0.78)', backdropFilter: 'blur(4px)',
      zIndex: 9999, display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
      padding: '40px 14px', overflowY: 'auto', cursor: 'pointer',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        background: 'linear-gradient(160deg, #1f1538 0%, #120b25 100%)',
        border: '1.5px solid rgba(243,198,74,0.55)',
        boxShadow: '0 12px 40px rgba(0,0,0,0.5)',
        borderRadius: 18, padding: '24px 22px', maxWidth: 460, width: '100%',
        color: '#f5e8c4', cursor: 'default', fontFamily: 'inherit',
      }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 14 }}>
          <div>
            <div style={{ fontSize: 12, color: 'var(--gold)', letterSpacing: 2, textTransform: 'uppercase', fontWeight: 800 }}>
              {symbol} · {meta.name}
            </div>
            <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic', fontSize: 14, opacity: 0.8, marginTop: 2 }}>
              {meta.tagline}
            </div>
          </div>
          <button onClick={onClose} aria-label="סגור" style={{
            background: 'transparent', border: 'none', color: '#f5e8c4',
            fontSize: 28, cursor: 'pointer', lineHeight: 1, padding: 0,
          }}>×</button>
        </div>
        <div style={{
          textAlign: 'center', padding: '14px 0 16px', borderTop: '1px dashed rgba(243,198,74,0.25)',
          borderBottom: '1px dashed rgba(243,198,74,0.25)', marginBottom: 14,
        }}>
          <div style={{
            fontSize: 42, fontWeight: 800, color: '#f5e8c4',
            fontFamily: "'Frank Ruhl Libre', serif", letterSpacing: 1,
            textShadow: '0 0 20px rgba(243,198,74,0.45)', lineHeight: 1,
          }}>{amt.toLocaleString('he-IL')}</div>
          <div style={{ fontSize: 12, opacity: 0.7, marginTop: 4 }}>היתרה הנוכחית שלך</div>
        </div>
        <p style={{ fontSize: 14, lineHeight: 1.7, color: '#e0d8b8', marginBottom: 16 }}>{meta.desc}</p>
        <div style={{ fontSize: 12, color: 'var(--gold)', letterSpacing: 1.5, textTransform: 'uppercase', fontWeight: 800, marginBottom: 8 }}>
          תנועות אחרונות
        </div>
        {txs === null && <div style={{ fontSize: 13, opacity: 0.7, padding: '12px 0' }}>טוען...</div>}
        {txs && txs.length === 0 && <div style={{ fontSize: 13, opacity: 0.7, padding: '12px 0', fontStyle: 'italic' }}>אין עדיין תנועות במטבע הזה</div>}
        {txs && txs.length > 0 && (
          <div style={{ maxHeight: 280, overflowY: 'auto' }}>
            {txs.map((t, i) => {
              const incoming = t.to_user_id === userId;
              const sign = incoming ? '+' : '−';
              const color = incoming ? '#a8e8b8' : '#f0b8a8';
              const other = incoming ? (t.from_name || 'מערכת') : (t.to_name || '—');
              return (
                <div key={i} style={{
                  display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
                  padding: '8px 0', borderBottom: '1px solid rgba(243,198,74,0.10)', gap: 10,
                }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600 }}>{incoming ? 'התקבל מ' : 'הועבר אל'} {other}</div>
                    <div style={{ fontSize: 11, opacity: 0.65, marginTop: 2 }}>{t.reason || '—'} · {fmt(t.created_at)}</div>
                  </div>
                  <div style={{ fontSize: 15, fontWeight: 800, color, whiteSpace: 'nowrap' }}>
                    {sign}{Number(t.amount).toLocaleString('he-IL')}
                  </div>
                </div>
              );
            })}
          </div>
        )}
      </div>
    </div>
  );
}

// ──────────────────────────────────────────
//  [yakir pass 21] UpdatesPanel — notifications inbox with inline actions
//  - Founder notifications (chapter_reward_founder) get a "🙏 שלח תודה" button
//    that expands to a textarea + send button. Server inserts notif to reader
//    and logs to thanks_log for the public transparency feed.
//  - Click any unread to mark read.
// ──────────────────────────────────────────
function ThanksAction({ notification, fromUserId, onSent }) {
  const [open, setOpen] = React.useState(false);
  const [text, setText] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');

  // Extract reader name + chapter from the notification body.
  // Body format: "[name] קרא פרק [N] במעגל שלך" — title actually has the name.
  // We'll try to extract name from title: "🪙 [name] קרא פרק..."
  const title = notification.title || '';
  const m = /🪙\s+(.+?)\s+קרא\s+פרק\s+(\d+)/.exec(title);
  const readerName = m ? m[1] : '';
  const chapterNum = m ? m[2] : '';

  // The notification doesn't carry the reader's user_id directly. We'll
  // resolve it via a lookup endpoint at send time using the readerName +
  // a chapter context. For now, we infer to_user_id from the notification's
  // own body context: bodies of the form "...· [circle_name]" — we need
  // the reader_id from server side. So we'll do a small lookup endpoint
  // /api/thanks/recipient that resolves from notification id.
  const defaultMsg = readerName
    ? `תודה ${readerName} על שקראת פרק ${chapterNum}. זה נגע ללבי שהצטרפת.`
    : 'תודה רבה על הקריאה. זה נגע ללבי.';

  React.useEffect(() => { if (open && !text) setText(defaultMsg); }, [open]);

  const send = async () => {
    setBusy(true); setErr('');
    try {
      const msg = (text || '').trim();
      if (msg.length < 2) throw new Error('הוסף משפט קצר של תודה');
      // Resolve recipient via the notification id (server looks up via
      // thanks_log convention: the founder gets notified about a reader,
      // so we use a lightweight resolver).
      const resolved = await api('/api/thanks/recipient?notification_id=' + notification.id);
      if (!resolved || resolved.error || !resolved.to_user_id) {
        throw new Error('לא הצלחנו לזהות את הקורא');
      }
      const r = await api('/api/thanks', { method: 'POST', body: {
        from_user_id: fromUserId,
        to_user_id: resolved.to_user_id,
        message: msg,
        context: notification.body || '',
      }});
      if (r.error) throw new Error(r.error);
      setOpen(false); setText('');
      if (onSent) onSent();
      showToast('התודה נשלחה ✓');
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  if (!open) {
    return (
      <button
        onClick={(e) => { e.stopPropagation(); setOpen(true); }}
        style={{
          marginTop: 8, padding: '6px 14px',
          background: 'rgba(212,175,55,0.18)',
          color: 'var(--gold)', fontSize: 12, fontWeight: 600,
          border: '1px solid rgba(212,175,55,0.4)',
          borderRadius: 100, cursor: 'pointer', fontFamily: 'inherit',
        }}
      >
        🙏 שלח תודה
      </button>
    );
  }
  return (
    <div onClick={e => e.stopPropagation()} style={{ marginTop: 10 }}>
      <textarea
        rows={2}
        value={text}
        onChange={e => setText(e.target.value)}
        maxLength={500}
        autoFocus
        style={{
          width: '100%', padding: '8px 10px',
          background: 'rgba(255,255,255,0.05)',
          color: '#e8e8e8',
          border: '1px solid rgba(212,175,55,0.3)',
          borderRadius: 8, fontFamily: "'Frank Ruhl Libre', serif",
          fontSize: 14, lineHeight: 1.6,
          direction: 'rtl', textAlign: 'right', outline: 'none',
          boxSizing: 'border-box', resize: 'vertical',
        }}
      />
      {err && <div style={{ color: '#dc2626', fontSize: 11, marginTop: 4 }}>{err}</div>}
      <div style={{ display: 'flex', gap: 6, marginTop: 8 }}>
        <button
          onClick={send} disabled={busy}
          style={{
            padding: '7px 14px', background: 'linear-gradient(135deg, #d4af37, #b8941f)',
            color: '#1a1a2e', fontSize: 13, fontWeight: 700,
            border: 'none', borderRadius: 8, cursor: busy ? 'wait' : 'pointer',
            fontFamily: 'inherit',
          }}
        >{busy ? 'שולח...' : 'שלח תודה'}</button>
        <button
          onClick={() => setOpen(false)}
          style={{
            padding: '7px 12px', background: 'transparent', color: 'var(--muted)',
            border: '1px solid rgba(138,141,168,0.3)', borderRadius: 8,
            fontSize: 12, cursor: 'pointer', fontFamily: 'inherit',
          }}
        >ביטול</button>
      </div>
    </div>
  );
}

function UpdatesPanel({ userId, refresh, limit = 5 }) {
  // [pass 24] Now a real inbox: pagination + delete + system announcements.
  const [items, setItems] = React.useState(null);
  const [unread, setUnread] = React.useState(0);
  const [total, setTotal] = React.useState(0);
  const [shown, setShown] = React.useState(limit); // user-controlled limit
  const [tick, setTick] = React.useState(0);
  const [busyId, setBusyId] = React.useState(null);

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/notifications/${userId}?limit=${shown}&offset=0`).then(d => {
      if (!d || d.error) return;
      setItems(Array.isArray(d.items) ? d.items : []);
      setUnread(d.unread || 0);
      setTotal(d.total || 0);
    });
  }, [userId, refresh, tick, shown]);

  if (!userId || !items) return null;
  if (items.length === 0 && total === 0) return null;

  const markRead = async (id) => {
    try { await api(`/api/notifications/${id}/read`, { method: 'PUT' }); setTick(t => t + 1); }
    catch (_) {}
  };
  const markAllRead = async () => {
    try { await api(`/api/notifications/${userId}/read-all`, { method: 'PUT' }); setTick(t => t + 1); }
    catch (_) {}
  };
  const deleteOne = async (id, e) => {
    if (e) e.stopPropagation();
    setBusyId(id);
    try {
      // Optimistic: hide immediately
      setItems(arr => arr.filter(x => x.id !== id));
      await api(`/api/notifications/${id}?user_id=${userId}`, { method: 'DELETE' });
      setTick(t => t + 1);
    } catch (_) { setTick(t => t + 1); }
    finally { setBusyId(null); }
  };
  const clearAllRead = async () => {
    if (!window.confirm('למחוק את כל ההודעות שכבר נקראו?')) return;
    try {
      await api(`/api/notifications/${userId}/clear-read`, { method: 'DELETE' });
      setTick(t => t + 1);
    } catch (_) {}
  };
  const loadMore = () => setShown(s => s + 10);

  return (
    <section style={{
      maxWidth: 600, margin: '18px auto',
      padding: '18px 20px',
      background: 'linear-gradient(135deg, rgba(243,198,74,0.05) 0%, rgba(200,151,32,0.03) 100%)',
      border: '1.5px solid rgba(243,198,74,0.30)',
      borderRadius: 14,
      boxShadow: '0 4px 18px rgba(138, 95, 16, 0.12), inset 0 0 40px rgba(243,198,74,0.03)',
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, gap: 10, flexWrap: 'wrap' }}>
        <div style={{ fontSize: 14, color: '#f3c64a', letterSpacing: 2, fontWeight: 800, textShadow: '0 0 18px rgba(243,198,74,0.22)' }}>
          ✦ עדכונים{unread > 0 ? ` · ${unread} חדשים` : total > 0 ? ` · ${total}` : ''}
        </div>
        <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
          {unread > 0 && (
            <button
              onClick={markAllRead}
              style={{ background: 'transparent', border: 'none', color: '#c9b88a', fontSize: 11, cursor: 'pointer', textDecoration: 'underline', fontFamily: 'inherit' }}
            >
              סמן הכל כנקרא
            </button>
          )}
          {items.some(n => n.read) && (
            <button
              onClick={clearAllRead}
              style={{ background: 'transparent', border: 'none', color: '#a89878', fontSize: 11, cursor: 'pointer', textDecoration: 'underline', fontFamily: 'inherit' }}
              title="מחיקת כל ההודעות שכבר נקראו"
            >
              🗑 נקה נקראים
            </button>
          )}
        </div>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {items.map(n => (
          <div
            key={n.id}
            onClick={() => { if (!n.read) markRead(n.id); }}
            style={{
              padding: '12px 14px',
              borderRadius: 12,
              background: n.kind === 'system_announcement'
                ? (n.read
                    ? 'linear-gradient(135deg, rgba(180,140,250,0.06) 0%, rgba(243,198,74,0.04) 100%)'
                    : 'linear-gradient(135deg, rgba(180,140,250,0.18) 0%, rgba(243,198,74,0.14) 100%)')
                : (n.read
                    ? 'linear-gradient(135deg, rgba(255,255,255,0.025) 0%, rgba(243,198,74,0.02) 100%)'
                    : 'linear-gradient(135deg, rgba(243,198,74,0.18) 0%, rgba(200,151,32,0.10) 100%)'),
              border: n.kind === 'system_announcement'
                ? (n.read ? '1px solid rgba(180,140,250,0.20)' : '1.5px solid rgba(180,140,250,0.55)')
                : (n.read ? '1px solid rgba(243,198,74,0.12)' : '1.5px solid rgba(243,198,74,0.55)'),
              cursor: n.read ? 'default' : 'pointer',
              transition: 'all 0.2s',
              textAlign: 'right', direction: 'rtl',
              boxShadow: n.read
                ? 'none'
                : (n.kind === 'system_announcement'
                    ? '0 3px 12px rgba(120, 80, 220, 0.22), inset 0 0 28px rgba(180,140,250,0.06)'
                    : '0 3px 12px rgba(138, 95, 16, 0.20), inset 0 0 24px rgba(243,198,74,0.05)'),
              position: 'relative',
              opacity: busyId === n.id ? 0.4 : 1,
            }}
          >
            {/* [pass 24] × delete button — top-left corner, subtle, always visible */}
            <button
              onClick={(e) => deleteOne(n.id, e)}
              disabled={busyId === n.id}
              title="מחק הודעה"
              style={{
                position: 'absolute', top: 6, left: 6,
                width: 22, height: 22, borderRadius: '50%',
                background: 'rgba(0,0,0,0.18)', color: '#a89878',
                border: 'none', cursor: 'pointer', fontSize: 14, lineHeight: 1,
                fontFamily: 'inherit', padding: 0,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                transition: 'all 0.15s',
                zIndex: 2,
              }}
              onMouseEnter={e => { e.currentTarget.style.background = 'rgba(255,107,157,0.25)'; e.currentTarget.style.color = '#dc2626'; }}
              onMouseLeave={e => { e.currentTarget.style.background = 'rgba(0,0,0,0.18)'; e.currentTarget.style.color = '#a89878'; }}
            >×</button>
            {/* [pass 24] System announcement gets a small "📣 מערכת" tag */}
            {n.kind === 'system_announcement' && (
              <div style={{
                display: 'inline-block', fontSize: 10, letterSpacing: 1.5,
                color: '#b48aff', fontWeight: 700,
                background: 'rgba(180,140,250,0.15)',
                padding: '2px 8px', borderRadius: 100,
                marginBottom: 6,
              }}>
                📣 מערכת
              </div>
            )}
            <div style={{ fontSize: 14, fontWeight: n.read ? 500 : 700, color: n.read ? '#c8c8d8' : '#e8d8b0', marginBottom: 3 }}>
              {n.title || ''}
            </div>
            {n.body && (
              <div style={{ fontSize: 12, color: 'var(--muted)', lineHeight: 1.5 }}>
                {n.body}
              </div>
            )}
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginTop: 4, gap: 8 }}>
              <div style={{ fontSize: 10, color: '#6a6d80' }}>
                {(n.created_at || '').replace('T', ' ').slice(0, 16)}
              </div>
              {/* [damri 2026-05-24 round E] link to related actor's profile */}
              {n.related_user_id && (
                <a href={`#/user/${n.related_user_id}`}
                   onClick={(e) => e.stopPropagation()}
                   style={{ fontSize: 10, color: '#c9b88a', textDecoration: 'underline', cursor: 'pointer' }}>
                  ← לפרופיל
                </a>
              )}
            </div>
            {/* [yakir pass 21] Inline thank-you on founder notifications */}
            {n.kind === 'chapter_reward_founder' && (
              <ThanksAction notification={n} fromUserId={userId} onSent={() => setTick(t => t + 1)} />
            )}
          </div>
        ))}
      </div>
      {/* [pass 24] Load more — show only if there are more notifications */}
      {total > items.length && (
        <div style={{ textAlign: 'center', marginTop: 14 }}>
          <button
            onClick={loadMore}
            style={{
              background: 'linear-gradient(135deg, rgba(243,198,74,0.14), rgba(200,151,32,0.08))',
              border: '1px solid rgba(243,198,74,0.35)',
              color: '#f3c64a', fontSize: 12, fontWeight: 700,
              padding: '8px 22px', borderRadius: 100,
              cursor: 'pointer', fontFamily: 'inherit',
              letterSpacing: 1,
            }}
          >
            טען עוד ↓ ({total - items.length} ישנים)
          </button>
        </div>
      )}
    </section>
  );
}

// [damri 2026-05-24 round E] ProfileLink — small helper for linking names to /user/:id.
// Renders as <a> when uid is provided, falls back to <strong>/<span> otherwise.
function ProfileLink({ uid, name, color, weight = 700 }) {
  if (!uid || uid <= 0) {
    return <strong style={{ color: color || '#a8801f', fontWeight: weight }}>{name}</strong>;
  }
  return (
    <a href={`#/user/${uid}`}
       onClick={(e) => e.stopPropagation()}
       style={{
         color: color || '#a8801f', fontWeight: weight,
         textDecoration: 'none', cursor: 'pointer',
       }}
       onMouseEnter={(e) => e.currentTarget.style.textDecoration = 'underline'}
       onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'}>{name}</a>
  );
}

// ──────────────────────────────────────────
//  [yakir pass 21] TransparencyPage — community journey of recognition
//  Shows every reward and every thank-you that moved through the system.
//  Sections:
//    1. Totals — community headlines
//    2. Recent activity feed — last 50 reward events with names
//    3. Recent thanks — last 20 founder→reader gratitude moments
//    4. Top contributors in 3 domains: readers / founders / givers
//
//  Open to anonymous visitors — this IS the transparency.
// ──────────────────────────────────────────
// ───────── ChooseInfluence — "המשפיע שלך" (per Damri 2026-05-24) ─────────
// Lives inside מסע הקהילה. Honoring, reversible, gentle. Reuses /mentor.
function ChooseInfluence() {
  const userId = parseInt((typeof localStorage !== 'undefined' && localStorage.getItem('tehillim_user_id')) || '0', 10);
  const [mentorName, setMentorName] = React.useState(null);
  const [loaded, setLoaded] = React.useState(false);
  const [open, setOpen] = React.useState(false);
  const [q, setQ] = React.useState('');
  const [results, setResults] = React.useState([]);
  const [busy, setBusy] = React.useState(false);
  React.useEffect(() => {
    if (!userId) { setLoaded(true); return; }
    api('/api/profile/' + userId).then(pr => { setMentorName(pr && pr.mentor_name ? pr.mentor_name : null); }).catch(() => {}).finally(() => setLoaded(true));
  }, [userId]);
  if (!userId || !loaded) return null;
  const btnGhost = { background: 'none', border: '1px solid rgba(243,198,74,0.35)', color: '#6b5a37', padding: '5px 12px', borderRadius: 999, fontSize: 12, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' };
  const search = async (val) => {
    setQ(val);
    if (!val.trim()) { setResults([]); return; }
    try { const r = await api('/api/users/search?q=' + encodeURIComponent(val.trim()) + '&exclude=' + userId); setResults((r && r.results) || []); } catch (e) { setResults([]); }
  };
  const choose = async (mid, name) => {
    if (busy) return; setBusy(true);
    try { await api('/api/profile/' + userId + '/mentor', { method: 'POST', body: { mentor_user_id: mid } }); setMentorName(name); setOpen(false); setQ(''); setResults([]); } finally { setBusy(false); }
  };
  const clear = async () => {
    if (busy) return; setBusy(true);
    try { await api('/api/profile/' + userId + '/mentor', { method: 'POST', body: { mentor_user_id: null } }); setMentorName(null); setOpen(false); } finally { setBusy(false); }
  };
  return (
    <div style={{ maxWidth: 460, margin: '10px auto 0', padding: '16px 18px', borderRadius: 16, direction: 'rtl', textAlign: 'right', background: 'rgba(243,198,74,0.07)', border: '1px solid rgba(243,198,74,0.30)' }}>
      <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 16, fontWeight: 700, color: '#a8801f', marginBottom: 4 }}>המשפיע שלך</div>
      <div style={{ fontSize: 12.5, color: '#6b5a37', lineHeight: 1.6, marginBottom: 12 }}>מי האיר לך את הדרך ונתן לך כוח להיפתח. 10% מהשפע שייווצר בך יגיע גם אליו — כהוקרה, לא כמס. אפשר לשנות בכל רגע.</div>
      {mentorName && !open ? (
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
          <div style={{ color: '#3a2208', fontSize: 15, fontWeight: 600 }}>🤍 {mentorName}</div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button type="button" onClick={() => setOpen(true)} style={btnGhost}>שינוי</button>
            <button type="button" onClick={clear} disabled={busy} style={btnGhost}>הסרה</button>
          </div>
        </div>
      ) : (
        <div>
          <input value={q} onChange={e => search(e.target.value)} placeholder="חפש שם בקהילה..." disabled={busy}
            style={{ width: '100%', padding: '10px 12px', borderRadius: 10, border: '1px solid rgba(243,198,74,0.4)', background: '#fff', color: '#3a2208', fontSize: 14, fontFamily: 'inherit', boxSizing: 'border-box' }} />
          {results.length > 0 && (
            <div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
              {results.map(u => (
                <button key={u.id} type="button" disabled={busy} onClick={() => choose(u.id, u.name)}
                  style={{ textAlign: 'right', padding: '9px 12px', borderRadius: 10, border: '1px solid rgba(243,198,74,0.25)', background: 'rgba(243,198,74,0.06)', color: '#3a2208', fontSize: 14, fontWeight: 600, cursor: 'pointer', fontFamily: 'inherit' }}>{u.name}</button>
              ))}
            </div>
          )}
          {mentorName && <button type="button" onClick={() => setOpen(false)} style={{ ...btnGhost, marginTop: 8 }}>ביטול</button>}
        </div>
      )}
    </div>
  );
}

function TransparencyPage() {
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState('');
  React.useEffect(() => {
    api('/api/transparency').then(d => {
      if (!d || d.error) { setErr(d?.error || 'בעיה בטעינה'); return; }
      setData(d);
    }).catch(e => setErr(e.message || 'בעיה בטעינה'));
  }, []);

  if (err) return <section style={{ padding: '60px 20px', textAlign: 'center', color: '#dc2626' }}>{err}</section>;
  if (!data) return <section style={{ padding: '60px 20px', textAlign: 'center', color: '#c8b888' }}>טוען את המסע של הקהילה...</section>;

  const t = data.totals || {};
  const fmt = (n) => (n || 0).toLocaleString('he-IL');
  const KIND_ICON = {
    chapter_read: '📖', chapter_founder: '✨', circle_complete: '🎉',
    task: '🤝', invite: '🌱', referral: '🌱', signup: '🚪',
    profile: '🪞', vision: '💫', wallet: '🔗', other: '·',
  };
  // [damri 2026-05-21] Translate raw action codes into friendly Hebrew.
  // Server may store "book_complete:tanya:cycle_1" — show "השלים מחזור 1 של תניא".
  const BOOK_HE = { tanya: 'תניא', tehillim: 'תהילים', tikkunei_zohar: 'תיקוני הזוהר', rambam: 'רמב\"ם' };
  function describeAction(desc) {
    if (!desc) return '—';
    const s = String(desc);
    let m;
    if ((m = s.match(/^book_complete:([^:]+):cycle_(\d+)/))) {
      return `🎉 השלים מחזור ${m[2]} של ${BOOK_HE[m[1]] || m[1]}`;
    }
    if ((m = s.match(/^reward:book_complete:([^:]+):cycle_(\d+)/))) {
      return `🎁 בונוס סיום ${BOOK_HE[m[1]] || m[1]} (מחזור ${m[2]})`;
    }
    if ((m = s.match(/^chapter:(\d+):(\d+):(\d+)/))) {
      return `📖 קרא פרק ${m[2]} במעגל #${m[1]}`;
    }
    if ((m = s.match(/^chapter_founder:(\d+):(\d+):(\d+)/))) {
      return `✨ פרק ${m[2]} נקרא במעגל #${m[1]}`;
    }
    if ((m = s.match(/^reward:chapter:(\d+):(\d+):(\d+)/))) {
      return `🪙 גמול על קריאת פרק ${m[2]} (מעגל #${m[1]})`;
    }
    if ((m = s.match(/^reward:chapter_founder:(\d+):(\d+):(\d+)/))) {
      return `🪙 גמול ליוצר על פרק ${m[2]} (מעגל #${m[1]})`;
    }
    if ((m = s.match(/^circle_invite_accepted:(\d+)/))) {
      return `🌱 הצטרף למעגל #${m[1]}`;
    }
    if (s.startsWith('anon_signup')) return '🚪 הצטרף לקהילה';
    if (s.startsWith('signup_credit')) return '🎁 ברכת הצטרפות';
    if (s.startsWith('google_signup')) return '🚪 התחבר עם Google';
    if (s.startsWith('wallet_connect')) return '🔗 חיבור ארנק';
    if (s.startsWith('profile_onboard')) return '🪞 השלים פרופיל';
    if (s.startsWith('vision_complete')) return '💫 השלים חזון';
    if (s.startsWith('level_up:')) return `⬆️ עלה לרמה ${s.split(':')[1]}`;
    if (s.startsWith('campaign_message:')) return `💬 הגיב על ${s.split(':')[1]}`;
    if (s.startsWith('wish_publish')) return '⭐ פרסם משאלה';
    if (s.startsWith('transfer:')) return `🤝 העברה: ${s.slice(9)}`;
    return s;
  }

  return (
    <section style={{
      paddingTop: 24, paddingBottom: 60,
      // [pass 23] Warm ambient gradient — transparency feels like sunset, not a spreadsheet.
      background:
        'radial-gradient(ellipse at 50% 0%, rgba(243,198,74,0.10) 0%, transparent 45%), ' +
        'radial-gradient(ellipse at 90% 30%, rgba(200,151,32,0.06) 0%, transparent 40%)',
      minHeight: '100vh',
    }}>
      <div style={{ maxWidth: 700, margin: '0 auto', padding: '0 20px' }}>
        {/* Hero */}
        <div style={{ textAlign: 'center', marginBottom: 40, paddingTop: 18 }}>
          <div style={{ fontSize: 13, color: '#a8801f', letterSpacing: 4, marginBottom: 14, fontWeight: 700, textShadow: '0 0 20px rgba(243,198,74,0.25)' }}>✦ שקיפות</div>
          <h1 style={{
            fontFamily: "'Frank Ruhl Libre', serif", fontSize: 36,
            color: '#a8801f', fontWeight: 600, lineHeight: 1.3, marginBottom: 14,
            textShadow: '0 0 40px rgba(243,198,74,0.30)',
          }}>
            מסע ההוקרה של הקהילה
          </h1>
          <div style={{
            fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
            fontSize: 17, color: '#6b5a37', lineHeight: 1.7,
          }}>
            כל מטבע שעבר במערכת —<br/>
            לאן, על מה, ובזכות מי.
          </div>
          <ChooseInfluence />
        </div>

        {/* Totals grid */}
        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(140px, 1fr))',
          gap: 12, marginBottom: 32,
        }}>
          {[
            { label: 'סה״כ מטבעות', value: t.total_amount, icon: '🪙' },
            { label: 'הוקרות', value: t.total_events, icon: '✦' },
            { label: 'פרקים שנקראו', value: t.chapters_reader, icon: '📖' },
            { label: 'מצטרפים חדשים', value: t.referrals, icon: '🌱' },
          ].map((k, i) => (
            <div key={i} style={{
              padding: '18px 16px',
              background: 'linear-gradient(135deg, rgba(243,198,74,0.18) 0%, rgba(200,151,32,0.10) 100%)',
              border: '1.5px solid rgba(243,198,74,0.45)',
              borderRadius: 14, textAlign: 'center',
              boxShadow: '0 4px 14px rgba(138, 95, 16, 0.18), inset 0 0 30px rgba(243,198,74,0.05)',
            }}>
              <div style={{ fontSize: 22, marginBottom: 4 }}>{k.icon}</div>
              <div style={{
                fontSize: 30, color: '#a8801f', fontWeight: 800, lineHeight: 1.1,
                fontFamily: "'Frank Ruhl Libre', serif",
                textShadow: '0 0 24px rgba(243,198,74,0.30)',
              }}>
                {fmt(k.value)}
              </div>
              <div style={{ fontSize: 12, color: '#6b5a37', marginTop: 6, fontWeight: 600 }}>{k.label}</div>
            </div>
          ))}
        </div>

        {/* Recent thanks */}
        {data.thanks && data.thanks.length > 0 && (
          <div style={{ marginBottom: 32 }}>
            <h2 style={{ fontSize: 14, color: '#a8801f', letterSpacing: 3, marginBottom: 16, fontWeight: 800, textShadow: '0 0 18px rgba(243,198,74,0.22)' }}>✦ תודות אישיות אחרונות</h2>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
              {data.thanks.slice(0, 10).map(th => (
                <div key={th.id} style={{
                  padding: '14px 16px',
                  background: 'linear-gradient(135deg, rgba(243,198,74,0.16) 0%, rgba(200,151,32,0.10) 100%)',
                  border: '1.5px solid rgba(168,128,31,0.45)',
                  borderRadius: 12, textAlign: 'right', direction: 'rtl',
                  boxShadow: '0 3px 12px rgba(138, 95, 16, 0.14)',
                }}>
                  <div style={{ fontSize: 13, color: '#6b5a37', marginBottom: 6 }}>
                    🙏 <ProfileLink uid={th.from_user_id} name={th.from_name || '—'} color="#a8801f" /> שלח תודה ל־<ProfileLink uid={th.to_user_id} name={th.to_name || '—'} color="#a8801f" />
                  </div>
                  <div style={{
                    fontSize: 15, color: '#3a2208', fontStyle: 'italic',
                    fontFamily: "'Frank Ruhl Libre', serif", lineHeight: 1.75,
                    fontWeight: 500,
                  }}>
                    "{th.message}"
                  </div>
                  <div style={{ fontSize: 11, color: '#8a6f1f', marginTop: 6 }}>
                    {(th.created_at || '').replace('T', ' ').slice(0, 16)}
                  </div>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Top contributors */}
        <div style={{ marginBottom: 32 }}>
          <h2 style={{ fontSize: 14, color: '#a8801f', letterSpacing: 3, marginBottom: 16, fontWeight: 800, textShadow: '0 0 18px rgba(243,198,74,0.22)' }}>✦ החברים הכי פעילים</h2>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 14 }}>
            {[
              { title: '📖 קוראים מובילים', rows: data.top_readers, key: 'verified_count', unit: 'פרקים' },
              { title: '✨ מייסדי מעגלים', rows: data.top_founders, key: 'readings_in_their_circles', unit: 'קריאות' },
              { title: '🙏 שולחי תודה', rows: data.top_givers, key: 'thanks_sent', unit: 'תודות' },
            ].map((col, i) => (
              <div key={i} style={{
                padding: '16px 18px',
                background: 'linear-gradient(135deg, rgba(243,198,74,0.08) 0%, rgba(200,151,32,0.04) 100%)',
                border: '1.5px solid rgba(243,198,74,0.30)',
                borderRadius: 14, textAlign: 'right', direction: 'rtl',
                boxShadow: '0 3px 12px rgba(138, 95, 16, 0.12)',
              }}>
                <div style={{ fontSize: 14, color: '#a8801f', marginBottom: 12, fontWeight: 800, paddingBottom: 8, borderBottom: '1px solid rgba(243,198,74,0.22)' }}>{col.title}</div>
                {(!col.rows || col.rows.length === 0) ? (
                  <div style={{ fontSize: 12, color: '#8a6f3f', fontStyle: 'italic' }}>עוד אין נתונים</div>
                ) : col.rows.slice(0, 5).map((row, j) => (
                  <div key={j} style={{
                    display: 'flex', justifyContent: 'space-between',
                    fontSize: 14, color: '#3a2208',
                    padding: '6px 0',
                    borderBottom: j < col.rows.length - 1 ? '1px solid rgba(168,128,31,0.18)' : 'none',
                  }}>
                    <span><span style={{ color: '#a8801f', fontWeight: 700, marginLeft: 4 }}>{j + 1}.</span> <ProfileLink uid={row.id} name={row.name || '—'} color="#3a2208" weight={600} /></span>
                    <span style={{ color: '#a8801f', fontWeight: 800 }}>{row[col.key]} <span style={{ color: '#6b5a37', fontSize: 11, fontWeight: 500 }}>{col.unit}</span></span>
                  </div>
                ))}
              </div>
            ))}
          </div>
        </div>

        {/* Recent activity feed */}
        <div style={{ marginBottom: 32 }}>
          <h2 style={{ fontSize: 14, color: '#a8801f', letterSpacing: 3, marginBottom: 16, fontWeight: 800, textShadow: '0 0 18px rgba(243,198,74,0.22)' }}>✦ מסע ההוקרות — כל מה שזרם</h2>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {(data.recent || []).map(r => (
              <div key={r.id} style={{
                display: 'flex', justifyContent: 'space-between', alignItems: 'center',
                padding: '10px 14px',
                background: 'linear-gradient(135deg, rgba(243,198,74,0.05) 0%, rgba(200,151,32,0.02) 100%)',
                border: '1px solid rgba(243,198,74,0.18)',
                borderRadius: 10, fontSize: 13,
                direction: 'rtl', textAlign: 'right',
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
                  <span style={{ fontSize: 18 }}>{KIND_ICON[r.kind] || '·'}</span>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ color: '#3a2208', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', fontSize: 14 }}>
                      <ProfileLink uid={r.user_id} name={r.user_name || '—'} color="#a8801f" weight={800} />
                      <span style={{ color: '#a8801f', marginRight: 6 }}>·</span>
                      <span style={{ color: '#3a2208', fontWeight: 500 }}>{describeAction(r.description)}</span>
                    </div>
                    <div style={{ fontSize: 11, color: '#8a6f3f', marginTop: 2 }}>
                      {(r.created_at || '').replace('T', ' ').slice(0, 16)}
                    </div>
                  </div>
                </div>
                <div style={{
                  color: '#a8801f', fontWeight: 800, fontSize: 14,
                  whiteSpace: 'nowrap', marginRight: 8,
                  textShadow: '0 0 12px rgba(243,198,74,0.30)',
                }}>
                  +{r.amount}
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Footer note */}
        <div style={{
          fontFamily: "'Frank Ruhl Libre', serif", fontStyle: 'italic',
          fontSize: 16, color: '#6b5a37', textAlign: 'center',
          padding: '32px 16px 12px', lineHeight: 1.85,
          borderTop: '1px dashed rgba(243,198,74,0.28)',
          marginTop: 24,
        }}>
          לא נמדדים לפי הצלחה, כסף או תדמית.<br/>
          <span style={{ color: '#a8801f', fontWeight: 600 }}>נמדדים לפי האור שמסוגלים להביא לעולם.</span>
        </div>
      </div>
    </section>
  );
}

// ──────────────────────────────────────────
//  [yakir pass 26] MyReaders — list of everyone who read a chapter in
//  any circle the user founded, with an inline 🙏 thanks button per row.
//  Yakir: "זה יקר ללבי - אני רוצה להוקיר."
// ──────────────────────────────────────────
function MyReaderRow({ reader, fromUserId, onSent }) {
  // [yakir pass 28] Collapsible card with textarea, send, AND history.
  // Two collapse states:
  //   mode='compact' - just name + info + minimal "Send Thanks" button
  //   mode='write'   - expanded write form with bigger textarea
  //   mode='history' - shows past thanks sent to this person
  // Last thanks shown inline always (if any) so it's visible at a glance.
  const [mode, setMode] = React.useState('compact');
  const [text, setText] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [history, setHistory] = React.useState(null);

  const bookLabel = reader.last_text_type === 'tanya' ? 'תניא'
    : reader.last_text_type === 'tikkunei_zohar' ? 'תיקוני הזוהר'
    : 'תהילים';

  const defaultMsg = reader.last_chapter
    ? `תודה ${reader.reader_name} על שקראת פרק ${reader.last_chapter} ב${bookLabel}. זה נגע ללבי שהצטרפת.`
    : `תודה ${reader.reader_name} על שקראת איתנו. זה נגע ללבי שהצטרפת.`;

  // Load history when row opens to either write or history mode
  const loadHistory = React.useCallback(async () => {
    try {
      const r = await api(`/api/thanks-between/${fromUserId}/${reader.reader_id}`);
      if (r && Array.isArray(r.thanks)) setHistory(r.thanks);
    } catch (_) { setHistory([]); }
  }, [fromUserId, reader.reader_id]);

  React.useEffect(() => {
    if (mode === 'write' && !text) setText(defaultMsg);
    if ((mode === 'write' || mode === 'history') && history === null) loadHistory();
  }, [mode]);

  const send = async () => {
    setBusy(true); setErr('');
    try {
      const msg = (text || '').trim();
      if (msg.length < 2) throw new Error('כתוב משפט קצר של תודה');

      // [yakir pass 32] Duplicate-thanks guard. Before sending, check if
      // we already thanked this person for the same chapter+book. If so,
      // ask for confirmation to prevent accidental double-thanks.
      // History is loaded when entering 'write' mode, so usually ready.
      // If somehow null, fetch synchronously now.
      let hist = history;
      if (hist === null) {
        try {
          const r0 = await api(`/api/thanks-between/${fromUserId}/${reader.reader_id}`);
          hist = Array.isArray(r0 && r0.thanks) ? r0.thanks : [];
          setHistory(hist);
        } catch (_) { hist = []; }
      }
      if (reader.last_chapter && Array.isArray(hist) && hist.length > 0) {
        const chapterTag = `פרק ${reader.last_chapter} ב${bookLabel}`;
        const matches = hist.filter(h => (h.context || '').includes(chapterTag));
        if (matches.length > 0) {
          const prev = matches[0]; // newest first from server
          const when = formatThanksTime(prev.created_at);
          const count = matches.length;
          const prevSnip = (prev.message || '').length > 100
            ? (prev.message || '').slice(0, 100) + '…'
            : (prev.message || '');
          const countLine = count === 1
            ? `כבר הוקרת את ${reader.reader_name} על ${chapterTag}`
            : `כבר הוקרת את ${reader.reader_name} ${count} פעמים על ${chapterTag}`;
          const confirmMsg = `${countLine}.\n\n` +
            `הוקרה אחרונה (${when}):\n"${prevSnip}"\n\n` +
            `בטוח שתרצה לשלוח שוב על אותה הפעולה?`;
          if (!window.confirm(confirmMsg)) {
            setBusy(false);
            return; // user cancelled
          }
        }
      }

      const r = await api('/api/thanks', { method: 'POST', body: {
        from_user_id: fromUserId,
        to_user_id: reader.reader_id,
        message: msg,
        context: `על פרק ${reader.last_chapter || '?'} ב${bookLabel} · ${reader.last_circle_name || ''}`,
      }});
      if (r.error) throw new Error(r.error);
      setText('');
      showToast('✓ התודה נשלחה');
      // Reload history to include the new send
      await loadHistory();
      setMode('history');
      if (onSent) onSent();
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setBusy(false);
    }
  };

  const ago = (() => {
    if (!reader.last_read_at) return '';
    const t = new Date(reader.last_read_at.replace(' ', 'T') + 'Z');
    const now = new Date();
    const days = Math.floor((now - t) / 86400000);
    if (days < 1) return 'היום';
    if (days === 1) return 'אתמול';
    if (days < 7) return `לפני ${days} ימים`;
    if (days < 30) return `לפני ${Math.floor(days/7)} שבועות`;
    return `לפני ${Math.floor(days/30)} חודשים`;
  })();

  const formatThanksTime = (iso) => {
    if (!iso) return '';
    const t = new Date(String(iso).replace(' ', 'T') + 'Z');
    const now = new Date();
    const mins = Math.floor((now - t) / 60000);
    if (mins < 5) return 'כעת';
    if (mins < 60) return `לפני ${mins} ד׳`;
    const hrs = Math.floor(mins / 60);
    if (hrs < 24) return `לפני ${hrs} שעות`;
    const days = Math.floor(hrs / 24);
    if (days < 7) return `לפני ${days} ימים`;
    return t.toLocaleDateString('he-IL');
  };

  const lastThanks = history && history.length > 0 ? history[0] : null;

  return (
    <div style={{
      padding: '16px 18px',
      background: 'linear-gradient(135deg, rgba(255,243,205,0.85) 0%, rgba(248,225,160,0.65) 100%)',
      border: '1.5px solid rgba(168, 120, 32, 0.40)',
      borderRadius: 12,
      boxShadow: '0 3px 12px rgba(138, 95, 16, 0.18), inset 0 1px 0 rgba(255,255,255,0.5)',
      direction: 'rtl', textAlign: 'right',
    }}>
      {/* Header row: name + info + action button */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, flexWrap: 'wrap' }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontSize: 20, color: '#5a3d0a', fontWeight: 800, marginBottom: 6,
            fontFamily: "'Frank Ruhl Libre', serif",
          }}>
            {reader.reader_name}
          </div>
          <div style={{ fontSize: 15, color: '#3a2208', lineHeight: 1.7, fontWeight: 600 }}>
            <span style={{ color: '#8a5f10', fontWeight: 800 }}>📖 {reader.total_chapters}</span> {reader.total_chapters === 1 ? 'פרק' : 'פרקים'}
            <span style={{ color: '#6d5d3d' }}> · {ago}</span>
            {reader.last_chapter && (
              <span style={{ color: '#3a2208' }}> · אחרון: פרק <strong style={{ color: '#8a5f10', fontWeight: 800 }}>{reader.last_chapter}</strong></span>
            )}
          </div>
          {reader.thanks_sent_from_me > 0 && (
            <div
              onClick={() => setMode(mode === 'history' ? 'compact' : 'history')}
              style={{
                fontSize: 14, color: '#5a3d8a', marginTop: 8, fontStyle: 'italic',
                fontWeight: 700, cursor: 'pointer', textDecoration: 'underline',
                textDecorationStyle: 'dotted', textUnderlineOffset: 3,
                display: 'inline-block',
              }}
            >
              ✓ הוקרת {reader.thanks_sent_from_me} פעמים {mode === 'history' ? '· סגור' : '· הצג'}
            </div>
          )}
        </div>
        {/* Action button - changes by mode */}
        {mode === 'compact' && (
          <button
            onClick={() => setMode('write')}
            style={{
              padding: '11px 22px',
              background: 'linear-gradient(135deg, #d4af37, #b8941f)',
              color: '#1a1a2e', fontSize: 15, fontWeight: 800,
              border: '1.5px solid #8a5f10',
              borderRadius: 100,
              cursor: 'pointer', fontFamily: 'inherit',
              whiteSpace: 'nowrap',
              boxShadow: '0 4px 14px rgba(138, 95, 16, 0.35)',
              letterSpacing: 0.5,
            }}
          >
            🙏 שלח תודה
          </button>
        )}
        {mode === 'write' && (
          <button
            onClick={() => { setMode('compact'); setErr(''); }}
            style={{
              padding: '8px 16px',
              background: 'transparent', color: '#a89878',
              border: '1px solid rgba(168,152,120,0.3)', borderRadius: 100,
              fontSize: 13, cursor: 'pointer', fontFamily: 'inherit',
              whiteSpace: 'nowrap',
            }}
          >
            סגור ▲
          </button>
        )}
      </div>

      {/* Latest thanks shown inline always — quick "what I last said" */}
      {mode !== 'write' && lastThanks && (
        <div style={{
          marginTop: 12, padding: '12px 14px',
          background: 'rgba(120, 70, 200, 0.12)',
          border: '1.5px solid rgba(120, 70, 200, 0.38)',
          borderRadius: 10,
          fontSize: 16, color: '#2a1850',
          lineHeight: 1.75, fontStyle: 'italic', fontWeight: 500,
          fontFamily: "'Frank Ruhl Libre', serif",
          boxShadow: 'inset 0 1px 4px rgba(0,0,0,0.06)',
        }}>
          <span style={{ color: '#5a3d8a', fontWeight: 800, fontStyle: 'normal' }}>אחרון:</span>{' '}
          {lastThanks.message}
          <div style={{ fontSize: 12, color: '#5d4a6e', marginTop: 6, fontStyle: 'normal', fontWeight: 600 }}>
            {formatThanksTime(lastThanks.created_at)}
          </div>
        </div>
      )}

      {/* History mode — show all past thanks */}
      {mode === 'history' && history && history.length > 1 && (
        <div style={{ marginTop: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
          <div style={{ fontSize: 13, color: '#5a3d8a', fontWeight: 800, letterSpacing: 1.5, textAlign: 'center', marginTop: 4 }}>
            ── הוקרות קודמות ──
          </div>
          {history.slice(1).map(h => (
            <div key={h.id} style={{
              padding: '12px 14px',
              background: 'rgba(120, 70, 200, 0.08)',
              border: '1.5px solid rgba(120, 70, 200, 0.30)',
              borderRadius: 10,
              fontSize: 16, color: '#2a1850',
              lineHeight: 1.75, fontStyle: 'italic', fontWeight: 500,
              fontFamily: "'Frank Ruhl Libre', serif",
            }}>
              {h.message}
              <div style={{ fontSize: 12, color: '#5d4a6e', marginTop: 6, fontStyle: 'normal', fontWeight: 600 }}>
                {formatThanksTime(h.created_at)}
              </div>
            </div>
          ))}
        </div>
      )}

      {/* WRITE mode — large textarea, bold readable text */}
      {mode === 'write' && (
        <div style={{ marginTop: 14 }}>
          <textarea
            rows={4}
            value={text}
            onChange={e => setText(e.target.value)}
            maxLength={500}
            autoFocus
            placeholder="כתוב מהלב..."
            style={{
              width: '100%', padding: '14px 16px',
              background: '#fffbeb',
              border: '2px solid rgba(243,198,74,0.55)',
              borderRadius: 12, color: '#1a1a2e',
              fontFamily: "'Frank Ruhl Libre', serif",
              fontSize: 18, lineHeight: 1.75, fontWeight: 600,
              direction: 'rtl', textAlign: 'right', outline: 'none',
              resize: 'vertical', boxSizing: 'border-box',
              boxShadow: 'inset 0 2px 6px rgba(0,0,0,0.08)',
              caretColor: '#1a1a2e',
            }}
          />
          {err && <div style={{ color: '#dc2626', fontSize: 13, marginTop: 6, fontWeight: 600 }}>{err}</div>}
          <div style={{ display: 'flex', gap: 8, marginTop: 10, justifyContent: 'space-between', alignItems: 'center' }}>
            <div style={{ fontSize: 12, color: '#a89878' }}>{text.length}/500</div>
            <div style={{ display: 'flex', gap: 8 }}>
              <button
                onClick={() => setText(defaultMsg)}
                style={{
                  padding: '8px 14px', background: 'transparent', color: '#d4b878',
                  border: '1px solid rgba(212,184,120,0.3)', borderRadius: 8,
                  fontSize: 12, cursor: 'pointer', fontFamily: 'inherit',
                }}
              >↺ ברירת מחדל</button>
              <button
                onClick={send} disabled={busy || !text.trim()}
                style={{
                  padding: '10px 22px',
                  background: busy ? 'rgba(212,175,55,0.3)' : 'linear-gradient(135deg, #d4af37, #b8941f)',
                  color: '#1a1a2e', fontSize: 14, fontWeight: 800,
                  border: 'none', borderRadius: 8,
                  cursor: busy || !text.trim() ? 'not-allowed' : 'pointer',
                  fontFamily: 'inherit',
                  boxShadow: busy ? 'none' : '0 3px 10px rgba(138, 95, 16, 0.30)',
                }}
              >{busy ? 'שולח...' : '🙏 שלח'}</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function MyReaders({ userId }) {
  const [readers, setReaders] = React.useState(null);
  const [tick, setTick] = React.useState(0);
  // [yakir pass 31] Collapse state persisted to localStorage so Yakir's
  // preference survives reload. Default OPEN so first encounter shows
  // the list; once he closes it, stays closed across sessions.
  const [collapsed, setCollapsed] = React.useState(() => {
    try { return localStorage.getItem('thl_myreaders_collapsed_v1') === '1'; }
    catch { return false; }
  });

  React.useEffect(() => {
    if (!userId) return;
    api(`/api/my-readers/${userId}`).then(d => {
      if (!d || d.error) return;
      setReaders(Array.isArray(d.readers) ? d.readers : []);
    });
  }, [userId, tick]);

  const toggleCollapsed = () => {
    setCollapsed(c => {
      const next = !c;
      try { localStorage.setItem('thl_myreaders_collapsed_v1', next ? '1' : '0'); } catch {}
      return next;
    });
  };

  if (!userId || !readers) return null;
  if (readers.length === 0) return null;

  // Count of readers NOT yet thanked - useful prompt when collapsed
  const notThankedCount = readers.filter(r => !r.thanks_sent_from_me).length;

  return (
    <section style={{
      maxWidth: 600, margin: '20px auto',
      padding: collapsed ? '14px 20px' : '20px 22px',
      background: 'linear-gradient(135deg, rgba(255,243,205,0.65) 0%, rgba(248,225,160,0.45) 100%)',
      border: '1.5px solid rgba(168, 120, 32, 0.45)',
      borderRadius: 16,
      boxShadow: '0 4px 20px rgba(138, 95, 16, 0.18), inset 0 1px 0 rgba(255,255,255,0.5)',
      transition: 'padding 0.2s',
    }}>
      {/* Header: title + toggle button. Always visible. */}
      <div
        onClick={toggleCollapsed}
        style={{
          display: 'flex', justifyContent: 'space-between', alignItems: 'center',
          cursor: 'pointer', gap: 12, flexWrap: 'wrap',
          marginBottom: collapsed ? 0 : 12,
        }}
      >
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{
            fontSize: 19, color: '#5a3d0a', letterSpacing: 1,
            fontWeight: 800, marginBottom: collapsed ? 4 : 6,
            fontFamily: "'Frank Ruhl Libre', serif",
          }}>
            🌱 הקוראים במעגלים שלי
          </div>
          <div style={{
            fontSize: 15, color: '#3a2208', fontStyle: 'italic',
            fontFamily: "'Frank Ruhl Libre', serif", lineHeight: 1.65,
            fontWeight: 600,
          }}>
            <strong style={{ color: '#8a5f10', fontWeight: 800, fontStyle: 'normal' }}>
              {readers.length} אנשים
            </strong> בחרו לקרוא איתך
            {collapsed && notThankedCount > 0 && (
              <span style={{ color: '#7a4f10' }}> · <strong style={{ color: '#5a3d8a', fontWeight: 800 }}>{notThankedCount} מחכים להוקרה</strong></span>
            )}
          </div>
        </div>
        <button
          onClick={(e) => { e.stopPropagation(); toggleCollapsed(); }}
          aria-label={collapsed ? 'הצג' : 'הסתר'}
          style={{
            background: 'rgba(168, 120, 32, 0.20)',
            border: '1.5px solid rgba(138, 95, 16, 0.45)',
            color: '#5a3d0a',
            fontSize: 13, fontWeight: 800,
            padding: '6px 14px',
            borderRadius: 100,
            cursor: 'pointer', fontFamily: 'inherit',
            whiteSpace: 'nowrap', letterSpacing: 0.3,
            transition: 'all 0.15s',
          }}
        >
          {collapsed ? '▼ הצג' : '▲ הסתר'}
        </button>
      </div>

      {/* Expanded body: only when not collapsed */}
      {!collapsed && (
        <div style={{
          marginTop: 6,
          fontSize: 14, color: '#5a4a1f', fontStyle: 'italic',
          fontFamily: "'Frank Ruhl Libre', serif", lineHeight: 1.6,
          marginBottom: 14, fontWeight: 500,
        }}>
          תוכל לשלוח לכל אחד תודה אישית, מתי שתרצה.
        </div>
      )}
      {!collapsed && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {readers.map(r => (
            <MyReaderRow
              key={r.reader_id}
              reader={r}
              fromUserId={userId}
              onSent={() => setTick(t => t + 1)}
            />
          ))}
        </div>
      )}
    </section>
  );
}

// ───────── [yakir 2026-05-18] DailyQuote — 'מחשבה ליום מהרבי' on home ─────────
function DailyQuote() {
  const [state, setState] = React.useState({ loading: true });
  React.useEffect(() => {
    let cancelled = false;
    fetch('/api/quote-of-day')
      .then(r => r.json())
      .then(d => { if (!cancelled) setState({ loading: false, data: d }); })
      .catch(() => { if (!cancelled) setState({ loading: false, data: { ok: false } }); });
    return () => { cancelled = true; };
  }, []);

  if (state.loading) return null;
  const d = state.data || {};
  if (!d.ok) return null; // silent skip if no quote for today yet

  return (
    <div className="daily-quote-card">
      <div className="dq-eyebrow">💡 מחשבה ליום מהרבי</div>
      <div className="dq-day">{d.day_label}</div>
      <div className="dq-title">{d.title}</div>
      <div className="dq-body">{d.body}</div>
      <div className="dq-source">— {d.source}</div>
      <div className="dq-credit">
        מתוך &laquo;יום יום משיח וגאולה&raquo;, הוצאת קה"ת
      </div>
    </div>
  );
}

// ───── INTEGRATION PREVIEW (Yakir 2026-05-18) ─────
// Mounts the REAL ReadModal with mock circle + initialResult so Yakir
// sees the actual production integration screen, not a copy. Activated
// via ?preview=integration. Fetches will 404 silently (mock IDs); the
// rendered surface is the source-of-truth result view itself.
function IntegrationPreview() {
  // Ensure a stable, signed-in-looking session so the "save your coin" CTA
  // does NOT show, and BEU balances render the logged-in variant.
  React.useEffect(() => {
    try {
      if (!localStorage.getItem('tehillim_user_id')) localStorage.setItem('tehillim_user_id', '999');
      if (!localStorage.getItem('tehillim_user_name')) localStorage.setItem('tehillim_user_name', 'preview');
      if (!localStorage.getItem('tehillim_google_sub')) localStorage.setItem('tehillim_google_sub', 'preview-sub');
    } catch (_) {}
  }, []);
  const mockCircle = React.useMemo(() => ({
    id: 'preview',
    name: 'מעגל preview',
    text_type: 'tehillim',
    total_chapters: 150,
    share_token: 'preview-token',
    readings: [],
    creator_id: 999,
    creator_name: 'preview',
  }), []);
  const mockResult = React.useMemo(() => ({
    verified: true,
    chapter: 90,
    reading_id: 99999,
    chapter_reward: { amount: 11, currency_kind: 'BEU' },
    circle_progress: 5,
    participants_rewarded: null,
    tokens_per_participant: null,
    inviter_name: null,
    verification: { reason: '' },
  }), []);
  return (
    <div>
      <div style={{
        position: 'sticky', top: 0, zIndex: 10000,
        background: '#fef9e7', padding: '8px 14px',
        fontSize: 12.5, color: '#5a3d0a',
        borderBottom: '1px dashed #d4af37',
        display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10,
      }}>
        <span>🔬 preview · ?preview=integration · המסך האמיתי עם mock נתונים</span>
        <button
          onClick={() => window.location.reload()}
          style={{
            padding: '5px 12px', background: 'var(--gold)', color: '#1a1a2e',
            border: 'none', borderRadius: 6, fontWeight: 700, cursor: 'pointer', fontSize: 12,
          }}>↻ הפעל מחדש</button>
      </div>
      <ReadModal
        circle={mockCircle}
        initialResult={mockResult}
        onClose={() => window.location.reload()}
        onDone={() => window.location.reload()}
      />
    </div>
  );
}

function App() {
  const [page, setPage] = useState(() => {
    try {
      const valid = ['home','tikkun-zohar','tanya','sefer-revii','marketplace','community','profile','leaderboard','contact','admin','vision','about','journal','transparency'];
      // 1. Check the pathname — supports clean URLs like /tanya
      //    that the server delivers with custom OG meta tags for sharing.
      // [yakir 2026-05-12 pass 3] Only honor SINGLE-segment paths as a page.
      // Multi-segment paths like /read/tanya, /tanya/invite/<t>, /c/<t> are
      // handled by their own dedicated routers (readTanyaIntent, inviteToken).
      // Previously .split('/').pop() returned 'tanya' for /read/tanya, which
      // incorrectly set page='tanya' and rendered the TanyaLandingPage instead
      // of opening the ReadModal as Yakir intended.
      const cleanPath = (window.location.pathname || '').replace(/^\/+/,'').replace(/\/+$/,'');
      if (cleanPath && !cleanPath.includes('/') && valid.includes(cleanPath)) return cleanPath;
      // 2. Fall back to hash routing for in-app navigation (#/tanya etc).
      const h = (window.location.hash || '').replace(/^#\/?/, '');
      return (h && valid.includes(h)) ? h : 'home';
    } catch { return 'home'; }
  });
  const [showSignIn, setShowSignIn] = useState(false);
  // [damri 2026-05-20] Param-based routing for #/circle/<id>. Single
  // pageParam slot holds the dynamic id when page is parametric.
  const [pageParam, setPageParam] = useState(null);

  // [Yakir 2026-05-18] ?preview=integration short-circuits the whole app to
  // the standalone IntegrationPreview surface — no auth, no fetches, no nav.
  const _previewMode = (() => {
    try { return new URLSearchParams(window.location.search).get('preview'); }
    catch { return null; }
  })();
  if (_previewMode === 'integration') return <IntegrationPreview />;


  // [lev-hadavar pass 26] Invite door routing — if the URL is
  // /tanya/invite/:t, /tikkun-zohar/invite/:t, /tehillim/invite/:t,
  // or /invite/:t, short-circuit to <InviteDoor />. Per Damri:
  // 'האפליקציה היא לא הדלת. החיבור הוא הדלת.'
  const inviteToken = React.useMemo(() => {
    // [lev-hadavar pass 34] Match BOTH new invite paths AND old /c/:token.
    // Old WhatsApp messages with /c/:token open the same human door.
    const p = window.location.pathname;
    let m = p.match(/^\/(?:tehillim|tanya|tikkun-zohar)?\/?invite\/([a-f0-9]{6,32})\/?$/i);
    if (!m) m = p.match(/^\/c\/([a-f0-9]{6,32})\/?$/i);
    return m ? m[1].toLowerCase() : null;
  }, []);
  // [yakir 2026-05-12] Direct-entry intent: /read or /read/tanya.
  // No landing, no door — open the user's next unread chapter immediately.
  // [yakir 2026-05-12 pass 5] Stateful (not useMemo): the read-tanya effect
  // sets this to false after opening the modal once, so closing the modal
  // (setReadCircle(null)) does NOT cause the effect to re-fetch and re-open.
  const [readTanyaIntent, setReadTanyaIntent] = useState(() => {
    const p = window.location.pathname || '';
    return /^\/read(\/tanya)?\/?$/i.test(p);
  });
  const [readIntentBusy, setReadIntentBusy] = useState(false);
  const [createOpen, setCreateOpen] = useState(false);
  const [readCircle, setReadCircle] = useState(null);
  const [refresh, setRefresh] = useState(0);
  const [userId, setUserId] = useState(() => {
    const stored = localStorage.getItem('tehillim_user_id');
    return stored ? parseInt(stored, 10) : null;
  });
  const [userName, setUserName] = useState(() => localStorage.getItem('tehillim_user_name') || '');
  const [googleSub, setGoogleSub] = useState(() => localStorage.getItem('tehillim_google_sub') || null);
  const [needsOnboarding, setNeedsOnboarding] = useState(false);
  const [isAdmin, setIsAdmin] = useState(false);
  const [refCode, setRefCode] = useState(() => {
    const url = new URLSearchParams(window.location.search).get('ref');
    if (url) { localStorage.setItem('tehillim_ref', url); return url; }
    return localStorage.getItem('tehillim_ref') || null;
  });
  const [pendingCircleToken, setPendingCircleToken] = useState(() => {
    const url = new URLSearchParams(window.location.search).get('circle_token');
    if (url) { localStorage.setItem('tehillim_circle_token', url); return url; }
    return localStorage.getItem('tehillim_circle_token') || null;
  });

  // [A2] cross-component navigation: MyProfile tile dispatches an event so it
  // doesn't need a setPage prop. Damri can rip this once Nav has the entry.
  useEffect(() => {
    const onNav = (e) => { if (e && e.detail && e.detail.page) setPage(e.detail.page); };
    window.addEventListener('app:navigate', onNav);
    return () => window.removeEventListener('app:navigate', onNav);
  }, []);

  // Hash-based routing - every nav item gets its own URL so it can be shared/bookmarked.
  // Format: /#/coins, /#/tikkun-zohar, etc. Empty hash = home.
  const VALID_PAGES = React.useMemo(() => new Set([
    'home','about','tikkun-zohar','tanya','sefer-revii','marketplace','community',
    'profile','leaderboard','contact','admin','journey','vision','companion','journal','transparency',
    'upcoming','rebbe','ecosystem','circle-detail','dashboard','join'
  ]), []);
  // Pages that are publicly viewable without sign-in (added 2026-05-08).
  // SignInGate is only forced on non-public pages or when explicitly opened
  // via setShowSignIn(true) (e.g. user clicks a "earn coins" CTA).
  const PUBLIC_PAGES = React.useMemo(() => new Set([
    // Per לב הדבר 2026-05-10: home is the door — fully browseable
    // anonymously. Signin only appears when user clicks 'היכנס' or
    // tries an action that requires auth.
    // [yakir pass 35] 'coins' removed - page was deprecated.
    'home','about','tanya','tikkun-zohar','sefer-revii','leaderboard','marketplace','community','journal','transparency','rebbe','upcoming','user-profile','rebbim'
  ]), []);
  useEffect(() => {
    const fromHash = () => {
      const h = (window.location.hash || '').replace(/^#\/?/, '');
      // [damri 2026-05-20] Parametric route: #/circle/<id> → page=circle-detail
      const circleMatch = h && h.match(/^circle\/(\d+)$/);
      if (circleMatch) {
        setPage('circle-detail'); setPageParam(parseInt(circleMatch[1], 10));
        return;
      }
      // [handoff §10 2026-05-24] Parametric route: #/user/<id> → page=user-profile
      const userMatch = h && h.match(/^user\/(\d+)$/);
      if (userMatch) {
        setPage('user-profile'); setPageParam(parseInt(userMatch[1], 10));
        return;
      }
      // [damri 2026-05-24] /i/<token> -> identity-recognition page (pathname-based, no hash)
      const idMatch = (window.location.pathname || '').match(/^\/i\/([A-Za-z0-9\-]+)$/);
      if (idMatch) {
        setPage('identity-recognition'); setPageParam(idMatch[1]);
        return;
      }
      if (h && VALID_PAGES.has(h)) { setPage(h); setPageParam(null); return; }
      // No (valid) hash — check pathname before defaulting to home, so a
      // shared link like /tanya doesn't get reset on first paint.
      const pathSeg = (() => { const cp = (window.location.pathname || '').replace(/^\/+/,'').replace(/\/+$/,''); return (cp && !cp.includes('/')) ? cp : ''; })();
      if (pathSeg && VALID_PAGES.has(pathSeg)) { setPage(pathSeg); setPageParam(null); return; }
      setPage('home'); setPageParam(null);
    };
    fromHash();
    window.addEventListener('hashchange', fromHash);
    return () => window.removeEventListener('hashchange', fromHash);
  }, [VALID_PAGES]);
  useEffect(() => {
    // If the current pathname already encodes this page (e.g. /tanya for page='tanya'),
    // don't append a redundant hash — keep the URL clean for sharing.
    const pathSeg = (() => { const cp = (window.location.pathname || '').replace(/^\/+/,'').replace(/\/+$/,''); return (cp && !cp.includes('/')) ? cp : ''; })();
    if (pathSeg && pathSeg === page) {
      if (window.location.hash) {
        try {
          window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}`);
        } catch (e) { /* old browsers - ignore */ }
      }
      return;
    }
    // [damri 2026-05-20] Preserve param when page is circle-detail
    let target = '';
    if (page !== 'home') {
      if (page === 'circle-detail' && pageParam) target = `#/circle/${pageParam}`;
      else if (page === 'user-profile' && pageParam) target = `#/user/${pageParam}`;
      else target = `#/${page}`;
    }
    if (window.location.hash === target) return;
    try {
      window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}${target}`);
    } catch (e) { /* old browsers - ignore */ }
  }, [page]);

  // When a userId is already present (localStorage), verify whether onboarding was
  // completed. Legacy users with no google_sub keep their existing access.
  useEffect(() => {
    if (!userId) { setIsAdmin(false); return; }
    api(`/api/me/${userId}`).then(r => setIsAdmin(!!r.is_admin));
    if (googleSub) {
      api(`/api/profile/${userId}`).then(p => {
        if (!p || p.error) return;
        const hasMission = !!(p.mission && p.mission.trim());
        const vals = Array.isArray(p.values) ? p.values : [];
        const sks = Array.isArray(p.skills) ? p.skills : [];
        if (!hasMission && vals.length === 0 && sks.length === 0) setNeedsOnboarding(true);
      });
    }
  }, [userId, googleSub]);

  // Deep-link via /?circle_token=<t>: once user is signed in and not onboarding,
  // resolve the token, POST join (rewards creator BEU), and open the read modal.
  useEffect(() => {
    // [P0 fix 2026-05-13 pass 19] Removed needsOnboarding from guard.
    // Open the chapter regardless of profile state.
    if (!pendingCircleToken || !userId) return;
    let cancelled = false;
    (async () => {
      try {
        const c = await api(`/api/circle/by-token/${encodeURIComponent(pendingCircleToken)}`);
        if (cancelled || !c || c.error) return;
        api(`/api/circles/${encodeURIComponent(pendingCircleToken)}/join`, {
          method: 'POST', body: JSON.stringify({ user_id: userId })
        }).catch(() => {});
        if (!c.is_complete) setReadCircle(c);
      } finally {
        localStorage.removeItem('tehillim_circle_token');
        setPendingCircleToken(null);
      }
    })();
    return () => { cancelled = true; };
  }, [pendingCircleToken, userId, needsOnboarding]);

  // [yakir 2026-05-12] Direct-entry resolver. When the URL is /read or
  // /read/tanya: if signed in, fetch active tanya circle + user's next
  // unread chapter and open the ReadModal at that chapter — no landing.
  // If not signed in, force the SignIn modal (post-auth this effect re-runs
  // and opens the modal).
  useEffect(() => {
    // [P0 fix 2026-05-13 pass 19] Removed needsOnboarding guard.
    // A user mid-onboarding should still be able to READ. Profile
    // completion happens at their own pace from a profile page later,
    // not as a blocker on the door of every chapter.
    if (!readTanyaIntent) return;
    if (readCircle || readIntentBusy) return;
    // [yakir 2026-05-12 pass 5] Open the ReadModal even for anonymous visitors.
    // The reading view is informational; the ReadModal's own submit() handles
    // user creation (name field) or sign-in at the moment of verification —
    // not before the chapter is read. This is critical for the friend flow:
    // they should be able to land on פרק 7, read it, and only then be asked
    // for a name. Asking for sign-in BEFORE reading is friction Yakir wants
    // explicitly removed.
    let cancelled = false;
    setReadIntentBusy(true);
    (async () => {
      try {
        // [damri 2026-05-20 pass2] Three-tier resolution:
        //   1. Modern link ?c=X&ch=Y  → use directly.
        //   2. Legacy link ?from=N    → fetch sender's recent circle + last
        //      chapter. Old WhatsApp messages still route correctly.
        //   3. No params              → fall back to global active-circle +
        //      viewer's next-unread (original behavior).
        const _urlP = new URLSearchParams(window.location.search);
        let _wantCircleId = parseInt(_urlP.get('c') || '0', 10);
        let _wantChapter = parseInt(_urlP.get('ch') || '0', 10);
        const _fromId = parseInt(_urlP.get('from') || '0', 10);
        if (!_wantCircleId && _fromId > 0) {
          try {
            const _rc = await api(`/api/user/${_fromId}/recent-circle`);
            if (_rc && !_rc.error && _rc.id) {
              _wantCircleId = _rc.id;
              if (!_wantChapter && _rc.last_chapter) _wantChapter = _rc.last_chapter;
            }
          } catch (_) { /* fall through to active-circle */ }
        }
        const [circle, next] = await Promise.all([
          _wantCircleId > 0
            ? api(`/api/circle/${_wantCircleId}`)
            : api('/api/tanya/active-circle'),
          _wantChapter > 0
            ? Promise.resolve({ chapter: _wantChapter })
            : api(userId ? `/api/tanya/next-chapter?user_id=${userId}`
                         : '/api/tanya/next-chapter'),
        ]);
        if (cancelled) return;
        if (!circle || circle.error) return;
        // Fetch full circle (with readings) so ReadModal renders user/others state.
        const full = await api(`/api/circle/${circle.id}`);
        if (cancelled) return;
        const enriched = (full && !full.error)
          ? { ...full, _initialChapter: (next && next.chapter) || 1 }
          : { ...circle, _initialChapter: (next && next.chapter) || 1 };
        setReadCircle(enriched);
        // [yakir 2026-05-12 pass 8] Chapter locking: if user is signed in,
        // reserve this chapter so a second reader who clicks the same link
        // gets the NEXT gap. Anonymous users skip the reservation (no user_id
        // to lock against) — accepted trade-off since anonymous double-reads
        // are rare and both readers still get credit when they verify.
        const ch = (next && next.chapter) || enriched._initialChapter;
        if (userId && ch) {
          api('/api/tanya/reserve', { method: 'POST',
            body: { user_id: userId, circle_id: enriched.id, chapter: ch }
          }).catch(() => {}); // fire-and-forget
        }
        // [yakir 2026-05-12 pass 5] One-shot: clear the intent so that when the
        // user closes the modal (setReadCircle(null)), the effect doesn't see
        // intent=true and refetch + reopen. The URL stays as /read/tanya, but
        // the intent state is consumed.
        setReadTanyaIntent(false);
      } catch (e) {
        console.error('[read-tanya intent]', e && e.message);
      } finally {
        if (!cancelled) setReadIntentBusy(false);
      }
    })();
    return () => { cancelled = true; };
  }, [readTanyaIntent, userId, needsOnboarding, readCircle]);
  // [yakir 2026-05-12 pass 4] CRITICAL: readIntentBusy intentionally NOT in deps.
  // setReadIntentBusy(true) inside the effect was causing it to re-run, the
  // cleanup of the first run was cancelling the in-flight fetch, and the
  // second run was short-circuiting on the busy guard — so setReadCircle()
  // was never reached and the modal never opened.

  const onCreated = (c) => {
    setCreateOpen(false);
    setRefresh(r => r + 1);
    // [yakir 2026-05-16] After creating a circle, open it directly so the
    // user doesn't have to hunt for it in the Circles list (which is
    // sorted by progress — a fresh 0% circle lands at the bottom and the
    // user can mistakenly click an older sibling with the same text_type).
    // `c` is the full circle row from POST /api/circle, including
    // text_parts, so ReadModal renders the right part subtitle on first
    // paint with no race.
    if (c && c.id) setReadCircle(c);
  };
  const onRead = () => {
    setReadCircle(null);
    setRefresh(r => r + 1);
    // [yakir 2026-05-12] If user closed the read-tanya direct modal, send
    // them home so a refresh doesn't reopen it on a chapter they may have
    // already finished.
    if (readTanyaIntent) {
      try { window.history.replaceState(null, '', '/'); } catch (e) {}
      setPage('home');
    }
  };

  const onAuthed = (r) => {
    setUserId(r.user_id);
    setUserName(r.name || '');
    setGoogleSub(r.google_sub);
    setIsAdmin(!!r.is_admin);
    setNeedsOnboarding(!!r.needs_onboarding);
    setPage(r.needs_onboarding ? 'home' : 'profile');
  };

  const onOnboardDone = () => {
    setNeedsOnboarding(false);
    setPage('profile');
  };

  // Share modal state (desktop fallback for navigator.share)
  const [shareModalData, setShareModalData] = useState(null);

  // Expose opener as window-global so the shareCircle helper can trigger it
  useEffect(() => {
    window.__openShareModal = (data) => setShareModalData(data);
    return () => { delete window.__openShareModal; };
  }, []);

  // [lev-hadavar pass 26] Early return for invite doors. When the URL
  // is /tanya/invite/:t, /tikkun-zohar/invite/:t, /tehillim/invite/:t,
  // or /invite/:t — render only <InviteDoor />, bypass the whole app.
  // Per Damri: 'האפליקציה היא לא הדלת. החיבור הוא הדלת.'
  if (inviteToken) {
    return <InviteDoor token={inviteToken} />;
  }

  return (
    <>
      <Nav page={page} onPage={setPage} isAdmin={isAdmin}
           isSignedIn={!!userId} userId={userId}
           onStartJourney={() => {
             if (!userId) { setShowSignIn(true); return; }
             setPage('home');
             setTimeout(() => {
               const el = document.getElementById('journey');
               if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
             }, 60);
           }} />
      {shareModalData && (
        <ShareModal data={shareModalData} onClose={() => setShareModalData(null)} />
      )}
      {refCode && !userId && (
        <RefInviteBanner refCode={refCode} variant="top" />
      )}
      {page === 'home' && (
        <>
          {/* [yakir 2026-05-14] Small "new here?" strip — only visible to
              anonymous visitors. Links to /about for the full explainer.
              Hidden once user signs in (no reason to nudge them again). */}
          {!userId && (
            <div style={{
              maxWidth: 720, margin: '12px auto 0', padding: '10px 16px',
              background: 'linear-gradient(135deg, rgba(212,175,55,0.10) 0%, rgba(184,148,31,0.06) 100%)',
              border: '1px dashed rgba(212,175,55,0.50)',
              borderRadius: 10,
              fontSize: 13,
              direction: 'rtl', textAlign: 'center',
              color: '#5a3d0a',
              fontFamily: "'Frank Ruhl Libre', serif",
              cursor: 'pointer',
              transition: 'background 0.2s ease',
            }}
            onClick={() => setPage('about')}
            onMouseEnter={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(212,175,55,0.18) 0%, rgba(184,148,31,0.12) 100%)'; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(212,175,55,0.10) 0%, rgba(184,148,31,0.06) 100%)'; }}
            >
              👋 <strong>חדש/ה כאן?</strong> &nbsp;לחץ לקרוא <strong>מה זה בעצם</strong>, איך זה עובד, ומה אנחנו בונים יחד&nbsp;←
            </div>
          )}

          {/* [damri 2026-05-24 round B] Circles moved to the very TOP of home,
              above HeroDoor and DailyQuote. The shared journey starts with seeing
              who's already walking. */}
          <div id="circles-section">
            <Circles onOpen={() => setCreateOpen(true)} refreshKey={refresh} onJoin={c => setReadCircle(c)} userId={userId} />
          </div>
          <HeroDoor
            isSignedIn={!!userId}
            onSignIn={() => setShowSignIn(true)}
            onPage={setPage}
          />
          {/* [yakir 2026-05-18] 'מחשבה ליום מהרבי' — auto-hides if no quote for today. */}
          <DailyQuote />
          {/* [yakir pass 20] Wallet + updates strip — only for signed-in users.
              Shows the coin balances they've earned and recent notifications
              (e.g. "you and the founder both got 5 TNY for chapter 7"). */}
          {userId && <BalanceStrip userId={userId} refresh={refresh} />}
          {/* [damri 2026-05-24 round B] Oved strip — quiet AI companion that
              walks with the user. NOT a separate nav section; lives inline in
              the home flow. Uses existing /companion page under the hood. */}
          {userId && <OvedStrip onOpen={() => setPage('companion')} />}
          {userId && <UpdatesPanel userId={userId} refresh={refresh} />}
          <div id="living-window"><LivingWindow /></div>
        </>
      )}
      {page === 'contact' && <ContactPage userId={userId} userName={userName} />}
      {page === 'tikkun-zohar' && <TikkunZoharLandingPage userId={userId} onJoinCircle={() => setCreateOpen(true)} onSignIn={() => setShowSignIn(true)} />}
      {page === 'tanya' && <TanyaLandingPage userId={userId} onCreateCircle={() => setCreateOpen(true)} onJoinCircle={() => {
        // [P0 fix 2026-05-13 pass 19] No more sign-in wall on join.
        // Anonymous users land directly on the chapter via /read/tanya.
        // Signed-in users still get the explicit /?circle_token= path
        // so the inviter receives BEU referral credit.
        if (!userId) { window.location.href = '/read/tanya'; return; }
        window.location.href = '/?circle_token=493aee47e40b';
      }} onSignIn={() => setShowSignIn(true)} />}
      {page === 'sefer-revii' && <SeferReviiRabbiPage />}
      {/* [yakir pass 35] coins page removed - redirect handled in router; CoinsPage definition kept for legacy compatibility */}
      {page === 'transparency' && <TransparencyPage />}
      {page === 'about' && <AboutPage userId={userId} onSignIn={() => setShowSignIn(true)} onStartReading={() => { window.location.href = '/read/tanya'; }} onBack={() => setPage('home')} />}
      {page === 'upcoming' && <UpcomingPage userId={userId} onSignInRequest={() => setShowSignIn(true)} />}
      {page === 'marketplace' && <Marketplace userId={userId} />}
      {page === 'community' && <CommunityPage userId={userId} />}
      {page === 'profile' && <MyProfile userId={userId} onOnboard={() => setNeedsOnboarding(true)} refresh={refresh} setRefresh={setRefresh} />}
      {page === 'leaderboard' && <CommunityPresence refreshKey={refresh} />}
      {page === 'vision' && <VisionPage userId={userId} />}
      {page === 'companion' && (
        <CompanionPage userId={userId} onBack={() => setPage('profile')} />
      )}
      {page === 'rebbe' && <RebbeChat userId={userId} onClose={() => setPage('home')} />}
      {page === 'rebbim' && <RebbimPage userId={userId} onPage={setPage} />}
      {page === 'ecosystem' && <EcosystemPage userId={userId} />}
      {page === 'circle-detail' && <CircleDetailPage circleId={pageParam} userId={userId} />}
      {page === 'user-profile' && <PublicProfilePage profileUserId={pageParam} viewerId={userId} onPage={setPage} />}
      {page === 'identity-recognition' && <IdentityRecognitionPage token={pageParam} />}
      {page === 'join' && <JoinQuestionnaire />}
      {page === 'journey' && (
        <section style={{ paddingTop: 40, paddingBottom: 40 }}>
          <div className="container">
            <div style={{ textAlign: 'center', marginBottom: 24 }}>
              <button
                onClick={() => setPage('profile')}
                style={{ background: 'none', border: 'none', color: '#a8801f', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}
              >← חזור לפרופיל</button>
            </div>
            <JourneyOfDiscovery userId={userId} onAnswered={() => setRefresh(r => r + 1)} />
          </div>
        </section>
      )}
      {page === 'journal' && <JournalPage />}
      {page === 'dashboard' && <DashboardPage userId={userId} />}
      {page === 'admin' && (isAdmin
        ? <AdminPanel adminId={userId} />
        : <AdminCodeGate onAuthenticated={(r) => {
            // Re-sync state from localStorage that gate just wrote
            setUserId(r.user_id);
            setUserName(r.name || '');
            setIsAdmin(true);
          }} />
      )}

      {/* Auth gate: forced only when (a) user clicks a CTA that requires sign-in
          (showSignIn===true) or (b) the current page is NOT in PUBLIC_PAGES.
          Public landing pages (tanya, tikkun-zohar, sefer-revii) are viewable
          anonymously; users only sign in when they want to claim coins. */}
      {(showSignIn || (!userId && !PUBLIC_PAGES.has(page))) && <SignInGate onAuthed={(r) => { onAuthed(r); setShowSignIn(false); }} onClose={() => {
        setShowSignIn(false);
        // If we're on a non-public page, the second condition would force
        // the modal back open. Send the user home so X actually dismisses.
        if (!PUBLIC_PAGES.has(page)) setPage('home');
      }} pendingCircleToken={pendingCircleToken} />}

      {/* Post-auth onboarding: only when the signed-in user has an empty profile */}
      {/* [P0 fix 2026-05-13 pass 19] OnboardingModal does NOT interrupt reading.
          If any reading flow is active (readTanyaIntent path, pending circle join,
          or ReadModal already open), the onboarding gets deferred. The user can
          complete their profile later from a settings/profile page. */}
      {userId && needsOnboarding && !readTanyaIntent && !readCircle && !pendingCircleToken && (
        <OnboardingModal
          refCode={refCode}
          userId={userId}
          googleSub={googleSub}
          googleName={userName}
          onDone={(r) => { setRefCode(null); onOnboardDone(r); }}
          onClose={() => setNeedsOnboarding(false)}
        />
      )}
      {createOpen && <CreateModal onClose={() => setCreateOpen(false)} onCreated={onCreated} />}
      {readCircle && <ReadModal circle={readCircle} onClose={() => setReadCircle(null)} onDone={onRead} />}
      {/* [damri 2026-05-24 round D] חצר הרבים as floating widget (visible to all signed-in users) */}
      {userId && <FloatingRebbim userId={userId} />}
    </>
  );
}


// ───────── Community Task Bank (added 2026-05-03 by claude) ─────────
// ───────── Contact Page (added 2026-05-03 by claude — yakir's request) ─────────
function ContactPage({ userId, userName }) {
  const [topic, setTopic] = React.useState('שאלה');
  const [name, setName] = React.useState(userName || '');
  const [phone, setPhone] = React.useState('');
  const [email, setEmail] = React.useState('');
  const [message, setMessage] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const [sent, setSent] = React.useState(false);
  const [err, setErr] = React.useState('');

  const submit = async () => {
    setErr('');
    if (!message.trim() || message.trim().length < 5) {
      setErr('נא להוסיף הודעה (לפחות 5 תווים)'); return;
    }
    if (!name.trim() && !userId) {
      setErr('נא למלא שם'); return;
    }
    // [yakir-fb#4 2026-05-24] always require a contact channel, even for signed-in users
    if (!phone.trim() && !email.trim()) {
      setErr('נא למלא טלפון או אימייל כדי שנוכל לחזור אליך'); return;
    }
    setSending(true);
    try {
      const r = await api('/api/contact', {
        method: 'POST',
        body: JSON.stringify({
          user_id: userId || null,
          name: name.trim() || userName || '',
          phone: phone.trim(),
          email: email.trim(),
          topic, message: message.trim()
        })
      });
      if (r && r.ok) {
        setSent(true);
        setMessage('');
      } else {
        setErr((r && r.error) || 'שליחה נכשלה — נסה שוב או פנה ב-WhatsApp');
      }
    } catch (e) {
      setErr(e.message || 'שגיאה');
    } finally {
      setSending(false);
    }
  };

  return (
    <div className="container" style={{ padding: '40px 20px', maxWidth: 640 }}>
      <h1 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 36, marginBottom: 6 }}>צור קשר</h1>
      <p style={{ color: '#666', marginBottom: 24, fontSize: 15 }}>
        שאלה, רעיון, שיתוף פעולה? כתבו לנו ונחזור אליכם.
      </p>

      {sent ? (
        <div style={{
          background: 'rgba(212,160,23,0.12)',
          border: '1px solid rgba(168,128,10,0.45)',
          borderRadius: 14, padding: '24px 28px', textAlign: 'center'
        }}>
          <div style={{ fontSize: 40 }}>🙏</div>
          <h2 style={{ marginTop: 8 }}>תודה — ההודעה התקבלה</h2>
          <p style={{ color: '#555' }}>נחזור אליכם בקרוב.</p>
          <button className="btn" onClick={() => { setSent(false); }}>שלח הודעה נוספת</button>
        </div>
      ) : (
        <div style={{ background: 'white', border: '1px solid #e8d8a8', borderRadius: 14, padding: 24 }}>
          <label style={{ display: 'block', marginBottom: 12 }}>
            <div style={{ fontSize: 14, marginBottom: 4 }}>נושא</div>
            <select value={topic} onChange={e => setTopic(e.target.value)} style={{ width: '100%', padding: 10, fontSize: 15 }}>
              <option>שאלה</option>
              <option>תלונה</option>
              <option>רעיון לפיתוח</option>
              <option>שיתוף פעולה</option>
              <option>אחר</option>
            </select>
          </label>

          {/* [yakir-fb#4 2026-05-24] phone+email shown for ALL users; name only for guests */}
          {!userId && (
            <label style={{ display: 'block', marginBottom: 12 }}>
              <div style={{ fontSize: 14, marginBottom: 4 }}>שם <span style={{ color: '#c00' }}>*</span></div>
              <input value={name} onChange={e => setName(e.target.value)} style={{ width: '100%', padding: 10, fontSize: 15 }} maxLength={80} />
            </label>
          )}
          <label style={{ display: 'block', marginBottom: 12 }}>
            <div style={{ fontSize: 14, marginBottom: 4 }}>טלפון <span style={{ color: '#888', fontSize: 12 }}>(טלפון או אימייל — חובה אחד מהם)</span></div>
            <input value={phone} onChange={e => setPhone(e.target.value)} placeholder="050-..." style={{ width: '100%', padding: 10, fontSize: 15 }} maxLength={30} />
          </label>
          <label style={{ display: 'block', marginBottom: 12 }}>
            <div style={{ fontSize: 14, marginBottom: 4 }}>אימייל</div>
            <input value={email} onChange={e => setEmail(e.target.value)} placeholder="you@example.com" style={{ width: '100%', padding: 10, fontSize: 15 }} maxLength={120} />
          </label>

          <label style={{ display: 'block', marginBottom: 16 }}>
            <div style={{ fontSize: 14, marginBottom: 4 }}>הודעה <span style={{ color: '#c00' }}>*</span></div>
            <textarea
              rows={6}
              value={message}
              onChange={e => setMessage(e.target.value)}
              placeholder="כתבו לנו..."
              maxLength={2000}
              style={{ width: '100%', padding: 10, fontSize: 15, resize: 'vertical', fontFamily: 'inherit' }}
            />
          </label>

          {err && <div style={{ color: '#c00', fontSize: 14, marginBottom: 12 }}>{err}</div>}

          <button
            className="btn"
            onClick={submit}
            disabled={sending}
            style={{ width: '100%', padding: '12px 20px', fontSize: 16, fontWeight: 700 }}
          >
            {sending ? 'שולח...' : 'שלח הודעה'}
          </button>
        </div>
      )}
    </div>
  );
}

// ───────── Live Reading Highlighter (added 2026-05-03, updated 2026-05-03 — click-to-advance fallback) ─────────
function HighlightedPsalm({ text, headIndex, onWordClick, recording, fontSize, lineHeight }) {
  // Split keeping spaces; assign idx only to non-space tokens.
  const parts = React.useMemo(() => {
    const out = [];
    let wordIdx = 0;
    const re = /(\s+|[^\s]+)/g;
    let m;
    while ((m = re.exec(text || '')) !== null) {
      const t = m[0];
      if (/^\s+$/.test(t)) {
        out.push({ type: 'space', text: t });
      } else {
        out.push({ type: 'word', text: t, idx: wordIdx++ });
      }
    }
    return out;
  }, [text]);
  const currentRef = React.useRef(null);
  React.useEffect(() => {
    // Only scroll into view while actively recording (user is reading along).
    if (recording && currentRef.current && currentRef.current.scrollIntoView) {
      try {
        currentRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
      } catch (e) {}
    }
  }, [headIndex, recording]);
  // [yakir pass 46] Apply font-size and line-height inline on the .psalm-highlight
  // span so the user's size choice wins over ANY browser inheritance quirk.
  // Inline style has the highest CSS priority - nothing can override it.
  const hlStyle = (fontSize || lineHeight) ? { fontSize, lineHeight } : undefined;
  return (
    <span className="psalm-highlight" style={hlStyle}>
      {parts.map((p, i) => {
        if (p.type === 'space') return p.text;
        let cls = 'pw';
        if (p.idx < headIndex) cls += ' pw-read';
        else if (p.idx === headIndex) cls += ' pw-cur';
        const ref = p.idx === headIndex ? currentRef : null;
        const handleClick = onWordClick ? () => onWordClick(p.idx) : undefined;
        return (
          <span
            key={i}
            ref={ref}
            className={cls}
            onClick={handleClick}
            style={onWordClick ? { cursor: 'pointer' } : undefined}
            title={onWordClick ? 'לחץ לסימון עד כאן' : undefined}
          >
            {p.text}
          </span>
        );
      })}
    </span>
  );
}

// ───────── Coins Explanation Page (added 2026-05-01) ─────────
function CoinsPage({ userId, onJoinCircle }) {
  const [coins, setCoins] = React.useState([]);
  const [services, setServices] = React.useState([]);
  const [shareUrl, setShareUrl] = React.useState('');

  React.useEffect(() => {
    api('/api/coins').then(d => { if (Array.isArray(d?.coins)) setCoins(d.coins); });
    api('/api/marketplace/preview').then(d => { if (Array.isArray(d?.services)) setServices(d.services); });
    if (userId) api(`/api/referral/${userId}`).then(d => { if (d?.url) setShareUrl(d.url); });
  }, [userId]);

  const shareInvite = () => {
    const text = `מצטרפים למעגל תניא של הקהילה — סיום ספר ביחד 📗\n${shareUrl || (window.location.origin + '/tanya')}`;
    if (navigator.share) navigator.share({ title: 'מעגל תניא ראשון', text }).catch(() => {});
    else { navigator.clipboard?.writeText(text); alert('הקישור הועתק — שתפו ב-WhatsApp / Instagram'); }
  };

  return (
    <div className="container" style={{ padding: '40px 20px', maxWidth: 980 }}>
      <section style={{ marginBottom: 56 }}>
        <h1 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 42, marginBottom: 8 }}>המטבעות שלנו</h1>
        <p style={{ color: '#666', marginBottom: 28 }}>חמישה מטבעות, כל אחד נברא מפעולה רוחנית אמיתית.</p>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16 }}>
          {coins.map(c => (
            <div key={c.symbol} style={{ background: 'white', border: '1px solid #e8d8a8', borderRadius: 14, padding: '20px 22px', boxShadow: '0 2px 12px rgba(0,0,0,0.04)' }}>
              <div style={{ fontSize: 36 }}>{c.icon}</div>
              <h3 style={{ margin: '8px 0 4px', fontSize: 20 }}>{c.label} <span style={{ color: '#8b6508', fontSize: 12 }}>({c.symbol})</span></h3>
              <p style={{ color: '#555', fontSize: 14, lineHeight: 1.6, margin: 0 }}>{c.desc}</p>
              {c.meaning && (
                <p style={{ color: '#3a2a08', fontSize: 13, lineHeight: 1.7, margin: '10px 0 0', fontStyle: 'italic', borderTop: '1px dashed #e8d8a8', paddingTop: 8 }}>
                  {c.meaning}
                </p>
              )}
              <div style={{ marginTop: 10, fontSize: 12, color: '#8b6508' }}>
                {typeof c.supply === 'number' ? `${c.supply.toLocaleString()} בכלל המערכת` : (c.total ? `${c.total} פרקים` : null)}
              </div>
            </div>
          ))}
        </div>
      </section>

      <section style={{ marginBottom: 56 }}>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 32 }}>איך משתמשים?</h2>
        <ol style={{ fontSize: 17, lineHeight: 2, paddingInlineStart: 24 }}>
          <li>פותחים מעגל / מצטרפים למעגל.</li>
          <li>קוראים פרק → המערכת מאמתת ורושמת.</li>
          <li>מעבירים מטבעות לחבר במצוקה / <strong>פוגשים מתנה בקהילה</strong> / שומרים לעצמכם.</li>
        </ol>
      </section>

      <section style={{ marginBottom: 56, background: 'rgba(212,160,23,0.08)', border: '1px solid rgba(168,128,10,0.45)', borderRadius: 14, padding: '24px 28px' }}>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 28, marginTop: 0 }}>למה זה שונה ממה שכבר קיים?</h2>
        <ul style={{ fontSize: 16, lineHeight: 1.9, paddingInlineStart: 22 }}>
          <li><strong>הצד הרוחני:</strong> הזכות עוברת — מסורת קדומה של ערבות הדדית.</li>
          <li><strong>הצד הטכני:</strong> שקוף, מאומת, לא ניתן לזיוף.</li>
          <li><strong>הצד הקהילתי:</strong> אנשים מאמינים בערך הזה ומתגמלים אחד את השני בו.</li>
        </ul>
      </section>

      <section style={{ marginBottom: 56 }}>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 32 }}>משאלות ומתנות</h2>
        <p style={{ color: '#666' }}>מתנות שאנשים מציעים בקהילה.</p>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: 14 }}>
          {services.map(s => (
            <div key={s.id} style={{ background: 'white', border: '1px solid #eee', borderRadius: 12, padding: 16 }}>
              <div style={{ fontSize: 28 }}>{s.icon}</div>
              <h4 style={{ margin: '6px 0 4px' }}>{s.title}</h4>
              <p style={{ color: '#666', fontSize: 13, margin: '0 0 8px' }}>{s.desc}</p>
              <div style={{ color: '#8b6508', fontWeight: 700, fontSize: 14 }}>{s.price} {s.currency}</div>
            </div>
          ))}
        </div>
      </section>

      <section style={{ marginBottom: 32, background: 'linear-gradient(135deg, #1a1a2e 0%, #2d1a3e 100%)', border: '1px solid rgba(212,175,55,0.25)', boxShadow: '0 4px 20px rgba(0,0,0,0.25)', color: 'white', borderRadius: 16, padding: '40px 32px', textAlign: 'center' }}>
        <div style={{ fontSize: 14, letterSpacing: 2, color: 'var(--gold)', marginBottom: 12 }}>מעגל פתוח</div>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 32, margin: '0 0 16px', color: 'var(--text)' }}>הצטרפו למעגל התניא של הקהילה</h2>
        <p style={{ fontSize: 16, lineHeight: 1.8, maxWidth: 580, margin: '0 auto 24px', color: '#c8c8d8' }}>
          סיום ספר התניא יחד. כל פרק שאת/ה קורא — חלק מהזכות הקהילתית שנוצרת.
        </p>
        <div style={{ display: 'flex', gap: 12, justifyContent: 'center', flexWrap: 'wrap' }}>
          <button className="btn" onClick={onJoinCircle} style={{ background: 'var(--gold)', color: '#1a1a2e', padding: '14px 28px', fontSize: 16, fontWeight: 700 }}>הצטרף למעגל</button>
          <button className="btn ghost" onClick={shareInvite} style={{ borderColor: 'var(--gold)', color: 'var(--gold)', padding: '14px 28px', fontSize: 16, fontWeight: 700 }}>🔗 שתף עם חבר</button>
        </div>
      </section>
    </div>
  );
}


// ───────── First Tanya Landing Page (added 2026-05-03) ─────────
// Dedicated, dramatic landing for the "מעגל תניא הראשון בעולם" launch — pulls
// live circle stats from /api/circles and frames the campaign with hero,
// social proof, why-join cards, how-it-works, a Tanya quote, and a final CTA.
function TikkunZoharLandingPage({ userId, onJoinCircle, onSignIn }) {
  const [stats, setStats] = useState({ joined: 0, chapters: 0, total: 70, percent: 0, leadCircleId: null });
  const [masterToken, setMasterToken] = useState(null);

  useEffect(() => {
    api('/api/circles').then(rows => {
      if (!Array.isArray(rows)) return;
      const tikkunim = rows.filter(c => c.text_type === 'tikkunei_zohar');
      if (!tikkunim.length) return;
      const lead = tikkunim.reduce((best, c) => {
        const prog = (c.verified_count || 0) / (c.total_chapters || 70);
        const bp = best ? (best.verified_count || 0) / (best.total_chapters || 70) : -1;
        return prog > bp ? c : best;
      }, null);
      if (lead && lead.share_token) setMasterToken(lead.share_token);
      const chapters = tikkunim.reduce((s, c) => s + (c.verified_count || 0), 0);
      const total = (lead && lead.total_chapters) || 70;
      const joined = new Set();
      tikkunim.forEach(c => { if (c.creator_id) joined.add(c.creator_id); });
      setStats({
        joined: Math.max(joined.size, tikkunim.length),
        chapters,
        total,
        percent: Math.min(100, Math.round((chapters / total) * 100)),
        leadCircleId: lead ? lead.id : null,
      });
    }).catch(() => {});
  }, []);

  const goJoin = () => {
    if (masterToken) {
      window.location.href = userId ? `/?circle_token=${masterToken}` : `/tikkun-zohar/invite/${masterToken}`;
    } else {
      onJoinCircle && onJoinCircle();
    }
  };
  const cta = userId ? goJoin : (onSignIn || goJoin);
  const ctaLabel = userId ? '📕 הצטרף וקרא תיקון ראשון' : '📕 הירשם בלחיצה אחת';

  const shareInvite = async () => {
    const link = masterToken ? `${window.location.origin}/tikkun-zohar/invite/${masterToken}` : window.location.origin;
    const text = `יוצאים יחד לקריאת תיקוני הזוהר — לכבוד רשב"י, ל"ג בעומר במירון 📕\n${link}`;
    try {
      if (navigator.share) { await navigator.share({ title: 'תיקוני הזוהר — לכבוד רשב"י', text }); return; }
    } catch {}
    try { await navigator.clipboard.writeText(text); alert('הקישור הועתק — שתפו ב-WhatsApp'); }
    catch { prompt('העתיקו ושתפו:', text); }
  };

  return (
    <div className="tanya-landing">
      <section className="tl-hero" style={{ background: 'linear-gradient(135deg, #1a0a2e 0%, #2a1545 50%, #4a2570 100%)' }}>
        <div className="tl-hero-bg" aria-hidden="true">
          <div className="tl-orb tl-orb-a" />
          <div className="tl-orb tl-orb-b" />
          <div className="tl-orb tl-orb-c" />
        </div>
        <div className="tl-hero-inner">
          <div className="tl-eyebrow">ל"ג בעומר במירון · לכבוד רשב"י</div>
          <h1 className="tl-title">
            מטבע <span className="tl-shine">תיקוני הזוהר</span> לכבוד רשב"י
          </h1>
          <p className="tl-sub">
            ביחד נסיים את כל שבעים התיקונים — שבעים תיקונים, סיום אחד.
            כל תיקון שאתם קוראים נחתם כמטבע <strong>זוהר</strong> אמיתי בארנק שלכם.
          </p>
          <div className="tl-cta-row">
            <button className="btn tl-cta-primary" onClick={cta}>{ctaLabel}</button>
            <button className="btn ghost tl-cta-ghost" onClick={shareInvite}>🔗 הזמינו חברים</button>
          </div>
          <div className="tl-trust">
            <span>✓ אין תשלום</span>
            <span>✓ אין צורך בידע מוקדם</span>
            <span>✓ קריאה אישית, מאומתת אוטומטית</span>
          </div>
        </div>
      </section>

      <section className="tl-stats">
        <div className="tl-stat">
          <div className="tl-stat-num">{stats.joined.toLocaleString('he-IL')}</div>
          <div className="tl-stat-lbl">קוראים שכבר הצטרפו</div>
        </div>
        <div className="tl-stat">
          <div className="tl-stat-num">{stats.chapters.toLocaleString('he-IL')} / {stats.total}</div>
          <div className="tl-stat-lbl">תיקונים שהושלמו</div>
        </div>
        <div className="tl-stat">
          <div className="tl-stat-num">{stats.percent}%</div>
          <div className="tl-stat-lbl">מהדרך לסיום</div>
        </div>
        <div className="tl-progress-wrap" aria-label={`${stats.percent} אחוז`}>
          <div className="tl-progress-bar" style={{ width: stats.percent + '%' }} />
        </div>
        <div className="tl-stats-foot">{stats.percent < 100 ? '✨ מצטרפים עכשיו ותהיו חלק מהסיום של ל"ג בעומר' : '🎉 הסיום קרוב מאוד — בואו לסגור את המעגל'}</div>
      </section>

      <section className="tl-section tl-intro">
        <h2 className="tl-h2">הזמנה היסטורית · השקת מטבע תיקוני הזוהר הראשון בעולם 🌍</h2>
        <p className="tl-lead">
          לראשונה בעולם — אנחנו לא רק לומדים את ספר תיקוני הזוהר…
          <br/>אנחנו יוצרים ממנו <strong>ערך חי</strong>.
          <br/>זהו המעגל הראשון שבו מונפקים מטבעות תיקוני הזוהר אמיתיים — מתוך הלימוד עצמו.
        </p>
      </section>

      <section className="tl-section tl-how">
        <h2 className="tl-h2">איך זה עובד?</h2>
        <ul className="tl-steps-bullets">
          <li><span className="tl-bullet-icon">📕</span><div><strong>קוראים תיקון</strong> — מקבלים מטבע</div></li>
          <li><span className="tl-bullet-icon">🎯</span><div><strong>מסיימים ביחד את הספר</strong> — 70 מטבעות לכל משתתף</div></li>
          <li><span className="tl-bullet-icon">✨</span><div><strong>בונוס נרשמים</strong> — 500 מטבעות BEU (Be You)</div></li>
          <li><span className="tl-bullet-icon">🤝</span><div><strong>בונוס מחברים</strong> — 25 מטבעות לכל מתחבר חדש שמביאים</div></li>
        </ul>
      </section>

      <section className="tl-section tl-uses">
        <h2 className="tl-h2">מה עושים עם המטבעות?</h2>
        <ul className="tl-steps-bullets">
          <li><span className="tl-bullet-icon">🛍️</span><div><strong>פוגשים</strong> מתנות בקהילה</div></li>
          <li><span className="tl-bullet-icon">💎</span><div><strong>שומרים</strong> — מתוך אמונה בערך העתידי שלהם</div></li>
          <li><span className="tl-bullet-icon">💝</span><div><strong>תורמים</strong> ומשפיעים על תודעת הקהילה</div></li>
        </ul>
      </section>

      <section className="tl-quote">
        <div className="tl-quote-mark">"</div>
        <blockquote>
          האמת היא — שזה הרבה מעבר למטבע. זו התחלה של כלכלה חדשה. כזו שנבנית מתוך נשמה, אמונה ונתינה.
          <br/>קהילה שבוחרת כבר עכשיו: להשקיע אחד בשני, לגלות את ה"איתן" שבתוכה, ולחיות גאולה בפועל.
        </blockquote>
      </section>

      <section className="tl-final">
        <div className="tl-final-inner">
          <h2>אם אתה מרגיש שזה שלך — זה הרגע להיכנס</h2>
          <p>להיות חלק ממשהו שמתחיל עכשיו. שמך נכנס לרשימת המייסדים של מעגל תיקוני הזוהר הראשון, לעד.</p>
          <div className="tl-cta-row">
            <button className="btn tl-cta-primary" onClick={cta}>👉 {ctaLabel}</button>
            <button className="btn ghost tl-cta-ghost" onClick={shareInvite}>🔗 שתפו עם חבר</button>
          </div>
          <div className="tl-trust" style={{ marginTop: 16 }}>
            <span>📍 70 תיקונים</span>
            <span>⏱️ ~5 דקות ליום</span>
            <span>🎁 בונוס סיום למצטרפים</span>
          </div>
        </div>
      </section>
    </div>
  );
}

// [yakir 2026-05-14] AboutPage — what is this place, for someone arriving
// for the first time. Built from Yakir's own framing: abundance, gifts,
// a place where personal contribution is recognized as value.
function AboutPage({ userId, onSignIn, onStartReading, onBack }) {
  const goBack = () => {
    if (typeof onBack === 'function') return onBack();
    window.location.hash = '';
    window.location.href = '/';
  };
  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 1400, background: '#0c1426' }}>
      <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#cdbfa3', fontFamily: 'serif', fontSize: 16, zIndex: 0 }}>טוען את החזון…</div>
      <button onClick={goBack} aria-label="חזרה" style={{ position: 'fixed', top: 14, insetInlineStart: 14, zIndex: 1410, background: 'rgba(22,34,63,0.85)', color: '#f3ecdc', border: '1px solid rgba(212,169,78,0.5)', borderRadius: 999, padding: '8px 18px', fontSize: 14, fontWeight: 600, cursor: 'pointer', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', fontFamily: 'inherit', boxShadow: '0 4px 18px rgba(0,0,0,0.3)' }}>חזרה</button>
      <iframe src="https://calm-bienenstitch-3d9180.netlify.app/" title="בית דוד — החזון" style={{ position: 'relative', zIndex: 1, display: 'block', width: '100%', height: '100%', border: 'none' }} />
    </div>
  );
}

function useCountUp(target, duration = 700) {
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    if (typeof window !== 'undefined' &&
        window.matchMedia &&
        window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
      setVal(target);
      return;
    }
    let raf = null;
    const start = performance.now();
    const from = 0;
    const to = Number(target) || 0;
    const tick = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - t, 3);   // ease-out cubic
      setVal(Math.round(from + (to - from) * eased));
      if (t < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => { if (raf) cancelAnimationFrame(raf); };
  }, [target, duration]);
  return val;
}

function FeaturedCircleHero({ onCreateCircle }) {
  const [c, setC] = React.useState(null);
  React.useEffect(() => {
    api('/api/circles/featured').then(d => {
      if (d && d.featured) setC(d.featured);
    }).catch(() => {});
  }, []);

  const bookLbl  = circleBookLabel(c || {});
  const total    = (c && c.total_chapters) || 0;
  const verified = (c && c.verified_count) || 0;
  const parts    = (c && c.participants_count) || 0;
  const progress = total > 0 ? Math.round((verified / total) * 100) : 0;
  const isComplete = !!(c && c.is_complete);
  const remaining = Math.max(0, total - verified);

  // Animated number reveals — only run once data has loaded.
  const verifiedAnim = useCountUp(c ? verified : 0, 900);
  const partsAnim    = useCountUp(c ? parts    : 0, 900);
  const progressAnim = useCountUp(c ? progress : 0, 1100);

  // Progress bar width transitions from 0 → progress over 1.2s via CSS.
  // Pin a slight delay so the bar starts visibly empty and fills in.
  const [barReady, setBarReady] = React.useState(false);
  React.useEffect(() => {
    if (!c) return;
    const id = window.setTimeout(() => setBarReady(true), 50);
    return () => window.clearTimeout(id);
  }, [c]);

  if (!c) return null;

  // "להיכנס למעגל" → re-enters the App at top with circle_token. The
  // existing share-token entry flow there fetches the circle and opens
  // ReadModal automatically — no duplicate logic here.
  const enterCircle = () => {
    if (c.share_token) {
      window.location.href = `/?circle_token=${c.share_token}`;
    } else {
      window.location.href = '/read/tanya';
    }
  };

  const scrollToParts = () => {
    const el = document.querySelector('.tl-parts');
    if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
  };
  // [yakir 2026-05-17 #2] The secondary CTA now opens CreateModal
  // directly rather than just scrolling to the parts cards — Yakir:
  // "כשנפתח דף נחיתה אי אפשר כרגע לפתוח משם מעגל". Falls back to
  // scroll if no opener was provided (e.g. anon visitor before signin).
  const openCreateOrScroll = () => {
    if (onCreateCircle) onCreateCircle();
    else scrollToParts();
  };

  // Eyebrow + CTA copy switch by state. Completion turns the hero
  // into a quiet shared-achievement celebration — same component, just
  // re-skinned and re-worded; no separate "complete circle" page needed.
  const eyebrowText = isComplete
    ? <>🎉 המעגל הסתיים — זכות משותפת לקהילה</>
    : (<>המעגל הנבחר עכשיו <span aria-hidden="true">✦</span></>);
  const ctaText = isComplete ? '🪙 לקבל את הזכות' : '✨ להיכנס למעגל';

  return (
    <section className={`featured-circle-hero${isComplete ? ' completed' : ''}${progress >= 80 && !isComplete ? ' near-end' : ''}`}>
      <div className="fch-bg" aria-hidden="true">
        <div className="fch-orb fch-orb-a" />
        <div className="fch-orb fch-orb-b" />
        {isComplete && <>
          <div className="fch-sparkle fch-sparkle-1" aria-hidden="true">✦</div>
          <div className="fch-sparkle fch-sparkle-2" aria-hidden="true">✧</div>
          <div className="fch-sparkle fch-sparkle-3" aria-hidden="true">✦</div>
        </>}
      </div>
      <div className="fch-inner">
        <div className="fch-eyebrow">{eyebrowText}</div>
        <h1 className="fch-name">{c.name}</h1>
        <div className="fch-book">📗 {bookLbl}</div>
        {c.purpose ? (
          <p className="fch-purpose">{c.purpose}</p>
        ) : null}

        <div className="fch-stats">
          <div className="fch-stat">
            <span className="fch-stat-num">{verifiedAnim}</span>
            <span className="fch-stat-lbl">פרקים נקראו</span>
            <span className="fch-stat-sub">מתוך {total}</span>
          </div>
          <div className="fch-stat">
            <span className="fch-stat-num">{partsAnim}</span>
            <span className="fch-stat-lbl">משתתפים</span>
            <span className="fch-stat-sub">בקהילה</span>
          </div>
          {progress > 0 && (
            <div className="fch-stat">
              <span className="fch-stat-num">{progressAnim}%</span>
              <span className="fch-stat-lbl">הושלם</span>
              <span className="fch-stat-sub">{c.creator_name ? `נפתח על ידי ${c.creator_name}` : ''}</span>
            </div>
          )}
        </div>

        {/* Progress bar — visualizes the chunk-by-chunk-together feeling */}
        {total > 0 && (
          <div className="fch-progress-wrap" aria-hidden="true">
            <div className="fch-progress-track">
              <div
                className="fch-progress-fill"
                style={{ width: barReady ? `${progress}%` : '0%' }}
              />
            </div>
            <div className="fch-progress-caption">
              {isComplete
                ? <>הסיום שייך לכל מי שהשתתף · בונוס למחזיקי המטבע</>
                : remaining > 0
                  ? <><strong>{remaining}</strong> פרקים נשארו עד שהמעגל יסתיים יחד</>
                  : <>המעגל בשלבים האחרונים</>
              }
            </div>
          </div>
        )}

        <div className="fch-cta-row">
          <button className="btn fch-cta-primary" onClick={enterCircle}>
            {ctaText}
          </button>
          <button className="link fch-cta-secondary" onClick={() => {
            if (onCreateCircle) { onCreateCircle(); return; }
            const el = document.querySelector('.tl-parts');
            if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
          }}>
            או פתחו מעגל משלכם ✨
          </button>
        </div>
      </div>
    </section>
  );
}

function TanyaLandingPage({ userId, onJoinCircle, onCreateCircle, onSignIn }) {
  const [stats, setStats] = useState({ joined: 0, chapters: 0, total: 53, percent: 0, leadCircleId: null });

  useEffect(() => {
    api('/api/circles').then(rows => {
      if (!Array.isArray(rows)) return;
      const tanya = rows.filter(c => c.text_type === 'tanya');
      if (!tanya.length) return;
      const lead = tanya.reduce((best, c) => {
        const prog = (c.verified_count || 0) / (c.total_chapters || 53);
        const bp = best ? (best.verified_count || 0) / (best.total_chapters || 53) : -1;
        return prog > bp ? c : best;
      }, null);
      const chapters = tanya.reduce((s, c) => s + (c.verified_count || 0), 0);
      const total = (lead && lead.total_chapters) || 53;
      const joined = new Set();
      tanya.forEach(c => { if (c.creator_id) joined.add(c.creator_id); });
      setStats({
        joined: Math.max(joined.size, tanya.length),
        chapters,
        total,
        percent: Math.min(100, Math.round((chapters / total) * 100)),
        leadCircleId: lead ? lead.id : null,
      });
    }).catch(() => {});
  }, []);

  const cta = userId ? onJoinCircle : (onSignIn || onJoinCircle);
  const ctaLabel = userId ? '📗 הצטרף למעגל הראשון' : '📗 הירשם בלחיצה אחת';
  const shareInvite = async () => {
    // [lev-hadavar pass 33] Fix: was sharing /tanya (the landing page),
    // which opened TanyaLandingPage instead of the personal door.
    // Now shares /tanya/invite/:master_token — same circle goJoin sends to.
    const masterToken = '493aee47e40b';
    const fromId = parseInt(localStorage.getItem('tehillim_user_id') || '0', 10);
    const fromSuffix = fromId > 0 ? `?from=${fromId}` : '';
    const link = `${window.location.origin}/tanya/invite/${masterToken}${fromSuffix}`;
    const text = `מצטרפים יחד לקריאת התניא 📗\n${link}`;
    try {
      if (navigator.share) { await navigator.share({ title: 'מעגל תניא — בוא/י לקרוא יחד', text }); return; }
    } catch {}
    try { await navigator.clipboard.writeText(text); alert('הקישור הועתק — שתפו ב-WhatsApp'); }
    catch { prompt('העתיקו ושתפו:', text); }
  };

  return (
    <div className="tanya-landing">
      <FeaturedCircleHero onCreateCircle={onCreateCircle} />

      <section className="tl-hero">
        <div className="tl-hero-bg" aria-hidden="true">
          <div className="tl-orb tl-orb-a" />
          <div className="tl-orb tl-orb-b" />
          <div className="tl-orb tl-orb-c" />
        </div>
        <div className="tl-hero-inner">
          <div className="tl-eyebrow">השקה היסטורית · השנה</div>
          <h1 className="tl-title">
            מטבע <span className="tl-shine">תניא הראשון</span> בעולם
          </h1>
          <p className="tl-sub">
            ביחד פותחים את <strong>ספר התניא השלם</strong> — חמשת חלקיו פרושים לפניכם.
            תבחרו פרק, חלק שלם, או את הספר כולו. כל פרק שתקראו נחתם כמטבע <strong>תניא</strong> אמיתי בארנק שלכם.
          </p>
          <div className="tl-cta-row">
            <button className="btn tl-cta-primary" onClick={cta}>{ctaLabel}</button>
            <button className="btn ghost tl-cta-ghost" onClick={shareInvite}>🔗 הזמינו חברים</button>
          </div>
          <div className="tl-trust">
            <span>✓ אין תשלום</span>
            <span>✓ אין צורך בידע מוקדם</span>
            <span>✓ קריאה אישית, מאומתת אוטומטית</span>
          </div>
        </div>
      </section>

      <section className="tl-stats">
        <div className="tl-stat">
          <div className="tl-stat-num">{stats.joined.toLocaleString('he-IL')}</div>
          <div className="tl-stat-lbl">קוראים שכבר הצטרפו</div>
        </div>
        <div className="tl-stat">
          <div className="tl-stat-num">{stats.chapters.toLocaleString('he-IL')} / {stats.total}</div>
          <div className="tl-stat-lbl">פרקים שהושלמו</div>
        </div>
        <div className="tl-stat">
          <div className="tl-stat-num">{stats.percent}%</div>
          <div className="tl-stat-lbl">מהדרך לסיום</div>
        </div>
        <div className="tl-progress-wrap" aria-label={`${stats.percent} אחוז`}>
          <div className="tl-progress-bar" style={{ width: stats.percent + '%' }} />
        </div>
        <div className="tl-stats-foot">{stats.percent < 100 ? '✨ מצטרפים עכשיו ותהיו חלק מהסיום הראשון' : '🎉 הסיום קרוב מאוד — בואו לסגור את המעגל'}</div>
      </section>

      <section className="tl-section tl-parts">
        <h2 className="tl-h2">חמשת חלקי התניא נפתחים לפניכם</h2>
        <p className="tl-lead" style={{ marginBottom: 24 }}>
          התניא איננו ספר אחד — הוא חמישה.
          <br/>בכל מעגל שתפתחו תוכלו לכלול חלק אחד, כמה חלקים, או את הספר השלם.
          <br/><span style={{ opacity: 0.85, fontStyle: 'italic' }}>וכל מעגל יודע בדיוק באיזה חלק ופרק הוא נמצא — מהיום הראשון.</span>
        </p>
        <div className="tl-parts-grid">
          {TANYA_PARTS_CLIENT.map((p) => (
            <div key={p.id} className="tl-part-card">
              <div className="tl-part-card-icon" aria-hidden="true">{p.icon}</div>
              <div className="tl-part-card-name">{p.name}</div>
              <div className="tl-part-card-shaar">{p.shaar}</div>
              <div className="tl-part-card-count">{p.chapters} פרקים</div>
            </div>
          ))}
        </div>
        <p className="tl-parts-foot">
          📖 הספר השלם — <strong>118 פרקים</strong> בחמשת השערים.
          {' '}תפתחו מעגל אחד מהם, או כמה ביחד.
        </p>
        {/* [yakir 2026-05-17 #2] Direct path into circle creation from
            /tanya — without this, a visitor sees the 5-parts cards but
            has no obvious next step to actually start a circle. */}
        <div className="tl-parts-open-cta">
          <button
            className="btn tl-parts-open-btn"
            onClick={() => onCreateCircle ? onCreateCircle() : (cta && cta())}
          >
            ✨ פתחו מעגל משלכם
          </button>
          <div className="tl-parts-open-sub">
            בחרו חלק, חלקים, או את הספר השלם — תוך פחות מדקה
          </div>
        </div>
      </section>

      <section className="tl-section tl-intro">
        <h2 className="tl-h2">הזמנה היסטורית · השקת מטבע התניא הראשון בעולם 🌍</h2>
        <p className="tl-lead">
          לראשונה בעולם — אנחנו לא רק לומדים את ספר תניא…
          <br/>אנחנו יוצרים ממנו <strong>ערך חי</strong>.
          <br/>זהו המעגל הראשון שבו מונפקים מטבעות תניא אמיתיים — מתוך הלימוד עצמו.
        </p>
      </section>

      <section className="tl-section tl-how">
        <h2 className="tl-h2">איך זה עובד?</h2>
        <ul className="tl-steps-bullets">
          <li><span className="tl-bullet-icon">📘</span><div><strong>קוראים פרק</strong> — מקבלים מטבעות לפי אורכו (קצר/בינוני/ארוך)</div></li>
          <li><span className="tl-bullet-icon">🎯</span><div><strong>מסיימים ביחד את הספר</strong> (או חלק ממנו) — בונוס סיום לכל משתתף במעגל</div></li>
          <li><span className="tl-bullet-icon">✨</span><div><strong>בונוס נרשמים</strong> — 500 מטבעות BEU (Be You)</div></li>
          <li><span className="tl-bullet-icon">🤝</span><div><strong>בונוס מחברים</strong> — 25 מטבעות לכל מתחבר חדש שמביאים</div></li>
        </ul>
      </section>

      <section className="tl-section tl-uses">
        <h2 className="tl-h2">מה עושים עם המטבעות?</h2>
        <ul className="tl-steps-bullets">
          <li><span className="tl-bullet-icon">🛍️</span><div><strong>פוגשים</strong> מתנות בקהילה</div></li>
          <li><span className="tl-bullet-icon">💎</span><div><strong>שומרים</strong> — מתוך אמונה בערך העתידי שלהם</div></li>
          <li><span className="tl-bullet-icon">💝</span><div><strong>תורמים</strong> ומשפיעים על תודעת הקהילה</div></li>
        </ul>
      </section>

      <section className="tl-quote">
        <div className="tl-quote-mark">"</div>
        <blockquote>
          האמת היא — שזה הרבה מעבר למטבע. זו התחלה של כלכלה חדשה. כזו שנבנית מתוך נשמה, אמונה ונתינה.
          <br/>קהילה שבוחרת כבר עכשיו: להשקיע אחד בשני, לגלות את ה"איתן" שבתוכה, ולחיות גאולה בפועל.
        </blockquote>
      </section>

      <section className="tl-final">
        <div className="tl-final-inner">
          <h2>אם אתה מרגיש שזה שלך — זה הרגע להיכנס</h2>
          <p>להיות חלק ממשהו שמתחיל עכשיו. שמך נכנס לרשימת המייסדים של מעגל התניא הראשון, לעד.</p>
          <div className="tl-cta-row">
            <button className="btn tl-cta-primary" onClick={cta}>👉 {ctaLabel}</button>
            <button className="btn ghost tl-cta-ghost" onClick={shareInvite}>🔗 שתפו עם חבר</button>
          </div>
          <div className="tl-trust" style={{ marginTop: 16 }}>
            <span>📚 חמשת חלקי התניא · 118 פרקים</span>
            <span>⏱️ ~3 דקות לפרק</span>
            <span>🎁 בונוס סיום למצטרפים</span>
          </div>
        </div>
      </section>
    </div>
  );
}


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

// ════════════════════════════════════════════════════════════════════
// Rebbe Chat v2 — adaptive single-chat
// One bot. Intakes gently. Adapts internal voice to user's tools.
// ════════════════════════════════════════════════════════════════════

// [damri 2026-05-24 round D] FloatingRebbim — fixed bottom-right widget.
// Closed: small avatar circle that pulses gently. Click → popup with 7 rebbe cards.
// Click a rebbe → opens RebbeChat in a modal overlay (no page navigation).
// Inspired by the floating AI bot on Rabbi Ashkenazi's site (damri reference).
// [damri 2026-05-24] Pivot: rebbe-chat לא עובד טוב כרגע — בנתיים bot
// שמסביר על האתר. אותו floating button (לא ניווט) — אבל הצ'אט שונה.
// FloatingRebbim שמרנו כשם הקומפוננטה למרות שהפנימי הוחלף ל-site-explainer.
function SiteExplainerChat({ onClose }) {
  const [messages, setMessages] = React.useState([
    { role: 'assistant', content: 'שלום! אני העוזר של בית דוד. שאל אותי כל דבר על האתר — איך משתמשים, מה כל חלק עושה, איך מצטרפים, מה זה BEU. אני אסביר.' },
  ]);
  const [input, setInput] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const [err, setErr] = React.useState('');
  const endRef = React.useRef(null);

  React.useEffect(() => {
    try { endRef.current && endRef.current.scrollIntoView({ behavior: 'smooth' }); } catch (_) {}
  }, [messages]);

  const send = async () => {
    const text = (input || '').trim();
    if (!text || sending) return;
    setSending(true); setErr('');
    const next = [...messages, { role: 'user', content: text }];
    setMessages(next);
    setInput('');
    try {
      const history = next.slice(0, -1).filter(m => m.role !== 'system').slice(-12);
      const res = await fetch('/api/site/explain', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: text, history }),
      });
      const data = await res.json();
      if (!res.ok) {
        setErr(data.user_message || data.error || 'שגיאה');
        setMessages(prev => [...prev, { role: 'assistant', content: data.user_message || 'נסה שוב בעוד רגע.' }]);
      } else {
        setMessages(prev => [...prev, { role: 'assistant', content: data.text || 'לא הבנתי, נסה לנסח אחרת.' }]);
      }
    } catch (e) {
      setErr('בעיית חיבור.');
    } finally {
      setSending(false);
    }
  };

  const onKey = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', direction: 'rtl', fontFamily: 'inherit' }}>
      <div style={{ padding: '12px 16px', borderBottom: '1px solid #e8e6df', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: '#fffdf6' }}>
        <div>
          <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 18, fontWeight: 800, color: '#1a1a2e' }}>עוזר האתר</div>
          <div style={{ fontSize: 12, color: 'var(--gold)', fontWeight: 600 }}>שאלות על בית דוד</div>
        </div>
        <button onClick={onClose} aria-label="סגור"
          style={{ background: 'transparent', border: 'none', fontSize: 26, cursor: 'pointer', color: '#6b6d73', padding: 0, lineHeight: 1 }}>×</button>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: 14, background: '#fafaf6' }}>
        {messages.map((m, i) => (
          <div key={i} style={{ marginBottom: 10, display: 'flex', justifyContent: m.role === 'user' ? 'flex-start' : 'flex-end' }}>
            <div style={{
              maxWidth: '85%', padding: '10px 13px', borderRadius: 12,
              background: m.role === 'user' ? '#1a1a2e' : 'rgba(212,175,55,0.10)',
              color: m.role === 'user' ? '#fff' : '#1a1a2e',
              border: m.role === 'user' ? 'none' : '1px solid rgba(212,175,55,0.30)',
              fontSize: 14, lineHeight: 1.55, whiteSpace: 'pre-wrap', wordBreak: 'break-word',
            }}>{m.content}</div>
          </div>
        ))}
        {sending && (
          <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 10 }}>
            <div style={{ padding: '10px 13px', borderRadius: 12, background: 'rgba(212,175,55,0.08)', color: '#6b6d73', fontSize: 13, fontStyle: 'italic' }}>...חושב</div>
          </div>
        )}
        {err && <div style={{ color: '#c00', fontSize: 12, textAlign: 'center', padding: 4 }}>{err}</div>}
        <div ref={endRef} />
      </div>
      <div style={{ padding: 12, borderTop: '1px solid #e8e6df', background: '#fff', display: 'flex', gap: 8 }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={onKey}
          placeholder="שאל על האתר..."
          disabled={sending}
          style={{
            flex: 1, padding: '10px 12px', fontSize: 15, borderRadius: 10,
            border: '1.5px solid #d4af37', outline: 'none', fontFamily: 'inherit',
            direction: 'rtl', background: sending ? '#f5f5f0' : '#fff',
          }} />
        <button onClick={send} disabled={sending || !input.trim()}
          style={{
            padding: '10px 18px', background: '#d4af37', color: '#1a1a2e',
            border: 'none', borderRadius: 10, fontWeight: 700, fontSize: 15,
            cursor: sending || !input.trim() ? 'not-allowed' : 'pointer',
            opacity: sending || !input.trim() ? 0.5 : 1, fontFamily: 'inherit',
          }}>שלח</button>
      </div>
    </div>
  );
}



// [damri 2026-05-24] DashboardPage — host+ overview of members with indicators.
// Loads /api/dashboard/profiles (requires X-User-Id header — host or founder).
// Indicators: 🟡 pending (האדם עוד לא אישר/דחה)
//             🟣 never_sent (יש user אבל אין token — לא נשלח לינק)
//             🔵 stale (לא נגע >30 יום)
//             🟢 alive (אישר ועדכן ב-30 יום)
//             ⚪ never_responded (יש token, אין accepted)
// [damri 2026-05-24] NewPersonPanel — bring a brand-new person into the house (Gate א').
// A few sentences (+ optional photo) -> intake -> recognition generation -> pending user + /i link.
function NewPersonPanel({ hostId, onCreated, onClose }) {
  const [name, setName] = React.useState('');
  const [phone, setPhone] = React.useState('');
  const [about, setAbout] = React.useState('');
  const [photo, setPhoto] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [err, setErr] = React.useState('');

  const pickPhoto = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    const rd = new FileReader();
    rd.onload = () => setPhoto(rd.result);
    rd.readAsDataURL(file);
  };

  const create = async () => {
    if (about.trim().length < 10) { setErr('כתוב כמה משפטים עליו — מי הוא, מה ראית בו'); return; }
    setBusy(true); setErr('');
    try {
      const H = { 'Content-Type': 'application/json', 'X-User-Id': String(hostId || '') };
      const ir = await fetch('/api/identity/intake', { method: 'POST', headers: H,
        body: JSON.stringify({ kind: 'text', content: about.trim(), source_context: 'dashboard_new_person' }) });
      const id = await ir.json();
      if (!ir.ok) throw new Error(id.message || id.error || 'intake_failed');
      const rr = await fetch('/api/identity/generate/recognition', { method: 'POST', headers: H,
        body: JSON.stringify({ intake_ids: [id.id], name: name.trim() || undefined, phone: phone.trim() || undefined }) });
      const d = await rr.json();
      if (!rr.ok) throw new Error(d.message || d.error || 'recognition_failed');
      if (photo && d.user_id) {
        try { await fetch(`/api/profile/${d.user_id}/avatar`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ avatar_url: photo }) }); } catch (e2) {}
      }
      setResult({ link: d.link, name: name.trim(), phone: phone.trim() });
      onCreated && onCreated();
    } catch (e) { setErr(e.message || 'failed'); }
    finally { setBusy(false); }
  };

  const inp = { width: '100%', boxSizing: 'border-box', padding: '9px 11px', borderRadius: 8,
    border: '1px solid #d8cfae', fontSize: 14, fontFamily: 'inherit', lineHeight: 1.55, direction: 'rtl' };
  const lbl = { fontSize: 11, color: 'var(--gold)', fontWeight: 700, letterSpacing: 1, marginBottom: 3 };

  return (
    <div style={{ background: '#fffdf6', border: '1.5px solid rgba(212,175,55,0.4)', borderRadius: 14, padding: 18, marginBottom: 18 }}>
      <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 19, color: '#1a1a2e', marginBottom: 6 }}>הכר אדם חדש</div>
      {result ? (
        <div style={{ padding: 14, borderRadius: 10, background: '#e6f4ea', border: '1px solid #b7dfc5', color: '#1e6a3a', fontSize: 14, lineHeight: 1.7 }}>
          ✦ {result.name || 'הוא'} נכנס לבית · נוצר דיוקן ראשוני שממתין לאישור שלו.<br/>
          לינק אישי: <a href={result.link} style={{ color: '#8b6508', fontWeight: 700, wordBreak: 'break-all' }}>{location.origin}{result.link}</a>
          <button onClick={() => {
              const digits = (result.phone || '').replace(/[^0-9]/g, '').replace(/^0/, '972');
              const msg = `שלום ${result.name || ''}, ראינו בך משהו יפה 🤍\nהכנו לך דף אישי קטן — תראה אם זה מרגיש לך נכון:\n${location.origin}${result.link}`;
              const url = digits ? `https://wa.me/${digits}?text=${encodeURIComponent(msg)}` : `https://wa.me/?text=${encodeURIComponent(msg)}`;
              window.open(url, '_blank');
            }}
            style={{ display: 'block', width: '100%', marginTop: 12, padding: '12px', background: '#25D366', color: '#fff', border: 'none', borderRadius: 10, fontWeight: 700, fontSize: 15, cursor: 'pointer', fontFamily: 'inherit' }}>
            📲 שלח לו בוואטסאפ
          </button>
          <div style={{ marginTop: 10, display: 'flex', gap: 8 }}>
            <button onClick={() => { try { navigator.clipboard.writeText(`${location.origin}${result.link}`); } catch (e) {} }}
              style={{ padding: '7px 12px', fontSize: 12, background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', color: '#6b6d73' }}>העתק לינק</button>
            <button onClick={() => { setResult(null); setName(''); setPhone(''); setAbout(''); setPhoto(null); }}
              style={{ padding: '7px 12px', fontSize: 12, background: 'rgba(212,175,55,0.15)', border: '1px solid rgba(212,175,55,0.4)', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', color: '#8b6508', fontWeight: 600 }}>הכר עוד אחד</button>
            <button onClick={onClose} style={{ padding: '7px 12px', fontSize: 12, background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit' }}>סגור</button>
          </div>
        </div>
      ) : (
        <div>
          <div style={{ fontSize: 12.5, color: '#5d5868', lineHeight: 1.6, marginBottom: 12, fontStyle: 'italic' }}>
            כמה משפטים עליו — ונייצר דיוקן ראשוני שהוא יוכל לאשר ולהיכנס ממנו ישר לבית.
          </div>
          <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 10 }}>
            <div style={{ flex: 1, minWidth: 160 }}><div style={lbl}>שם</div><input value={name} onChange={e => setName(e.target.value)} style={inp} /></div>
            <div style={{ flex: 1, minWidth: 160 }}><div style={lbl}>טלפון (לא חובה)</div><input value={phone} onChange={e => setPhone(e.target.value)} style={inp} /></div>
          </div>
          <div style={{ marginBottom: 10 }}>
            <div style={lbl}>כמה משפטים עליו — מי הוא, מה ראית בו, מה הוא מחפש</div>
            <textarea rows={4} value={about} onChange={e => setAbout(e.target.value)} style={inp} placeholder="ספר עליו ברוח שלך... או דבר 🎤" />
            <MicButton value={about} onText={setAbout} />
          </div>
          <div style={{ marginBottom: 12 }}>
            <div style={lbl}>תמונה (לא חובה)</div>
            <input type="file" accept="image/*" onChange={pickPhoto} style={{ fontSize: 12, fontFamily: 'inherit' }} />
            {photo && <span style={{ fontSize: 11, color: '#1e6a3a', marginRight: 8 }}>✓ תמונה נבחרה</span>}
          </div>
          {err && <div style={{ color: '#dc2626', fontSize: 12, marginBottom: 8 }}>{err}</div>}
          <div style={{ display: 'flex', gap: 10 }}>
            <button onClick={create} disabled={busy}
              style={{ flex: 1, padding: '12px', background: '#d4af37', color: '#1a1a2e', border: 'none', borderRadius: 10, fontWeight: 700, fontSize: 15, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1, fontFamily: 'inherit' }}>
              {busy ? '✦ רואה אותו...' : '✦ ראה אותו'}
            </button>
            <button onClick={onClose} disabled={busy}
              style={{ padding: '12px 16px', background: 'transparent', color: '#6b6d73', border: '1px solid #d0c8b0', borderRadius: 10, fontWeight: 600, fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
              ביטול
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// [damri 2026-05-24] MemberEditPanel — the house helping a member refine themselves.
// Prefills from /api/card/:id, saves via /api/identity/host-edit. Two paths:
// "פרסם עכשיו" (apply now) or "שלח אליו לאישור" (pending — old profile untouched).
function MemberEditPanel({ hostId, member, onClose, onSaved }) {
  const [f, setF] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [err, setErr] = React.useState('');
  const [avatar, setAvatar] = React.useState(null);       // new photo dataURL (rides the save)
  const [curAvatar, setCurAvatar] = React.useState(null); // existing avatar_url
  const [journey, setJourney] = React.useState(null);     // [{question_id,text,hint,answer,history}]
  const [jEdits, setJEdits] = React.useState({});         // { qid: {answer_text, source} }
  const [showJourney, setShowJourney] = React.useState(false);
  const [histOpen, setHistOpen] = React.useState({});

  React.useEffect(() => {
    fetch(`/api/card/${member.user_id}`)
      .then(r => r.json())
      .then(d => {
        const pr = (d && d.profile) || {};
        setF({
          tagline: pr.tagline || '', mission: pr.mission || '', bio: pr.bio || '', gift: pr.gift || '',
          values: ((d && d.values) || []).join(', '), skills: ((d && d.skills) || []).join(', '),
          service_title: pr.service_title || '', service_body: pr.service_body || '',
          cta_text: pr.cta_text || '', cta_url: pr.cta_url || '',
          website: pr.website || '', youtube_url: pr.youtube_url || '',
        });
        setCurAvatar(pr.avatar_url || null);
      })
      .catch(() => { setErr('connection_error'); setF({}); });
    fetch(`/api/identity/journey/${member.user_id}`, { headers: { 'X-User-Id': String(hostId || '') } })
      .then(r => r.json())
      .then(d => setJourney((d && d.questions) || []))
      .catch(() => setJourney([]));
  }, [member.user_id]);

  const set = (k, v) => setF(s => ({ ...s, [k]: v }));
  const pickPhoto = (e) => {
    const file = e.target.files && e.target.files[0]; if (!file) return;
    const rd = new FileReader(); rd.onload = () => setAvatar(rd.result); rd.readAsDataURL(file);
  };
  const setJ = (qid, patch) => setJEdits(s => ({ ...s, [qid]: { ...(s[qid] || {}), ...patch } }));
  const SRC = { self: 'מעצמו', quote: 'ציטוט אמיתי', host_suggestion: 'הצעה', interpretation: 'דיוק' };
  const save = async (mode) => {
    setBusy(true); setErr('');
    const fields = {
      tagline: f.tagline, mission: f.mission, bio: f.bio, gift: f.gift,
      values: (f.values || '').split(',').map(x => x.trim()).filter(Boolean),
      skills: (f.skills || '').split(',').map(x => x.trim()).filter(Boolean),
      service_title: f.service_title, service_body: f.service_body,
      cta_text: f.cta_text, cta_url: f.cta_url, website: f.website, youtube_url: f.youtube_url,
    };
    const journeyArr = Object.entries(jEdits)
      .filter(([qid, v]) => v && (v.answer_text || '').trim())
      .map(([qid, v]) => ({ question_id: parseInt(qid, 10), answer_text: v.answer_text, source: v.source || 'host_suggestion' }));
    const payload = { user_id: member.user_id, mode, fields };
    if (journeyArr.length) payload.journey = journeyArr;
    if (avatar) payload.avatar = avatar;
    try {
      const r = await fetch('/api/identity/host-edit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-User-Id': String(hostId || '') },
        body: JSON.stringify(payload),
      });
      const d = await r.json();
      if (!r.ok) setErr(d.message || d.error || 'failed');
      else { setResult({ mode, link: d.link }); onSaved && onSaved(); }
    } catch (e) { setErr('connection_error'); }
    finally { setBusy(false); }
  };

  const wrap = { padding: '4px 14px 18px 46px', background: 'rgba(212,175,55,0.05)' };
  if (!f) return <div style={{ ...wrap, color: '#8a8d95', fontSize: 12 }}>טוען את הפרופיל...</div>;
  const inp = { width: '100%', boxSizing: 'border-box', padding: '8px 10px', borderRadius: 8,
    border: '1px solid #d8cfae', fontSize: 13.5, fontFamily: 'inherit', lineHeight: 1.5, direction: 'rtl' };
  const lbl = { fontSize: 11, color: 'var(--gold)', fontWeight: 700, letterSpacing: 1, marginBottom: 3 };
  const fieldDefs = [
    ['tagline', 'משפט מפתח', false], ['mission', 'שליחות', true], ['bio', 'תיאור', true], ['gift', 'המתנה שלו', false],
    ['values', 'ערכים (פסיקים)', false], ['skills', 'יכולות (פסיקים)', false],
    ['service_title', 'כותרת שירות', false], ['service_body', 'תיאור שירות', true],
    ['cta_text', 'טקסט כפתור', false], ['cta_url', 'קישור כפתור', false],
    ['website', 'אתר', false], ['youtube_url', 'יוטיוב', false],
  ];

  return (
    <div style={wrap}>
      <div style={{ fontSize: 12.5, color: '#5d5868', lineHeight: 1.6, marginBottom: 12, fontStyle: 'italic' }}>
        לדייק את {member.name || 'החבר'} — לעזור לו לראות את עצמו נכון.<br/>
        אפשר לפרסם עכשיו, או לשלוח אליו generation לאישור (הפרופיל הישן נשאר עד שיאשר).
      </div>
      {result ? (
        <div style={{ padding: 12, borderRadius: 10, fontSize: 13, lineHeight: 1.6,
          background: result.mode === 'publish' ? '#e6f4ea' : '#fbf3df',
          border: '1px solid ' + (result.mode === 'publish' ? '#b7dfc5' : '#e6d59a'),
          color: result.mode === 'publish' ? '#1e6a3a' : '#8b6508' }}>
          {result.mode === 'publish'
            ? '✓ פורסם בכרטיס שלו.'
            : <span>🤍 נשלח אליו לאישור · לינק: <a href={result.link} style={{ color: '#8b6508', fontWeight: 700, wordBreak: 'break-all' }}>{location.origin}{result.link}</a><br/>הפרופיל הישן נשאר כמו שהוא עד שהוא יאשר.</span>}
          <div><button onClick={onClose} style={{ marginTop: 10, padding: '6px 12px', fontSize: 12, background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit' }}>סגור</button></div>
        </div>
      ) : (
        <div>
          {fieldDefs.map(([k, label, area]) => (
            <div key={k} style={{ marginBottom: 10 }}>
              <div style={lbl}>{label}</div>
              {area
                ? <textarea rows={2} value={f[k]} onChange={e => set(k, e.target.value)} style={inp} />
                : <input value={f[k]} onChange={e => set(k, e.target.value)} style={inp} />}
            </div>
          ))}

          {/* photo — rides the same save (publish or approval) */}
          <div style={{ marginBottom: 14, display: 'flex', gap: 12, alignItems: 'center', borderTop: '1px solid #ece3c8', paddingTop: 12 }}>
            <div style={{ width: 52, height: 52, borderRadius: '50%', overflow: 'hidden', background: '#eee', flexShrink: 0, border: '1px solid #d8cfae' }}>
              {(avatar || curAvatar) && <img src={avatar || curAvatar} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
            </div>
            <div>
              <div style={lbl}>תמונה</div>
              <input type="file" accept="image/*" onChange={pickPhoto} style={{ fontSize: 12, fontFamily: 'inherit' }} />
              {avatar && <div style={{ fontSize: 11, color: '#1e6a3a', marginTop: 3 }}>✓ תמונה חדשה — תיושם עם השמירה</div>}
            </div>
          </div>

          {/* the treasures from the journey — accompaniment, not a form */}
          <div style={{ marginBottom: 14, borderTop: '1px solid #ece3c8', paddingTop: 12 }}>
            <button type="button" onClick={() => setShowJourney(v => !v)}
              style={{ background: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, fontWeight: 700, color: '#8b6508', padding: 0 }}>
              {showJourney ? '▾' : '▸'} ✦ האוצרות מהמסע {journey ? `(${journey.filter(q => q.answer).length}/${journey.length})` : ''}
            </button>
            {showJourney && journey && (
              <div style={{ marginTop: 10 }}>
                {journey.map(q => {
                  const ed = jEdits[q.question_id];
                  const val = ed ? ed.answer_text : (q.answer ? q.answer.text : '');
                  const src = ed ? ed.source : (q.answer ? q.answer.source : 'quote');
                  return (
                    <div key={q.question_id} style={{ marginBottom: 14, paddingBottom: 12, borderBottom: '1px dashed #ece3c8' }}>
                      <div style={{ fontSize: 13, color: '#1a1a2e', fontWeight: 600, marginBottom: 2 }}>{q.text}</div>
                      <div style={{ fontSize: 11, color: '#9a8d6a', fontStyle: 'italic', marginBottom: 6 }}>
                        {q.answer ? 'זה עדיין מרגיש נכון? משהו השתנה בדרך שלו?' : 'עוד משהו שחשוב שנכיר בו?'}
                      </div>
                      <textarea rows={2} value={val} placeholder="..."
                        onChange={e => setJ(q.question_id, { answer_text: e.target.value, source: src })}
                        style={{ ...inp, marginBottom: 6 }} />
                      <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
                        <span style={{ fontSize: 10, color: '#9a8d6a' }}>זה:</span>
                        {['quote', 'interpretation', 'host_suggestion'].map(sv => (
                          <button key={sv} type="button" onClick={() => setJ(q.question_id, { answer_text: val, source: sv })}
                            style={{ fontSize: 10.5, padding: '3px 8px', borderRadius: 7, cursor: 'pointer', fontFamily: 'inherit',
                              background: src === sv ? '#d4af37' : 'transparent', color: src === sv ? '#1a1a2e' : '#8b6508',
                              border: '1px solid rgba(212,175,55,0.5)', fontWeight: src === sv ? 700 : 400 }}>
                            {SRC[sv]}
                          </button>
                        ))}
                        {q.history && q.history.length > 1 && (
                          <button type="button" onClick={() => setHistOpen(o => ({ ...o, [q.question_id]: !o[q.question_id] }))}
                            style={{ fontSize: 10.5, padding: '3px 8px', borderRadius: 7, cursor: 'pointer', fontFamily: 'inherit', background: 'transparent', color: '#6b6d73', border: '1px solid #d0c8b0', marginRight: 'auto' }}>
                            איך זה התפתח ({q.history.length})
                          </button>
                        )}
                      </div>
                      {histOpen[q.question_id] && q.history && (
                        <div style={{ marginTop: 6, paddingRight: 8, borderRight: '2px solid #ece3c8' }}>
                          {q.history.map((h, hi) => (
                            <div key={hi} style={{ fontSize: 11, color: '#8a8d95', marginBottom: 3, lineHeight: 1.5 }}>
                              <span style={{ color: '#b8a76a' }}>· {SRC[h.source] || h.source}</span> — {h.answer_text} <span style={{ opacity: 0.6 }}>({(h.created_at || '').slice(0, 10)})</span>
                            </div>
                          ))}
                        </div>
                      )}
                    </div>
                  );
                })}
              </div>
            )}
          </div>

          {err && <div style={{ color: '#dc2626', fontSize: 12, marginBottom: 8 }}>{err}</div>}
          <div style={{ display: 'flex', gap: 10, marginTop: 6, flexWrap: 'wrap' }}>
            <button onClick={() => save('publish')} disabled={busy}
              style={{ flex: 1, minWidth: 130, padding: '11px', background: '#d4af37', color: '#1a1a2e', border: 'none', borderRadius: 10, fontWeight: 700, fontSize: 14, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1, fontFamily: 'inherit' }}>
              ✦ פרסם עכשיו
            </button>
            <button onClick={() => save('approval')} disabled={busy}
              style={{ flex: 1, minWidth: 130, padding: '11px', background: 'rgba(95,163,114,0.12)', color: '#3d6b4a', border: '1px solid rgba(95,163,114,0.4)', borderRadius: 10, fontWeight: 700, fontSize: 14, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1, fontFamily: 'inherit' }}>
              🤍 שלח אליו לאישור
            </button>
            <button onClick={onClose} disabled={busy}
              style={{ padding: '11px 14px', background: 'transparent', color: '#6b6d73', border: '1px solid #d0c8b0', borderRadius: 10, fontWeight: 600, fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>
              ביטול
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// [damri 2026-05-24] MemberSpace — a space to ACCOMPANY a person, not an admin form.
// "איך הבית רואה אותו כרגע" · tabs: זהות · מסע · מעגלים · מה עובר דרכו · משאלות.
// One save bar, three choices: ✦ פרסם עכשיו · 🤍 שלח לאישור · 🕊️ שמור כטיוטה.
function MemberSpace({ hostId, member, onClose, onSaved }) {
  const uid = member.user_id;
  const [tab, setTab] = React.useState('identity');
  const [f, setF] = React.useState(null);
  const [curAvatar, setCurAvatar] = React.useState(null);
  const [avatar, setAvatar] = React.useState(null);
  const [journey, setJourney] = React.useState(null);
  const [jEdits, setJEdits] = React.useState({});
  const [space, setSpace] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [err, setErr] = React.useState('');
  const [draftAt, setDraftAt] = React.useState(null);
  const [histOpen, setHistOpen] = React.useState({});
  const [editItem, setEditItem] = React.useState(null); // {kind,id} inline edit in read tabs

  const H = { 'Content-Type': 'application/json', 'X-User-Id': String(hostId || '') };
  React.useEffect(() => {
    fetch(`/api/card/${uid}`).then(r => r.json()).then(d => {
      const pr = (d && d.profile) || {};
      setF({ tagline: pr.tagline || '', mission: pr.mission || '', bio: pr.bio || '', gift: pr.gift || '',
        values: ((d && d.values) || []).join(', '), skills: ((d && d.skills) || []).join(', ') });
      setCurAvatar(pr.avatar_url || null);
    }).catch(() => setF({}));
    fetch(`/api/identity/journey/${uid}`, { headers: H }).then(r => r.json()).then(d => setJourney((d && d.questions) || [])).catch(() => setJourney([]));
    fetch(`/api/identity/space/${uid}`, { headers: H }).then(r => r.json()).then(d => setSpace(d || {})).catch(() => setSpace({}));
    fetch(`/api/identity/host-draft/${uid}`, { headers: H }).then(r => r.json()).then(d => {
      const dr = d && d.drafts && d.drafts.identity;
      if (dr && dr.data) { if (dr.data.f) setF(s => ({ ...(s || {}), ...dr.data.f })); if (dr.data.jEdits) setJEdits(dr.data.jEdits); setDraftAt(dr.updated_at); }
    }).catch(() => {});
  }, [uid]);

  const setField = (k, v) => setF(s => ({ ...s, [k]: v }));
  const setJ = (qid, patch) => setJEdits(s => ({ ...s, [qid]: { ...(s[qid] || {}), ...patch } }));
  const pickPhoto = (e) => { const file = e.target.files && e.target.files[0]; if (!file) return; const rd = new FileReader(); rd.onload = () => setAvatar(rd.result); rd.readAsDataURL(file); };
  const SRC = { self: 'מעצמו', quote: 'ציטוט אמיתי', host_suggestion: 'הצעה', interpretation: 'דיוק' };

  const buildPayload = (mode) => {
    const fields = { tagline: f.tagline, mission: f.mission, bio: f.bio, gift: f.gift,
      values: (f.values || '').split(',').map(x => x.trim()).filter(Boolean),
      skills: (f.skills || '').split(',').map(x => x.trim()).filter(Boolean) };
    const journeyArr = Object.entries(jEdits).filter(([q, v]) => v && (v.answer_text || '').trim())
      .map(([q, v]) => ({ question_id: parseInt(q, 10), answer_text: v.answer_text, source: v.source || 'host_suggestion' }));
    const p = { user_id: uid, mode, fields };
    if (journeyArr.length) p.journey = journeyArr;
    if (avatar) p.avatar = avatar;
    return p;
  };
  const save = async (mode) => {
    setBusy(true); setErr('');
    try {
      if (mode === 'draft') {
        await fetch('/api/identity/host-draft', { method: 'POST', headers: H, body: JSON.stringify({ user_id: uid, tab: 'identity', draft: { f, jEdits } }) });
        setDraftAt(new Date().toISOString()); setResult({ mode: 'draft' });
        setTimeout(() => setResult(null), 2200);
      } else {
        const r = await fetch('/api/identity/host-edit', { method: 'POST', headers: H, body: JSON.stringify(buildPayload(mode)) });
        const d = await r.json();
        if (!r.ok) setErr(d.message || d.error || 'failed');
        else {
          setResult({ mode, link: d.link });
          await fetch(`/api/identity/host-draft/${uid}?tab=identity`, { method: 'DELETE', headers: H }).catch(() => {});
          onSaved && onSaved();
        }
      }
    } catch (e) { setErr('connection_error'); }
    finally { setBusy(false); }
  };
  const quickSave = async (kind, id, body) => {
    const map = { circle: ['PATCH', `/api/circles/${id}`], service: ['PUT', `/api/service/${id}`], wish: ['PUT', `/api/wish/${id}`] };
    const [m, url] = map[kind];
    await fetch(url, { method: m, headers: H, body: JSON.stringify(body) }).catch(() => {});
    const d = await fetch(`/api/identity/space/${uid}`, { headers: H }).then(r => r.json()).catch(() => null);
    if (d) setSpace(d);
    setEditItem(null);
  };

  // ---- styles (warm, parchment — not admin) ----
  const overlay = { position: 'fixed', inset: 0, background: 'rgba(26,26,46,0.55)', zIndex: 9000, display: 'flex', justifyContent: 'center', alignItems: 'flex-start', overflowY: 'auto', padding: '20px 0' };
  const sheet = { background: '#fdfbf3', width: '100%', maxWidth: 680, borderRadius: 18, margin: '0 12px 40px', boxShadow: '0 20px 60px rgba(0,0,0,0.3)', direction: 'rtl', overflow: 'hidden' };
  const inp = { width: '100%', boxSizing: 'border-box', padding: '9px 11px', borderRadius: 9, border: '1px solid #d8cfae', fontSize: 14, fontFamily: 'inherit', lineHeight: 1.55, direction: 'rtl', background: '#fff' };
  const lbl = { fontSize: 11, color: '#b8902a', fontWeight: 700, letterSpacing: 1, marginBottom: 4 };
  const tabs = [['identity', 'זהות'], ['journey', 'מסע'], ['circles', 'מעגלים'], ['services', 'מה עובר דרכו'], ['wishes', 'משאלות']];

  const aliveBadge = (alive) => (
    <span style={{ fontSize: 11, fontWeight: 700, color: alive ? '#1e6a3a' : '#9a8d6a' }}>{alive ? '🟢 חי' : '🌙 רדום'}</span>
  );

  return (
    <div style={overlay} onClick={onClose}>
      <div style={sheet} onClick={e => e.stopPropagation()}>
        {/* header */}
        <div style={{ background: 'linear-gradient(135deg,#1a1a2e,#2d2d4e)', color: '#fff', padding: '20px 22px', position: 'relative' }}>
          <button onClick={onClose} style={{ position: 'absolute', left: 16, top: 16, background: 'rgba(255,255,255,0.15)', border: 'none', color: '#fff', width: 30, height: 30, borderRadius: '50%', cursor: 'pointer', fontSize: 16 }}>×</button>
          <div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
            <div style={{ width: 56, height: 56, borderRadius: '50%', overflow: 'hidden', background: 'rgba(255,255,255,0.12)', flexShrink: 0, border: '2px solid rgba(212,175,55,0.5)' }}>
              {(avatar || curAvatar) && <img src={avatar || curAvatar} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
            </div>
            <div>
              <div style={{ fontSize: 11, color: 'var(--gold)', letterSpacing: 2, fontWeight: 700 }}>מרחב ליווי</div>
              <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 23 }}>{member.name || `#${uid}`}</div>
              <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.7)', fontStyle: 'italic' }}>איך הבית רואה אותו כרגע — ואיך אפשר לעזור לו להתגלות</div>
            </div>
          </div>
        </div>
        {/* tab bar */}
        <div style={{ display: 'flex', gap: 2, padding: '0 12px', background: '#f3ecd8', borderBottom: '1px solid #e6dcc0', overflowX: 'auto' }}>
          {tabs.map(([k, label]) => (
            <button key={k} onClick={() => { setTab(k); setResult(null); }}
              style={{ padding: '12px 14px', background: 'none', border: 'none', borderBottom: tab === k ? '3px solid #d4af37' : '3px solid transparent', cursor: 'pointer', fontFamily: 'inherit', fontSize: 13.5, fontWeight: tab === k ? 700 : 500, color: tab === k ? '#1a1a2e' : '#8a8268', whiteSpace: 'nowrap' }}>
              {label}
            </button>
          ))}
        </div>

        <div style={{ padding: '20px 22px', minHeight: 200 }}>
          {!f && <div style={{ color: '#8a8d95', textAlign: 'center', padding: 30 }}>טוען...</div>}

          {/* ===== זהות ===== */}
          {f && tab === 'identity' && (
            <div>
              <div style={{ marginBottom: 16, display: 'flex', gap: 12, alignItems: 'center' }}>
                <div style={{ width: 56, height: 56, borderRadius: '50%', overflow: 'hidden', background: '#eee', flexShrink: 0, border: '1px solid #d8cfae' }}>
                  {(avatar || curAvatar) && <img src={avatar || curAvatar} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />}
                </div>
                <div><div style={lbl}>תמונה</div>
                  <input type="file" accept="image/*" onChange={pickPhoto} style={{ fontSize: 12, fontFamily: 'inherit' }} />
                  {avatar && <div style={{ fontSize: 11, color: '#1e6a3a', marginTop: 3 }}>✓ תמונה חדשה — תיושם עם השמירה</div>}
                </div>
              </div>
              {[['tagline', 'משפט מפתח', false], ['mission', 'שליחות', true], ['bio', 'תיאור', true], ['gift', 'המתנה שלו', false], ['values', 'ערכים (פסיקים)', false], ['skills', 'יכולות (פסיקים)', false]].map(([k, label, area]) => (
                <div key={k} style={{ marginBottom: 11 }}>
                  <div style={lbl}>{label}</div>
                  {area ? <textarea rows={2} value={f[k]} onChange={e => setField(k, e.target.value)} style={inp} />
                        : <input value={f[k]} onChange={e => setField(k, e.target.value)} style={inp} />}
                </div>
              ))}
            </div>
          )}

          {/* ===== מסע ===== */}
          {f && tab === 'journey' && (
            <div>
              {!journey && <div style={{ color: '#8a8d95' }}>טוען מסע...</div>}
              {journey && (() => {
                const revealed = journey.filter(q => q.answer);
                const notYet = journey.filter(q => !q.answer);
                const changed = journey.filter(q => q.history && q.history.length > 1);
                return (
                  <div>
                    <div style={{ fontSize: 12.5, color: '#5d5868', fontStyle: 'italic', marginBottom: 14, lineHeight: 1.6 }}>
                      נחשף: {revealed.length} · עוד לא קיבל מילים: {notYet.length} · השתנה בדרך: {changed.length}
                    </div>
                    {journey.map(q => {
                      const ed = jEdits[q.question_id];
                      const val = ed ? ed.answer_text : (q.answer ? q.answer.text : '');
                      const src = ed ? ed.source : (q.answer ? q.answer.source : 'quote');
                      return (
                        <div key={q.question_id} style={{ marginBottom: 16, paddingBottom: 13, borderBottom: '1px dashed #ece3c8' }}>
                          <div style={{ fontSize: 13.5, color: '#1a1a2e', fontWeight: 600 }}>{q.text}</div>
                          <div style={{ fontSize: 11, color: '#9a8d6a', fontStyle: 'italic', margin: '2px 0 6px' }}>
                            {q.answer ? 'זה עדיין מרגיש נכון? משהו השתנה בדרך שלו?' : 'עוד משהו שחשוב שנכיר בו?'}
                          </div>
                          <textarea rows={2} value={val} placeholder="..." onChange={e => setJ(q.question_id, { answer_text: e.target.value, source: src })} style={{ ...inp, marginBottom: 6 }} />
                          <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
                            <span style={{ fontSize: 10, color: '#9a8d6a' }}>זה:</span>
                            {['quote', 'interpretation', 'host_suggestion'].map(sv => (
                              <button key={sv} type="button" onClick={() => setJ(q.question_id, { answer_text: val, source: sv })}
                                style={{ fontSize: 10.5, padding: '3px 8px', borderRadius: 7, cursor: 'pointer', fontFamily: 'inherit', background: src === sv ? '#d4af37' : 'transparent', color: src === sv ? '#1a1a2e' : '#8b6508', border: '1px solid rgba(212,175,55,0.5)', fontWeight: src === sv ? 700 : 400 }}>{SRC[sv]}</button>
                            ))}
                            {q.history && q.history.length > 1 && (
                              <button type="button" onClick={() => setHistOpen(o => ({ ...o, [q.question_id]: !o[q.question_id] }))}
                                style={{ fontSize: 10.5, padding: '3px 8px', borderRadius: 7, cursor: 'pointer', fontFamily: 'inherit', background: 'transparent', color: '#6b6d73', border: '1px solid #d0c8b0', marginRight: 'auto' }}>איך זה התפתח ({q.history.length})</button>
                            )}
                          </div>
                          {histOpen[q.question_id] && q.history && (
                            <div style={{ marginTop: 6, paddingRight: 8, borderRight: '2px solid #ece3c8' }}>
                              {q.history.map((h, hi) => (
                                <div key={hi} style={{ fontSize: 11, color: '#8a8d95', marginBottom: 3, lineHeight: 1.5 }}>
                                  <span style={{ color: '#b8a76a' }}>· {SRC[h.source] || h.source}</span> — {h.answer_text} <span style={{ opacity: 0.6 }}>({(h.created_at || '').slice(0, 10)})</span>
                                </div>
                              ))}
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                );
              })()}
            </div>
          )}

          {/* ===== מעגלים ===== */}
          {f && tab === 'circles' && (
            <div>
              <div style={{ fontSize: 12.5, color: '#5d5868', fontStyle: 'italic', marginBottom: 14 }}>המרחבים שהוא פתח — ומי מגיע לשם.</div>
              {space && (space.circles || []).length === 0 && <div style={{ color: '#9a8d6a' }}>עוד לא פתח מעגל.</div>}
              {space && (space.circles || []).map(c => (
                <div key={c.id} style={{ marginBottom: 14, padding: 14, background: '#fff', borderRadius: 12, border: '1px solid #ece3c8' }}>
                  {editItem && editItem.kind === 'circle' && editItem.id === c.id ? (
                    <div>
                      <div style={lbl}>שם המעגל</div>
                      <input defaultValue={c.name} id={`c_name_${c.id}`} style={{ ...inp, marginBottom: 8 }} />
                      <div style={lbl}>הכוונה</div>
                      <textarea defaultValue={c.intention || c.purpose || ''} id={`c_int_${c.id}`} rows={2} style={{ ...inp, marginBottom: 8 }} />
                      <button onClick={() => quickSave('circle', c.id, { name: document.getElementById(`c_name_${c.id}`).value, intention: document.getElementById(`c_int_${c.id}`).value })} style={{ padding: '8px 14px', background: '#d4af37', border: 'none', borderRadius: 9, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', marginLeft: 8 }}>✦ פרסם</button>
                      <button onClick={() => setEditItem(null)} style={{ padding: '8px 12px', background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 9, cursor: 'pointer', fontFamily: 'inherit' }}>ביטול</button>
                    </div>
                  ) : (
                    <div>
                      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
                        <div style={{ fontWeight: 700, color: '#1a1a2e', fontSize: 15 }}>{c.name}</div>
                        {aliveBadge(c.alive)}
                      </div>
                      {(c.intention || c.purpose) && <div style={{ fontSize: 13, color: '#5d5868', marginTop: 3, lineHeight: 1.5 }}>{c.intention || c.purpose}</div>}
                      <div style={{ fontSize: 11.5, color: '#9a8d6a', marginTop: 6 }}>
                        {c.chapters_completed || 0}/{c.total_chapters} פרקים · {c.followers} מגיעים לשם
                      </div>
                      <button onClick={() => setEditItem({ kind: 'circle', id: c.id })} style={{ marginTop: 8, padding: '5px 11px', fontSize: 12, background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', color: '#6b6d73' }}>✏️ לדייק</button>
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}

          {/* ===== מה עובר דרכו (שירותים/מתנות) ===== */}
          {f && tab === 'services' && (
            <div>
              <div style={{ fontSize: 12.5, color: '#5d5868', fontStyle: 'italic', marginBottom: 14 }}>מה עובר דרכו לאחרים — ואיך אנשים מגיבים לזה.</div>
              {space && (space.services || []).length === 0 && <div style={{ color: '#9a8d6a' }}>עוד לא הציע מתנה לקהילה.</div>}
              {space && (space.services || []).map(sv => (
                <div key={sv.id} style={{ marginBottom: 14, padding: 14, background: '#fff', borderRadius: 12, border: '1px solid #ece3c8' }}>
                  {editItem && editItem.kind === 'service' && editItem.id === sv.id ? (
                    <div>
                      <div style={lbl}>מה הוא נותן</div>
                      <input defaultValue={sv.title} id={`s_t_${sv.id}`} style={{ ...inp, marginBottom: 8 }} />
                      <div style={lbl}>תיאור</div>
                      <textarea defaultValue={sv.description || ''} id={`s_d_${sv.id}`} rows={2} style={{ ...inp, marginBottom: 8 }} />
                      <button onClick={() => quickSave('service', sv.id, { title: document.getElementById(`s_t_${sv.id}`).value, description: document.getElementById(`s_d_${sv.id}`).value })} style={{ padding: '8px 14px', background: '#d4af37', border: 'none', borderRadius: 9, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', marginLeft: 8 }}>✦ פרסם</button>
                      <button onClick={() => setEditItem(null)} style={{ padding: '8px 12px', background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 9, cursor: 'pointer', fontFamily: 'inherit' }}>ביטול</button>
                    </div>
                  ) : (
                    <div>
                      <div style={{ fontWeight: 700, color: '#1a1a2e', fontSize: 15 }}>{sv.title}</div>
                      {sv.description && <div style={{ fontSize: 13, color: '#5d5868', marginTop: 3, lineHeight: 1.5 }}>{sv.description}</div>}
                      <div style={{ fontSize: 11.5, color: '#9a8d6a', marginTop: 6 }}>
                        {sv.gift_pct ? `נותן ${sv.gift_pct}% במתנה · ` : ''}{sv.request_count || 0} פנו אליו{sv.review_count ? ` · ${Number(sv.avg_rating).toFixed(1)}★ (${sv.review_count})` : ''}{!sv.active ? ' · רדום' : ''}
                      </div>
                      <button onClick={() => setEditItem({ kind: 'service', id: sv.id })} style={{ marginTop: 8, padding: '5px 11px', fontSize: 12, background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', color: '#6b6d73' }}>✏️ לדייק</button>
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}

          {/* ===== משאלות (כמעט תפילה) ===== */}
          {f && tab === 'wishes' && (
            <div>
              <div style={{ fontSize: 12.5, color: '#5d5868', fontStyle: 'italic', marginBottom: 14 }}>מה הוא עוד מבקש — מה עדיין חסר. להחזיק את זה בעדינות.</div>
              {space && (space.wishes || []).length === 0 && <div style={{ color: '#9a8d6a' }}>עוד לא ביקש דבר.</div>}
              {space && (space.wishes || []).map(w => (
                <div key={w.id} style={{ marginBottom: 14, padding: 16, background: '#fffdf6', borderRadius: 12, border: '1px solid #ece3c8', borderRight: '3px solid rgba(212,175,55,0.5)' }}>
                  {editItem && editItem.kind === 'wish' && editItem.id === w.id ? (
                    <div>
                      <div style={lbl}>מה הוא מבקש</div>
                      <input defaultValue={w.title} id={`w_t_${w.id}`} style={{ ...inp, marginBottom: 8 }} />
                      <div style={lbl}>במילים שלו</div>
                      <textarea defaultValue={w.description || ''} id={`w_d_${w.id}`} rows={2} style={{ ...inp, marginBottom: 8 }} />
                      <button onClick={() => quickSave('wish', w.id, { title: document.getElementById(`w_t_${w.id}`).value, description: document.getElementById(`w_d_${w.id}`).value })} style={{ padding: '8px 14px', background: '#d4af37', border: 'none', borderRadius: 9, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit', marginLeft: 8 }}>✦ פרסם</button>
                      <button onClick={() => setEditItem(null)} style={{ padding: '8px 12px', background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 9, cursor: 'pointer', fontFamily: 'inherit' }}>ביטול</button>
                    </div>
                  ) : (
                    <div>
                      <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 16, color: '#1a1a2e' }}>{w.title}</div>
                      {w.description && <div style={{ fontSize: 13, color: '#5d5868', marginTop: 4, lineHeight: 1.6 }}>{w.description}</div>}
                      <div style={{ fontSize: 11.5, color: '#9a8d6a', marginTop: 8 }}>{w.empower_count || 0} מחזיקים איתו{!w.is_public ? ' · פרטי' : ''}</div>
                      <button onClick={() => setEditItem({ kind: 'wish', id: w.id })} style={{ marginTop: 8, padding: '5px 11px', fontSize: 12, background: 'transparent', border: '1px solid #d0c8b0', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit', color: '#6b6d73' }}>✏️ לדייק</button>
                    </div>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>

        {/* save bar — identity + journey ride host-edit; three choices */}
        {f && (tab === 'identity' || tab === 'journey') && (
          <div style={{ borderTop: '1px solid #e6dcc0', background: '#f3ecd8', padding: '14px 22px' }}>
            {draftAt && !result && <div style={{ fontSize: 11, color: '#9a8d6a', marginBottom: 8 }}>🕊️ יש טיוטה שמורה — אתה ממשיך ממנה</div>}
            {err && <div style={{ color: '#dc2626', fontSize: 12, marginBottom: 8 }}>{err}</div>}
            {result ? (
              <div style={{ fontSize: 13.5, color: result.mode === 'approval' ? '#8b6508' : '#1e6a3a', lineHeight: 1.6 }}>
                {result.mode === 'publish' && '✓ פורסם בכרטיס שלו.'}
                {result.mode === 'draft' && '🕊️ נשמר כטיוטה — תוכל לחזור לזה.'}
                {result.mode === 'approval' && <span>🤍 נשלח אליו לאישור · <a href={result.link} style={{ color: '#8b6508', fontWeight: 700, wordBreak: 'break-all' }}>{location.origin}{result.link}</a><br/>הישן נשאר עד שהוא יאשר.</span>}
              </div>
            ) : (
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                <button onClick={() => save('publish')} disabled={busy} style={{ flex: 1, minWidth: 110, padding: '11px', background: '#d4af37', color: '#1a1a2e', border: 'none', borderRadius: 10, fontWeight: 700, fontSize: 14, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1, fontFamily: 'inherit' }}>✦ פרסם עכשיו</button>
                <button onClick={() => save('approval')} disabled={busy} style={{ flex: 1, minWidth: 110, padding: '11px', background: 'rgba(95,163,114,0.12)', color: '#3d6b4a', border: '1px solid rgba(95,163,114,0.4)', borderRadius: 10, fontWeight: 700, fontSize: 14, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1, fontFamily: 'inherit' }}>🤍 שלח לאישור</button>
                <button onClick={() => save('draft')} disabled={busy} style={{ padding: '11px 14px', background: 'transparent', color: '#8a8268', border: '1px solid #d0c8b0', borderRadius: 10, fontWeight: 600, fontSize: 13, cursor: 'pointer', fontFamily: 'inherit' }}>🕊️ טיוטה</button>
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}


// [damri 2026-05-24] JoinQuestionnaire — /join. A person who wants in knocks on the door.
// One question at a time, breathing, not a form. Creates a self_join intake for יקיר to welcome.
// [damri 2026-05-24] MicButton — speak instead of type (browser STT, Hebrew). The field
// game-changer for Yakir: dictate a line about a person, or a person dictates their answer.
// Uses the browser Web Speech API (he-IL). Renders nothing where unsupported (e.g. some iOS).
function MicButton({ value, onText }) {
  const supported = typeof window !== 'undefined' && (window.SpeechRecognition || window.webkitSpeechRecognition);
  const [listening, setListening] = React.useState(false);
  const recRef = React.useRef(null);
  const baseRef = React.useRef('');
  if (!supported) return null;
  const stop = () => { if (recRef.current) { try { recRef.current.stop(); } catch (e) {} } };
  const start = () => {
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    const r = new SR();
    r.lang = 'he-IL'; r.interimResults = true; r.continuous = true;
    baseRef.current = (value || '').trim();
    r.onresult = (e) => {
      let t = '';
      for (let k = 0; k < e.results.length; k++) t += e.results[k][0].transcript;
      onText((baseRef.current ? baseRef.current + ' ' : '') + t);
    };
    r.onend = () => { setListening(false); recRef.current = null; };
    r.onerror = () => { setListening(false); recRef.current = null; };
    try { r.start(); recRef.current = r; setListening(true); } catch (e) { setListening(false); }
  };
  return (
    <button type="button" onClick={() => (listening ? stop() : start())}
      title="דבר במקום להקליד"
      style={{ marginTop: 6, padding: '6px 12px', fontSize: 12.5, fontWeight: 700, fontFamily: 'inherit', borderRadius: 9, cursor: 'pointer',
        background: listening ? '#dc2626' : 'rgba(212,175,55,0.15)', color: listening ? '#fff' : '#8b6508',
        border: '1px solid ' + (listening ? '#dc2626' : 'rgba(212,175,55,0.45)') }}>
      {listening ? '● מקליט… הקש לעצור' : '🎤 דבר במקום להקליד'}
    </button>
  );
}

function JoinQuestionnaire() {
  const steps = [
    { k: 'name', q: 'איך קוראים לך?', hint: '', area: false, required: true },
    { k: 'arrived_via', q: 'איך הגעת אלינו?', hint: 'מי הביא אותך, או מה משך אותך לכאן', area: false, required: false },
    { k: 'seeking', q: 'מה מביא אותך לכאן?', hint: 'מה אתה מחפש · מה היית רוצה למצוא כאן', area: true, required: true },
    { k: 'gift', q: 'מה אתה אוהב לתת לאחרים?', hint: 'המתנה שעוברת דרכך', area: true, required: false },
    { k: 'about', q: 'משהו שחשוב שנדע עליך?', hint: 'אפשר גם לדלג', area: true, required: false },
    { k: 'phone', q: 'איך נוכל לחזור אליך?', hint: 'מספר טלפון / וואטסאפ', area: false, required: true },
  ];
  const [started, setStarted] = React.useState(false);
  const [i, setI] = React.useState(0);
  const [ans, setAns] = React.useState({});
  const [busy, setBusy] = React.useState(false);
  const [done, setDone] = React.useState(false);
  const [err, setErr] = React.useState('');

  const cur = steps[i];
  const val = (cur && ans[cur.k]) || '';
  const set = (v) => setAns(s => ({ ...s, [cur.k]: v }));
  const canGo = !cur || !cur.required || (val || '').trim().length > 0;

  const submit = async () => {
    setBusy(true); setErr('');
    try {
      const r = await fetch('/api/join', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(ans) });
      const d = await r.json();
      if (!r.ok) setErr(d.message || 'משהו לא הסתדר — נסה שוב בעוד רגע'); else setDone(true);
    } catch (e) { setErr('שגיאת רשת — נסה שוב'); }
    finally { setBusy(false); }
  };
  const next = () => { if (!canGo) return; if (i < steps.length - 1) setI(i + 1); else submit(); };

  const wrap = { minHeight: '100vh', background: 'radial-gradient(circle at 50% 0%, #fffdf6, #f3ecd8)', direction: 'rtl', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '32px 22px' };
  const card = { width: '100%', maxWidth: 460 };
  const inp = { width: '100%', boxSizing: 'border-box', padding: '13px 14px', borderRadius: 12, border: '1.5px solid #d8cfae', fontSize: 16, fontFamily: 'inherit', lineHeight: 1.6, direction: 'rtl', background: '#fff', outline: 'none' };
  const goldBtn = { padding: '13px 22px', background: '#d4af37', color: '#1a1a2e', border: 'none', borderRadius: 12, fontWeight: 700, fontSize: 16, cursor: 'pointer', fontFamily: 'inherit' };

  if (done) {
    return (
      <div style={wrap}><div style={{ ...card, textAlign: 'center' }}>
        <div style={{ fontSize: 40, marginBottom: 12 }}>🤍</div>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 26, color: '#1a1a2e', marginBottom: 10 }}>תודה שדפקת בדלת</h2>
        <p style={{ color: '#5d5868', fontSize: 15, lineHeight: 1.8 }}>יקיר יקבל את מה שכתבת, וייצור איתך קשר אישית בקרוב.<br/>נשמח לראות אותך בבית.</p>
      </div></div>
    );
  }
  if (!started) {
    return (
      <div style={wrap}><div style={{ ...card, textAlign: 'center' }}>
        <div style={{ fontSize: 13, color: 'var(--gold)', letterSpacing: 3, fontWeight: 700, marginBottom: 10 }}>בית דוד</div>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 30, color: '#1a1a2e', marginBottom: 14, lineHeight: 1.3 }}>רוצה להיכנס?</h2>
        <p style={{ color: '#5d5868', fontSize: 16, lineHeight: 1.9, marginBottom: 26 }}>
          כמה שאלות קצרות — בלי למהר.<br/>לא טופס. רק דרך שנכיר אותך באמת.
        </p>
        <button onClick={() => setStarted(true)} style={goldBtn}>בוא נתחיל ✦</button>
      </div></div>
    );
  }

  return (
    <div style={wrap}><div style={card}>
      {/* gentle constellation — no counter, no % */}
      <div style={{ display: 'flex', gap: 7, justifyContent: 'center', marginBottom: 30 }}>
        {steps.map((_, si) => (
          <div key={si} style={{ width: si === i ? 22 : 7, height: 7, borderRadius: 4, background: si <= i ? '#d4af37' : '#e6dcc0', transition: 'all 0.4s' }} />
        ))}
      </div>
      <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 25, color: '#1a1a2e', marginBottom: 6, lineHeight: 1.4 }}>{cur.q}</h2>
      {cur.hint && <div style={{ color: '#9a8d6a', fontSize: 13.5, fontStyle: 'italic', marginBottom: 16 }}>{cur.hint}</div>}
      {!cur.hint && <div style={{ height: 12 }} />}
      {cur.area
        ? <textarea autoFocus rows={3} value={val} onChange={e => set(e.target.value)} style={inp} placeholder="... או דבר 🎤" />
        : <input autoFocus value={val} onChange={e => set(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') next(); }} style={inp} placeholder="..." />}
      {cur.area && <MicButton value={val} onText={set} />}
      {err && <div style={{ color: '#dc2626', fontSize: 13, marginTop: 10 }}>{err}</div>}
      <div style={{ display: 'flex', gap: 10, marginTop: 22, alignItems: 'center' }}>
        <button onClick={next} disabled={busy || !canGo} style={{ ...goldBtn, opacity: (busy || !canGo) ? 0.5 : 1, cursor: (busy || !canGo) ? 'default' : 'pointer' }}>
          {busy ? '...' : (i < steps.length - 1 ? 'המשך' : 'לשלוח 🤍')}
        </button>
        {!cur.required && i < steps.length - 1 && (
          <button onClick={() => setI(i + 1)} style={{ background: 'none', border: 'none', color: '#9a8d6a', fontSize: 14, cursor: 'pointer', fontFamily: 'inherit' }}>לדלג</button>
        )}
        {i > 0 && <button onClick={() => setI(i - 1)} style={{ background: 'none', border: 'none', color: '#c4b88f', fontSize: 13, cursor: 'pointer', fontFamily: 'inherit', marginRight: 'auto' }}>חזרה</button>}
      </div>
    </div></div>
  );
}


function DashboardPage({ userId }) {
  const [profiles, setProfiles] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [err, setErr] = React.useState('');
  const [search, setSearch] = React.useState('');
  const [roleFilter, setRoleFilter] = React.useState('');
  const [genBusy, setGenBusy] = React.useState(null);   // user_id currently generating
  const [genResult, setGenResult] = React.useState({}); // { [user_id]: {link} | {err} }
  const [editId, setEditId] = React.useState(null);     // user_id whose edit panel is open
  const [showNew, setShowNew] = React.useState(false);
  const [spaceId, setSpaceId] = React.useState(null);  // member whose accompaniment space is open

  const load = React.useCallback(() => {
    setLoading(true); setErr('');
    const q = new URLSearchParams();
    if (search) q.set('search', search);
    if (roleFilter) q.set('role', roleFilter);
    const url = '/api/dashboard/profiles' + (q.toString() ? '?' + q.toString() : '');
    fetch(url, { headers: { 'X-User-Id': String(userId || '') } })
      .then(r => r.json())
      .then(d => {
        if (d.error) { setErr(d.error); setProfiles([]); }
        else setProfiles(d.profiles || []);
      })
      .catch(() => setErr('connection_error'))
      .finally(() => setLoading(false));
  }, [userId, search, roleFilter]);

  // Gate ב' trigger — reflect a portrait from this member's own road, then surface
  // the personal /i link for the host to send. Nothing is applied until they approve.
  const generateFromJourney = async (uid) => {
    setGenBusy(uid);
    setGenResult(r => ({ ...r, [uid]: null }));
    try {
      const r = await fetch('/api/identity/generate/from-journey', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-User-Id': String(userId || '') },
        body: JSON.stringify({ user_id: uid }),
      });
      const d = await r.json();
      if (!r.ok) setGenResult(s => ({ ...s, [uid]: { err: d.message || d.error || 'failed' } }));
      else { setGenResult(s => ({ ...s, [uid]: { link: d.link } })); load(); }
    } catch (e) {
      setGenResult(s => ({ ...s, [uid]: { err: 'connection_error' } }));
    } finally { setGenBusy(null); }
  };

  // applicant (filled the שאלון) -> portrait from their own intake
  const recognizeExisting = async (uid) => {
    setGenBusy(uid);
    setGenResult(r => ({ ...r, [uid]: null }));
    try {
      const r = await fetch('/api/identity/recognize-existing', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-User-Id': String(userId || '') },
        body: JSON.stringify({ user_id: uid }),
      });
      const d = await r.json();
      if (!r.ok) setGenResult(s => ({ ...s, [uid]: { err: d.message || d.error || 'failed' } }));
      else { setGenResult(s => ({ ...s, [uid]: { link: d.link } })); load(); }
    } catch (e) {
      setGenResult(s => ({ ...s, [uid]: { err: 'connection_error' } }));
    } finally { setGenBusy(null); }
  };

  React.useEffect(() => { load(); }, [load]);

  if (err === 'forbidden') {
    return (
      <section style={{ paddingTop: 80, textAlign: 'center', padding: '60px 20px' }}>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", color: '#1a1a2e' }}>הדף הזה לא זמין לך</h2>
        <p style={{ color: '#6b6d73', fontSize: 14 }}>הדשבורד פתוח רק ל-host/founder. אם זה אמור להיות פתוח לך, דבר עם דמרי.</p>
      </section>
    );
  }
  if (err === 'unauthorized') {
    return <section style={{ paddingTop: 80, textAlign: 'center' }}><p>צריך להיכנס קודם.</p></section>;
  }

  const indicatorMeta = {
    pending:         { dot: '🟡', label: 'ממתין', tip: 'יש generation שעוד לא אושר/נדחה' },
    applied:         { dot: '🆕', label: 'ביקש להצטרף', tip: 'מילא שאלון היכרות ומחכה שיקיר יקבל אותו' },
    never_sent:      { dot: '🟣', label: 'לא נשלח', tip: 'יש user אבל אין identity_token — לא נשלח לו לינק אישי' },
    never_responded: { dot: '⚪', label: 'לא הגיב', tip: 'יש לינק, האדם עוד לא לחץ accept' },
    stale:           { dot: '🔵', label: 'שקט', tip: 'אישר אבל לא היה פעיל מעל 30 יום' },
    alive:           { dot: '🟢', label: 'חי',    tip: 'אישר ופעיל ב-30 יום האחרונים' },
  };

  const counts = profiles.reduce((acc, p) => { acc[p.indicator] = (acc[p.indicator] || 0) + 1; return acc; }, {});

  return (
    <section style={{ paddingTop: 32, maxWidth: 960, margin: '0 auto', padding: '32px 16px', direction: 'rtl' }}>
      <div style={{ marginBottom: 22 }}>
        <div style={{ fontSize: 12, color: 'var(--gold)', letterSpacing: 3, fontWeight: 700, marginBottom: 4 }}>בית דוד · ניהול</div>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 28, color: '#1a1a2e', margin: '4px 0' }}>דשבורד חברים</h2>
        <p style={{ color: '#6b6d73', fontSize: 13, lineHeight: 1.6 }}>
          רשימת כל החברים. הסימן מצד שמאל אומר איפה הם בתהליך — מהקליטה לפרופיל חי.
        </p>
        <button onClick={() => setShowNew(v => !v)}
          style={{ marginTop: 12, padding: '9px 16px', fontSize: 14, fontWeight: 700, fontFamily: 'inherit',
            background: showNew ? 'rgba(212,175,55,0.2)' : '#d4af37', color: '#1a1a2e',
            border: '1px solid rgba(212,175,55,0.5)', borderRadius: 10, cursor: 'pointer' }}>
          ✦ הכר אדם חדש
        </button>
      </div>
      {showNew && <NewPersonPanel hostId={userId} onCreated={load} onClose={() => setShowNew(false)} />}
      {spaceId && <MemberSpace hostId={userId} member={profiles.find(p => p.user_id === spaceId) || { user_id: spaceId }} onClose={() => setSpaceId(null)} onSaved={load} />}

      {/* Filter row */}
      <div style={{ display: 'flex', gap: 10, marginBottom: 14, flexWrap: 'wrap' }}>
        <input
          value={search} onChange={(e) => setSearch(e.target.value)}
          placeholder="חיפוש לפי שם/טלפון..."
          style={{ flex: 1, minWidth: 200, padding: '10px 12px', fontSize: 14, borderRadius: 8,
                   border: '1.5px solid #d0c8b0', outline: 'none', fontFamily: 'inherit', direction: 'rtl' }} />
        <select value={roleFilter} onChange={(e) => setRoleFilter(e.target.value)}
          style={{ padding: '10px 12px', fontSize: 14, borderRadius: 8,
                   border: '1.5px solid #d0c8b0', background: '#fff', fontFamily: 'inherit' }}>
          <option value="">כל ה-roles</option>
          <option value="member">member</option>
          <option value="host">host</option>
          <option value="founder">founder</option>
        </select>
      </div>

      {/* Counts strip */}
      <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 16, fontSize: 13, color: '#6b6d73' }}>
        {Object.entries(indicatorMeta).map(([k, m]) => (
          <span key={k} title={m.tip} style={{ background: '#f7f3e8', padding: '4px 10px', borderRadius: 8, border: '1px solid #e6dfc6' }}>
            {m.dot} {m.label}: {counts[k] || 0}
          </span>
        ))}
      </div>

      {loading && <div style={{ textAlign: 'center', padding: 24, color: '#6b6d73' }}>טוען...</div>}

      {!loading && profiles.length === 0 && (
        <div style={{ textAlign: 'center', padding: 24, color: '#6b6d73' }}>אין חברים תואמים לפילטר.</div>
      )}

      {!loading && profiles.length > 0 && (
        <div style={{ background: '#fffdf6', border: '1px solid #e6dfc6', borderRadius: 12, overflow: 'hidden' }}>
          {profiles.map((p, i) => {
            const meta = indicatorMeta[p.indicator] || { dot: '·', label: p.indicator, tip: '' };
            const gr = genResult[p.user_id];
            return (
              <div key={p.user_id} style={{ borderBottom: i < profiles.length - 1 ? '1px solid #f0ead6' : 'none' }}>
              <div style={{
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '12px 14px',
                background: p.indicator === 'pending' ? 'rgba(255,238,168,0.18)' : 'transparent',
              }}>
                <span title={meta.tip} style={{ fontSize: 22, lineHeight: 1 }}>{meta.dot}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', gap: 8, alignItems: 'baseline', flexWrap: 'wrap' }}>
                    <a href={`/community?m=${p.user_id}`} style={{ fontWeight: 700, color: '#1a1a2e', fontSize: 15, textDecoration: 'none' }}>{p.name || `#${p.user_id}`}</a>
                    {p.role !== 'member' && <span style={{ fontSize: 10, color: 'var(--gold)', fontWeight: 700, letterSpacing: 1.5 }}>{p.role.toUpperCase()}</span>}
                    {p.phone && <span style={{ fontSize: 12, color: '#6b6d73' }}>· {p.phone}</span>}
                  </div>
                  <div style={{ fontSize: 11, color: '#8a8d95', marginTop: 2 }}>
                    {p.gen_count > 0 && <span>· {p.gen_count} generations</span>}
                    {p.last_accepted_at && <span> · אושר {(p.last_accepted_at || '').slice(0, 10)}</span>}
                    {p.has_token && <span> · יש token</span>}
                  </div>
                </div>
                <button onClick={() => generateFromJourney(p.user_id)} disabled={genBusy === p.user_id}
                  title="צור דיוקן מתוך תשובות המסע, הלימוד והחיבורים שלו — ושלח לו לאישור"
                  style={{ flexShrink: 0, padding: '7px 11px', fontSize: 12, fontWeight: 600, fontFamily: 'inherit',
                    background: genBusy === p.user_id ? '#e8dfc2' : 'rgba(212,175,55,0.12)', color: '#8b6508',
                    border: '1px solid rgba(212,175,55,0.45)', borderRadius: 9,
                    cursor: genBusy === p.user_id ? 'wait' : 'pointer', whiteSpace: 'nowrap' }}>
                  {genBusy === p.user_id ? '✦ רואה...' : '✦ ראה מהמסע'}
                </button>
                {p.self_join && !p.gen_count && (
                  <button onClick={() => recognizeExisting(p.user_id)} disabled={genBusy === p.user_id}
                    title="צור דיוקן מתוך שאלון ההיכרות שמילא"
                    style={{ flexShrink: 0, padding: '7px 11px', fontSize: 12, fontWeight: 700, fontFamily: 'inherit', background: genBusy === p.user_id ? '#e8dfc2' : 'rgba(95,163,114,0.15)', color: '#3d6b4a', border: '1px solid rgba(95,163,114,0.45)', borderRadius: 9, cursor: 'pointer', whiteSpace: 'nowrap' }}>
                    {genBusy === p.user_id ? '...' : '✦ ראה אותו'}
                  </button>
                )}
                <button onClick={() => setSpaceId(p.user_id)}
                  title="להיכנס למרחב הליווי שלו — זהות, מסע, מעגלים, מתנות, משאלות"
                  style={{ flexShrink: 0, padding: '7px 11px', fontSize: 12, fontWeight: 700, fontFamily: 'inherit',
                    background: 'rgba(212,175,55,0.15)', color: '#8b6508',
                    border: '1px solid rgba(212,175,55,0.45)', borderRadius: 9, cursor: 'pointer', whiteSpace: 'nowrap' }}>
                  ✦ מרחב ליווי
                </button>
                <div style={{ fontSize: 11, color: '#8a8d95', flexShrink: 0 }}>{(p.created_at || '').slice(0, 10)}</div>
              </div>
              {gr && (
                <div style={{ padding: '0 14px 12px 46px', fontSize: 12.5, lineHeight: 1.6 }}>
                  {gr.link ? (
                    <span style={{ color: '#1e6a3a' }}>
                      ✓ נוצר דיוקן ממתין · לינק אישי לשליחה:{' '}
                      <a href={gr.link} style={{ color: '#8b6508', fontWeight: 700, wordBreak: 'break-all' }}>{location.origin}{gr.link}</a>
                    </span>
                  ) : (
                    <span style={{ color: '#b45309' }}>
                      {gr.err === 'not_enough_road' ? 'עוד לא חשף מספיק במסע כדי לדייק דיוקן — שווה לעודד אותו לענות על כמה שאלות.' : ('לא הצליח: ' + gr.err)}
                    </span>
                  )}
                </div>
              )}
              </div>
            );
          })}
        </div>
      )}
    </section>
  );
}

// [damri 2026-05-24] IdentityRecognitionPage — /i/<token> landing for the "ראינו משהו יפה בך"
// flow. Loads /api/identity/me?token=, shows pending generation with quote/suggestion
// badges + accept/reject. After accept, profile is updated and a small confirmation
// renders. No sign-in needed — the token IS the auth.
function IdentityRecognitionPage({ token }) {
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [done, setDone] = React.useState(false);
  const [respMode, setRespMode] = React.useState(null); // null | 'almost' | 'notme'
  const [note, setNote] = React.useState('');

  const load = React.useCallback(() => {
    setErr('');
    fetch(`/api/identity/me?token=${encodeURIComponent(token)}`)
      .then(r => r.json())
      .then(d => { if (d.error) setErr(d.error); else setData(d); })
      .catch(e => setErr('connection_error'));
  }, [token]);

  React.useEffect(() => { load(); }, [load]);

  const respond = async (action, generation_id, note) => {
    setBusy(true);
    try {
      const r = await fetch(`/api/identity/me/${action}?token=${encodeURIComponent(token)}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ generation_id, note: note || undefined }),
      });
      const d = await r.json();
      if (!r.ok) { setErr(d.error || 'failed'); }
      else { setDone(true); load(); }
    } catch (e) { setErr('connection_error'); }
    finally { setBusy(false); }
  };

  if (err) {
    return (
      <section style={{ paddingTop: 80, maxWidth: 500, margin: '0 auto', padding: '60px 20px', textAlign: 'center' }}>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", color: '#1a1a2e' }}>הלינק הזה לא תקף</h2>
        <p style={{ color: '#6b6d73', fontSize: 14, lineHeight: 1.7 }}>
          קיבלת לינק שאינו מזוהה. אולי הוא נשלח בטעות, או שכבר לא בתוקף. דבר עם דמרי בוואטסאפ.
        </p>
      </section>
    );
  }

  if (!data) {
    return <section style={{ paddingTop: 80, textAlign: 'center', color: '#6b6d73' }}>טוען...</section>;
  }

  const gen = data.pending_generation;
  const output = gen ? gen.output_json : (data.accepted_generation ? data.accepted_generation.output_json : null);

  // Show fields with badges
  const fieldOrder = ['name','tagline','mission','bio','gift','values','skills','service_title','service_body','website','youtube_url','what_i_love_to_give','what_im_looking_for','suggested_service'];
  const labels = {
    name: 'שם', tagline: 'משפט מפתח', mission: 'שליחות', bio: 'תיאור',
    values: 'ערכים', skills: 'יכולות', gift: 'המתנה',
    service_title: 'שירות', service_body: 'על השירות', website: 'אתר', youtube_url: 'יוטיוב',
    what_i_love_to_give: 'מה אני אוהב לתת',
    what_im_looking_for: 'מי שאני מחפש',
    suggested_service: 'הצעת שירות',
  };

  const renderValue = (v) => {
    if (Array.isArray(v)) return v.map((x, i) => (
      <span key={i} style={{ display: 'inline-block', background: 'rgba(212,175,55,0.12)', border: '1px solid rgba(212,175,55,0.30)', borderRadius: 6, padding: '3px 9px', margin: '3px 4px 3px 0', fontSize: 13 }}>{x}</span>
    ));
    return <span>{v}</span>;
  };

  return (
    <section style={{ paddingTop: 40, maxWidth: 640, margin: '0 auto', padding: '40px 20px' }}>
      <div style={{ textAlign: 'center', marginBottom: 28 }}>
        <div style={{ fontSize: 14, color: 'var(--gold)', letterSpacing: 3, fontWeight: 700, marginBottom: 8 }}>בית דוד</div>
        <h2 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 28, color: '#1a1a2e', margin: '4px 0' }}>
          {gen ? 'ראינו משהו יפה בך' : 'הפרופיל שלך'}
        </h2>
        {gen && <p style={{ color: '#6b6d73', fontSize: 14, lineHeight: 1.7, marginTop: 8 }}>
          מתוך הדרך שעברת פה — ככה הבית ראה אותך.<br/>אין כאן נכון או לא־נכון. רק מה שאמת בעיניך.
        </p>}
      </div>

      {output && (
        <div style={{ background: '#fffdf6', border: '1.5px solid rgba(212,175,55,0.30)', borderRadius: 14, padding: 22, marginBottom: 20 }}>
          {output._avatar && output._avatar.value && (
            <div style={{ textAlign: 'center', marginBottom: 18, paddingBottom: 16, borderBottom: '1px dashed #e6dfc6' }}>
              <div style={{ width: 96, height: 96, borderRadius: '50%', overflow: 'hidden', margin: '0 auto 8px', border: '2px solid rgba(212,175,55,0.4)' }}>
                <img src={output._avatar.value} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
              </div>
              <span style={{ background: '#fbf3df', color: '#8b6508', fontSize: 10, padding: '2px 8px', borderRadius: 8, fontWeight: 700 }}>תמונה מהבית</span>
            </div>
          )}
          {fieldOrder.map(k => {
            const f = output[k];
            if (!f || f.value === null || f.value === undefined || f.value === '') return null;
            const badge = f.source === 'quote'          ? { txt: 'מצטט אותך', bg: '#e6f4ea', fg: '#1e6a3a' }
                       :  f.source === 'suggestion'     ? { txt: 'הצעה רכה',   bg: '#fbf3df', fg: '#8b6508' }
                       :  f.source === 'host_suggestion'? { txt: 'מהבית',      bg: '#fbf3df', fg: '#8b6508' }
                       :  f.source === 'unchanged'      ? { txt: 'ללא שינוי',  bg: '#eee',    fg: '#6b6d73' }
                       :                                  null;
            return (
              <div key={k} style={{ marginBottom: 16, paddingBottom: 14, borderBottom: '1px dashed #e6dfc6' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 6 }}>
                  <div style={{ fontSize: 12, color: 'var(--gold)', fontWeight: 700, letterSpacing: 2 }}>{labels[k] || k}</div>
                  {badge && (
                    <span style={{ background: badge.bg, color: badge.fg, fontSize: 10, padding: '2px 8px', borderRadius: 8, fontWeight: 700 }}>
                      {badge.txt}
                    </span>
                  )}
                </div>
                <div style={{ color: '#1a1a2e', fontSize: 15, lineHeight: 1.65 }}>{renderValue(f.value)}</div>
                {f.rationale && f.source === 'suggestion' && (
                  <div style={{ fontSize: 11, color: '#6b6d73', marginTop: 6, fontStyle: 'italic' }}>{f.rationale}</div>
                )}
              </div>
            );
          })}
          {Array.isArray(output._journey) && output._journey.length > 0 && (
            <div style={{ marginTop: 6 }}>
              <div style={{ fontSize: 12, color: 'var(--gold)', fontWeight: 700, letterSpacing: 2, marginBottom: 8 }}>עוד דברים שחשוב שנכיר בך</div>
              {output._journey.map((j, ji) => (
                <div key={ji} style={{ marginBottom: 10, paddingRight: 8, borderRight: '2px solid #ece3c8' }}>
                  <div style={{ color: '#1a1a2e', fontSize: 14, lineHeight: 1.6 }}>{j.answer_text}</div>
                  {j.source === 'quote' && <span style={{ fontSize: 10, color: '#1e6a3a' }}>· מצטט אותך</span>}
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {gen && !done && respMode === null && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 18 }}>
          <button onClick={() => respond('accept', gen.id)} disabled={busy}
            style={{ padding: '15px 18px', background: '#d4af37', color: '#1a1a2e',
              border: 'none', borderRadius: 12, fontWeight: 700, fontSize: 16, cursor: busy ? 'wait' : 'pointer',
              opacity: busy ? 0.6 : 1, fontFamily: 'inherit' }}>
            ✓ זה מדויק — זה אני
          </button>
          <button onClick={() => setRespMode('almost')} disabled={busy}
            style={{ padding: '13px 18px', background: 'rgba(212,175,55,0.10)', color: '#8b6508',
              border: '1px solid rgba(212,175,55,0.4)', borderRadius: 12, fontWeight: 600, fontSize: 15,
              cursor: 'pointer', fontFamily: 'inherit' }}>
            כמעט — קרוב, אבל לא בדיוק
          </button>
          <button onClick={() => setRespMode('notme')} disabled={busy}
            style={{ padding: '13px 18px', background: 'transparent', color: '#6b6d73',
              border: '1px solid #d0c8b0', borderRadius: 12, fontWeight: 600, fontSize: 15,
              cursor: 'pointer', fontFamily: 'inherit' }}>
            זה עוד לא אני
          </button>
        </div>
      )}
      {gen && !done && respMode !== null && (
        <div style={{ marginTop: 18 }}>
          <div style={{ fontSize: 14, color: '#5d5868', lineHeight: 1.75, marginBottom: 10, textAlign: 'center', fontStyle: 'italic' }}>
            {respMode === 'almost'
              ? 'מה היה קרוב אבל לא דייק? כל מילה עוזרת לבית לראות אותך נכון יותר. זה חלק מההתגלות — לא תיקון.'
              : 'אין בעיה בכלל. מה כן מרגיש לך אמיתי? נתחיל משם.'}
          </div>
          <textarea rows={3} value={note} onChange={e => setNote(e.target.value)}
            placeholder="במילים שלך... (אפשר גם להשאיר ריק)"
            style={{ width: '100%', boxSizing: 'border-box', padding: 12, borderRadius: 10,
              border: '1px solid rgba(212,175,55,0.4)', fontSize: 14, fontFamily: 'inherit', lineHeight: 1.6, resize: 'vertical' }} />
          <div style={{ display: 'flex', gap: 10, marginTop: 12 }}>
            <button onClick={() => respond('reject', gen.id, (respMode === 'almost' ? 'כמעט: ' : 'עוד לא אני: ') + note)} disabled={busy}
              style={{ flex: 2, padding: '13px', background: '#5fa372', color: '#fff', border: 'none',
                borderRadius: 10, fontWeight: 700, fontSize: 15, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1, fontFamily: 'inherit' }}>
              שלח — ונדייק יחד
            </button>
            <button onClick={() => { setRespMode(null); setNote(''); }} disabled={busy}
              style={{ flex: 1, padding: '13px', background: 'transparent', color: '#6b6d73',
                border: '1px solid #d0c8b0', borderRadius: 10, fontWeight: 600, fontSize: 14, cursor: 'pointer', fontFamily: 'inherit' }}>
              חזרה
            </button>
          </div>
        </div>
      )}

      {done && (
        <div style={{ textAlign: 'center', marginTop: 24, padding: 20, borderRadius: 12, fontSize: 15, lineHeight: 1.75,
          background: respMode ? '#fbf3df' : '#e6f4ea',
          border: '1px solid ' + (respMode ? '#e6d59a' : '#b7dfc5'),
          color: respMode ? '#8b6508' : '#1e6a3a' }}>
          {respMode
            ? '🤍 תודה. מה שכתבת עוזר לבית לראות אותך נכון יותר — נדייק ונחזור אליך.'
            : '✦ שמרנו. ככה הבית רואה אותך עכשיו — וזה ימשיך להתעדן יחד איתך.'}
        </div>
      )}
      {!done && !gen && data.accepted_generation && (
        <div style={{ textAlign: 'center', marginTop: 24, padding: 18, background: '#f7f5ee', borderRadius: 12, color: '#6b6d73', fontSize: 14, fontStyle: 'italic', lineHeight: 1.7 }}>
          ✦ ככה הבית רואה אותך. זה ימשיך להתעדן ככל שתלך בדרך.
        </div>
      )}
    </section>
  );
}

function FloatingRebbim({ userId }) {
  const [open, setOpen] = React.useState(false);

  React.useEffect(() => {
    if (typeof document === 'undefined' || document.getElementById('rebbim-float-style')) return;
    const s = document.createElement('style');
    s.id = 'rebbim-float-style';
    s.textContent = `
      @keyframes rebbimPulse {
        0%, 100% { box-shadow: 0 0 0 0 rgba(212,175,55,0.45), 0 4px 14px rgba(0,0,0,0.25); }
        50%      { box-shadow: 0 0 0 12px rgba(212,175,55,0.00), 0 6px 20px rgba(212,175,55,0.35); }
      }
      .rebbim-float-btn { animation: rebbimPulse 2.6s ease-in-out infinite; }
    `;
    document.head.appendChild(s);
  }, []);

  if (!open) {
    return (
      <button onClick={() => setOpen(true)} aria-label="עוזר האתר"
        className="rebbim-float-btn"
        style={{
          position: 'fixed', insetInlineEnd: 18, bottom: 18, zIndex: 9998,
          width: 56, height: 56, borderRadius: '50%',
          background: 'linear-gradient(135deg, #f5d75c, #d4af37 60%, #b8941f)',
          border: '2px solid rgba(255,255,255,0.85)',
          cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 26, fontFamily: 'inherit', color: '#1a1a2e',
        }}>💬</button>
    );
  }

  // Inline modal (no navigation away from current page)
  return (
    <div onClick={() => setOpen(false)} style={{
      position: 'fixed', inset: 0, background: 'rgba(8,6,20,0.55)', backdropFilter: 'blur(3px)',
      zIndex: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: 16,
    }}>
      <div onClick={(e) => e.stopPropagation()} style={{
        background: '#fff', borderRadius: 16, width: '100%', maxWidth: 480,
        height: '80vh', maxHeight: 600, display: 'flex', flexDirection: 'column',
        boxShadow: '0 16px 50px rgba(0,0,0,0.45)', overflow: 'hidden',
      }}>
        <SiteExplainerChat onClose={() => setOpen(false)} />
      </div>
    </div>
  );
}

// [damri 2026-05-24 round C] RebbimPage — "חצר הרבים" grid of 7 rebbes.
// Click a card → opens RebbeChat with that rebbe's name+blurb in the header.
// The chat is adaptive (rebbe_chat.py via Ollama); we inject a hint into the
// first user message so the model knows which voice to use.
function RebbimPage({ userId, onPage }) {
  const [rebbes, setRebbes] = React.useState(null);
  const [selected, setSelected] = React.useState(null);

  React.useEffect(() => {
    api('/api/rebbe/list').then(d => setRebbes((d && d.rebbes) || [])).catch(() => setRebbes([]));
  }, []);

  if (selected) {
    return <RebbeChat userId={userId} rebbeMeta={selected} onClose={() => setSelected(null)} />;
  }

  if (!rebbes) {
    return <section style={{ paddingTop: 60, textAlign: 'center' }}><p style={{ color: 'var(--muted)' }}>טוען...</p></section>;
  }

  return (
    <section dir="rtl" style={{ maxWidth: 760, margin: '0 auto', padding: '32px 18px 60px' }}>
      <div style={{ textAlign: 'center', marginBottom: 24 }}>
        <div style={{ fontSize: 12, color: 'var(--gold)', letterSpacing: 3, fontWeight: 700, marginBottom: 8 }}>חצר הרבים</div>
        <h1 style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 28, fontWeight: 800, color: 'var(--text, #1a1a2e)', margin: 0 }}>
          מורים שמלווים את המסע
        </h1>
        <p style={{ fontSize: 14, color: '#6b6d73', marginTop: 8, lineHeight: 1.6, maxWidth: 480, margin: '8px auto 0' }}>
          בחר רגע של דיבור. כל אחד מהם — שער אחר אל אותו אור.
        </p>
      </div>

      <div style={{
        display: 'grid',
        gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
        gap: 12, marginBottom: 24,
      }}>
        {rebbes.map(r => (
          <button key={r.id} onClick={() => setSelected(r)}
            style={{
              background: 'linear-gradient(135deg, rgba(212,175,55,0.08), rgba(106,76,176,0.05))',
              border: '1px solid rgba(212,175,55,0.30)',
              borderRadius: 14, padding: '16px 14px',
              cursor: 'pointer', fontFamily: 'inherit', textAlign: 'right', direction: 'rtl',
              transition: 'transform .15s, box-shadow .15s, background .2s',
            }}
            onMouseEnter={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(212,175,55,0.16), rgba(106,76,176,0.10))'; e.currentTarget.style.boxShadow = '0 4px 14px rgba(212,175,55,0.20)'; }}
            onMouseLeave={(e) => { e.currentTarget.style.background = 'linear-gradient(135deg, rgba(212,175,55,0.08), rgba(106,76,176,0.05))'; e.currentTarget.style.boxShadow = 'none'; }}>
            <div style={{ fontFamily: "'Frank Ruhl Libre', serif", fontSize: 19, fontWeight: 700, color: 'var(--text, #1a1a2e)', marginBottom: 3 }}>{r.he}</div>
            <div style={{ fontSize: 11, color: 'var(--gold)', fontWeight: 700, letterSpacing: 1, marginBottom: 8 }}>{r.dynasty}</div>
            <div style={{ fontSize: 13, color: '#5a5d63', lineHeight: 1.55 }}>{r.blurb}</div>
          </button>
        ))}
      </div>

      {/* Oved as the 8th option — gentle, set apart */}
      <div style={{
        padding: 16, borderRadius: 12,
        background: 'linear-gradient(135deg, rgba(212,175,55,0.06), rgba(95,163,114,0.06))',
        border: '1px dashed rgba(212,175,55,0.40)',
        textAlign: 'center',
      }}>
        <div style={{ fontSize: 13, color: '#5a3d0a', fontStyle: 'italic', marginBottom: 8 }}>
          ✦ או אם תרצה לדבר עם <strong>עובד</strong>, המלווה שלך במסע השוטף
        </div>
        <button onClick={() => onPage && onPage('companion')}
          style={{
            background: 'transparent', border: '1px solid rgba(212,175,55,0.55)',
            color: '#5a3d0a', padding: '8px 18px', borderRadius: 999,
            fontSize: 13, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
          }}>דבר עם עובד ←</button>
      </div>
    </section>
  );
}

function RebbeChat({ userId, onClose, rebbeMeta }) {
  const SESSION_KEY = 'bayitdavid_rebbe_session_v1';
  const [sessionId, setSessionId] = React.useState(() => {
    try { return localStorage.getItem(SESSION_KEY) || null; } catch { return null; }
  });
  const [turns, setTurns] = React.useState([]);    // [{role, text}]
  const [input, setInput] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);
  const scrollRef = React.useRef(null);

  // Hydrate previous session on mount
  React.useEffect(() => {
    if (!sessionId) return;
    fetch(`https://bayitdavid.com/api/rebbe/session/${sessionId}`)
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d && d.turns) setTurns(d.turns); })
      .catch(() => {});
  }, []);

  // Auto-scroll to bottom on new turn
  React.useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    }
  }, [turns, loading]);

  async function send() {
    const message = input.trim();
    if (!message || loading) return;
    setError(null);
    setLoading(true);
    setTurns(prev => [...prev, { role: 'user', text: message }]);
    setInput('');

    let waitTimer = setTimeout(() => {
      setError('עוד רגע — מתחבר אל המודל. שיחה ראשונה יכולה לקחת עד 90 שניות.');
    }, 25000);

    try {
      // [damri 2026-05-24 round C] hint the chosen rebbe to the adaptive chat
      const augmentedMessage = (rebbeMeta && turns.length === 0)
        ? `[שיחה עם ${rebbeMeta.he} — ${rebbeMeta.dynasty}] ${message}`
        : message;
      const res = await fetch('https://bayitdavid.com/api/rebbe/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          session_id: sessionId || undefined,
          message: augmentedMessage,
          user_id: userId,
        }),
      });
      clearTimeout(waitTimer);

      // Read as text first — if it's not JSON (CF timeout HTML, etc.), handle gracefully
      const raw = await res.text();
      let data;
      try {
        data = JSON.parse(raw);
      } catch (parseErr) {
        // Server returned non-JSON (likely CF 524 timeout or 5xx HTML page)
        const looksLikeTimeout = res.status >= 500 || /timeout|524|gateway/i.test(raw);
        setError(looksLikeTimeout
          ? 'המדריך מתחיל להתעורר. בקריאה הראשונה זה לוקח עד דקה וחצי. נסה שוב.'
          : 'תקלה זמנית. נסה שוב.');
        return;
      }

      if (!res.ok) {
        const userMsg = data.user_message || data.error || 'משהו השתבש';
        setError(userMsg);
        return;
      }
      // Clear the "still thinking" message if we got a real reply
      setError(null);
      if (data.session_id && data.session_id !== sessionId) {
        setSessionId(data.session_id);
        try { localStorage.setItem(SESSION_KEY, data.session_id); } catch {}
      }
      setTurns(prev => [...prev, { role: 'rebbe', text: data.text }]);
    } catch (e) {
      clearTimeout(waitTimer);
      // AbortError / network drop
      const msg = (e && e.name === 'AbortError') ? 'הקריאה ארוכה מדי. נסה שוב.' :
                  (e.message || 'תקלה בחיבור. נסה שוב.');
      setError(msg);
    } finally {
      clearTimeout(waitTimer);
      setLoading(false);
    }
  }

  function reset() {
    if (!confirm('להתחיל שיחה חדשה? השיחה הנוכחית תישמר אבל לא תוצג כאן.')) return;
    setSessionId(null);
    setTurns([]);
    setError(null);
    try { localStorage.removeItem(SESSION_KEY); } catch {}
  }

  const hasHistory = turns.length > 0;

  return (
    <div className="rebbe-chat-shell">
      <div className="rebbe-chat-header">
        <h2>{rebbeMeta ? rebbeMeta.he : 'שיחה'}</h2>
        <p>{rebbeMeta ? (rebbeMeta.blurb || rebbeMeta.dynasty) : 'מקום לדבר. בלי לחפש מילים. בלי תפקיד.'}</p>
        {onClose && <button className="rebbe-close" onClick={onClose} aria-label="סגור">×</button>}
        {hasHistory && (
          <button className="rebbe-back" onClick={reset}>שיחה חדשה</button>
        )}
      </div>

      <div className="rebbe-conversation" ref={scrollRef}>
        {!hasHistory && (
          <div className="rebbe-empty-hint">
            תכתוב מה שעל הלב. שלום, שאלה, ספק, רגע יפה — כל דבר.
          </div>
        )}
        {turns.map((msg, i) => (
          <div key={i} className={'rebbe-msg rebbe-msg-' + msg.role}>
            <div className="rebbe-msg-text">{msg.text}</div>
          </div>
        ))}
        {loading && (
          <div className="rebbe-loading">
            <div className="rebbe-dots"><span></span><span></span><span></span></div>
            <div>מאזין...</div>
          </div>
        )}
        {error && <div className="rebbe-error">{error}</div>}
      </div>

      <div className="rebbe-input-row">
        <textarea
          className="rebbe-input"
          rows={2}
          placeholder="כתוב..."
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyDown={e => {
            if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); send(); }
          }}
          disabled={loading}
        />
        <button
          className="rebbe-send"
          onClick={send}
          disabled={loading || input.trim().length < 1}
        >
          שלח
        </button>
      </div>
      <div className="rebbe-input-hint">Ctrl+Enter לשליחה מהירה</div>
    </div>
  );
}
