// Gantt Screen — full YBP Gantt v2
// Features: Tasks+Booklet, 3 zooms, 4-type dependencies+cascade, drag/resize,
//           multi-select, edit popup, Israeli calendar (Jewish+Muslim holidays),
//           resizable left panel, settings, PDF/Drive/WhatsApp/Gmail export
(function(){
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ── Constants ─────────────────────────────────────────────────────────────────
const LEFT_W_DEFAULT = 520;
const LEFT_W_MIN = 160;
const LEFT_W_MAX = 600;
const ROW_H = 36;

// Colors by status
const STATUS_COLOR = {
  done:'#27AE60', הסתיים:'#27AE60', אושר:'#27AE60',
  in_progress:'#4A90D9', בעבודה:'#4A90D9',
  open:'#F0A500', פתוח:'#F0A500',
  ממתין:'#95A5A6',
  'ממתין לאישור':'#E67E22',
  תקוע:'#E74C3C',
  'נשלח במייל':'#8E44AD',
  לתאם:'#16A085',
  מתואם:'#27AE60',
};
// v63 #5 — אפשרויות לדרופדאון הסטטוס בטבלה (מפתחות מ-STATUS_COLOR)
const STATUS_OPTIONS = ['open','in_progress','done','ממתין','ממתין לאישור','תקוע'];
const STAGE_COLOR = {
  תכנון:'#6C8EBF', אדריכלות:'#7B68EE', הנדסה:'#4A90D9',
  הגשה:'#F0A500', ביצוע:'#27AE60', גמר:'#E67E22',
  תיאום:'#9B59B6', פיקוח:'#2ECC71',
};
const DEP_COLOR = { FS:'#E74C3C', SS:'#3498DB', FF:'#27AE60', SF:'#F39C12' };

// Jewish + Muslim holidays 2025-2027
const HOLIDAYS = {
  // Jewish 2025
  '2025-04-13':'פסח א׳','2025-04-14':'פסח ב׳','2025-04-15':'חוה״מ פסח','2025-04-16':'חוה״מ פסח',
  '2025-04-17':'חוה״מ פסח','2025-04-18':'חוה״מ פסח','2025-04-19':'שביעי של פסח','2025-04-20':'אחרון של פסח',
  '2025-06-02':'שבועות','2025-09-23':'ראש השנה א׳','2025-09-24':'ראש השנה ב׳',
  '2025-10-02':'יום כיפור','2025-10-07':'סוכות א׳','2025-10-08':'סוכות ב׳',
  '2025-10-13':'הושענא רבה','2025-10-14':'שמיני עצרת','2025-10-15':'שמחת תורה',
  '2025-12-15':'חנוכה א׳','2025-12-22':'חנוכה ח׳',
  // Jewish 2026
  '2026-04-01':'פסח א׳','2026-04-02':'פסח ב׳','2026-04-03':'חוה״מ פסח',
  '2026-04-04':'חוה״מ פסח','2026-04-05':'חוה״מ פסח','2026-04-06':'חוה״מ פסח',
  '2026-04-07':'שביעי של פסח','2026-04-08':'אחרון של פסח',
  '2026-04-22':'יום העצמאות','2026-05-22':'שבועות',
  '2026-09-12':'ראש השנה א׳','2026-09-13':'ראש השנה ב׳',
  '2026-09-21':'יום כיפור','2026-09-26':'סוכות א׳',
  '2026-10-02':'הושענא רבה','2026-10-03':'שמיני עצרת','2026-10-04':'שמחת תורה',
  '2026-12-05':'חנוכה א׳',
  // Jewish 2027
  '2027-03-22':'פסח א׳','2027-04-12':'שבועות',
  '2027-10-02':'ראש השנה א׳','2027-10-11':'יום כיפור',
  // Muslim holidays (approximate)
  '2025-03-30':'עיד אל-פיטר','2025-03-31':'עיד אל-פיטר',
  '2025-06-06':'עיד אל-אדחא','2025-06-07':'עיד אל-אדחא','2025-06-08':'עיד אל-אדחא',
  '2025-06-27':'ראש שנה אסלאמי','2025-09-05':'מולד הנביא',
  '2026-03-20':'עיד אל-פיטר','2026-03-21':'עיד אל-פיטר',
  '2026-05-27':'עיד אל-אדחא','2026-05-28':'עיד אל-אדחא',
  '2026-06-16':'ראש שנה אסלאמי','2026-08-25':'מולד הנביא',
};
const isMuslim = (d) => ['עיד אל-פיטר','עיד אל-אדחא','ראש שנה אסלאמי','מולד הנביא'].some(h => HOLIDAYS[d] === h);

const ZOOM_CONF = {
  days:   { dayW:28, label:'ימים' },
  weeks:  { dayW:7,  label:'שבועות' },
  months: { dayW:4,  label:'חודשים' },
};

const DEP_TYPES = [
  { k:'FS', label:'Finish → Start', desc:'ב׳ לא מתחילה לפני שא׳ מסתיימת', color:'#E74C3C' },
  { k:'SS', label:'Start → Start',  desc:'ב׳ לא מתחילה לפני שא׳ מתחילה',  color:'#3498DB' },
  { k:'FF', label:'Finish → Finish',desc:'ב׳ לא מסתיימת לפני שא׳ מסתיימת',color:'#27AE60' },
  { k:'SF', label:'Start → Finish', desc:'ב׳ לא מסתיימת לפני שא׳ מתחילה', color:'#F39C12' },
];

// v60 — רשימת מקצועות קבועה לבורר אחראי + עזרי localStorage למקצועות מותאמים-אישית
const PROFESSIONS = [
  'קבלן ראשי', 'אדריכל', 'מהנדס/קונסטרוקטור', 'חשמלאי', 'אינסטלטור',
  'מיזוג אוויר', 'גבס', 'צבע', 'ריצוף/חיפוי', 'אלומיניום',
  'נגרות', 'מטבח', 'ספרינקלרים/כיבוי אש', 'איטום', 'פיתוח/גינון', 'מנהל עבודה',
];
const ASSIGNEES_KEY = 'ybp_gantt_assignees';
const loadCustomAssignees = () => {
  try { const a = JSON.parse(localStorage.getItem(ASSIGNEES_KEY) || '[]'); return Array.isArray(a) ? a : []; }
  catch { return []; }
};
const saveCustomAssignees = (arr) => {
  try { localStorage.setItem(ASSIGNEES_KEY, JSON.stringify(arr)); } catch {}
};

// ── Helpers ───────────────────────────────────────────────────────────────────
const toISO = (d) => d instanceof Date ? d.toISOString().slice(0,10) : (d||'');
const fromISO = (s) => s ? new Date(s + 'T00:00:00') : null;
const addDays = (iso, n) => {
  if (!iso || !iso.match(/^\d{4}-\d{2}-\d{2}$/)) return iso;
  const d = new Date(iso + 'T12:00:00');
  d.setDate(d.getDate() + n);
  return d.toISOString().slice(0,10);
};
const daysBetween = (a, b) => {
  if (!a || !b) return 0;
  return Math.round((new Date(b+'T12:00:00') - new Date(a+'T12:00:00')) / 86400000);
};

// ── v74 — ימי עבודה (דילוג סופ"ש/חגים) ──────────────────────────────
// ההגדרה האפקטיבית של משימה: עקיפת המשימה גוברת על ברירת המחדל של הפרויקט.
const effWork = (item, projSettings) => ({
  weekend:  (item && item.workWeekend  != null) ? !!item.workWeekend  : !!(projSettings && projSettings.workWeekend),
  holidays: !!(projSettings && projSettings.workHolidays),  // שלב 1: חגים ברמת פרויקט בלבד
});
// יום אי-עבודה לפי ההגדרה האפקטיבית (משתמש בשדות שכבר קיימים על dayObj מ-buildDateRange)
const isNonWorkingDay = (dayObj, eff) => {
  if (!dayObj) return false;
  if (dayObj.isWeekend && !eff.weekend)  return true;
  if (dayObj.holiday   && !eff.holidays) return true;
  return false;
};
// סוף תאריך אחרי nWork ימי עבודה (כולל יום ההתחלה). days = dateRange.days.
const addWorkingDays = (startISO, nWork, days, eff) => {
  if (!days || !days.length || nWork <= 0) return addDays(startISO, Math.max(0, nWork - 1));
  let i = days.findIndex(d => d.iso === startISO);
  if (i < 0) return addDays(startISO, nWork - 1);
  let counted = 0, last = startISO;
  for (; i < days.length; i++) {
    if (!isNonWorkingDay(days[i], eff)) { counted++; last = days[i].iso; if (counted >= nWork) break; }
  }
  return last;
};
// כמה ימי עבודה בין שני תאריכים (כולל קצוות).
const workingDaysBetween = (aISO, bISO, days, eff) => {
  if (!days || !days.length) return daysBetween(aISO, bISO) + 1;
  const ai = days.findIndex(d => d.iso === aISO), bi = days.findIndex(d => d.iso === bISO);
  if (ai < 0 || bi < 0) return daysBetween(aISO, bISO) + 1;
  const [lo, hi] = ai <= bi ? [ai, bi] : [bi, ai];
  let n = 0; for (let k = lo; k <= hi; k++) if (!isNonWorkingDay(days[k], eff)) n++;
  return Math.max(1, n);
};
const todayISO = () => {
  const d = new Date();
  return d.getFullYear() + '-' +
         String(d.getMonth() + 1).padStart(2, '0') + '-' +
         String(d.getDate()).padStart(2, '0');
};

// Scroll helpers — RTL containers (Chrome 85+): scrollLeft ∈ [-(maxScroll), 0]. Use abs() to normalize.
const isElRTL = (el) => { try { return getComputedStyle(el).direction === 'rtl'; } catch { return false; } };
const setScrollX = (el, x) => { if (el) el.scrollLeft = isElRTL(el) ? -x : x; };
const getScrollX = (el) => (el ? Math.abs(el.scrollLeft) : 0);

const fmtDateHe = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso + 'T00:00:00');
  return String(d.getDate()).padStart(2,'0') + '/' + String(d.getMonth()+1).padStart(2,'0') + '/' + d.getFullYear();
};

// v64 #2 — מדידת רוחב טקסט ל-autofit עמודות (canvas יחיד ממוחזר)
let _ybpMeasureCanvas = null;
const measureTextW = (text, font) => {
  try {
    if (!_ybpMeasureCanvas) _ybpMeasureCanvas = document.createElement('canvas');
    const ctx = _ybpMeasureCanvas.getContext('2d');
    ctx.font = font;
    return ctx.measureText(String(text || '')).width;
  } catch (_) { return String(text || '').length * 7; }  // fallback גס
};

const buildGmailUrl = ({ to, subject, body }) => {
  const params = new URLSearchParams();
  if (to && to.length) params.set('to', to.join(','));
  params.set('su', subject || '');
  params.set('body', body || '');
  return 'https://mail.google.com/mail/?view=cm&' + params.toString();
};

// v58 — צבע לפי מקור (לא לפי סטטוס). חיווי איחור נשמר נפרד כ-outline (ראה isItemOverdue).
const SOURCE_COLOR = {
  reject:  '#dc2626',  // אדום — ריג'קטים
  tasks:   '#1a2c4a',  // נייבי — משימות ידניות
  booklet: '#7c3aed',  // סגול — חוברת
};
const SOURCE_LABEL = { reject: "ריג'קטים", tasks: 'משימות', booklet: 'חוברת' };
const SOURCE_DOT   = { reject: '🔴', tasks: '🔵', booklet: '🟣' };

const isItemOverdue = (item) => {
  if (!item || !item.end) return false;
  if (item.status === 'done' || item.status === 'הסתיים' || item.status === 'אושר' || item.status === 'archived') return false;
  return item.end < todayISO();
};

const getItemColor = (item) => {
  return SOURCE_COLOR[item.srcKind] || STATUS_COLOR[item.status] || STAGE_COLOR[item.stage] || '#95A5A6';
};

// ── Dependencies storage (v57) ────────────────────────────────────────────────
// task-to-task deps live in tasks.data.deps and sync via Supabase.
// deps where any endpoint is a booklet phase ('bk_*') stay in localStorage as fallback.
const DEPS_LS_KEY = (projectId) => 'ybp_gd_' + projectId;
const DEPS_MIGRATED_KEY = (projectId) => 'ybp_gd_migrated_' + projectId;
const isBookletId = (id) => typeof id === 'string' && id.startsWith('bk_');

const loadLocalDeps = (projectId) => {
  try { return JSON.parse(localStorage.getItem(DEPS_LS_KEY(projectId)) || '[]'); } catch { return []; }
};
const saveLocalDeps = (projectId, deps) => {
  localStorage.setItem(DEPS_LS_KEY(projectId), JSON.stringify(deps));
};

// קרא deps: שטח את data.deps של משימות-מקור מהפרויקט + צרף את ה-deps שמערבים פריט חוברת.
const loadDeps = (projectId) => {
  const out = [];
  try {
    const store = SyncStore.get();
    (store.tasks || [])
      .filter(t => t.projectId === projectId && t.status !== 'archived')
      .forEach(t => {
        const depsArr = (t.data && Array.isArray(t.data.deps)) ? t.data.deps : [];
        depsArr.forEach(d => {
          if (!d || !d.to) return;
          out.push({ from: 'task_' + t.id, to: d.to, type: d.type || 'FS', rigid: !!d.rigid });
        });
      });
  } catch (e) { console.warn('[gantt] loadDeps tasks:', e); }
  // booklet-touching deps (לא ב-DB) — מהמקום הישן
  loadLocalDeps(projectId).forEach(d => {
    if (isBookletId(d.from) || isBookletId(d.to)) out.push(d);
  });
  return out;
};

// שמור deps: לכל משימת-מקור שמופיעה כ-from — עדכן data.deps שלה ב-SyncStore.
// deps שאחד מקצותיהם הוא חוברת — נשמרים ב-localStorage כפי שהיו.
const saveDeps = (projectId, deps) => {
  const taskDeps = new Map();        // taskId → [{to,type}]
  const bookletDeps = [];            // נשמרים מקומית
  (deps || []).forEach(d => {
    if (!d || !d.from || !d.to) return;
    if (isBookletId(d.from) || isBookletId(d.to)) { bookletDeps.push(d); return; }
    // from בפורמט 'task_<id>'
    const fromId = d.from.startsWith('task_') ? d.from.slice(5) : d.from;
    const list = taskDeps.get(fromId) || [];
    list.push({ to: d.to, type: d.type || 'FS', rigid: !!d.rigid });
    taskDeps.set(fromId, list);
  });
  // אפס deps על משימות שכבר אין להן יעדים בסט החדש (כדי שמחיקה תתפשט)
  try {
    const store = SyncStore.get();
    (store.tasks || [])
      .filter(t => t.projectId === projectId)
      .forEach(t => {
        const hadDeps = t.data && Array.isArray(t.data.deps) && t.data.deps.length > 0;
        const nextList = taskDeps.get(t.id) || [];
        const changed = JSON.stringify((t.data && t.data.deps) || []) !== JSON.stringify(nextList);
        if (changed || (hadDeps && nextList.length === 0)) {
          SyncStore.updateTask(t.id, { data: { ...(t.data || {}), deps: nextList } });
        }
        taskDeps.delete(t.id);
      });
    // משימות חדשות שלא היו ב-loop (נדיר אבל ייתכן)
    taskDeps.forEach((list, taskId) => {
      SyncStore.updateTask(taskId, { data: { deps: list } });
    });
  } catch (e) { console.warn('[gantt] saveDeps tasks:', e); }
  // השאר את booklet deps + הישן (גיבוי): כתוב רק את ה-booklet החדשים
  saveLocalDeps(projectId, bookletDeps);
};

// מיגרציה חד-פעמית — מעביר deps מ-ybp_gd_<projectId> ל-tasks.data.deps. שומר fallback.
const migrateGanttDepsToCloud = (projectId) => {
  const flag = DEPS_MIGRATED_KEY(projectId);
  if (localStorage.getItem(flag) === '1') return;
  const legacy = loadLocalDeps(projectId);
  if (!legacy.length) { localStorage.setItem(flag, '1'); return; }
  try {
    const taskTaskDeps = legacy.filter(d => !isBookletId(d.from) && !isBookletId(d.to));
    if (!taskTaskDeps.length) { localStorage.setItem(flag, '1'); return; }
    const byFrom = new Map();
    taskTaskDeps.forEach(d => {
      const fromId = d.from.startsWith('task_') ? d.from.slice(5) : d.from;
      const list = byFrom.get(fromId) || [];
      list.push({ to: d.to, type: d.type || 'FS' });
      byFrom.set(fromId, list);
    });
    const store = SyncStore.get();
    const taskIds = new Set((store.tasks || []).map(t => t.id));
    byFrom.forEach((list, taskId) => {
      if (!taskIds.has(taskId)) return;
      const existing = (store.tasks.find(t => t.id === taskId) || {}).data || {};
      SyncStore.updateTask(taskId, { data: { ...existing, deps: list } });
    });
    localStorage.setItem(flag, '1');
    console.info('[gantt] migrated', taskTaskDeps.length, 'deps from localStorage to cloud');
  } catch (e) { console.warn('[gantt] migrateGanttDepsToCloud:', e); }
};

// ── Cascade logic ─────────────────────────────────────────────────────────────
// v74 — days/projSettings אופציונליים: כשמועברים, FS/SS שומרים על מספר ימי-העבודה (דילוג סופ"ש/חג).
// FF/SF נשארים קלנדריים (חישוב לאחור בימי-עבודה = שלב 2). בלי הפרמטרים — התנהגות זהה לקודם.
const cascadeItems = (items, deps, fromId, daysDelta, visited = new Set(), days, projSettings) => {
  if (visited.has(fromId)) return items;
  visited.add(fromId);
  const fromItem = items.find(i => i.id === fromId);
  if (!fromItem) return items;
  let updated = [...items];
  const useWork = !!(days && days.length);
  // v60 — dep.rigid=true מדלג ב-cascade (Manually-Scheduled). false/undefined = דינמי.
  deps.filter(d => d.from === fromId && !d.rigid).forEach(dep => {
    const toIdx = updated.findIndex(i => i.id === dep.to);
    if (toIdx < 0) return;
    const to = { ...updated[toIdx] };
    const eff = effWork(to, projSettings);
    const calDur = daysBetween(to.start, to.end);                                   // span קלנדרי
    const workDur = useWork ? workingDaysBetween(to.start, to.end, days, eff) : (calDur + 1);
    const fwdEnd = (s) => useWork ? addWorkingDays(s, workDur, days, eff) : addDays(s, calDur);
    if (dep.type === 'FS') { to.start = addDays(fromItem.end, 1); to.end = fwdEnd(to.start); }
    else if (dep.type === 'SS') { to.start = fromItem.start; to.end = fwdEnd(to.start); }
    else if (dep.type === 'FF') { to.end = fromItem.end; to.start = addDays(to.end, -calDur); }
    else if (dep.type === 'SF') { to.end = fromItem.start; to.start = addDays(to.end, -calDur); }
    updated[toIdx] = to;
    updated = cascadeItems(updated, deps, dep.to, daysDelta, visited, days, projSettings);
  });
  return updated;
};

// ── Build items from store (v58: 4-way source filter + srcKind) ──────────────
// source: 'all' | 'tasks' | 'rejects' | 'booklet'
const buildItems = (projectId, store, source, projectArg) => {
  const items = [];
  const validISO = (s) => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(s);
  const DONE = ['done','הסתיים','אושר','הושלם','completed']; // v63 #2 — סטטוסים שנחשבים מושלמים
  const wantTasks   = source === 'all' || source === 'tasks' || source === 'rejects';
  const wantBooklet = source === 'all' || source === 'booklet';
  if (wantTasks) {
    // v63 #2 — הסתר משימות שהושלמו (עדיין קיימות ב-SyncStore, רק לא מוצגות בגאנט)
    (store.tasks || []).filter(t => t.projectId === projectId && t.status !== 'archived' && !DONE.includes(t.status)).forEach(t => {
      const srcKind = (t.source === 'reject' || t.source === 'finding') ? 'reject' : 'tasks';
      if (source === 'tasks'   && srcKind !== 'tasks')  return;
      if (source === 'rejects' && srcKind !== 'reject') return;
      const start = t.startDate || t.createdAt?.slice(0,10) || todayISO();
      const end = t.due || addDays(start, 7);
      if (!start.match(/^\d{4}-\d{2}-\d{2}$/) || !end.match(/^\d{4}-\d{2}-\d{2}$/)) return;
      items.push({ id:'task_'+t.id, rawId:t.id, name:t.title||'ללא שם', start, end,
        status:t.status||'open', type:'task', source:'task', srcKind, stage:t.stage||'', raw:t,
        assignee:t.assignee, percentDone: t.percentDone||0,
        workWeekend: (t.data && t.data.workWeekend != null) ? t.data.workWeekend : t.workWeekend }); // v74 — עקיפת סופ"ש
    });
  }
  if (wantBooklet) {
    // v63 #1 — מקור החוברת = loadBooklet האמיתי (project.phases ריק ב-Supabase). רק לא-מושלמים, ללא 'gantt'.
    const project = projectArg || YBP_DATA?.projects?.find(p => p.id === projectId);
    const PHASE_DAYS = 21;
    const baseStart = validISO(project?.startDate) ? project.startDate : todayISO();
    const bookletItems = (window.YBP_BOOKLET?.loadBooklet?.(projectId) || [])
      .filter(it => !it.completed && it.type !== 'gantt');
    bookletItems.forEach((it) => {
      const ord = (typeof it.order === 'number' && it.order > 0) ? it.order : 1;
      const end = validISO(it.defaultDue) ? it.defaultDue : addDays(baseStart, ord * PHASE_DAYS);
      const start = addDays(end, -(PHASE_DAYS - 1));
      if (!validISO(start) || !validISO(end)) return;
      items.push({ id:'bk_'+it.id, rawId:it.id, name:it.title||'משימת חוברת', start, end,
        status:'ממתין', type:'booklet', source:'booklet', srcKind:'booklet', stage:it.stage||'', raw:it,
        assignee:'', percentDone:0 });
    });
  }
  return items;
};

// ── Date range builder ────────────────────────────────────────────────────────
const buildDateRange = (items, project) => {
  // v62 #1 — התאם את הציר לטווח הפריטים בפועל (לא לכפות שנה → דחיסה).
  const today = todayISO();
  const minForward = addDays(today, 365); // v67 #2 — תמיד לפחות שנה קדימה (חלון קבוע, לא infinite scroll)
  const validDates = items.flatMap(i => [i.start, i.end]).filter(d => d && d.match(/^\d{4}-\d{2}-\d{2}$/));
  let start, end;
  if (validDates.length) {
    const minDate = validDates.reduce((m, d) => d < m ? d : m);
    const maxDate = validDates.reduce((m, d) => d > m ? d : m);
    start = addDays(minDate, -7);    // ריפוד אחורה
    end   = addDays(maxDate, 90);    // 90 יום אחרי המשימה האחרונה
    if (minForward > end) end = minForward;  // קח את המאוחר מבין השניים → לפחות שנה קדימה
    // אם לפרויקט endDate תקין ומאוחר יותר — הרחב עד שם (+14)
    const pEnd = project?.endDate?.match(/^\d{4}-\d{2}-\d{2}$/) ? project.endDate : null;
    if (pEnd && addDays(pEnd, 14) > end) end = addDays(pEnd, 14);
  } else {
    // אין פריטים — גם פרויקט ריק רואה שנה קדימה
    start = addDays(today, -10);
    end   = minForward;
  }
  // מינימום-טווח לקריאוּת
  if (daysBetween(start, end) < 30) end = addDays(start, 30);
  const days = [];
  let cur = start;
  let safe = 0;
  // תקרת בטיחות (התקרה הקיימת מספיקה לשנה+ריפוד)
  while (cur <= end && safe < 1200) {
    safe++;
    const d = new Date(cur + 'T12:00:00');
    if (isNaN(d.getTime())) break;
    const dow = d.getDay();
    days.push({ iso:cur, date:d, dow, isWeekend: dow === 5 || dow === 6,
      holiday: HOLIDAYS[cur], isMuslim: isMuslim(cur) });
    cur = addDays(cur, 1);
  }
  return { days, start, end };
};

// ── Dependency SVG Lines ──────────────────────────────────────────────────────
const DepLines = ({ items, deps, dateRange, dayW, totalW }) => {
  // v66 #1 — קנבס עוגן-ימין ברוחב totalW (כמו הברים, right-anchored), וקצוות מיושרים למוסכמת getBarPos.
  const edgeX = (item, edge) => {
    const si = dateRange.days.findIndex(d => d.iso === item.start);
    const ei = dateRange.days.findIndex(d => d.iso === item.end);
    if (si < 0 && ei < 0) return -1;
    const si2 = si < 0 ? 0 : si;
    const ei2 = ei < 0 ? dateRange.days.length - 1 : ei;
    // start = הקצה המוקדם (ימין הבר) = si2*dayW מימין → X-שמאל בקנבס; end = הקצה המאוחר (שמאל הבר) = (ei2+1)*dayW מימין
    return (edge === 'start') ? (totalW - si2 * dayW) : (totalW - (ei2 + 1) * dayW);
  };
  const getY = (id) => {
    const idx = items.findIndex(i => i.id === id);
    return idx >= 0 ? idx * ROW_H + ROW_H / 2 : -1;
  };
  return (
    <svg style={{ position:'absolute', top:0, right:0, width: totalW, height: items.length * ROW_H, pointerEvents:'none', zIndex:5 }}>
      {deps.map((dep, di) => {
        const fromItem = items.find(i => i.id === dep.from);
        const toItem = items.find(i => i.id === dep.to);
        if (!fromItem || !toItem) return null;
        const color = DEP_COLOR[dep.type] || '#999';
        let x1, y1, x2, y2;
        if (dep.type === 'FS') { x1 = edgeX(fromItem, 'end'); y1 = getY(dep.from); x2 = edgeX(toItem, 'start'); y2 = getY(dep.to); }
        else if (dep.type === 'SS') { x1 = edgeX(fromItem, 'start'); y1 = getY(dep.from); x2 = edgeX(toItem, 'start'); y2 = getY(dep.to); }
        else if (dep.type === 'FF') { x1 = edgeX(fromItem, 'end'); y1 = getY(dep.from); x2 = edgeX(toItem, 'end'); y2 = getY(dep.to); }
        else { x1 = edgeX(fromItem, 'start'); y1 = getY(dep.from); x2 = edgeX(toItem, 'end'); y2 = getY(dep.to); }
        if (x1 < 0 || x2 < 0) return null;
        const cx = (x1 + x2) / 2;
        return (
          <g key={di}>
            <path d={'M '+x1+' '+y1+' C '+cx+' '+y1+' '+cx+' '+y2+' '+x2+' '+y2} fill="none" stroke={color} strokeWidth={1.5} strokeDasharray={dep.rigid ? '6 3' : ''} opacity={0.8}/>
            <circle cx={x1} cy={y1} r={3} fill={color}/>
            <polygon points={(x2-5)+','+(y2-4)+' '+(x2+1)+','+y2+' '+(x2-5)+','+(y2+4)} fill={color}/>
          </g>
        );
      })}
    </svg>
  );
};

// ── Assignee Cell + Picker (v60 #5) ──────────────────────────────────────────
// ── Status Cell — דרופדאון inline (v63 #5) ───────────────────────────────────
const StatusCell = ({ item, isBooklet, cellBase, onChange }) => {
  const color = STATUS_COLOR[item.status] || 'var(--ybp-ink-soft)';
  if (isBooklet) {
    return (
      <div role="gridcell" style={{ ...cellBase, cursor:'default' }} title="פריט חוברת — לא ניתן לעריכה inline">
        <span style={{ color, fontWeight:600, fontSize:11, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{item.status || '—'}</span>
      </div>
    );
  }
  // ודא שהערך הנוכחי מופיע ברשימה גם אם אינו ב-STATUS_OPTIONS
  const opts = STATUS_OPTIONS.includes(item.status) ? STATUS_OPTIONS : [item.status, ...STATUS_OPTIONS].filter(Boolean);
  return (
    <div role="gridcell" style={{ ...cellBase, padding:'0 3px' }} onClick={(e) => e.stopPropagation()}>
      <select value={item.status || 'open'} onChange={(e) => onChange(e.target.value)}
        title="שנה סטטוס"
        style={{ width:'100%', height:24, border:'1px solid var(--ybp-border)', borderRadius:4,
          background:'var(--ybp-input-bg)', color, fontWeight:700, fontSize:11, fontFamily:'inherit', cursor:'pointer', outline:'none' }}>
        {opts.map(s => <option key={s} value={s} style={{ color:'var(--ybp-ink)', background:'var(--ybp-input-bg)' }}>{s}</option>)}
      </select>
    </div>
  );
};

const AssigneeCell = ({ item, rowIdx, colIdx, isBooklet, cellBase, isEditingThis, startCellEdit, setEditingCell, applyCellSave }) => {
  const [open, setOpen] = React.useState(false);
  const [customList, setCustomList] = React.useState(loadCustomAssignees);
  const [draft, setDraft] = React.useState('');

  React.useEffect(() => { if (isEditingThis) setOpen(true); }, [isEditingThis]);
  const handleClose = () => { setOpen(false); setEditingCell(null); };
  const pick = (val) => {
    applyCellSave(item, colIdx, val);
    handleClose();
  };
  const addCustom = () => {
    const v = draft.trim();
    if (!v) return;
    if (PROFESSIONS.includes(v) || customList.includes(v)) {
      pick(v);
      return;
    }
    const nextList = [...customList, v];
    setCustomList(nextList);
    saveCustomAssignees(nextList);
    setDraft('');
    pick(v);
  };
  const removeCustom = (name) => {
    const nextList = customList.filter(x => x !== name);
    setCustomList(nextList);
    saveCustomAssignees(nextList);
  };

  const cellOnClick = (e) => {
    if (isBooklet) return;
    e.stopPropagation();
    setOpen(true);
    startCellEdit(rowIdx, colIdx);
  };

  return (
    <div
      role="gridcell"
      style={{ ...cellBase, cursor: isBooklet ? 'default' : 'cell', justifyContent:'space-between' }}
      onClick={cellOnClick}
      title={isBooklet ? 'פריט חוברת — לא ניתן לעריכה inline' : 'קליק לבחירת אחראי'}
    >
      <span style={{ color: item.assignee ? 'var(--ybp-ink)' : 'var(--ybp-ink-faint)', overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', flex:1 }}>
        {item.assignee || '—'}
      </span>
      {!isBooklet && <span style={{ fontSize:9, opacity:0.5, marginInlineStart:4 }}>▾</span>}
      {open && (
        <div onClick={e => e.stopPropagation()} style={{
          position:'fixed', inset:0, background:'rgba(0,0,0,0.45)', zIndex:9100,
          display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'Assistant,sans-serif',
        }} onMouseDown={(e) => { if (e.target === e.currentTarget) handleClose(); }}>
          <div style={{ background:'var(--ybp-panel, #fff)', color:'var(--ybp-ink, #111827)', borderRadius:10, width:320,
            maxHeight:'70vh', display:'flex', flexDirection:'column', overflow:'hidden', direction:'rtl',
            boxShadow:'0 24px 64px rgba(0,0,0,0.35)' }}>
            <div style={{ padding:'12px 14px', borderBottom:'1px solid var(--ybp-border, #e5e7eb)',
              display:'flex', alignItems:'center', gap:8 }}>
              <span style={{ flex:1, fontSize:14, fontWeight:700 }}>בחר אחראי</span>
              <button onClick={handleClose} style={{ background:'transparent', border:'none', cursor:'pointer', fontSize:16, color:'var(--ybp-ink-soft, #6b7280)' }}>✕</button>
            </div>
            <div style={{ flex:1, overflowY:'auto', padding:'6px 8px' }}>
              {[...PROFESSIONS, ...customList].map(name => {
                const isCustom = !PROFESSIONS.includes(name);
                const isCurrent = name === item.assignee;
                return (
                  <div key={name} style={{ display:'flex', alignItems:'center', gap:6, padding:'6px 8px',
                    borderRadius:6, background: isCurrent ? 'var(--ybp-row-hover, #f1f3f8)' : 'transparent',
                    cursor:'pointer' }}
                    onClick={() => pick(name)}
                    onMouseEnter={e => { if (!isCurrent) e.currentTarget.style.background = 'var(--ybp-row-hover, #f1f3f8)'; }}
                    onMouseLeave={e => { if (!isCurrent) e.currentTarget.style.background = 'transparent'; }}>
                    <span style={{ flex:1, fontSize:13 }}>{name}</span>
                    {isCustom && (
                      <button onClick={(e) => { e.stopPropagation(); removeCustom(name); }}
                        title="הסר מהרשימה"
                        style={{ background:'transparent', border:'none', cursor:'pointer', color:'#dc2626', fontSize:13, padding:'2px 6px' }}>×</button>
                    )}
                    {isCurrent && <span style={{ fontSize:11, color:'var(--ybp-ink-soft, #6b7280)' }}>✓</span>}
                  </div>
                );
              })}
              {item.assignee && (
                <div style={{ borderTop:'1px solid var(--ybp-border-soft, #eef0f2)', marginTop:6, paddingTop:6 }}>
                  <button onClick={() => pick('')} style={{ width:'100%', padding:'6px 8px', textAlign:'right',
                    background:'transparent', border:'none', cursor:'pointer', color:'var(--ybp-ink-soft, #6b7280)',
                    fontSize:12, fontFamily:'inherit' }}>נקה אחראי</button>
                </div>
              )}
            </div>
            <div style={{ padding:'10px 12px', borderTop:'1px solid var(--ybp-border, #e5e7eb)',
              display:'flex', gap:6, alignItems:'center' }}>
              <input value={draft} onChange={(e) => setDraft(e.target.value)}
                onKeyDown={(e) => { if (e.key === 'Enter') addCustom(); if (e.key === 'Escape') handleClose(); }}
                placeholder="הוסף אחר..."
                style={{ flex:1, padding:'7px 9px', borderRadius:6, border:'1px solid var(--ybp-border, #e5e7eb)',
                  background:'var(--ybp-input-bg, #fff)', color:'var(--ybp-ink, #111827)', fontSize:13, fontFamily:'inherit', direction:'rtl' }}/>
              <button onClick={addCustom} disabled={!draft.trim()}
                style={{ padding:'7px 12px', borderRadius:6, border:'none', cursor: draft.trim() ? 'pointer' : 'not-allowed',
                  background: draft.trim() ? 'var(--ybp-navy, #1a2c4a)' : 'var(--ybp-border, #e5e7eb)',
                  color:'#fff', fontSize:13, fontWeight:700, fontFamily:'inherit' }}>+ הוסף</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

// ── Predecessor Cell + Picker (v60 #2 + #3) ──────────────────────────────────
const PredecessorCell = ({ item, cellBase, predText, predList, isBooklet, displayItems, deps, saveDepsLocal, onUpsertDep }) => {
  const [open, setOpen] = React.useState(false);
  // existing predecessors targeting this item (incoming deps where d.to === item.id)
  const existing = React.useMemo(() =>
    (deps || []).filter(d => d.to === item.id),
    [deps, item.id]
  );
  // candidates: any task not the current item, not booklet (for v-G4)
  const candidates = React.useMemo(() =>
    displayItems.filter(it => it.id !== item.id && it.source === 'task'),
    [displayItems, item.id]
  );

  const upsertDep = (fromId, patch) => {
    let next = deps.slice();
    const idx = next.findIndex(d => d.from === fromId && d.to === item.id);
    if (idx >= 0) next[idx] = { ...next[idx], ...patch };
    else next.push({ from: fromId, to: item.id, type: 'FS', rigid: false, ...patch });
    // v74 — cascade מיידי מ-fromId כך שתאריך המשימה התלויה זז עם הוספת/עדכון התלות
    if (onUpsertDep) onUpsertDep(next, fromId); else saveDepsLocal(next);
  };
  const removeDep = (fromId) => {
    saveDepsLocal(deps.filter(d => !(d.from === fromId && d.to === item.id)));
  };

  return (
    <div role="gridcell"
      style={{ ...cellBase, cursor: isBooklet ? 'default' : 'pointer', color:'var(--ybp-ink-soft)', justifyContent:'space-between' }}
      title={isBooklet ? 'פריט חוברת' : (predList.join(', ') || 'אין קודמים — קליק להוספה')}
      onClick={(e) => { if (isBooklet) return; e.stopPropagation(); setOpen(true); }}>
      <span style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', flex:1 }}>{predText}</span>
      {!isBooklet && <span style={{ fontSize:9, opacity:0.5, marginInlineStart:4 }}>▾</span>}
      {open && (
        <div onClick={e => e.stopPropagation()} style={{
          position:'fixed', inset:0, background:'rgba(0,0,0,0.45)', zIndex:9100,
          display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'Assistant,sans-serif',
        }} onMouseDown={(e) => { if (e.target === e.currentTarget) setOpen(false); }}>
          <div style={{ background:'var(--ybp-panel, #fff)', color:'var(--ybp-ink, #111827)', borderRadius:10,
            width:560, maxHeight:'75vh', display:'flex', flexDirection:'column', overflow:'hidden', direction:'rtl',
            boxShadow:'0 24px 64px rgba(0,0,0,0.35)' }}>
            <div style={{ padding:'12px 14px', borderBottom:'1px solid var(--ybp-border, #e5e7eb)',
              display:'flex', alignItems:'center', gap:8 }}>
              <span style={{ flex:1, fontSize:14, fontWeight:700 }}>קודמים — {item.name}</span>
              <button onClick={() => setOpen(false)} style={{ background:'transparent', border:'none', cursor:'pointer', fontSize:16, color:'var(--ybp-ink-soft, #6b7280)' }}>✕</button>
            </div>
            <div style={{ padding:'10px 12px', fontSize:11, color:'var(--ybp-ink-soft, #6b7280)' }}>
              "קודם" = משימה שמסתיימת/מתחילה לפני שזו מתחילה/מסתיימת. <b>דינמי</b> = cascade אוטומטי כשהקודמת זזה. <b>קשיח</b> = הקישור נשמר אבל לא זז את המשימה הזו.
            </div>
            <div style={{ flex:1, overflowY:'auto', padding:'4px 10px 12px', display:'flex', flexDirection:'column', gap:6 }}>
              {candidates.length === 0 && (
                <div style={{ padding:24, textAlign:'center', color:'var(--ybp-ink-faint, #9ca3af)', fontSize:13 }}>
                  אין משימות אחרות בפרויקט שניתן לבחור כקודמות.
                </div>
              )}
              {candidates.map(cand => {
                const ex = existing.find(d => d.from === cand.id);
                const checked = !!ex;
                const type = ex?.type || 'FS';
                const rigid = !!ex?.rigid;
                return (
                  <div key={cand.id} style={{ display:'grid', gridTemplateColumns:'24px 1fr 110px 130px',
                    gap:8, alignItems:'center', padding:'8px 10px',
                    background:'var(--ybp-row-hover, #f1f3f8)', borderRadius:7 }}>
                    <input type="checkbox" checked={checked}
                      onChange={(e) => {
                        if (e.target.checked) upsertDep(cand.id, { type, rigid });
                        else removeDep(cand.id);
                      }} style={{ accentColor:'var(--ybp-navy, #1a2c4a)' }}/>
                    <span style={{ fontSize:13, fontWeight:600, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{cand.name}</span>
                    <select value={type} disabled={!checked}
                      onChange={(e) => upsertDep(cand.id, { type: e.target.value, rigid })}
                      style={{ padding:'5px 6px', borderRadius:5, border:'1px solid var(--ybp-border, #e5e7eb)',
                        background:'var(--ybp-input-bg, #fff)', color:'var(--ybp-ink, #111827)', fontSize:12, fontFamily:'inherit' }}>
                      {DEP_TYPES.map(dt => <option key={dt.k} value={dt.k}>{dt.k}</option>)}
                    </select>
                    <div style={{ display:'flex', background:'var(--ybp-panel, #fff)', borderRadius:5, padding:2, gap:1, border:'1px solid var(--ybp-border, #e5e7eb)' }}>
                      {[['false','דינמי'],['true','קשיח']].map(([k,l]) => {
                        const active = String(rigid) === k;
                        return (
                          <button key={k} disabled={!checked}
                            onClick={() => upsertDep(cand.id, { type, rigid: k === 'true' })}
                            style={{ flex:1, padding:'4px 6px', border:'none', borderRadius:3, cursor: checked ? 'pointer' : 'not-allowed',
                              background: active ? 'var(--ybp-navy, #1a2c4a)' : 'transparent',
                              color: active ? '#fff' : (checked ? 'var(--ybp-ink-soft, #6b7280)' : 'var(--ybp-ink-faint, #9ca3af)'),
                              fontSize:11, fontWeight:600, fontFamily:'inherit' }}>{l}</button>
                        );
                      })}
                    </div>
                  </div>
                );
              })}
            </div>
            <div style={{ padding:'10px 12px', borderTop:'1px solid var(--ybp-border, #e5e7eb)', display:'flex', justifyContent:'flex-start' }}>
              <button onClick={() => setOpen(false)} style={{ padding:'8px 18px', borderRadius:6, border:'none',
                background:'var(--ybp-navy, #1a2c4a)', color:'#fff', fontSize:13, fontWeight:700, cursor:'pointer', fontFamily:'inherit' }}>סיום</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

// ── Edit Popup ────────────────────────────────────────────────────────────────
const EditPopup = ({ item, deps, allItems, onSave, onClose, onDeleteDep, onAddDep, onDelete, contacts }) => {
  if (!item) return null;
  const [name, setName] = useState(item.name);
  const [start, setStart] = useState(item.start);
  const [end, setEnd] = useState(item.end);
  const [status, setStatus] = useState(item.status);
  const [pct, setPct] = useState(item.percentDone || 0);
  const [assignee, setAssignee] = useState(item.assignee || '');
  const [stage, setStage] = useState(item.stage || '');
  const [notes, setNotes] = useState(item.notes || '');
  // v74 — עקיפת "עבודה בסופ"ש" ברמת משימה: '' = יורש מהפרויקט, 'yes' = כן, 'no' = לא
  const [workWeekend, setWorkWeekend] = useState(item.workWeekend == null ? '' : (item.workWeekend ? 'yes' : 'no'));
  const [confirmDelete, setConfirmDelete] = useState(false);
  const [showAddDep, setShowAddDep] = useState(false);
  const [newDepTo, setNewDepTo] = useState('');
  const [newDepType, setNewDepType] = useState('FS');

  const contactList = contacts || YBP_DATA?.projectDetail?.contacts || [];
  const stageOpts = Object.keys(STAGE_COLOR);

  const inDeps = deps.filter(d => d.to === item.id);
  const outDeps = deps.filter(d => d.from === item.id);

  const statusOpts = item.source === 'task'
    ? ['open','in_progress','done','archived']
    : ['ממתין','בעבודה','הסתיים','אושר','ממתין לאישור','תקוע'];

  return (
    <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', zIndex:9999, display:'flex', alignItems:'center', justifyContent:'center' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ background:'#fff', borderRadius:12, width:'min(480px, 96vw)', maxHeight:'85vh', overflow:'auto', direction:'rtl', fontFamily:'Assistant,sans-serif', boxShadow:'0 20px 60px rgba(0,0,0,0.3)' }}>

        {/* Header */}
        <div style={{ padding:'14px 18px', borderBottom:'1px solid #e5e7eb', display:'flex', alignItems:'center', gap:10 }}>
          <span style={{ fontSize:12, padding:'3px 8px', borderRadius:10, fontWeight:700,
            background: (SOURCE_COLOR[item.srcKind] || '#9ca3af') + '22',
            color: SOURCE_COLOR[item.srcKind] || '#9ca3af' }}>
            {(SOURCE_DOT[item.srcKind] || '') + ' ' + (SOURCE_LABEL[item.srcKind] || (item.source==='task' ? 'משימה' : 'חוברת'))}
          </span>
          <span style={{ flex:1, fontSize:14, fontWeight:700, color:'#1f2937' }}>עריכת פריט</span>
          <button onClick={onClose} style={{ background:'transparent', border:'none', cursor:'pointer', fontSize:18, color:'#6b7280' }}>✕</button>
        </div>

        <div style={{ padding:18, display:'flex', flexDirection:'column', gap:12 }}>
          <div>
            <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>שם</label>
            <input value={name} onChange={e=>setName(e.target.value)}
              style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}/>
          </div>

          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>התחלה</label>
              <input type="date" value={start} onChange={e => {
                const newStart = e.target.value;
                const dur = daysBetween(start, end);
                setStart(newStart);
                if (newStart && dur >= 0) setEnd(addDays(newStart, dur));
              }}
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}/>
            </div>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>סיום</label>
              <input type="date" value={end} onChange={e=>setEnd(e.target.value)}
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}/>
            </div>
          </div>

          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>סטטוס</label>
              <select value={status} onChange={e=>setStatus(e.target.value)}
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}>
                {statusOpts.map(s => <option key={s} value={s}>{s}</option>)}
              </select>
            </div>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>התקדמות: {pct}%</label>
              <input type="range" min={0} max={100} step={5} value={pct} onChange={e=>setPct(+e.target.value)}
                style={{ width:'100%', marginTop:6 }}/>
            </div>
          </div>

          {/* אחראי + שלב */}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>אחראי</label>
              <select value={assignee} onChange={e=>setAssignee(e.target.value)}
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}>
                <option value="">— ללא —</option>
                {/* v66 #4 — מקור אחראי אחיד עם הטבלה: מקצועות + מותאמים (לא contactList שבד"כ ריק) */}
                {(() => {
                  const base = [...PROFESSIONS, ...loadCustomAssignees()];
                  const list = assignee && !base.includes(assignee) ? [assignee, ...base] : base;
                  return list.map(name => <option key={name} value={name}>{name}</option>);
                })()}
              </select>
            </div>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>שלב</label>
              <select value={stage} onChange={e=>setStage(e.target.value)}
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}>
                <option value="">— ללא —</option>
                {stageOpts.map(s => <option key={s} value={s}>{s}</option>)}
              </select>
            </div>
          </div>

          {/* הערות */}
          <div>
            <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>הערות</label>
            <textarea value={notes} onChange={e=>setNotes(e.target.value)} rows={2}
              placeholder="הערות נוספות..."
              style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box', resize:'vertical', direction:'rtl' }}/>
          </div>

          {/* v74 — עבודה בסופי שבוע (למשימה זו) */}
          <div>
            <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>עבודה בסופי שבוע (למשימה זו)</label>
            <select value={workWeekend} onChange={e=>setWorkWeekend(e.target.value)}
              style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}>
              <option value="">יורש מהפרויקט</option>
              <option value="yes">כן — עובד בסופ"ש</option>
              <option value="no">לא — מדלג סופ"ש</option>
            </select>
          </div>

          {/* Dependencies */}
          <div style={{ borderTop:'1px solid #f3f4f6', paddingTop:12 }}>
            <div style={{ fontSize:12, fontWeight:700, color:'#374151', marginBottom:8 }}>תלויות</div>

            {inDeps.length > 0 && (
              <div style={{ marginBottom:8 }}>
                <div style={{ fontSize:10, color:'#9ca3af', fontWeight:600, marginBottom:4 }}>מקדים (Predecessor)</div>
                {inDeps.map((d,i) => {
                  const from = allItems.find(it => it.id === d.from);
                  return (
                    <div key={i} style={{ display:'flex', alignItems:'center', gap:8, padding:'5px 8px', background:'#f9fafb', borderRadius:5, marginBottom:3 }}>
                      <span style={{ width:28, fontSize:10, fontWeight:700, color:DEP_COLOR[d.type], background:'rgba(0,0,0,0.06)', padding:'2px 4px', borderRadius:3 }}>{d.type}</span>
                      <span style={{ flex:1, fontSize:12, color:'#374151' }}>{from?.name || d.from}</span>
                      <button onClick={() => onDeleteDep(d)} style={{ background:'transparent', border:'none', cursor:'pointer', color:'#dc2626', fontSize:14 }}>✕</button>
                    </div>
                  );
                })}
              </div>
            )}

            {outDeps.length > 0 && (
              <div style={{ marginBottom:8 }}>
                <div style={{ fontSize:10, color:'#9ca3af', fontWeight:600, marginBottom:4 }}>עוקב (Successor)</div>
                {outDeps.map((d,i) => {
                  const to = allItems.find(it => it.id === d.to);
                  return (
                    <div key={i} style={{ display:'flex', alignItems:'center', gap:8, padding:'5px 8px', background:'#f9fafb', borderRadius:5, marginBottom:3 }}>
                      <span style={{ width:28, fontSize:10, fontWeight:700, color:DEP_COLOR[d.type], background:'rgba(0,0,0,0.06)', padding:'2px 4px', borderRadius:3 }}>{d.type}</span>
                      <span style={{ flex:1, fontSize:12, color:'#374151' }}>{to?.name || d.to}</span>
                      <button onClick={() => onDeleteDep(d)} style={{ background:'transparent', border:'none', cursor:'pointer', color:'#dc2626', fontSize:14 }}>✕</button>
                    </div>
                  );
                })}
              </div>
            )}

            {showAddDep ? (
              <div style={{ background:'#f9fafb', borderRadius:8, padding:10, display:'flex', flexDirection:'column', gap:8 }}>
                <div style={{ display:'flex', gap:6 }}>
                  {DEP_TYPES.map(dt => (
                    <button key={dt.k} onClick={() => setNewDepType(dt.k)} style={{
                      flex:1, padding:'5px', borderRadius:5, border:`1px solid ${newDepType===dt.k ? dt.color : '#e5e7eb'}`,
                      background: newDepType===dt.k ? dt.color : '#fff',
                      color: newDepType===dt.k ? '#fff' : '#6b7280',
                      fontSize:11, fontWeight:700, cursor:'pointer',
                    }}>{dt.k}</button>
                  ))}
                </div>
                <div style={{ fontSize:11, color:'#6b7280' }}>{DEP_TYPES.find(dt=>dt.k===newDepType)?.desc}</div>
                <select value={newDepTo} onChange={e=>setNewDepTo(e.target.value)}
                  style={{ padding:'7px', border:'1px solid #e5e7eb', borderRadius:5, fontSize:12, fontFamily:'inherit' }}>
                  <option value="">— בחר פריט —</option>
                  {allItems.filter(it => it.id !== item.id).map(it => (
                    <option key={it.id} value={it.id}>{it.name}</option>
                  ))}
                </select>
                <div style={{ display:'flex', gap:6 }}>
                  <button onClick={() => { if(newDepTo) { onAddDep({from:item.id,to:newDepTo,type:newDepType}); setShowAddDep(false); setNewDepTo(''); }}}
                    style={{ flex:1, padding:'7px', borderRadius:5, border:'none', background:'var(--ybp-panel)', color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer' }}>הוסף</button>
                  <button onClick={() => setShowAddDep(false)}
                    style={{ padding:'7px 12px', borderRadius:5, border:'1px solid #e5e7eb', background:'#fff', fontSize:12, cursor:'pointer' }}>ביטול</button>
                </div>
              </div>
            ) : (
              <button onClick={() => setShowAddDep(true)} style={{
                width:'100%', padding:'7px', borderRadius:5, border:'1px dashed #cbd5e1',
                background:'transparent', color:'#6b7280', fontSize:12, cursor:'pointer', fontFamily:'inherit',
              }}>+ הוסף תלות</button>
            )}
          </div>
        </div>

        {/* Footer */}
        <div style={{ padding:'12px 18px', borderTop:'1px solid #e5e7eb', display:'flex', gap:8, flexWrap:'wrap' }}>
          {/* מחיקה */}
          {onDelete && !confirmDelete && (
            <button onClick={() => setConfirmDelete(true)} style={{
              padding:'9px 14px', borderRadius:6, border:'1px solid #fecaca',
              background:'#fff', color:'#dc2626', fontSize:13, fontWeight:600,
              cursor:'pointer', fontFamily:'inherit',
            }}>🗑 מחק</button>
          )}
          {onDelete && confirmDelete && (
            <div style={{ display:'flex', gap:6, alignItems:'center' }}>
              <span style={{ fontSize:12, color:'#dc2626', fontWeight:600 }}>בטוח?</span>
              <button onClick={() => { onDelete(item); onClose(); }} style={{
                padding:'9px 12px', borderRadius:6, border:'none',
                background:'#dc2626', color:'#fff', fontSize:12, fontWeight:700,
                cursor:'pointer', fontFamily:'inherit',
              }}>כן, מחק</button>
              <button onClick={() => setConfirmDelete(false)} style={{
                padding:'9px 12px', borderRadius:6, border:'1px solid #e5e7eb',
                background:'#fff', fontSize:12, cursor:'pointer', fontFamily:'inherit',
              }}>ביטול</button>
            </div>
          )}
          <div style={{ flex:1 }}/>
          <button onClick={onClose} style={{ padding:'9px 16px', borderRadius:6, border:'1px solid #e5e7eb', background:'#fff', fontSize:13, cursor:'pointer' }}>ביטול</button>
          <button onClick={() => onSave({...item, name, start, end, status, percentDone:pct, assignee, stage, notes, workWeekend: workWeekend === '' ? undefined : workWeekend === 'yes'})} style={{
            padding:'9px 18px', borderRadius:6, border:'none', background:'var(--ybp-panel)',
            color:'#fff', fontSize:13, fontWeight:700, cursor:'pointer',
          }}>שמור</button>
        </div>
      </div>
    </div>
  );
};

// ── Settings Modal ────────────────────────────────────────────────────────────
const SettingsModal = ({ settings, onChange, onClose }) => (
  <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.4)', zIndex:9999, display:'flex', alignItems:'center', justifyContent:'center' }}>
    <div style={{ background:'#fff', borderRadius:12, width:340, padding:24, direction:'rtl', fontFamily:'Assistant,sans-serif' }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:20 }}>
        <span style={{ fontSize:15, fontWeight:700 }}>⚙️ הגדרות גאנט</span>
        <button onClick={onClose} style={{ background:'transparent', border:'none', cursor:'pointer', fontSize:18 }}>✕</button>
      </div>
      {[['workWeekend','עבודה בסופי שבוע'],['workHolidays','עבודה בחגים']].map(([k,label]) => (
        <div key={k} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'12px 0', borderBottom:'1px solid #f3f4f6' }}>
          <span style={{ fontSize:13, color:'#374151' }}>{label}</span>
          <button onClick={() => onChange({...settings,[k]:!settings[k]})} style={{
            width:44, height:24, borderRadius:12, border:'none', cursor:'pointer', position:'relative',
            background: settings[k] ? 'var(--ybp-navy)' : 'var(--ybp-border)', transition:'background .2s',
          }}>
            <span style={{ position:'absolute', top:2, width:20, height:20, borderRadius:10, background:'#fff',
              boxShadow:'0 1px 3px rgba(0,0,0,0.2)', transition:'left .2s',
              left: settings[k] ? 22 : 2 }}/>
          </button>
        </div>
      ))}
      <button onClick={onClose} style={{ width:'100%', marginTop:16, padding:'10px', borderRadius:6, border:'none', background:'var(--ybp-panel)', color:'#fff', fontSize:13, fontWeight:700, cursor:'pointer' }}>שמור</button>
    </div>
  </div>
);

// ── Dep Manager Modal ─────────────────────────────────────────────────────────
const DepManager = ({ deps, items, onDelete, onClose }) => (
  <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.4)', zIndex:9999, display:'flex', alignItems:'center', justifyContent:'center' }}>
    <div style={{ background:'#fff', borderRadius:12, width:480, maxHeight:'75vh', overflow:'auto', padding:20, direction:'rtl', fontFamily:'Assistant,sans-serif' }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:16 }}>
        <span style={{ fontSize:15, fontWeight:700 }}>🔗 כל התלויות ({deps.length})</span>
        <button onClick={onClose} style={{ background:'transparent', border:'none', cursor:'pointer', fontSize:18 }}>✕</button>
      </div>
      {deps.length === 0 ? (
        <div style={{ textAlign:'center', padding:30, color:'#9ca3af' }}>אין תלויות מוגדרות</div>
      ) : deps.map((d,i) => {
        const from = items.find(it => it.id === d.from);
        const to = items.find(it => it.id === d.to);
        return (
          <div key={i} style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 10px', background:'#f9fafb', borderRadius:7, marginBottom:6 }}>
            <span style={{ fontWeight:700, fontSize:11, color:DEP_COLOR[d.type], background:'rgba(0,0,0,0.06)', padding:'2px 6px', borderRadius:4, width:26, textAlign:'center' }}>{d.type}</span>
            <span style={{ flex:1, fontSize:12 }}><strong>{from?.name||d.from}</strong> → {to?.name||d.to}</span>
            <button onClick={() => onDelete(d)} style={{ background:'transparent', border:'none', cursor:'pointer', color:'#dc2626', fontSize:14 }}>✕</button>
          </div>
        );
      })}
    </div>
  </div>
);

// ── Bulk Edit Bar ─────────────────────────────────────────────────────────────
const BulkEditBar = ({ selected, items, onApply, onClear }) => {
  const [status, setStatus] = useState('');
  const [shiftDays, setShiftDays] = useState(0);
  if (selected.size === 0) return null;
  return (
    <div style={{ position:'absolute', bottom:48, left:'50%', transform:'translateX(-50%)', zIndex:20,
      background:'var(--ybp-panel)', color:'var(--ybp-ink)', borderRadius:10, padding:'10px 16px',
      border:'1px solid var(--ybp-border)',
      display:'flex', alignItems:'center', gap:12, boxShadow:'0 4px 20px rgba(0,0,0,0.3)',
      fontFamily:'Assistant,sans-serif', whiteSpace:'nowrap', }}>
      <span style={{ fontSize:13, fontWeight:700, color:'var(--ybp-ink)' }}>{selected.size} נבחרו</span>
      <div style={{ width:1, height:20, background:'var(--ybp-border)' }}/>
      <select value={status} onChange={e=>setStatus(e.target.value)} style={{
        padding:'5px 8px', borderRadius:5, border:'1px solid var(--ybp-border)',
        background:'var(--ybp-input-bg)', color:'var(--ybp-ink)', fontSize:12, fontFamily:'inherit',
      }}>
        <option value="">שנה סטטוס...</option>
        {['open','in_progress','done','ממתין','בעבודה','הסתיים'].map(s=><option key={s} value={s}>{s}</option>)}
      </select>
      <div style={{ display:'flex', alignItems:'center', gap:6, fontSize:12, color:'var(--ybp-ink-soft)' }}>
        <span>הזז ימים:</span>
        <input type="number" value={shiftDays} onChange={e=>setShiftDays(+e.target.value)}
          style={{ width:54, padding:'4px 6px', borderRadius:4, border:'1px solid var(--ybp-border)', background:'var(--ybp-input-bg)', color:'var(--ybp-ink)', fontSize:12, fontFamily:'inherit' }}/>
      </div>
      <button onClick={() => { onApply({status: status||undefined, shiftDays}); setStatus(''); setShiftDays(0); }} style={{
        padding:'6px 14px', borderRadius:5, border:'none', background:'#b5a882', color:'var(--ybp-navy)',
        fontSize:12, fontWeight:700, cursor:'pointer',
      }}>החל</button>
      <button onClick={onClear} style={{ padding:'6px 10px', borderRadius:5, border:'1px solid var(--ybp-border)', background:'transparent', color:'var(--ybp-ink-soft)', fontSize:12, cursor:'pointer' }}>ביטול</button>
    </div>
  );
};

// ── Add Item Modal ────────────────────────────────────────────────────────────
const AddItemModal = ({ projectId, onClose, onAdd }) => {
  const today = todayISO();
  const [name, setName]           = useState('');
  const [start, setStart]         = useState(today);
  const [end, setEnd]             = useState(addDays(today, 7));
  const [type, setType]           = useState('task');
  const [status, setStatus]       = useState('open');
  const [assignee, setAssignee]   = useState('');
  const [milestone, setMilestone] = useState(false);

  const handleAdd = () => {
    if (!name.trim()) return;
    const id = type === 'task' ? 'task_t-'+Date.now() : 'bk_'+Date.now();
    onAdd({ id, rawId:Date.now(), name:name.trim(), start, end: milestone ? start : end,
      status, type, source:type, stage:'', assignee, percentDone:0, milestone, raw:{} });
  };

  return (
    <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', zIndex:9999, display:'flex', alignItems:'center', justifyContent:'center' }}
      onClick={e => e.target === e.currentTarget && onClose()}>
      <div style={{ background:'#fff', borderRadius:12, width:440, padding:0, direction:'rtl', fontFamily:'Assistant,sans-serif', boxShadow:'0 20px 60px rgba(0,0,0,0.3)', overflow:'hidden' }}>
        <div style={{ padding:'14px 18px', borderBottom:'1px solid #e5e7eb', display:'flex', alignItems:'center', gap:10, background:'var(--ybp-panel)' }}>
          <span style={{ flex:1, fontSize:14, fontWeight:700, color:'#fff' }}>+ הוספת פריט לגאנט</span>
          <button onClick={onClose} style={{ background:'transparent', border:'none', cursor:'pointer', fontSize:18, color:'var(--ybp-ink-soft)' }}>✕</button>
        </div>
        <div style={{ padding:18, display:'flex', flexDirection:'column', gap:12 }}>

          {/* Type */}
          <div style={{ display:'flex', gap:6 }}>
            {[['task','✅ משימה'],['booklet','📋 חוברת']].map(([k,l]) => (
              <button key={k} onClick={() => setType(k)} style={{
                flex:1, padding:'8px', borderRadius:6, border:`1px solid ${type===k?'var(--ybp-navy)':'var(--ybp-border)'}`,
                background: type===k?'var(--ybp-navy)':'var(--ybp-panel)', color:type===k?'#fff':'#6b7280',
                fontSize:12, fontWeight:700, cursor:'pointer', fontFamily:'inherit',
              }}>{l}</button>
            ))}
          </div>

          {/* Name */}
          <div>
            <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>שם *</label>
            <input value={name} onChange={e=>setName(e.target.value)} placeholder="שם הפריט..."
              style={{ width:'100%', padding:'9px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}/>
          </div>

          {/* Milestone toggle */}
          <label style={{ display:'flex', alignItems:'center', gap:8, cursor:'pointer', fontSize:13, color:'#374151' }}>
            <input type="checkbox" checked={milestone} onChange={e=>setMilestone(e.target.checked)} style={{ width:15, height:15, accentColor:'#b5a882' }}/>
            🔶 אבן דרך (Milestone) — ללא משך זמן
          </label>

          {/* Dates */}
          <div style={{ display:'grid', gridTemplateColumns: milestone ? '1fr' : '1fr 1fr', gap:10 }}>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>{milestone ? 'תאריך' : 'התחלה'}</label>
              <input type="date" value={start} onChange={e => {
                const newStart = e.target.value;
                const dur = daysBetween(start, end);
                setStart(newStart);
                if (newStart && dur >= 0) setEnd(addDays(newStart, dur));
              }}
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}/>
            </div>
            {!milestone && (
              <div>
                <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>סיום</label>
                <input type="date" value={end} onChange={e=>setEnd(e.target.value)}
                  style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}/>
              </div>
            )}
          </div>

          {/* Status + Assignee */}
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10 }}>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>סטטוס</label>
              <select value={status} onChange={e=>setStatus(e.target.value)}
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}>
                {(type==='task' ? ['open','in_progress','done'] : ['ממתין','בעבודה','הסתיים','אושר']).map(s => <option key={s} value={s}>{s}</option>)}
              </select>
            </div>
            <div>
              <label style={{ fontSize:11, fontWeight:700, color:'#6b7280', display:'block', marginBottom:4 }}>אחראי</label>
              <input value={assignee} onChange={e=>setAssignee(e.target.value)} placeholder="שם אחראי..."
                style={{ width:'100%', padding:'8px 10px', border:'1px solid #e5e7eb', borderRadius:6, fontSize:13, fontFamily:'inherit', boxSizing:'border-box' }}/>
            </div>
          </div>
        </div>

        <div style={{ padding:'12px 18px', borderTop:'1px solid #e5e7eb', display:'flex', gap:8 }}>
          <button onClick={handleAdd} disabled={!name.trim()} style={{
            flex:1, padding:'10px', borderRadius:6, border:'none',
            background: name.trim() ? 'var(--ybp-navy)' : 'var(--ybp-border)',
            color: name.trim() ? '#fff' : '#9ca3af',
            fontSize:13, fontWeight:700, cursor: name.trim() ? 'pointer' : 'default',
          }}>הוסף לגאנט</button>
          <button onClick={onClose} style={{ padding:'10px 16px', borderRadius:6, border:'1px solid #e5e7eb', background:'#fff', fontSize:13, cursor:'pointer' }}>ביטול</button>
        </div>
      </div>
    </div>
  );
};

// ── MAIN COMPONENT ────────────────────────────────────────────────────────────
const GanttScreen = ({ projectId, onBack }) => {
  const [store, setStore] = useState(SyncStore.get());
  useEffect(() => SyncStore.subscribe(setStore), []);
  const vp = useViewport();
  const { isMobile } = vp;

  // v65 #4 — מובייל-landscape בגאנט = מסך מלא: הדלק דגל גלובלי ש-app-shell מסתיר לפיו הדר/פוטר.
  useEffect(() => {
    const fs = vp.isMobile && vp.isLandscape;
    window.YBP_FULLSCREEN = fs;
    window.dispatchEvent(new Event('ybp-fullscreen'));
    return () => { window.YBP_FULLSCREEN = false; window.dispatchEvent(new Event('ybp-fullscreen')); };
  }, [vp.isMobile, vp.isLandscape]);

  // v61 #3 — חפש קודם ב-YBP_DATA, ואז ב-ybp_user_projects (פרויקטים שנוצרו/נטענו מהענן נושאים שם את ה-phases).
  const project = useMemo(() => {
    const fromData = YBP_DATA?.projects?.find(p => p.id === projectId);
    if (fromData) return fromData;
    try {
      const userProjects = JSON.parse(localStorage.getItem('ybp_user_projects') || '[]');
      const fromLocal = (userProjects || []).find(p => p.id === projectId);
      if (fromLocal) return fromLocal;
    } catch (_) {}
    return YBP_DATA?.projects?.[0];
  }, [projectId]);

  const [viewMode, setViewMode] = useState('gantt'); // 'gantt' | 'calendar'
  const [zoom, setZoom] = useState('weeks');
  const [source, setSource] = useState('all'); // 'all' | 'tasks' | 'rejects' | 'booklet'
  const [mobileView, setMobileView] = useState('list'); // v61 #7 — 'list' | 'timeline'
  const [mobileNameW, setMobileNameW] = useState(110); // v65 #3 — רוחב עמודת השמות במיני-גאנט (טוגל צר/בינוני/רחב)
  const [assigneeFilter, setAssigneeFilter] = useState(''); // '' = הכל
  const [printOrientation, setPrintOrientation] = useState('landscape'); // v76 — ברירת מחדל landscape (הציר רחב)
  const [leftW, setLeftW] = useState(LEFT_W_DEFAULT);
  const [selectedIds, setSelectedIds] = useState(new Set());
  const [editItem, setEditItem] = useState(null);
  const [showSettings, setShowSettings] = useState(false);
  const [showDepMgr, setShowDepMgr] = useState(false);
  // v74 — הגדרות גאנט לכל פרויקט (מפתח 'ybp_gantt_settings_'+projectId)
  const [settings, setSettings] = useState(() => {
    try { return JSON.parse(localStorage.getItem('ybp_gantt_settings_' + projectId) || '{"workWeekend":false,"workHolidays":false}'); } catch { return {workWeekend:false,workHolidays:false}; }
  });
  // v57 — מיגרציה חד-פעמית של deps מקומיים לענן (לפני setState של deps)
  useEffect(() => { migrateGanttDepsToCloud(projectId); }, [projectId]);
  const [deps, setDeps] = useState(() => loadDeps(projectId));
  // רענון deps כשה-store מתעדכן (סנכרון cross-device)
  useEffect(() => { setDeps(loadDeps(projectId)); }, [projectId, store]);
  const [tooltip, setTooltip] = useState(null); // {text, x, y}
  const [dragBar, setDragBar] = useState(null); // {id, startX, origStart, origEnd, mode:'move'|'resize'}

  const scrollRef = useRef(null);
  const headerScrollRef = useRef(null);
  const leftScrollRef = useRef(null);
  const resizeRef = useRef(null);
  const sliderDraggingRef = useRef(false);
  const didDragRef = useRef(false); // v61 #2 — האם הייתה תזוזה אמיתית (עמיד לסדר אירועי mouseup→click)
  const leftWRef = useRef(LEFT_W_DEFAULT); // v61 #4 — leftW מסונכרן ל-ref כדי ש-listeners יקראו ערך עדכני
  const resizeStateRef = useRef({ dragging:false, startX:0, startW:0 }); // v61 #4
  const [showAddModal, setShowAddModal] = useState(false);
  const [scrollPct, setScrollPct] = useState(0);

  const Z = ZOOM_CONF[zoom];
  const dayW = Z.dayW;

  const rawItems = useMemo(() => buildItems(projectId, store, source, project), [projectId, store, source, project]);
  const [items, setItems] = useState(rawItems);
  useEffect(() => { setItems(rawItems); }, [rawItems]);

  // v58 — סינון לפי אחראי (מעל ה-items הפנימי כדי לא לאבד את ה-state של drag)
  const assigneeOptions = useMemo(() => {
    const set = new Set();
    items.forEach(it => { if (it.assignee) set.add(it.assignee); });
    return Array.from(set).sort((a, b) => a.localeCompare(b, 'he'));
  }, [items]);
  // v70 — מיון טבלה לפי עמודה. sortBy = {key:'name'|'start'|'assignee', dir:'asc'|'desc'} | null
  const [sortBy, setSortBy] = useState(null);
  const toggleSort = (key) => setSortBy(prev =>
    !prev || prev.key !== key ? { key, dir: 'asc' } : prev.dir === 'asc' ? { key, dir: 'desc' } : null
  );
  const displayItems = useMemo(() => {
    let list = assigneeFilter ? items.filter(it => (it.assignee || '') === assigneeFilter) : items;
    if (sortBy) {
      const dir = sortBy.dir === 'desc' ? -1 : 1;
      list = [...list].sort((a, b) => {
        if (sortBy.key === 'start') {            // start = ISO 'YYYY-MM-DD' → השוואת מחרוזת = כרונולוגי
          const av = a.start || '', bv = b.start || '';
          return (av < bv ? -1 : av > bv ? 1 : 0) * dir;
        }
        const av = (a[sortBy.key] || '').toString(), bv = (b[sortBy.key] || '').toString();
        return av.localeCompare(bv, 'he') * dir;
      });
    }
    return list;
  }, [items, assigneeFilter, sortBy]);

  // v59 — תצוגת predecessor (read-only): item.id -> [שמות-מקור]
  const predecessorNamesByItemId = useMemo(() => {
    const idToItem = new Map(displayItems.map(it => [it.id, it]));
    const m = new Map();
    (deps || []).forEach(d => {
      if (!d || !d.to) return;
      const from = idToItem.get(d.from);
      const list = m.get(d.to) || [];
      if (from) list.push(from.name); else list.push(d.from);
      m.set(d.to, list);
    });
    return m;
  }, [deps, displayItems]);

  // v59/v60 — עריכה inline בתאי הטבלה (Excel-like)
  // editingCell = { rowIdx, colIdx, value }   |   null
  // v63 #5 — colIdx (editable, = GRID index - 1): 0=name 1=start 2=end 3=percent 4=days 5=status 6=assignee
  const EDIT_COLS = ['name', 'start', 'end', 'percentDone', 'days', 'status', 'assignee'];
  const [editingCell, setEditingCell] = useState(null);

  // v63 #5 — נוספה עמודת סטטוס. GRID: 0=checkbox 1=name 2=start 3=end 4=% 5=days 6=status 7=assignee 8=predecessor(flex)
  const COL_W_DEFAULT = [28, 200, 72, 72, 38, 42, 76, 74, 64]; // v65 #1 — דיפולטים צרים יותר כדי שלא יאלצו גלישה
  const MIN_COL_W = 28;
  // v65 #1 — מפתח חדש (_v65): המודל השתנה (שם גמיש + דיפולטים צרים). מתעלם מרוחבים ישנים שגרמו לגלישה.
  const COLW_KEY = 'ybp_gantt_colw_v65';
  const [colW, setColW] = useState(() => {
    try {
      const saved = JSON.parse(localStorage.getItem(COLW_KEY) || 'null');
      if (Array.isArray(saved) && saved.length === COL_W_DEFAULT.length && saved[1] >= MIN_COL_W) return saved;
    } catch (_) {}
    return COL_W_DEFAULT.slice();
  });
  useEffect(() => { localStorage.setItem(COLW_KEY, JSON.stringify(colW)); }, [colW]);
  const colWRef = useRef(colW);
  useEffect(() => { colWRef.current = colW; }, [colW]);
  const colResizeRef = useRef({ col:-1, startX:0, startW:0 });
  const onColResizeDown = (e, col) => {
    e.preventDefault();
    e.stopPropagation();
    colResizeRef.current = { col, startX: e.clientX, startW: colWRef.current[col] };
  };
  // listeners נבנים פעם אחת. RTL: גרירת הידית שמאלה (clientX קטֵן) מרחיבה → newW = startW + (startX - clientX)
  useEffect(() => {
    const onMove = (e) => {
      const st = colResizeRef.current;
      if (st.col < 0) return;
      const newW = Math.max(MIN_COL_W, st.startW + (st.startX - e.clientX));
      setColW(prev => { const next = prev.slice(); next[st.col] = newW; return next; });
    };
    const onUp = () => { colResizeRef.current = { col:-1, startX:0, startW:0 }; };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
  }, []);
  // v64 #2 — autofit: דאבל-קליק על גורר עמודה מתאים את רוחבה לתא/כותרת הארוכים ביותר (כמו אקסל).
  // col = אינדקס GRID (2..8). עמודת השם(1) גמישה — אין לה autofit.
  const autoFitColumn = (col) => {
    const headerLabels = { 1:'שם משימה', 2:'התחלה', 3:'סיום', 4:'%', 5:'ימים', 6:'סטטוס', 7:'אחראי', 8:'קודם' };
    const cellText = (it) => {
      switch (col) {
        case 1: return it.name || '';
        case 2: return fmtDateHe(it.start);
        case 3: return fmtDateHe(it.end);
        case 4: return (it.percentDone || 0) + '%';
        case 5: return String(itemDurationDays(it) || '');
        case 6: return it.status || '';
        case 7: return it.assignee || '';
        case 8: { const pl = predecessorNamesByItemId.get(it.id) || []; return pl.length ? pl.join(', ') : '—'; }
        default: return '';
      }
    };
    const dataFont = '12px Assistant, sans-serif';
    const headFont = '700 11px Assistant, sans-serif';
    let max = measureTextW(headerLabels[col] || '', headFont);
    displayItems.forEach(it => { const w = measureTextW(cellText(it), dataFont); if (w > max) max = w; });
    const target = Math.min(400, Math.max(MIN_COL_W, Math.ceil(max) + 16)); // +padding, תקרה 400
    setColW(prev => { const next = prev.slice(); next[col] = target; return next; });
  };
  const applyCellSave = (item, colIdx, raw) => {
    if (!item || item.source !== 'task') return;        // booklet read-only
    const field = EDIT_COLS[colIdx];
    if (!field) return;
    const itemPatch = {};
    const syncPatch = {};
    if (field === 'name')        { itemPatch.name = raw;  syncPatch.title = raw; }
    else if (field === 'start')  { itemPatch.start = raw; syncPatch.startDate = raw; }
    else if (field === 'end')    { itemPatch.end = raw;   syncPatch.due = raw; }
    else if (field === 'percentDone') {
      const v = Math.max(0, Math.min(100, Math.round(Number(raw) || 0)));
      itemPatch.percentDone = v;
      syncPatch.percentDone = v;
    }
    else if (field === 'days') {
      // v74 — משך בימי עבודה: end = addWorkingDays(start, nDays) לפי ההגדרה האפקטיבית של המשימה.
      const nDays = Math.max(1, Math.round(Number(raw) || 0));
      const validISO = (s) => typeof s === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(s);
      const start = validISO(item.start) ? item.start : todayISO();
      const eff = effWork(item, settings);
      const end = addWorkingDays(start, nDays, dateRange.days, eff);
      itemPatch.start = start; itemPatch.end = end;
      syncPatch.startDate = start; syncPatch.due = end;
    }
    else if (field === 'status') { itemPatch.status = raw; syncPatch.status = raw; }  // v63 #5
    else if (field === 'assignee') { itemPatch.assignee = raw; syncPatch.assignee = raw; }
    // cascade על שינוי start/end/days עבור deps דינמיים
    const cascadeFields = new Set(['start', 'end', 'days']);
    setItems(prev => {
      const next = prev.map(it => it.id === item.id ? { ...it, ...itemPatch } : it);
      if (cascadeFields.has(field)) {
        try { return cascadeItems(next, deps, item.id, 0, undefined, dateRange.days, settings); }
        catch (e) { console.warn('[gantt] cascade after inline save:', e); return next; }
      }
      return next;
    });
    try { if (item.rawId) SyncStore.updateTask(item.rawId, syncPatch); } catch (e) { console.warn('[gantt] inline save:', e); }
  };
  // v74 — מחזיר משך בימי עבודה (אם המשימה לא עובדת בסופ"ש/חג); אחרת ספירה קלנדרית.
  const itemDurationDays = (it) => {
    if (!it || !it.start || !it.end) return 0;
    const eff = effWork(it, settings);
    return workingDaysBetween(it.start, it.end, dateRange.days, eff);
  };
  const valuesByCol = (it) => [
    it.name,
    it.start,
    it.end,
    it.percentDone || 0,
    itemDurationDays(it),
    it.status || 'open',
    it.assignee || '',
  ];
  const startCellEdit = (rowIdx, colIdx) => {
    const it = displayItems[rowIdx];
    if (!it || it.source === 'booklet') return;
    setEditingCell({ rowIdx, colIdx, value: String(valuesByCol(it)[colIdx] ?? '') });
  };
  const commitCellEdit = (ec) => {
    if (!ec) return;
    const item = displayItems[ec.rowIdx];
    applyCellSave(item, ec.colIdx, ec.value);
  };
  const moveEditTo = (rowIdx, colIdx) => {
    if (rowIdx < 0 || rowIdx >= displayItems.length || colIdx < 0 || colIdx >= EDIT_COLS.length) {
      setEditingCell(null);
      return;
    }
    const next = displayItems[rowIdx];
    if (!next || next.source === 'booklet') {
      setEditingCell(null);
      return;
    }
    setEditingCell({ rowIdx, colIdx, value: String(valuesByCol(next)[colIdx] ?? '') });
  };

  // v63 #3 — מחיקת משימה ישירות מהשורה (מחיקה רכה=archived). רק למשימות, לא לחוברת.
  const deleteTaskRow = (item) => {
    if (!item || item.source !== 'task') return;
    if (!window.confirm(`למחוק את "${item.name}"?`)) return;
    try { if (item.rawId) SyncStore.deleteTask(item.rawId); } catch (e) { console.warn('[gantt] delete:', e); }
    setItems(prev => prev.filter(it => it.id !== item.id));
    saveDepsLocal(deps.filter(d => d.from !== item.id && d.to !== item.id));
  };

  // v63 #4 — הוספת שורת משימה inline (בלי מודל)
  const [newRowName, setNewRowName] = useState('');
  const addRowCommit = () => {
    const title = newRowName.trim();
    if (!title) return;
    const today = todayISO();
    try {
      SyncStore.addTask({ projectId, title, source:'gantt', startDate:today, due:addDays(today,7), status:'open', percentDone:0 });
    } catch (e) { console.warn('[gantt] add-row:', e); }
    setNewRowName(''); // המיקוד נשאר על אותו שדה (שורה ריקה חדשה)
  };

  const dateRange = useMemo(() => buildDateRange(items, project), [items, project]);
  const totalW = dateRange.days.length * dayW;
  const todayIdx = dateRange.days.findIndex(d => d.iso === todayISO());
  const todayX = todayIdx >= 0 ? todayIdx * dayW : -1;

  // Save settings (v74 — לכל פרויקט)
  useEffect(() => { localStorage.setItem('ybp_gantt_settings_' + projectId, JSON.stringify(settings)); }, [settings, projectId]);
  // v74 — טען מחדש הגדרות כשהפרויקט מתחלף (אם ה-component לא מתרנדר מחדש)
  useEffect(() => {
    try { setSettings(JSON.parse(localStorage.getItem('ybp_gantt_settings_' + projectId) || '{"workWeekend":false,"workHolidays":false}')); }
    catch { setSettings({ workWeekend:false, workHolidays:false }); }
  }, [projectId]);

  // Sync deps
  const saveDepsLocal = useCallback((newDeps) => { setDeps(newDeps); saveDeps(projectId, newDeps); }, [projectId]);

  // v74 — שמירת תלות + cascade מיידי על המשימה התלויה (anchorFromId = ה-from של התלות שנוספה/עודכנה)
  const saveDepsAndCascade = useCallback((newDeps, anchorFromId) => {
    saveDepsLocal(newDeps);
    if (anchorFromId) {
      setItems(prev => {
        try { return cascadeItems(prev, newDeps, anchorFromId, 0, undefined, dateRange.days, settings); }
        catch (e) { console.warn('[gantt] cascade after add-dep:', e); return prev; }
      });
    }
  }, [saveDepsLocal, dateRange, settings]);

  // Get bar position
  const getBarPos = (item) => {
    if (!item.start?.match(/^\d{4}-\d{2}-\d{2}$/) || !item.end?.match(/^\d{4}-\d{2}-\d{2}$/)) return null;
    const si = dateRange.days.findIndex(d => d.iso === item.start);
    const ei = dateRange.days.findIndex(d => d.iso === item.end);
    if (si < 0 && ei < 0) return null;
    const si2 = si < 0 ? 0 : si;
    const ei2 = ei < 0 ? dateRange.days.length - 1 : ei;
    return { x: si2 * dayW, w: Math.max((ei2 - si2 + 1) * dayW, 8) };
  };

  // v76.1 — הדפסה: טווח אמיתי של הפרויקט (משימה ראשונה → מסירה) + דחיסה לרוחב עמוד אחד (לא year-forward, לא bands).
  const PRINT_NAME_W = 300;     // רוחב עמודת השמות+תאריכים
  const PRINT_PAGE_W = 1040;    // רוחב מודפס שמיש ב-A4 landscape @96dpi (margin צד = 0)
  const fmtPrint = (iso) => { const d = new Date((iso||'') + 'T12:00:00'); return isNaN(d) ? '' : (String(d.getDate()).padStart(2,'0') + '/' + String(d.getMonth()+1).padStart(2,'0')); };
  const isWeekendISO = (iso) => { const g = new Date((iso||'') + 'T12:00:00').getDay(); return g === 5 || g === 6; }; // שישי/שבת
  // נק' 4 — טווח: מתחילת המשימה הראשונה עד max(סיום אחרון, תאריך מסירת הפרויקט).
  const printRange = useMemo(() => {
    let lo = null, hi = null;
    displayItems.forEach(i => {
      if (i.start && /^\d{4}-\d{2}-\d{2}$/.test(i.start)) { if (!lo || i.start < lo) lo = i.start; }
      if (i.end   && /^\d{4}-\d{2}-\d{2}$/.test(i.end))   { if (!hi || i.end   > hi) hi = i.end; }
    });
    if (project?.endDate && /^\d{4}-\d{2}-\d{2}$/.test(project.endDate) && (!hi || project.endDate > hi)) hi = project.endDate;
    if (!lo) lo = todayISO();
    if (!hi || hi < lo) hi = lo;
    const days = []; let cur = lo, guard = 0;
    while (cur <= hi && guard < 1500) { days.push({ iso: cur, weekend: isWeekendISO(cur) }); cur = addDays(cur, 1); guard++; }
    return { lo, hi, days };
  }, [displayItems, project]);
  // נק' 7 — דחיסה: רוחב-יום מותאם כך שכל הטווח נכנס לרוחב העמוד (תקרה 28, רצפה 2).
  const printDayW = useMemo(() => {
    const n = Math.max(1, printRange.days.length);
    return Math.max(2, Math.min(28, Math.floor((PRINT_PAGE_W - PRINT_NAME_W) / n)));
  }, [printRange.days.length]);
  const printAxisW = printRange.days.length * printDayW;
  // מיקום בר בהדפסה (right יחסי לקצה-ימין, חיתוך לטווח ההדפסה).
  const printBarPos = (item) => {
    if (!item.start || !item.end) return null;
    const si = printRange.days.findIndex(d => d.iso === item.start);
    const ei = printRange.days.findIndex(d => d.iso === item.end);
    if (si < 0 && ei < 0) return null;
    const s = si < 0 ? 0 : si, e = ei < 0 ? printRange.days.length - 1 : ei;
    return { right: s * printDayW, width: Math.max((e - s + 1) * printDayW, 3) };
  };

  // v74 — מקטעי בר: רק עמודות ימי-עבודה רצופות (רווח על סופ"ש/חג). פעיל בזום "ימים" בלבד.
  const getBarSegments = (item) => {
    const full = getBarPos(item);                 // המעטפת {x,w}
    if (!full) return [];
    const eff = effWork(item, settings);
    if (zoom !== 'days' || (eff.weekend && eff.holidays)) return [full];
    const si = dateRange.days.findIndex(d => d.iso === item.start);
    const ei = dateRange.days.findIndex(d => d.iso === item.end);
    if (si < 0 || ei < 0 || ei < si) return [full];
    const segs = []; let runStart = -1;
    for (let k = si; k <= ei; k++) {
      const off = isNonWorkingDay(dateRange.days[k], eff);
      if (!off && runStart < 0) runStart = k;
      if ((off || k === ei) && runStart >= 0) {
        const runEnd = off ? k - 1 : k;
        segs.push({ x: runStart * dayW, w: Math.max((runEnd - runStart + 1) * dayW, 4) });
        runStart = -1;
      }
    }
    return segs.length ? segs : [full];
  };

  // Bar drag
  const onBarMouseDown = (e, item, mode) => {
    e.stopPropagation();
    didDragRef.current = false; // v61 #2 — אתחל דגל תזוזה בתחילת תפיסה
    setDragBar({ id: item.id, startX: e.clientX, origStart: item.start, origEnd: item.end, mode });
  };
  useEffect(() => {
    if (!dragBar) return;
    const onMove = (e) => {
      const dx = e.clientX - dragBar.startX;
      // v61 #1 — RTL: הברים ממוקמים right:x (מוקדם=ימין). הופכים סימן כדי שגרירה ימינה תזיז ימינה (move + resize).
      const days = -Math.round(dx / dayW);
      if (Math.abs(dx) > 3) didDragRef.current = true; // v61 #2 — תזוזה אמיתית
      setItems(prev => prev.map(it => {
        if (it.id !== dragBar.id) return it;
        if (dragBar.mode === 'move') return { ...it, start: addDays(dragBar.origStart, days), end: addDays(dragBar.origEnd, days) };
        if (dragBar.mode === 'resize') {
          const newEnd = addDays(dragBar.origEnd, days);
          return newEnd >= it.start ? { ...it, end: newEnd } : it;
        }
        return it;
      }));
    };
    const onUp = () => {
      setItems(prev => {
        const moved = prev.find(it => it.id === dragBar.id);
        if (moved) {
          const delta = daysBetween(dragBar.origStart, moved.start);
          if (delta !== 0) return cascadeItems(prev, deps, dragBar.id, delta);
        }
        return prev;
      });
      setDragBar(null);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
  }, [dragBar, dayW, deps]);

  // שמור leftW ב-ref מסונכרן (v61 #4)
  useEffect(() => { leftWRef.current = leftW; }, [leftW]);

  // v69 #3 — גרירת ידית הפאנל עברה ל-onMouseDown של React על הידית עצמה (כמו colHandle),
  // כי ה-useEffect הקודם נקשר רק אם הידית קיימת ב-mount — ובגאנט ריק היא לא מרונדרת → לא נקשר לעולם.

  // Shift+wheel = horizontal scroll
  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    const onWheel = (e) => {
      if (e.shiftKey) {
        e.preventDefault();
        const cur = getScrollX(el);
        setScrollX(el, cur + e.deltaY);
        setScrollX(headerScrollRef.current, getScrollX(el));
      }
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, []);

  // Auto-scroll לתחילת השבוע הנוכחי (יום ראשון) כש-mount או zoom משתנה
  React.useEffect(() => {
    const timer = setTimeout(() => {
      const el = scrollRef.current;
      if (!el || !dateRange.days.length) return;
      const today = todayISO();
      const todayIdx = dateRange.days.findIndex(d => d.iso === today);
      if (todayIdx < 0) return;
      // יום ראשון = 0. אם היום שלישי (2), אחזור 2 ימים אחורה.
      const todayDow = new Date(today + 'T12:00:00').getDay();
      const weekStartIdx = Math.max(0, todayIdx - todayDow);
      const targetX = weekStartIdx * dayW;
      setScrollX(el, targetX);
      setScrollX(headerScrollRef.current, targetX);
      // עדכן את ה-slider
      const maxScroll = el.scrollWidth - el.clientWidth;
      if (maxScroll > 0) {
        // מיפוי הפוך: RTL + slider direction:ltr → slider ימין = תאריכים מוקדמים (ימין בגאנט)
        const pct = Math.round(((maxScroll - targetX) / maxScroll) * 100);
        setScrollPct(Math.max(0, Math.min(100, pct)));
      }
    }, 100); // לתת זמן ל-DOM להתרנדר
    return () => clearTimeout(timer);
    // תלוי ב-zoom ובמספר הפריטים: מתרכז ב-mount, בשינוי zoom ובטעינת פריטים, בלי לקפוץ בכל עריכה/גרירה
  }, [zoom, items.length]);

  // איפוס sliderDraggingRef גם אם המשתמש משחרר מחוץ לסליידר
  useEffect(() => {
    const reset = () => { sliderDraggingRef.current = false; };
    window.addEventListener('pointerup', reset);
    window.addEventListener('pointercancel', reset);
    window.addEventListener('mouseup', reset);
    window.addEventListener('touchend', reset);
    window.addEventListener('touchcancel', reset);
    return () => {
      window.removeEventListener('pointerup', reset);
      window.removeEventListener('pointercancel', reset);
      window.removeEventListener('mouseup', reset);
      window.removeEventListener('touchend', reset);
      window.removeEventListener('touchcancel', reset);
    };
  }, []);

  const onTimelineScroll = (e) => {
    const el = e.target;
    if (leftScrollRef.current) leftScrollRef.current.scrollTop = el.scrollTop;
    if (headerScrollRef.current) setScrollX(headerScrollRef.current, getScrollX(el));
    // אל תעדכן scrollPct בזמן שהמשתמש גורר את ה-slider — מונע שבירת drag
    if (sliderDraggingRef.current) return;
    const maxScroll = el.scrollWidth - el.clientWidth;
    if (maxScroll > 0) {
      // מיפוי הפוך: RTL + slider direction:ltr → slider ימין = תאריכים מוקדמים (ימין בגאנט)
      const pct = Math.round(((maxScroll - Math.abs(el.scrollLeft)) / maxScroll) * 100);
      setScrollPct(Math.max(0, Math.min(100, pct)));
    }
  };
  const onLeftScroll = (e) => { if (scrollRef.current) scrollRef.current.scrollTop = e.target.scrollTop; };

  // Select
  const toggleSelect = (id) => setSelectedIds(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const clearSelect = () => setSelectedIds(new Set());

  // Bulk edit
  const applyBulk = ({status, shiftDays}) => {
    setItems(prev => prev.map(it => {
      if (!selectedIds.has(it.id)) return it;
      let upd = {...it};
      if (status) upd.status = status;
      if (shiftDays) { upd.start = addDays(upd.start, shiftDays); upd.end = addDays(upd.end, shiftDays); }
      return upd;
    }));
    clearSelect();
  };

  // Edit save
  const onEditSave = (updated) => {
    setItems(prev => prev.map(it => it.id === updated.id ? updated : it));
    if (updated.source === 'task') {
      // v74 — workWeekend נשמר תחת data (jsonb) כדי לשרוד גם אם הסכמה לא מכירה את השדה
      const data = Object.assign({}, (updated.raw && updated.raw.data) || {}, { workWeekend: updated.workWeekend });
      SyncStore.updateTask(updated.rawId, { title:updated.name, due:updated.end, startDate:updated.start, status:updated.status, percentDone:updated.percentDone, assignee:updated.assignee, data });
    }
    setEditItem(null);
  };

  const [showSendModal, setShowSendModal] = useState(false);
  const [pdfPrinted, setPdfPrinted] = useState(false);

  // Export PDF
  const exportPDF = () => {
    setTooltip(null); // v74.1 — נקה טולטיפ hover שלא יודפס כקופסה קפואה
    let s = document.getElementById('gantt-print-page');
    if (!s) { s = document.createElement('style'); s.id = 'gantt-print-page'; document.head.appendChild(s); }
    // v76.2 — הדפסת גאנט תמיד landscape; margin:8mm 6mm דרוש לפגינציה תקינה של ה-table ולהתחלה גבוהה.
    const orient = 'landscape';
    s.textContent = '@page { size: A4 ' + orient + '; margin: 8mm 6mm; }';
    window.print();
  };

  const handlePrintAndMark = () => { exportPDF(); setPdfPrinted(true); };

  const getDist = () => {
    try { return JSON.parse(localStorage.getItem('ybp_dist_' + projectId) || '{}'); } catch { return {}; }
  };

  const sendGmailGantt = () => {
    const dist = getDist();
    const emails = (dist.emails || '').split(',').map(e => e.trim()).filter(Boolean);
    const subject = 'גאנט פרויקט — ' + (project?.name || '');
    const body = 'שלום,\n\nמצורף גאנט הפרויקט:\n📁 ' + (project?.name||'') + '\n📊 התקדמות: ' + (project?.progress||0) + '%\n📅 סיום מתוכנן: ' + (project?.endDate||'') + '\n\n(מצורף PDF)\n\nYBPROJECTS · ניהול פרויקטים בבנייה';
    window.open(buildGmailUrl({ to: emails, subject, body }), '_blank');
  };

  const sendWhatsAppGantt = () => {
    const dist = getDist();
    const phone = dist.waPhone || '';
    const txt = '📊 *גאנט פרויקט — ' + (project?.name||'') + '*\n\nהתקדמות: ' + (project?.progress||0) + '%\nסיום מתוכנן: ' + (project?.endDate||'') + '\n\n(מצורף PDF)\n\nYBPROJECTS';
    const url = phone ? 'https://wa.me/' + phone.replace(/\D/g,'') + '?text=' + encodeURIComponent(txt) : 'https://wa.me/?text=' + encodeURIComponent(txt);
    window.open(url, '_blank');
  };

  // Drive — שמירת PDF ב-Make → Drive
  const MAKE_GANTT_WEBHOOK = 'https://hook.eu2.make.com/cwhco2m6u2m48ey692n7n7wagoz8cg0i';
  const MAKE_SK = '1h4M4SEXbhoYF1YVW1mS6YJZs84yxSVHaBjPDDxMpbak';
  const saveToDrive = async () => {
    const project = YBP_DATA.projects.find(p => p.id === projectId) || {};
    const pmName = ((window.YBP_AUTH && window.YBP_AUTH.getCurrentUser) ? window.YBP_AUTH.getCurrentUser().name : YBP_DATA.user?.name) || 'מנהל';
    const date = new Date().toISOString().slice(0, 10);
    try {
      const resp = await fetch(MAKE_GANTT_WEBHOOK, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          _sk: MAKE_SK,
          manager: pmName,
          project: project.name || projectId,
          type_name: 'גאנט',
          date,
          format: 'html',
          html: `<h2>גאנט — ${project.name || projectId}</h2><p>תאריך: ${date}</p>`,
        }),
      });
      if (resp.ok) {
        const data = await resp.json().catch(() => ({}));
        alert('✓ הגאנט נשלח ל-Drive בהצלחה!\n📁 ' + (data.file_url || project.name));
      } else alert('שגיאה בשליחה ל-Drive — בדוק webhook');
    } catch {
      alert('⚠️ שגיאת חיבור ל-Drive');
    }
  };

  // Month groups for header
  const monthGroups = useMemo(() => {
    const groups = [];
    let cur = null;
    dateRange.days.forEach((d, i) => {
      const m = d.date.getMonth() + '_' + d.date.getFullYear();
      if (m !== cur) { groups.push({ label: d.date.toLocaleString('he-IL',{month:'short',year:'numeric'}), startIdx: i, count: 1 }); cur = m; }
      else groups[groups.length-1].count++;
    });
    return groups;
  }, [dateRange]);

  const isEmpty = displayItems.length === 0;

  // ── Mobile (v61 #7): בורר מקור + טוגל רשימה/ציר-זמן + מיני-גאנט נגלל ────────
  if (isMobile) {
    const fmtD = (iso) => iso ? iso.split('-').reverse().join('/') : '';
    const SOURCES = [['all','הכל'],['tasks','משימות'],['rejects',"ריג'קטים"],['booklet','חוברת']];
    const MROW = 34;       // גובה שורה (שמות + ברים מיושרים)
    const MHEAD = 24;      // גובה כותרת החודשים
    const MSUB = (zoom === 'days' || zoom === 'weeks') ? 20 : 0;  // v68 #3 — גובה תת-כותרת ימים/שבועות
    const MHEAD_TOTAL = MHEAD + MSUB;  // גובה כותרת כולל (לסנכרון עם עמודת השמות וקו היום)
    const shortName = (n) => (n && n.length > 16) ? n.slice(0, 15) + '…' : (n || '');
    const chipStyle = (active) => ({
      padding:'5px 12px', borderRadius:99, border:'1px solid var(--ybp-border)', fontSize:12, fontWeight:600,
      cursor:'pointer', whiteSpace:'nowrap', flexShrink:0, fontFamily:'inherit',
      background: active ? 'var(--ybp-accent)' : 'transparent',
      color: active ? '#fff' : 'var(--ybp-ink-soft)',
    });
    return (
      <div style={{ minHeight:'100vh', background:'var(--ybp-bg,#f6f7f9)', fontFamily:'Assistant,sans-serif', direction:'rtl', display:'flex', flexDirection:'column' }}>
        {/* Header — תמה-תקין (היה color:#fff על panel → לבן-על-בהיר) */}
        <div style={{ background:'var(--ybp-panel)', borderBottom:'1px solid var(--ybp-border)', padding:'12px 14px 10px', flexShrink:0 }}>
          <div style={{ display:'flex', alignItems:'center', gap:10 }}>
            <button onClick={onBack} style={{ background:'none', border:'none', color:'var(--ybp-ink-soft)', cursor:'pointer', fontSize:22, lineHeight:1, padding:0 }}>›</button>
            <div style={{ flex:1 }}>
              <div style={{ fontWeight:800, fontSize:16, color:'var(--ybp-ink)' }}>📅 גאנט — {project?.name || ''}</div>
              <div style={{ fontSize:12, color:'var(--ybp-ink-soft)', marginTop:2 }}>{displayItems.length} פריטים</div>
            </div>
            {/* טוגל תצוגה */}
            <div style={{ display:'flex', background:'var(--ybp-input-bg)', borderRadius:7, padding:2, gap:2 }}>
              {[['list','רשימה'],['timeline','ציר זמן']].map(([k,l]) => (
                <button key={k} onClick={() => setMobileView(k)} style={{
                  padding:'5px 9px', borderRadius:5, border:'none', fontSize:11, fontWeight:700, cursor:'pointer', fontFamily:'inherit',
                  background: mobileView===k ? 'var(--ybp-accent)' : 'transparent',
                  color: mobileView===k ? '#fff' : 'var(--ybp-ink-soft)',
                }}>{l}</button>
              ))}
            </div>
          </div>
          {/* בורר מקור — צ'יפים נגללים אופקית */}
          <div style={{ display:'flex', gap:6, marginTop:10, overflowX:'auto', paddingBottom:2 }}>
            {SOURCES.map(([k,l]) => (
              <button key={k} onClick={() => setSource(k)} style={chipStyle(source===k)}>{l}</button>
            ))}
          </div>
          {/* v65 #2/#3 — בקרות "ציר זמן": זום ימים/שבועות/חודשים + טוגל רוחב עמודת השמות */}
          {mobileView === 'timeline' && (
            <div style={{ display:'flex', gap:8, marginTop:10, alignItems:'center', flexWrap:'wrap' }}>
              <div style={{ display:'flex', background:'var(--ybp-input-bg)', borderRadius:7, padding:2, gap:2 }}>
                {[['days','ימים'],['weeks','שבועות'],['months','חודשים']].map(([k,l]) => (
                  <button key={k} onClick={() => setZoom(k)} style={{
                    padding:'5px 10px', borderRadius:5, border:'none', fontSize:11, fontWeight:700, cursor:'pointer', fontFamily:'inherit',
                    background: zoom===k ? 'var(--ybp-accent)' : 'transparent',
                    color: zoom===k ? '#fff' : 'var(--ybp-ink-soft)',
                  }}>{l}</button>
                ))}
              </div>
              <button onClick={() => setMobileNameW(w => w >= 200 ? 70 : (w >= 140 ? 220 : 150))}
                title="רוחב עמודת שמות"
                style={{ padding:'5px 10px', borderRadius:7, border:'1px solid var(--ybp-border)', background:'transparent',
                  color:'var(--ybp-ink-soft)', fontSize:11, fontWeight:600, cursor:'pointer', fontFamily:'inherit', whiteSpace:'nowrap' }}>
                ⇔ שמות
              </button>
            </div>
          )}
        </div>

        {/* גוף */}
        {displayItems.length === 0 ? (
          <div style={{ textAlign:'center', color:'var(--ybp-ink-soft)', padding:'40px 16px', fontSize:14 }}>
            אין משימות בגאנט לפרויקט זה
          </div>
        ) : mobileView === 'list' ? (
          /* ── תצוגת רשימה (קיימת) ── */
          <div style={{ padding:'12px 16px' }}>
            {displayItems.map(item => {
              const overdue = isItemOverdue(item);
              return (
              <div key={item.id} onClick={() => setEditItem(item)} style={{
                background:'var(--ybp-panel,#fff)',
                border: overdue ? '2px solid #dc2626' : '1px solid var(--ybp-border,#e5e7eb)',
                borderRadius:10, padding:'12px 14px', marginBottom:8, cursor:'pointer',
                borderRight: `4px solid ${SOURCE_COLOR[item.srcKind] || '#9ca3af'}`,
              }}>
                <div style={{ fontWeight:700, fontSize:14, color:'var(--ybp-ink,#111827)', marginBottom:4 }}>
                  <span style={{ fontSize:11, marginLeft:4 }}>{SOURCE_DOT[item.srcKind] || ''}</span>
                  {item.name}
                </div>
                <div style={{ display:'flex', gap:8, flexWrap:'wrap', alignItems:'center', fontSize:12, color:'var(--ybp-ink-soft)' }}>
                  <span style={{
                    padding:'1px 7px', borderRadius:99, fontSize:11, fontWeight:700,
                    background: (STATUS_COLOR[item.status] || '#9ca3af') + '22',
                    color: STATUS_COLOR[item.status] || '#9ca3af',
                  }}>{item.status || 'פתוח'}</span>
                  {overdue && <span style={{ fontSize:11, fontWeight:700, color:'#dc2626' }}>⚠️ עבר תאריך</span>}
                  {item.start && <span>📅 {fmtD(item.start)}</span>}
                  {item.end   && <span>→ {fmtD(item.end)}</span>}
                  {item.assignee && <span>👤 {item.assignee}</span>}
                  {item.percentDone > 0 && <span style={{ color:'var(--ybp-accent)' }}>{item.percentDone}%</span>}
                </div>
              </div>
              );
            })}
          </div>
        ) : (
          /* ── מיני-גאנט נגלל (read-only). מנוע התאריכים: dateRange/dayW/getBarPos הקיימים. ── */
          /* הערה: קווי-תלות (DepLines) דולגו במובייל לפישוט. */
          <div style={{ flex:1, display:'flex', overflow:'hidden', borderTop:'1px solid var(--ybp-border)' }}>
            {/* עמודת שמות — רוחב מתחלף (v65 #3) */}
            <div style={{ width:mobileNameW, flexShrink:0, borderLeft:'1px solid var(--ybp-border)', background:'var(--ybp-bg)', overflow:'hidden' }}>
              {/* v69 #4 — כותרת "משימה" (היה ספייסר ריק) */}
              <div style={{ height:MHEAD_TOTAL, borderBottom:'1px solid var(--ybp-border)', background:'var(--ybp-panel)', display:'flex', alignItems:'center', padding:'0 6px', fontSize:10, fontWeight:700, color:'var(--ybp-ink-soft)', boxSizing:'border-box' }}>משימה</div>
              {displayItems.map(item => (
                <div key={item.id} onClick={() => setEditItem(item)} title={item.name}
                  style={{ height:MROW, display:'flex', alignItems:'center', gap:4, padding:'0 6px', cursor:'pointer',
                    borderBottom:'1px solid var(--ybp-border-soft)', fontSize:11, color:'var(--ybp-ink)', overflow:'hidden', whiteSpace:'nowrap' }}>
                  <span style={{ flexShrink:0 }}>{SOURCE_DOT[item.srcKind] || '•'}</span>
                  <span style={{ overflow:'hidden', textOverflow:'ellipsis' }}>{item.name}</span>
                </div>
              ))}
            </div>
            {/* v69 #4 — ידית גרירה בין שמות לציר (RTL: עמודת השמות מימין → גרירה שמאלה=הרחבה) */}
            <div onPointerDown={(e) => {
                e.preventDefault();
                const startX = e.clientX; const startW = mobileNameW;
                const onMove = (ev) => { const dx = ev.clientX - startX;
                  setMobileNameW(Math.max(70, Math.min(260, startW - dx))); };
                const onUp = () => { window.removeEventListener('pointermove', onMove); window.removeEventListener('pointerup', onUp); };
                window.addEventListener('pointermove', onMove); window.addEventListener('pointerup', onUp);
              }}
              style={{ width:12, flexShrink:0, cursor:'col-resize', touchAction:'none', background:'var(--ybp-border)' }}/>
            {/* אזור ציר נגלל אופקית */}
            <div style={{ flex:1, overflowX:'auto', overflowY:'hidden', WebkitOverflowScrolling:'touch' }}>
              <div style={{ width: totalW, minWidth:'100%', position:'relative' }}>
                {/* כותרת חודשים קומפקטית */}
                <div style={{ height:MHEAD, display:'flex', background:'var(--ybp-panel)', borderBottom:'1px solid var(--ybp-border)' }}>
                  {monthGroups.map((mg, i) => (
                    <div key={i} style={{ width: mg.count * dayW, flexShrink:0, fontSize:9, fontWeight:700, color:'var(--ybp-ink)',
                      borderLeft:'1px solid var(--ybp-border)', display:'flex', alignItems:'center', justifyContent:'center', whiteSpace:'nowrap', overflow:'hidden' }}>
                      {mg.label}
                    </div>
                  ))}
                </div>
                {/* v68 #3 — תת-כותרת לפי zoom: ימים/שבועות (חודשים מסתפק בכותרת למעלה) */}
                {zoom === 'days' && (
                  <div style={{ height:MSUB, display:'flex', background:'var(--ybp-row-hover)', borderBottom:'1px solid var(--ybp-border-soft)' }}>
                    {dateRange.days.map((d, i) => (
                      <div key={i} style={{ width:dayW, flexShrink:0, fontSize:8, textAlign:'center', lineHeight:1,
                        color:d.holiday ? (d.isMuslim?'#6ee7b7':'#93c5fd') : d.isWeekend ? 'var(--ybp-ink-faint)' : 'var(--ybp-ink-soft)',
                        borderLeft:'1px solid var(--ybp-border-soft)', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center' }}>
                        <span>{'אבגדהוש'[d.dow] || ''}</span>
                        <span>{d.date.getDate()}</span>
                      </div>
                    ))}
                  </div>
                )}
                {zoom === 'weeks' && (
                  <div style={{ height:MSUB, display:'flex', background:'var(--ybp-row-hover)', borderBottom:'1px solid var(--ybp-border-soft)' }}>
                    {(() => {
                      const weeks = [];
                      let cur = null;
                      dateRange.days.forEach((d, i) => {
                        if (i === 0 || d.dow === 0) { if (cur) weeks.push(cur); cur = { count:1, firstDay:d }; }
                        else if (cur) cur.count++;
                      });
                      if (cur) weeks.push(cur);
                      return weeks.map((w, i) => (
                        <div key={i} style={{ width: w.count * dayW, flexShrink:0, fontSize:8, fontWeight:600, color:'var(--ybp-ink-soft)',
                          borderLeft:'1px solid var(--ybp-border-soft)', display:'flex', alignItems:'center', justifyContent:'center', whiteSpace:'nowrap', overflow:'hidden' }}>
                          {w.firstDay.date.getDate()}/{w.firstDay.date.getMonth()+1}
                        </div>
                      ));
                    })()}
                  </div>
                )}
                {/* קו "היום" */}
                {todayX >= 0 && (
                  <div style={{ position:'absolute', top:MHEAD_TOTAL, bottom:0, right:todayX, width:2, background:'#F59E0B', zIndex:3, pointerEvents:'none' }}/>
                )}
                {/* שורות */}
                {displayItems.map(item => {
                  const bp = getBarPos(item);
                  const overdue = isItemOverdue(item);
                  return (
                    <div key={item.id} onClick={() => setEditItem(item)}
                      style={{ height:MROW, position:'relative', borderBottom:'1px solid var(--ybp-border-soft)', cursor:'pointer' }}>
                      {bp && (
                        <div style={{ position:'absolute', top:5, height:MROW-10, right:bp.x, width:bp.w,
                          background: SOURCE_COLOR[item.srcKind] || '#9ca3af', borderRadius:4,
                          outline: overdue ? '2px solid #dc2626' : 'none', outlineOffset:'-2px', zIndex:2 }}/>
                      )}
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
        )}

        {/* עורך — טאפ על בר/שורה פותח (read-only מובייל אך עריכה זמינה) */}
        <EditPopup item={editItem} deps={deps} allItems={items}
          onSave={onEditSave}
          onClose={() => setEditItem(null)}
          onDelete={(item) => {
            setItems(prev => prev.filter(it => it.id !== item.id));
            saveDepsLocal(deps.filter(d => d.from !== item.id && d.to !== item.id));
            if (item.source === 'task' && item.rawId) SyncStore.updateTask(item.rawId, { status: 'archived' });
            setEditItem(null);
          }}
          onDeleteDep={(dep) => saveDepsLocal(deps.filter(d => !(d.from===dep.from && d.to===dep.to && d.type===dep.type)))}
          onAddDep={(dep) => saveDepsAndCascade([...deps, dep], dep.from)}/>
      </div>
    );
  }

  return (
    <div style={{ height:'100%', display:'flex', flexDirection:'column', background:'var(--ybp-bg)', fontFamily:'Assistant,sans-serif', direction:'rtl', position:'relative', userSelect:'none', WebkitUserSelect:'none' }}>

      {/* ── SEND PDF MODAL ── */}
      {showSendModal && (
        <div className="no-print" style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.55)', zIndex:10000, display:'flex', alignItems:'center', justifyContent:'center' }}
          onClick={e => e.target === e.currentTarget && setShowSendModal(false)}>
          <div style={{ background:'#fff', borderRadius:14, width:360, direction:'rtl', fontFamily:'Assistant,sans-serif', boxShadow:'0 24px 64px rgba(0,0,0,0.35)', overflow:'hidden' }}>
            {/* Header */}
            <div style={{ background:'var(--ybp-panel)', padding:'14px 18px', display:'flex', alignItems:'center', gap:10 }}>
              <span style={{ flex:1, fontSize:15, fontWeight:700, color:'#fff' }}>📤 שליחת גאנט PDF</span>
              <button onClick={() => setShowSendModal(false)} style={{ background:'transparent', border:'none', color:'var(--ybp-ink-soft)', fontSize:18, cursor:'pointer', lineHeight:1 }}>✕</button>
            </div>
            <div style={{ padding:18, display:'flex', flexDirection:'column', gap:14 }}>
              {/* Step 1 */}
              <div style={{ background: pdfPrinted ? '#f0fdf4' : '#f8fafc', border: `1px solid ${pdfPrinted ? '#86efac' : '#e2e8f0'}`, borderRadius:10, padding:14 }}>
                <div style={{ fontSize:12, fontWeight:700, color:'#374151', marginBottom:8, display:'flex', alignItems:'center', gap:6 }}>
                  <span style={{ width:20, height:20, borderRadius:10, background: pdfPrinted ? '#16a34a' : 'var(--ybp-navy)', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:800, flexShrink:0 }}>{pdfPrinted ? '✓' : '1'}</span>
                  {pdfPrinted ? 'PDF מוכן לשליחה ✓' : 'שמור קודם את הגאנט כ-PDF'}
                </div>
                {!pdfPrinted && (
                  <div style={{ fontSize:11, color:'#6b7280', marginBottom:10 }}>
                    לחץ "הדפס / שמור PDF", המתן לחלון ההדפסה, שמור כ-PDF.
                  </div>
                )}
                <div style={{ display:'flex', gap:8, alignItems:'center' }}>
                  {/* orientation */}
                  <div style={{ display:'flex', background:'#f1f5f9', borderRadius:6, padding:2, gap:1 }}>
                    {[['portrait','📄 עמוד'],['landscape','📋 רוחב']].map(([v,l]) => (
                      <button key={v} onClick={() => setPrintOrientation(v)} style={{ padding:'4px 8px', borderRadius:4, border:'none', fontSize:10, fontWeight:600, cursor:'pointer', background: printOrientation===v ? '#fff' : 'transparent', color: printOrientation===v ? 'var(--ybp-navy)' : 'var(--ybp-ink-soft)' }}>{l}</button>
                    ))}
                  </div>
                  <button onClick={handlePrintAndMark} style={{ flex:1, padding:'9px', borderRadius:7, border:'none', background:'var(--ybp-panel)', color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer', fontFamily:'inherit' }}>
                    🖨️ {pdfPrinted ? 'הדפס שוב' : 'הדפס / שמור PDF'}
                  </button>
                </div>
              </div>

              {/* Step 2 */}
              <div style={{ background: pdfPrinted ? '#fff' : '#f8fafc', border:'1px solid #e2e8f0', borderRadius:10, padding:14, opacity: pdfPrinted ? 1 : 0.5 }}>
                <div style={{ fontSize:12, fontWeight:700, color:'#374151', marginBottom:10, display:'flex', alignItems:'center', gap:6 }}>
                  <span style={{ width:20, height:20, borderRadius:10, background:'var(--ybp-panel)', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:800, flexShrink:0 }}>2</span>
                  שתף את ה-PDF
                </div>
                {(() => {
                  const dist = getDist();
                  return (
                    <div style={{ fontSize:11, color:'#9ca3af', marginBottom:10 }}>
                      {dist.waPhone ? `📱 ${dist.waPhone}` : 'לא הוגדר טלפון'}
                      {' · '}
                      {dist.emails ? dist.emails : 'לא הוגדרו מיילים'}
                    </div>
                  );
                })()}
                <div style={{ display:'flex', gap:8 }}>
                  <button onClick={() => { if (!pdfPrinted) return; sendWhatsAppGantt(); }} style={{ flex:1, padding:'10px', borderRadius:7, border:'none', background: pdfPrinted ? '#25d366' : '#e5e7eb', color: pdfPrinted ? '#fff' : '#9ca3af', fontSize:12, fontWeight:700, cursor: pdfPrinted ? 'pointer' : 'not-allowed', fontFamily:'inherit' }}>
                    💬 WhatsApp
                  </button>
                  <button onClick={() => { if (!pdfPrinted) return; sendGmailGantt(); }} style={{ flex:1, padding:'10px', borderRadius:7, border:'none', background: pdfPrinted ? '#ea4335' : '#e5e7eb', color: pdfPrinted ? '#fff' : '#9ca3af', fontSize:12, fontWeight:700, cursor: pdfPrinted ? 'pointer' : 'not-allowed', fontFamily:'inherit' }}>
                    ✉️ Gmail
                  </button>
                </div>
                {!pdfPrinted && (
                  <div style={{ fontSize:10, color:'#9ca3af', marginTop:8, textAlign:'center' }}>שמור PDF קודם כדי להפעיל שליחה</div>
                )}
              </div>
            </div>
          </div>
        </div>
      )}

      {/* ── TOP BAR ── */}
      <header style={{ background:'var(--ybp-panel)', color:'#fff', padding:'0 14px', height:52, display:'flex', alignItems:'center', gap:10, flexShrink:0, borderBottom:'1px solid var(--ybp-border)' }}>
        <button onClick={onBack} style={{ background:'rgba(255,255,255,0.1)', border:'1px solid rgba(255,255,255,0.2)', color:'#fff', width:32, height:32, borderRadius:7, display:'flex', alignItems:'center', justifyContent:'center', cursor:'pointer', flexShrink:0 }}>
          <Icon name="chevron" size={16}/>
        </button>
        <span style={{ fontSize:14, fontWeight:700, flex:1 }}>
          {viewMode === 'calendar' ? '📅' : '📊'} {viewMode === 'calendar' ? 'יומן' : 'גאנט'} — {project?.name}
        </span>
        {/* View toggle */}
        <div style={{ display:'flex', background:'rgba(255,255,255,0.08)', borderRadius:6, padding:2, gap:1 }}>
          {[['gantt','📊 גאנט'],['calendar','📅 יומן']].map(([k,l]) => (
            <button key={k} onClick={() => setViewMode(k)} style={{
              padding:'4px 10px', borderRadius:4, border:'none', fontSize:11, fontWeight:600, cursor:'pointer', fontFamily:'inherit',
              background: viewMode===k ? '#b5a882' : 'transparent',
              color: viewMode===k ? 'var(--ybp-navy)' : 'var(--ybp-ink-soft)',
            }}>{l}</button>
          ))}
        </div>

        {/* Source filter — 4-way (v58) */}
        {viewMode === 'gantt' && <div style={{ display:'flex', background:'rgba(255,255,255,0.08)', borderRadius:6, padding:2, gap:1, overflowX:'auto', maxWidth:'100%', flexShrink:0 }}>
          {[['all','הכל'],['tasks','משימות'],['rejects',"ריג'קטים"],['booklet','חוברת']].map(([k,l]) => (
            <button key={k} onClick={() => setSource(k)} style={{
              padding:'4px 10px', borderRadius:4, border:'none', fontSize:11, fontWeight:600, cursor:'pointer', fontFamily:'inherit',
              background: source===k ? '#fff' : 'transparent',
              color: source===k ? 'var(--ybp-navy)' : 'var(--ybp-ink-soft)',
              whiteSpace:'nowrap',
            }}>{l}</button>
          ))}
        </div>}

        {/* Assignee filter — disabled כשמקור=booklet (אין אחראים) */}
        {viewMode === 'gantt' && (() => {
          const bookletOnly = source === 'booklet';
          return (
            <select
              value={assigneeFilter}
              onChange={e => setAssigneeFilter(e.target.value)}
              disabled={bookletOnly}
              style={{
                padding:'4px 8px', borderRadius:5, border:'1px solid rgba(255,255,255,0.2)',
                background: bookletOnly ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.08)',
                color: bookletOnly ? 'rgba(255,255,255,0.3)' : '#e5e7eb',
                fontSize:11, fontFamily:'inherit', cursor: bookletOnly ? 'not-allowed' : 'pointer',
                minWidth:120, maxWidth:160,
              }}
              title={bookletOnly ? 'אין אחראים בפריטי חוברת' : 'סינון לפי אחראי'}
            >
              <option value="">👤 כל האחראים</option>
              {assigneeOptions.map(a => <option key={a} value={a}>{a}</option>)}
            </select>
          );
        })()}

        {/* Zoom — גאנט בלבד */}
        {viewMode === 'gantt' && <div style={{ display:'flex', background:'rgba(255,255,255,0.08)', borderRadius:6, padding:2, gap:1 }}>
          {[['days','ימים'],['weeks','שבועות'],['months','חודשים']].map(([k,l]) => (
            <button key={k} onClick={() => setZoom(k)} style={{
              padding:'4px 10px', borderRadius:4, border:'none', fontSize:11, fontWeight:600, cursor:'pointer', fontFamily:'inherit',
              background: zoom===k ? '#fff' : 'transparent',
              color: zoom===k ? 'var(--ybp-navy)' : 'var(--ybp-ink-soft)',
            }}>{l}</button>
          ))}
        </div>}

        {/* כיוון הדפסה — גאנט בלבד */}
        {viewMode === 'gantt' && <div style={{ display:'flex', gap:3, background:'rgba(255,255,255,0.08)', borderRadius:6, padding:'3px 6px' }}>
          {[['portrait','📄'],['landscape','📋']].map(([val,icon]) => (
            <button key={val} onClick={() => setPrintOrientation(val)} style={{
              padding:'3px 7px', borderRadius:4, border:'none', fontSize:12, cursor:'pointer', fontFamily:'inherit',
              background: printOrientation===val ? 'rgba(255,255,255,0.25)' : 'transparent', color:'#fff',
            }}>{icon}</button>
          ))}
        </div>}

        {/* Actions — גאנט בלבד */}
        {viewMode === 'gantt' && <>
          <input type="range" min={0} max={100} value={scrollPct}
            onPointerDown={() => { sliderDraggingRef.current = true; }}
            onPointerUp={() => { sliderDraggingRef.current = false; }}
            onPointerCancel={() => { sliderDraggingRef.current = false; }}
            onChange={e => {
              const pct = +e.target.value;
              setScrollPct(pct);
              const el = scrollRef.current;
              if (el) {
                const maxScroll = el.scrollWidth - el.clientWidth;
                const targetX = ((100 - pct) / 100) * maxScroll;
                setScrollX(el, targetX);
                setScrollX(headerScrollRef.current, targetX);
              }
            }}
            style={{ width:140, accentColor:'#b5a882', cursor:'pointer', flexShrink:0, direction:'ltr' }}/>
          <button onClick={() => setShowDepMgr(true)} style={{ padding:'5px 10px', borderRadius:5, border:'1px solid rgba(255,255,255,0.25)', background:'transparent', color:'var(--ybp-ink-soft)', fontSize:11, cursor:'pointer', fontFamily:'inherit' }}>🔗 תלויות</button>
          <button onClick={() => { setPdfPrinted(false); setShowSendModal(true); }} style={{ padding:'5px 12px', borderRadius:5, border:'none', background:'#b5a882', color:'var(--ybp-navy)', fontSize:11, fontWeight:700, cursor:'pointer', fontFamily:'inherit' }}>📤 שלח PDF</button>
          <button onClick={() => setShowAddModal(true)} style={{ padding:'5px 10px', borderRadius:5, border:'none', background:'#b5a882', color:'var(--ybp-navy)', fontSize:11, fontWeight:700, cursor:'pointer', fontFamily:'inherit' }}>+ משימה</button>
          <button onClick={() => setShowSettings(true)} style={{ padding:'5px 8px', borderRadius:5, border:'1px solid rgba(255,255,255,0.2)', background:'transparent', color:'var(--ybp-ink-soft)', fontSize:14, cursor:'pointer' }}>⚙️</button>
        </>}
      </header>

      {/* ── CALENDAR VIEW ── */}
      {viewMode === 'calendar' && (
        <div style={{ flex:1, overflow:'hidden', display:'flex', flexDirection:'column' }}>
          <CalendarView
            items={displayItems}
            projectId={projectId}
            project={project}
            onEditItem={(item) => setEditItem(item)}
          />
        </div>
      )}

      {/* ── EMPTY STATE ── */}
      {viewMode === 'gantt' && isEmpty && (
        <div style={{ flex:1, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', color:'var(--ybp-ink-soft)' }}>
          <div style={{ fontSize:48, marginBottom:12 }}>📅</div>
          <div style={{ fontSize:16, fontWeight:600, color:'var(--ybp-ink)', marginBottom:6 }}>אין פריטים להצגה</div>
          <div style={{ fontSize:13 }}>הוסף משימות לפרויקט או בחר מקור אחר</div>
        </div>
      )}

      {/* v68 #1 — המיני-מקרא העליון הוסר (אוחד למקרא התחתון), משחרר ~34px גובה ל-MAIN AREA */}

      {/* ── MAIN AREA ── */}
      {viewMode === 'gantt' && !isEmpty && (
        <div id="gantt-print-root" style={{ flex:1, display:'flex', overflow:'hidden' }}>
          {/* v76 — נייר המכתבים הועבר מחוץ ל-#gantt-print-root (אחרי הגאנט) כדי שלא ייעלם בהדפסת ה-bands. */}
          {/* LEFT PANEL — v59 grid (MS-style) */}
          <div style={{ width:leftW, flexShrink:0, display:'flex', flexDirection:'column', borderLeft:'1px solid var(--ybp-border)', background:'var(--ybp-bg)', overflow:'hidden' }}>
            {/* v68 #2 — גלילה אופקית פנימית בפאנל (בטוח: הפאנל width:leftW + overflow:hidden חיצוני, הציר sibling נפרד אחרי הידית) */}
            <div style={{ flex:1, display:'flex', flexDirection:'column', overflowX:'auto', overflowY:'hidden' }}>
              {(() => {
                // v68 #2 — כבד את רוחב השם הנגרר (colW[1]) במקום לחנוק אותו. מינימום קריא 120.
                const nameW = Math.max(120, colW[1]);
                const gridW = colW[0] + nameW + colW.slice(2).reduce((a, b) => a + b, 0);
                const GRID = colW[0] + 'px ' + nameW + 'px ' + colW.slice(2).map(w => w + 'px').join(' ');
                // ידית גרירה — נראית תמיד (קו דק) ומודגשת ב-hover. בקצה הפיזי-שמאלי (RTL) → משנה רוחב אותה עמודה.
                // v64 #2 — דאבל-קליק = autofit לתוכן הארוך ביותר (כמו אקסל).
                const colHandle = (col) => (
                  <div onMouseDown={(e) => onColResizeDown(e, col)}
                    onDoubleClick={(e) => { e.preventDefault(); e.stopPropagation(); autoFitColumn(col); }}
                    title="גרור לשינוי רוחב · דאבל-קליק להתאמה אוטומטית"
                    style={{ position:'absolute', left:0, top:0, bottom:0, width:6, cursor:'col-resize', zIndex:5,
                      borderLeft:'1px solid var(--ybp-border)' }}
                    onMouseEnter={(e) => e.currentTarget.style.borderLeft = '3px solid var(--ybp-accent)'}
                    onMouseLeave={(e) => e.currentTarget.style.borderLeft = '1px solid var(--ybp-border)'} />
                );
                const HEAD_BG = 'var(--ybp-panel)';
                const ROW_BORDER = '1px solid var(--ybp-border-soft)';
                const cellBase = { display:'flex', alignItems:'center', padding:'0 6px', overflow:'hidden', whiteSpace:'nowrap', textOverflow:'ellipsis', fontSize:12, color:'var(--ybp-ink)', boxSizing:'border-box', minWidth:0 };
                const inputBase = { width:'100%', height:24, padding:'0 6px', border:'1px solid #b5a882', borderRadius:3, background:'var(--ybp-input-bg)', color:'#fff', fontSize:12, fontFamily:'inherit', boxSizing:'border-box', outline:'none' };
                const onCellKeyDown = (e, rowIdx, colIdx) => {
                  const ec = editingCell;
                  if (!ec) return;
                  if (e.key === 'Escape') {
                    e.preventDefault();
                    setEditingCell(null);
                  } else if (e.key === 'Enter') {
                    e.preventDefault();
                    commitCellEdit(ec);
                    moveEditTo(rowIdx + 1, colIdx);     // Enter → שורה הבאה, אותה עמודה
                  } else if (e.key === 'Tab') {
                    e.preventDefault();
                    commitCellEdit(ec);
                    if (e.shiftKey) {
                      if (colIdx > 0) moveEditTo(rowIdx, colIdx - 1);
                      else            moveEditTo(rowIdx - 1, EDIT_COLS.length - 1);
                    } else {
                      if (colIdx < EDIT_COLS.length - 1) moveEditTo(rowIdx, colIdx + 1);
                      else                               moveEditTo(rowIdx + 1, 0);
                    }
                  }
                };
                return (
                  <div style={{ display:'flex', flexDirection:'column', width: Math.max(gridW, leftW), height:'100%' }}>
                    {/* Header row */}
                    <div role="row" style={{
                      display:'grid', gridTemplateColumns:GRID, height:56,
                      background:HEAD_BG, borderBottom:'1px solid var(--ybp-border)', flexShrink:0, alignItems:'center',
                    }}>
                      <div style={{ ...cellBase, justifyContent:'center', color:'var(--ybp-ink-faint)' }}>
                        <input type="checkbox" disabled style={{ accentColor:'#b5a882', cursor:'not-allowed', opacity:0.5 }}/>
                      </div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)', letterSpacing:0.4 }}>{colHandle(1)}<span onClick={() => toggleSort('name')} style={{ cursor:'pointer', userSelect:'none' }}>שם משימה{sortBy?.key==='name' ? (sortBy.dir==='asc'?' ▲':' ▼') : ''}</span></div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)' }}>{colHandle(2)}<span onClick={() => toggleSort('start')} style={{ cursor:'pointer', userSelect:'none' }}>התחלה{sortBy?.key==='start' ? (sortBy.dir==='asc'?' ▲':' ▼') : ''}</span></div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)' }}>{colHandle(3)}סיום</div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)', justifyContent:'center' }}>{colHandle(4)}%</div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)', justifyContent:'center' }}>{colHandle(5)}ימים</div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)' }}>{colHandle(6)}סטטוס</div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)' }}>{colHandle(7)}<span onClick={() => toggleSort('assignee')} style={{ cursor:'pointer', userSelect:'none' }}>אחראי{sortBy?.key==='assignee' ? (sortBy.dir==='asc'?' ▲':' ▼') : ''}</span></div>
                      <div style={{ ...cellBase, position:'relative', fontSize:11, fontWeight:700, color:'var(--ybp-ink-soft)' }}>{colHandle(8)}קודם</div>
                    </div>
                    {/* Body rows */}
                    <div ref={leftScrollRef} style={{ flex:1, overflowY:'auto', overflowX:'hidden' }} onScroll={onLeftScroll}>
                      {displayItems.map((item, rowIdx) => {
                        const isBooklet = item.source === 'booklet';
                        const isSel = selectedIds.has(item.id);
                        const predList = predecessorNamesByItemId.get(item.id) || [];
                        const predText = predList.length ? predList.join(', ') : '—';
                        const renderCell = (colIdx, content, opts = {}) => {
                          const isEditingThis = editingCell && editingCell.rowIdx === rowIdx && editingCell.colIdx === colIdx;
                          const editable = !isBooklet && colIdx >= 0 && colIdx < EDIT_COLS.length && colIdx !== 5; // 5=assignee → picker (#5)
                          // colIdx: 0=name(text) 1=start(date) 2=end(date) 3=%(number) 4=days(number) 5=assignee(picker)
                          const inputType = (colIdx === 1 || colIdx === 2) ? 'date'
                                          : (colIdx === 3 || colIdx === 4) ? 'number'
                                          : 'text';
                          return (
                            <div
                              role="gridcell"
                              style={{ ...cellBase, ...opts.style, cursor: editable ? 'cell' : (opts.cursor || 'default') }}
                              title={opts.title}
                              onClick={(e) => {
                                if (!editable) return;
                                if (isEditingThis) return;
                                e.stopPropagation();
                                startCellEdit(rowIdx, colIdx);
                              }}
                            >
                              {isEditingThis ? (
                                <input
                                  autoFocus
                                  type={inputType}
                                  value={editingCell.value}
                                  min={colIdx === 3 ? 0 : (colIdx === 4 ? 1 : undefined)}
                                  max={colIdx === 3 ? 100 : undefined}
                                  step={(colIdx === 3 || colIdx === 4) ? 1 : undefined}
                                  onChange={(e) => setEditingCell(ec => ec ? { ...ec, value: e.target.value } : ec)}
                                  onBlur={() => { commitCellEdit(editingCell); setEditingCell(null); }}
                                  onKeyDown={(e) => onCellKeyDown(e, rowIdx, colIdx)}
                                  style={inputBase}
                                />
                              ) : content}
                            </div>
                          );
                        };
                        return (
                          <div key={item.id} role="row" style={{
                            display:'grid', gridTemplateColumns:GRID, height:ROW_H, alignItems:'center',
                            borderBottom: ROW_BORDER,
                            background: isSel ? 'rgba(181,168,130,0.15)' : 'transparent',
                          }}>
                            {/* checkbox */}
                            <div style={{ ...cellBase, justifyContent:'center' }}>
                              <input type="checkbox" checked={isSel} onChange={() => toggleSelect(item.id)}
                                style={{ width:14, height:14, cursor:'pointer', accentColor:'#b5a882' }}/>
                            </div>
                            {/* name (+ source dot, double-click opens full EditPopup) */}
                            {renderCell(0, (
                              <div
                                onDoubleClick={(e) => { e.stopPropagation(); setEditItem(item); }}
                                style={{ display:'flex', alignItems:'center', gap:6, width:'100%', overflow:'hidden' }}
                                title={item.name + (isBooklet ? '' : ' — דאבל-קליק לעריכה מלאה')}
                              >
                                <span style={{ fontSize:11, flexShrink:0 }} title={SOURCE_LABEL[item.srcKind] || ''}>{SOURCE_DOT[item.srcKind] || '•'}</span>
                                <span style={{ flex:1, fontWeight:600, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{item.name}</span>
                                {/* v63 #3 — מחיקת משימה ישירות מהשורה (לא לחוברת) */}
                                {!isBooklet && (
                                  <button onClick={(e) => { e.stopPropagation(); deleteTaskRow(item); }}
                                    title="מחק משימה"
                                    style={{ flexShrink:0, background:'transparent', border:'none', cursor:'pointer',
                                      color:'var(--ybp-ink-faint)', fontSize:13, padding:'0 2px', lineHeight:1 }}
                                    onMouseEnter={(e) => e.currentTarget.style.color = '#dc2626'}
                                    onMouseLeave={(e) => e.currentTarget.style.color = 'var(--ybp-ink-faint)'}>🗑</button>
                                )}
                              </div>
                            ), { title: isBooklet ? 'פריט חוברת — לא ניתן לעריכה inline' : undefined })}
                            {/* start */}
                            {renderCell(1, <span style={{ color:'var(--ybp-ink-soft)' }}>{fmtDateHe(item.start)}</span>)}
                            {/* end */}
                            {renderCell(2, <span style={{ color:'var(--ybp-ink-soft)' }}>{fmtDateHe(item.end)}</span>)}
                            {/* % */}
                            {renderCell(3, <span style={{ width:'100%', textAlign:'center', color: item.percentDone > 0 ? '#b5a882' : 'rgba(255,255,255,0.4)' }}>{item.percentDone || 0}%</span>, { style:{ justifyContent:'center' } })}
                            {/* days (v60) */}
                            {renderCell(4, <span style={{ width:'100%', textAlign:'center', color:'var(--ybp-ink-soft)' }}>{itemDurationDays(item) || '—'}</span>, { style:{ justifyContent:'center' } })}
                            {/* status — dropdown inline (v63 #5), colIdx 5 */}
                            <StatusCell item={item} isBooklet={isBooklet} cellBase={cellBase}
                              onChange={(val) => applyCellSave(item, 5, val)}/>
                            {/* assignee — picker (v60 #5), colIdx 6 */}
                            <AssigneeCell item={item} rowIdx={rowIdx} colIdx={6} isBooklet={isBooklet} cellBase={cellBase}
                              isEditingThis={editingCell && editingCell.rowIdx === rowIdx && editingCell.colIdx === 6}
                              startCellEdit={startCellEdit}
                              commitCellEdit={commitCellEdit}
                              setEditingCell={setEditingCell}
                              applyCellSave={applyCellSave}
                              />
                            {/* predecessor — picker (v60 #2/#3) */}
                            <PredecessorCell item={item} cellBase={cellBase}
                              predText={predText}
                              predList={predList}
                              isBooklet={isBooklet}
                              displayItems={displayItems}
                              deps={deps}
                              saveDepsLocal={saveDepsLocal}
                              onUpsertDep={saveDepsAndCascade}/>
                          </div>
                        );
                      })}
                      {/* v63 #4 — שורת הוספה inline (Enter יוצר משימה בלי מודל). מוסתר בתצוגת חוברת בלבד. */}
                      {source !== 'booklet' && (
                      <div role="row" style={{
                        display:'grid', gridTemplateColumns:GRID, height:ROW_H, alignItems:'center',
                        borderBottom:ROW_BORDER, background:'transparent',
                      }}>
                        <div style={{ ...cellBase, justifyContent:'center', color:'var(--ybp-ink-faint)', fontSize:14 }}>+</div>
                        <div style={{ ...cellBase }}>
                          <input
                            value={newRowName}
                            onChange={(e) => setNewRowName(e.target.value)}
                            onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addRowCommit(); } if (e.key === 'Escape') setNewRowName(''); }}
                            placeholder="+ משימה חדשה..."
                            style={{ width:'100%', height:26, padding:'0 6px', border:'1px solid var(--ybp-border)', borderRadius:4,
                              background:'var(--ybp-input-bg)', color:'var(--ybp-ink)', fontSize:12, fontFamily:'inherit', boxSizing:'border-box', outline:'none' }}/>
                        </div>
                        <div style={{ ...cellBase }}/>
                        <div style={{ ...cellBase }}/>
                        <div style={{ ...cellBase }}/>
                        <div style={{ ...cellBase }}/>
                        <div style={{ ...cellBase }}/>
                        <div style={{ ...cellBase }}/>
                        <div style={{ ...cellBase }}/>
                      </div>
                      )}
                    </div>
                  </div>
                );
              })()}
            </div>
          </div>

          {/* RESIZE HANDLE — v69 #3: גרירה דרך onMouseDown של React (עמיד ל-mount בגאנט ריק) */}
          <div ref={resizeRef}
            onMouseDown={(e) => {
              e.preventDefault();
              const st = resizeStateRef.current;
              st.dragging = true; st.startX = e.clientX; st.startW = leftWRef.current;
              // v70 — בלי מגבלת רוחב קשיחה: מקסימום דינמי = רוחב הקונטיינר פחות 120px (להשאיר ציר זמן).
              const parentW = (e.currentTarget.parentElement && e.currentTarget.parentElement.clientWidth) || window.innerWidth;
              const maxW = Math.max(LEFT_W_MIN, parentW - 120);
              const onMove = (ev) => { if (!st.dragging) return;
                // v62 #3 — RTL: הפאנל בצד ימין. גרירה שמאלה מרחיבה, ימינה מצרה.
                const newW = Math.min(maxW, Math.max(LEFT_W_MIN, st.startW - (ev.clientX - st.startX)));
                setLeftW(newW); };
              const onUp = () => { st.dragging = false; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
              window.addEventListener('mousemove', onMove); window.addEventListener('mouseup', onUp);
            }}
            style={{ width:6, flexShrink:0, cursor:'col-resize', background:'var(--ybp-border)', borderRight:'1px solid var(--ybp-border)', transition:'background .15s' }}
            onMouseEnter={e=>e.currentTarget.style.background='var(--ybp-accent)'}
            onMouseLeave={e=>e.currentTarget.style.background='var(--ybp-border)'}/>

          {/* TIMELINE */}
          <div style={{ flex:1, display:'flex', flexDirection:'column', overflow:'hidden', position:'relative' }}>

            {/* Sticky headers — synced horizontal scroll */}
            <div ref={headerScrollRef} style={{ overflowX:'hidden', flexShrink:0 }}>
              {/* months */}
              <div style={{ height:28, display:'flex', background:'var(--ybp-panel)', borderBottom:'1px solid var(--ybp-border)', width:totalW, minWidth:'100%' }}>
                {monthGroups.map((mg, i) => (
                  <div key={i} style={{ width: mg.count * dayW, flexShrink:0, fontSize:10, fontWeight:700, color:'var(--ybp-ink)',
                    padding:'0 6px', display:'flex', alignItems:'center', borderLeft:'1px solid var(--ybp-border)', whiteSpace:'nowrap', overflow:'hidden' }}>
                    {mg.label}
                  </div>
                ))}
              </div>
              {/* sub-header: days */}
              {zoom === 'days' && (
                <div style={{ height:28, display:'flex', background:'var(--ybp-row-hover)', borderBottom:'1px solid var(--ybp-border)', width:totalW, minWidth:'100%' }}>
                  {dateRange.days.map((d, i) => (
                    <div key={i} style={{ width:dayW, flexShrink:0, fontSize:9, textAlign:'center', color:d.holiday ? (d.isMuslim?'#6ee7b7':'#93c5fd') : d.isWeekend ? 'var(--ybp-ink-faint)' : 'var(--ybp-ink-soft)',
                      borderLeft:'1px solid var(--ybp-border-soft)', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', cursor:'default' }}
                      onMouseEnter={d.holiday ? (e) => setTooltip({text:(d.isMuslim?'🟢 ':'🔵 ')+d.holiday, x:e.clientX, y:e.clientY}) : null}
                      onMouseLeave={() => setTooltip(null)}>
                      <span>{'אבגדהוש'[d.dow]||''}</span>
                      <span>{d.date.getDate()}</span>
                    </div>
                  ))}
                </div>
              )}

              {/* sub-header: weeks */}
              {zoom === 'weeks' && (
                <div style={{ height:28, display:'flex', background:'var(--ybp-panel)', borderBottom:'1px solid var(--ybp-border)', width:totalW, minWidth:'100%' }}>
                  {(() => {
                    // קיבוץ ימים לפי שבועות (יום ראשון = תחילת שבוע ישראלי)
                    const weeks = [];
                    let cur = null;
                    dateRange.days.forEach((d, i) => {
                      if (i === 0 || d.dow === 0) {
                        if (cur) weeks.push(cur);
                        cur = { count: 1, firstDay: d };
                      } else if (cur) {
                        cur.count++;
                      }
                    });
                    if (cur) weeks.push(cur);
                    return weeks.map((w, i) => (
                      <div key={i} title={`שבוע מתחיל ${w.firstDay.date.getDate()}/${w.firstDay.date.getMonth()+1}`}
                        style={{ width: w.count * dayW, flexShrink:0, fontSize:10, fontWeight:700, color:'var(--ybp-ink)',
                          borderLeft:'1px solid var(--ybp-border)', display:'flex', alignItems:'center', justifyContent:'center', whiteSpace:'nowrap', overflow:'hidden', padding:'0 2px' }}>
                        {w.firstDay.date.getDate()}/{w.firstDay.date.getMonth()+1}
                      </div>
                    ));
                  })()}
                </div>
              )}

              {/* sub-header: months */}
              {zoom === 'months' && (
                <div style={{ height:28, display:'flex', background:'var(--ybp-panel)', borderBottom:'1px solid var(--ybp-border)', width:totalW, minWidth:'100%' }}>
                  {monthGroups.map((mg, i) => {
                    const firstDay = dateRange.days[mg.startIdx];
                    return (
                      <div key={i} style={{ width: mg.count * dayW, flexShrink:0, fontSize:10, fontWeight:700, color:'var(--ybp-ink)',
                        borderLeft:'1px solid var(--ybp-border)', display:'flex', alignItems:'center', justifyContent:'center', whiteSpace:'nowrap', overflow:'hidden', padding:'0 4px' }}>
                        {firstDay ? `${firstDay.date.getDate()}/${firstDay.date.getMonth()+1}` : ''}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>

            {/* Scrollable timeline */}
            <div ref={scrollRef} style={{ flex:1, overflow:'auto', position:'relative', touchAction:'pan-x pan-y' }} onScroll={onTimelineScroll}>
              <div style={{ width:totalW, position:'relative', minWidth:'100%' }}>

                {/* Background stripes */}
                <div style={{ position:'absolute', top:0, left:0, right:0, bottom:0, display:'flex' }}>
                  {dateRange.days.map((d, i) => (
                    <div key={i} style={{ width:dayW, flexShrink:0, background:
                      d.iso === todayISO() ? 'rgba(243,156,18,.18)' :
                      d.holiday ? (d.isMuslim ? 'rgba(0,180,80,.07)' : 'rgba(0,112,243,.07)') :
                      d.isWeekend ? 'rgba(100,100,100,.08)' : 'transparent' }}/>
                  ))}
                </div>

                {/* Today marker (v58 — קו + תווית) */}
                {todayX >= 0 && (
                  <>
                    <div style={{ position:'absolute', top:0, bottom:0, right:todayX, width:2, background:'#F59E0B', zIndex:6, pointerEvents:'none' }}/>
                    <div style={{ position:'absolute', top:0, right:todayX, transform:'translateX(50%)',
                      background:'#F59E0B', color:'var(--ybp-navy)', fontSize:9, fontWeight:800,
                      padding:'1px 6px', borderRadius:'0 0 4px 4px', zIndex:7, pointerEvents:'none',
                      letterSpacing:0.3, lineHeight:1.3, fontFamily:'inherit' }}>היום</div>
                  </>
                )}

                {/* Dep lines */}
                <DepLines items={displayItems} deps={deps} dateRange={dateRange} dayW={dayW} totalW={totalW}/>

                {/* Rows */}
                {displayItems.map((item) => {
                  const barPos = getBarPos(item);
                  if (!barPos) return null;
                  const { x, w } = barPos;
                  const color = getItemColor(item);
                  const overdue = isItemOverdue(item);
                  const isDragging = dragBar?.id === item.id;
                  return (
                    <div key={item.id} style={{ height:ROW_H, position:'relative', borderBottom:'1px solid var(--ybp-border-soft)',
                      background: selectedIds.has(item.id) ? 'rgba(181,168,130,0.08)' : 'transparent' }}>
                      {/* Bar — v74: מקטעי ימי-עבודה (רווח על סופ"ש/חג בזום ימים). תווית/ידיות = מקטע ראשון בלבד. */}
                      {getBarSegments(item).map((seg, segIdx) => segIdx === 0 ? (
                      <div key="seg0" style={{ position:'absolute', top:6, height:ROW_H-12, right:seg.x, width:seg.w,
                        background:color, borderRadius:4, cursor:isDragging?'grabbing':'grab',
                        outline: overdue ? '2px solid #dc2626' : 'none',
                        outlineOffset: overdue ? '-2px' : '0',
                        boxShadow: isDragging ? '0 4px 12px rgba(0,0,0,0.4)' : (overdue ? '0 0 0 1px rgba(220,38,38,0.4), 0 1px 3px rgba(0,0,0,0.3)' : '0 1px 3px rgba(0,0,0,0.3)'),
                        display:'flex', alignItems:'center', overflow:'hidden', zIndex:4 }}
                        onMouseDown={e => onBarMouseDown(e, item, 'move')}
                        onClick={e => { e.stopPropagation(); if (didDragRef.current) { didDragRef.current = false; return; } setEditItem(item); }}>

                        {/* Progress fill */}
                        {item.percentDone > 0 && (
                          <div style={{ position:'absolute', top:0, left:0, height:'100%', width: (item.percentDone/100*seg.w) + 'px', background:'rgba(255,255,255,0.25)', borderRadius:'4px 0 0 4px' }}/>
                        )}

                        {/* Label */}
                        <span style={{ padding:'0 6px', fontSize:10, fontWeight:700, color:'#fff', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', flex:1, position:'relative', zIndex:1 }}>
                          {item.assignee && <span style={{ fontSize:9, opacity:0.8 }}>👤 </span>}
                          {item.name}
                          {item.percentDone > 0 && <span style={{ fontSize:9, opacity:0.8 }}> {item.percentDone}%</span>}
                        </span>

                        {/* Milestone diamond */}
                        {item.milestone && (
                          <div style={{ position:'absolute', top:'50%', left:'50%', transform:'translate(-50%,-50%) rotate(45deg)',
                            width:16, height:16, background:color, border:'2px solid rgba(255,255,255,0.5)', zIndex:2 }}/>
                        )}
                      {/* Resize handle */}
                        {!item.milestone && <div style={{ width:8, height:'100%', cursor:'ew-resize', background:'rgba(255,255,255,0.2)', flexShrink:0, borderRadius:'0 4px 4px 0', position:'relative', zIndex:1 }}
                          onMouseDown={e => { e.stopPropagation(); onBarMouseDown(e, item, 'resize'); }}/>}
                      </div>
                      ) : (
                        // מקטע המשך (אחרי רווח אי-עבודה) — rect "מת", אותו צבע, בלי אירועי עכבר
                        <div key={'seg'+segIdx} style={{ position:'absolute', top:6, height:ROW_H-12, right:seg.x, width:seg.w,
                          background:color, borderRadius:4, zIndex:4, pointerEvents:'none',
                          boxShadow:'0 1px 3px rgba(0,0,0,0.3)' }}/>
                      ))}
                    </div>
                  );
                })}

              </div>
            </div>
          </div>
        </div>
      )}

      {/* v76.2 — שכבת הדפסה ב-portal ל-body (מחוץ לעץ הגאנט) כ-table: פגינציה אמיתית + thead חוזר בכל עמוד. */}
      {viewMode === 'gantt' && !isEmpty && ReactDOM.createPortal(
        <div id="ybp-print-portal" aria-hidden="true">
          <table className="gpb-table">
            <colgroup>
              <col style={{ width: PRINT_NAME_W }} />
              <col style={{ width: printAxisW }} />
            </colgroup>
            <thead>
              {/* שורת מותג: כותרת (שם+טווח) + לוגו — חוזרת בראש כל עמוד, מונעת עמוד-1 ריק */}
              <tr>
                <th colSpan={2} className="gpb-brand">
                  <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, direction:'rtl' }}>
                    <span style={{ fontSize:12, fontWeight:700, color:'#1a2c4a', whiteSpace:'nowrap' }}>{(project && project.name) || ''} &nbsp;·&nbsp; {fmtPrint(printRange.lo)} — {fmtPrint(printRange.hi)}</span>
                    <img src={(window.YBP_DATA && window.YBP_DATA.company && window.YBP_DATA.company.logoFull) || 'assets/ybp-logo-new.png'} alt="YBP" style={{ height:30, objectFit:'contain' }} />
                  </div>
                </th>
              </tr>
              <tr>
                <th className="gpb-name-h" style={{ width: PRINT_NAME_W }}>שם משימה</th>
                <th className="gpb-axis-h" style={{ width: printAxisW }}>
                  <div style={{ position:'relative', width:printAxisW, height:16, direction:'rtl' }}>
                    {printRange.days.map((d, di) => (
                      <div key={di} style={{ position:'absolute', top:0, right: di*printDayW, width:printDayW, height:16, fontSize:7, lineHeight:'16px', textAlign:'center', overflow:'hidden', background: d.weekend ? '#e9ebf0' : 'transparent', borderRight:'1px solid #f0f0f0', color:'#444' }}>
                        {printDayW >= 11 ? new Date(d.iso + 'T12:00:00').getDate() : ''}
                      </div>
                    ))}
                  </div>
                </th>
              </tr>
            </thead>
            <tbody>
              {displayItems.map(item => {
                const bp = printBarPos(item);
                return (
                  <tr key={item.id} className="gpb-row">
                    <td className="gpb-name">
                      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', gap:6, direction:'rtl', width:PRINT_NAME_W }}>
                        <span style={{ fontSize:10.5, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis', flex:1 }}>{item.name}</span>
                        <span style={{ fontSize:8, color:'#666', whiteSpace:'nowrap', direction:'ltr', flexShrink:0 }}>{fmtPrint(item.start)}–{fmtPrint(item.end)}</span>
                      </div>
                    </td>
                    <td className="gpb-axis">
                      <div style={{ position:'relative', width:printAxisW, height:ROW_H, direction:'rtl' }}>
                        {printRange.days.map((d, di) => d.weekend ? (
                          <div key={'w'+di} style={{ position:'absolute', top:0, bottom:0, right:di*printDayW, width:printDayW, background:'#f3f4f8' }}/>
                        ) : null)}
                        {bp && (
                          <div style={{ position:'absolute', top:6, height:ROW_H-12, right:bp.right, width:bp.width, background: getItemColor(item), borderRadius:4, opacity:0.92 }}/>
                        )}
                      </div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>,
        document.body
      )}

      {/* ── LEGEND ── (v68 #1 — מאוחד: מקורות + סטטוסים + תלות + חגים בפס תחתון אחד) */}
      <div style={{ height:40, background:'var(--ybp-input-bg)', borderTop:'1px solid var(--ybp-border)', display:'flex', alignItems:'center', padding:'0 14px', gap:14, flexShrink:0, overflowX:'auto' }}>
        {/* v68 #1 — צבעי מקורות (הועברו מהמקרא העליון שנמחק) */}
        {[['reject',"ריג'קטים"],['tasks','משימות'],['booklet','חוברת']].map(([k,l]) => (
          <div key={'src_'+k} style={{ display:'flex', alignItems:'center', gap:5, fontSize:10, color:'var(--ybp-ink-soft)', whiteSpace:'nowrap' }}>
            <div style={{ width:12, height:12, borderRadius:3, background:SOURCE_COLOR[k] }}/>
            {l}
          </div>
        ))}
        <div style={{ display:'flex', alignItems:'center', gap:4, fontSize:10, color:'#dc2626', whiteSpace:'nowrap' }}>
          <div style={{ width:12, height:12, borderRadius:3, background:'transparent', border:'2px solid #dc2626' }}/> עבר תאריך
        </div>
        <div style={{ width:1, height:16, background:'rgba(255,255,255,0.15)' }}/>
        {[['#27AE60','הושלם'],['#4A90D9','בביצוע'],['#F0A500','פתוח'],['#95A5A6','ממתין'],['#E74C3C','באיחור']].map(([c,l]) => (
          <div key={l} style={{ display:'flex', alignItems:'center', gap:5, fontSize:10, color:'var(--ybp-ink-soft)', whiteSpace:'nowrap' }}>
            <div style={{ width:12, height:12, borderRadius:3, background:c }}/>
            {l}
          </div>
        ))}
        <div style={{ width:1, height:16, background:'rgba(255,255,255,0.15)' }}/>
        {DEP_TYPES.map(dt => (
          <div key={dt.k} style={{ display:'flex', alignItems:'center', gap:4, fontSize:10, color:'var(--ybp-ink-faint)', whiteSpace:'nowrap' }}>
            <div style={{ width:16, height:2, background:dt.color, borderRadius:1 }}/>→ {dt.k}
          </div>
        ))}
        <div style={{ width:1, height:16, background:'rgba(255,255,255,0.15)' }}/>
        <div style={{ display:'flex', alignItems:'center', gap:4, fontSize:10, color:'var(--ybp-ink-faint)' }}>
          <div style={{ width:2, height:12, background:'#F59E0B' }}/> היום
        </div>
        <div style={{ display:'flex', alignItems:'center', gap:4, fontSize:10, color:'var(--ybp-ink-faint)' }}>
          <div style={{ width:12, height:12, background:'rgba(0,112,243,.3)', borderRadius:2 }}/> חג יהודי
        </div>
        <div style={{ display:'flex', alignItems:'center', gap:4, fontSize:10, color:'var(--ybp-ink-faint)' }}>
          <div style={{ width:12, height:12, background:'rgba(0,180,80,.3)', borderRadius:2 }}/> חג מוסלמי
        </div>
        <div style={{ marginRight:'auto', fontSize:10, color:'rgba(255,255,255,0.3)' }}>לחץ פעמיים על בר לעריכה</div>
      </div>

      {/* Bulk edit bar */}
      <BulkEditBar selected={selectedIds} items={displayItems} onApply={applyBulk} onClear={clearSelect}/>

      {/* Tooltip */}
      {tooltip && (
        <div style={{ position:'fixed', top: tooltip.y - 36, left: tooltip.x - 60, background:'var(--ybp-panel)', color:'#fff', padding:'5px 10px', borderRadius:6, fontSize:12, fontWeight:600, pointerEvents:'none', zIndex:9999, boxShadow:'0 4px 12px rgba(0,0,0,0.3)' }}>
          {tooltip.text}
        </div>
      )}

      {/* Modals */}
      {showAddModal && (
        <AddItemModal
          projectId={projectId}
          onClose={() => setShowAddModal(false)}
          onAdd={(newItem) => {
            if (newItem.source === 'task') {
              // v57 — דרך SyncStore.addTask בלבד; לא כותבים ישירות ל-localStorage
              SyncStore.addTask({
                projectId,
                title: newItem.name,
                due: newItem.end,
                status: newItem.status,
                assignee: newItem.assignee,
                source: 'gantt',
                startDate: newItem.start,
                percentDone: newItem.percentDone || 0,
                stage: newItem.stage || '',
              });
              // ה-store subscriber יחזיר rawItems מ-buildItems אוטומטית
            } else {
              // booklet/local פריט בלבד — נכנס ל-state המקומי
              setItems(prev => [...prev, newItem]);
            }
            setShowAddModal(false);
          }}
        />
      )}
        <EditPopup item={editItem} deps={deps} allItems={items}
          onSave={onEditSave}
          onClose={() => setEditItem(null)}
          onDelete={(item) => {
            setItems(prev => prev.filter(it => it.id !== item.id));
            saveDepsLocal(deps.filter(d => d.from !== item.id && d.to !== item.id));
            if (item.source === 'task' && item.rawId) SyncStore.updateTask(item.rawId, { status: 'archived' });
            setEditItem(null);
          }}
          onDeleteDep={(dep) => saveDepsLocal(deps.filter(d => !(d.from===dep.from && d.to===dep.to && d.type===dep.type)))}
          onAddDep={(dep) => saveDepsAndCascade([...deps, dep], dep.from)}/>
      )}
      {showSettings && <SettingsModal settings={settings} onChange={setSettings} onClose={() => setShowSettings(false)}/>}
      {showDepMgr && <DepManager deps={deps} items={displayItems} onDelete={(dep) => saveDepsLocal(deps.filter(d => !(d.from===dep.from && d.to===dep.to)))} onClose={() => setShowDepMgr(false)}/>}

      {/* Print styles — v62 #5: בודד את הגאנט בלבד, כפה תמה בהירה, אפשר לציר להיפרש.
          מותנה ב-gantt בלבד כדי לא לבלוע את הדפסת היומן (ל-CalendarView כללי-הדפסה משלו). */}
      {viewMode === 'gantt' && (
      <style>{`
        #ybp-print-portal { display:none; }   /* על המסך — מוסתר לגמרי (portal ב-body) */
        @media print {
          /* הצג רק את שכבת ההדפסה; הסתר את האפליקציה */
          body > *:not(#ybp-print-portal) { display:none !important; }
          html, body { background:#fff !important; margin:0 !important; padding:0 !important; }   /* v76.4 — אפס רקע אפליקציה (היה פס אפור בראש עמ' 1) */
          #ybp-print-portal { display:block !important; background:#fff !important; color:#000 !important; margin:0 !important; padding:0 12px; }
          #ybp-print-portal *::-webkit-scrollbar { display:none !important; width:0 !important; height:0 !important; }
          .gpb-brand { padding:2px 0 6px; border-bottom:none; vertical-align:middle; }
          .gpb-brand img { height:30px; object-fit:contain; }
          .gpb-table { border-collapse:collapse; width:auto; table-layout:fixed; margin:0 !important; }
          .gpb-table thead { display:table-header-group; }   /* ציר הימים חוזר בכל עמוד */
          .gpb-table tr { break-inside:avoid; page-break-inside:avoid; }
          .gpb-name-h { font-size:10px; font-weight:700; color:#1a2c4a; text-align:right; padding-inline-end:18px; border-bottom:1px solid #ddd; vertical-align:bottom; }
          .gpb-axis-h { border-bottom:1px solid #ddd; vertical-align:bottom; padding:0; }
          .gpb-row td { border-bottom:1px solid #eee; padding:0; }   /* גובה השורה נשלט ע"י ה-div הפנימי (height:ROW_H) */
          .gpb-name { padding-inline-end:18px; vertical-align:middle; }
          .gpb-axis { padding:0; vertical-align:middle; }
          @page { size: A4 landscape; margin: 8mm 6mm; }   /* שוליים קטנים — מתחיל גבוה, כל המשימות מתפצלות */
          header, .no-print { display:none !important; }
        }
      `}</style>
      )}
    </div>
  );
};

// ── Calendar View ─────────────────────────────────────────────────────────────
const WEEK_DAYS_HE = ['א׳','ב׳','ג׳','ד׳','ה׳','ו׳','ש׳'];
const MONTH_NAMES_HE = ['ינואר','פברואר','מרץ','אפריל','מאי','יוני','יולי','אוגוסט','ספטמבר','אוקטובר','נובמבר','דצמבר'];

const isDone = (item) => ['done','הסתיים','אושר'].includes(item.status);

// build color by assignee — same assignee = same color
const ASSIGNEE_PALETTE = ['#4A90D9','#E67E22','#9B59B6','#16A085','#E74C3C','#2ECC71','#F39C12','#1ABC9C','#8E44AD','#D35400'];
const assigneeColorMap = {};
let assigneeColorIdx = 0;
const getAssigneeColor = (assignee) => {
  if (!assignee) return '#6b7280';
  if (!assigneeColorMap[assignee]) {
    assigneeColorMap[assignee] = ASSIGNEE_PALETTE[assigneeColorIdx % ASSIGNEE_PALETTE.length];
    assigneeColorIdx++;
  }
  return assigneeColorMap[assignee];
};

const CalendarView = ({ items, projectId, project, onEditItem }) => {
  const today = new Date();
  const [viewMode, setViewMode] = useState('month'); // 'month' | 'week'
  const [curYear, setCurYear] = useState(today.getFullYear());
  const [curMonth, setCurMonth] = useState(today.getMonth()); // 0-indexed
  const [curWeekStart, setCurWeekStart] = useState(() => {
    // start of current week (Sunday)
    const d = new Date(today);
    d.setDate(d.getDate() - d.getDay());
    return d;
  });
  const [showPdfModal, setShowPdfModal] = useState(false);
  const [pdfPrinted, setPdfPrinted] = useState(false);
  const [dayPopup, setDayPopup] = useState(null); // { iso, items, label }

  // active (non-done) items only
  const activeItems = items.filter(it => !isDone(it));

  // navigate month
  const prevMonth = () => { if (curMonth === 0) { setCurMonth(11); setCurYear(y => y-1); } else setCurMonth(m => m-1); };
  const nextMonth = () => { if (curMonth === 11) { setCurMonth(0); setCurYear(y => y+1); } else setCurMonth(m => m+1); };
  const prevWeek = () => { const d = new Date(curWeekStart); d.setDate(d.getDate()-7); setCurWeekStart(d); };
  const nextWeek = () => { const d = new Date(curWeekStart); d.setDate(d.getDate()+7); setCurWeekStart(d); };
  const goToday = () => {
    setCurYear(today.getFullYear()); setCurMonth(today.getMonth());
    const d = new Date(today); d.setDate(d.getDate() - d.getDay()); setCurWeekStart(d);
  };

  // build calendar grid for month view
  const buildMonthGrid = () => {
    const firstDay = new Date(curYear, curMonth, 1).getDay(); // 0=sun
    const daysInMonth = new Date(curYear, curMonth+1, 0).getDate();
    const cells = [];
    // leading empty cells
    for (let i = 0; i < firstDay; i++) cells.push(null);
    for (let d = 1; d <= daysInMonth; d++) {
      const iso = `${curYear}-${String(curMonth+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
      cells.push({ day: d, iso, date: new Date(curYear, curMonth, d) });
    }
    return cells;
  };

  // build week grid
  const buildWeekGrid = () => {
    const days = [];
    for (let i = 0; i < 7; i++) {
      const d = new Date(curWeekStart);
      d.setDate(d.getDate() + i);
      const iso = d.toISOString().slice(0,10);
      days.push({ day: d.getDate(), iso, date: d });
    }
    return days;
  };

  // get items that span or start on a given day
  const getItemsForDay = (iso) => {
    return activeItems.filter(it => {
      if (!it.start || !it.end) return false;
      return it.start <= iso && it.end >= iso;
    });
  };

  // check if item starts on this day (for bar rendering)
  const itemStartsOn = (item, iso) => item.start === iso;
  const itemEndsOn = (item, iso) => item.end === iso;
  const itemSpansDays = (item) => item.start !== item.end;

  const todayISO2 = today.toISOString().slice(0,10);

  // build "bar slots" for month view — avoid overlaps within a week row
  // Returns: { [iso]: [ {item, slot, startOfBar, endOfBar, isStart, isEnd, spanCols} ] }
  const buildMonthBars = (cells) => {
    // group cells into weeks
    const weeks = [];
    let week = [];
    cells.forEach((c, i) => {
      week.push(c);
      if (week.length === 7) { weeks.push(week); week = []; }
    });
    if (week.length > 0) {
      while (week.length < 7) week.push(null);
      weeks.push(week);
    }

    const barMap = {}; // iso → [{item, slot, isStart, isEnd, spanCols}]
    const slotTracker = {}; // itemId+weekIdx → slot

    weeks.forEach((weekCells, wi) => {
      // collect all items visible this week
      const weekIsos = weekCells.map(c => c ? c.iso : null).filter(Boolean);
      if (weekIsos.length === 0) return;
      const weekStart = weekIsos[0];
      const weekEnd = weekIsos[weekIsos.length-1];

      const weekItems = activeItems.filter(it =>
        it.start <= weekEnd && it.end >= weekStart
      );

      // assign slots
      const slots = []; // slot index → item ids occupying it
      weekItems.forEach(item => {
        let slot = 0;
        while (slots[slot] && slots[slot].some(iso => {
          const itemInSlot = activeItems.find(it => it.id === slotTracker[it.id+'_'+wi] === slot);
          return false; // simplified: just find first free slot
        })) slot++;
        // find first free slot
        let freeSlot = 0;
        outer: while(true) {
          for (let c = 0; c < weekCells.length; c++) {
            const iso = weekCells[c]?.iso;
            if (!iso) continue;
            if (item.start <= iso && item.end >= iso) {
              if (barMap[iso] && barMap[iso].some(b => b.slot === freeSlot)) {
                freeSlot++;
                continue outer;
              }
            }
          }
          break;
        }

        slotTracker[item.id+'_'+wi] = freeSlot;

        // find start/end cols within this week
        const startInWeek = Math.max(0, weekCells.findIndex(c => c && c.iso === item.start));
        const actualStart = item.start >= weekStart ? startInWeek : 0;
        const endInWeek = weekCells.findIndex(c => c && c.iso === item.end);
        const actualEnd = item.end <= weekEnd ? (endInWeek >= 0 ? endInWeek : weekCells.length-1) : weekCells.length-1;
        const spanCols = actualEnd - actualStart + 1;

        weekCells.forEach((c, ci) => {
          if (!c) return;
          if (item.start <= c.iso && item.end >= c.iso) {
            if (!barMap[c.iso]) barMap[c.iso] = [];
            const isStart = ci === actualStart;
            const isEnd = ci === actualEnd;
            barMap[c.iso].push({
              item, slot: freeSlot,
              isStart,
              isEnd,
              spanCols: isStart ? spanCols : 0,
              isFirstDayOfItem: item.start === c.iso,
            });
          }
        });
      });
    });

    return barMap;
  };

  const cells = buildMonthGrid();
  const weekDays = buildWeekGrid();
  const barMap = buildMonthBars(cells);

  const exportCalPDF = () => {
    window.print();
    setPdfPrinted(true);
  };

  const getDist = () => { try { return JSON.parse(localStorage.getItem('ybp_dist_' + projectId) || '{}'); } catch { return {}; } };

  const CAL_CELL_MIN_H = 110;

  return (
    <div style={{ height:'100%', display:'flex', flexDirection:'column', background:'#f6f7f9', fontFamily:'Assistant,sans-serif', direction:'rtl' }}>

      {/* PDF Send Modal */}
      {showPdfModal && (
        <div className="no-print" style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', zIndex:10000, display:'flex', alignItems:'center', justifyContent:'center' }}
          onClick={e => e.target===e.currentTarget && setShowPdfModal(false)}>
          <div style={{ background:'#fff', borderRadius:14, width:340, direction:'rtl', fontFamily:'Assistant,sans-serif', overflow:'hidden', boxShadow:'0 24px 64px rgba(0,0,0,0.3)' }}>
            <div style={{ background:'var(--ybp-panel)', padding:'14px 18px', display:'flex', alignItems:'center', gap:10 }}>
              <span style={{ flex:1, fontSize:15, fontWeight:700, color:'#fff' }}>📤 שליחת יומן PDF</span>
              <button onClick={() => setShowPdfModal(false)} style={{ background:'transparent', border:'none', color:'var(--ybp-ink-soft)', fontSize:18, cursor:'pointer' }}>✕</button>
            </div>
            <div style={{ padding:18, display:'flex', flexDirection:'column', gap:14 }}>
              <div style={{ background: pdfPrinted ? '#f0fdf4' : '#f8fafc', border:`1px solid ${pdfPrinted?'#86efac':'#e2e8f0'}`, borderRadius:10, padding:14 }}>
                <div style={{ fontSize:12, fontWeight:700, color:'#374151', marginBottom:8, display:'flex', alignItems:'center', gap:6 }}>
                  <span style={{ width:20, height:20, borderRadius:10, background: pdfPrinted?'#16a34a':'var(--ybp-navy)', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:800 }}>{pdfPrinted?'✓':'1'}</span>
                  {pdfPrinted ? 'PDF מוכן ✓' : 'שמור יומן כ-PDF'}
                </div>
                <button onClick={exportCalPDF} style={{ width:'100%', padding:'9px', borderRadius:7, border:'none', background:'var(--ybp-panel)', color:'#fff', fontSize:12, fontWeight:700, cursor:'pointer', fontFamily:'inherit' }}>
                  🖨️ {pdfPrinted ? 'הדפס שוב' : 'הדפס / שמור PDF'}
                </button>
              </div>
              <div style={{ opacity: pdfPrinted?1:0.5, border:'1px solid #e2e8f0', borderRadius:10, padding:14 }}>
                <div style={{ fontSize:12, fontWeight:700, color:'#374151', marginBottom:10, display:'flex', alignItems:'center', gap:6 }}>
                  <span style={{ width:20, height:20, borderRadius:10, background:'var(--ybp-panel)', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:800 }}>2</span>
                  שתף
                </div>
                {(() => { const dist = getDist(); return (
                  <div style={{ fontSize:11, color:'#9ca3af', marginBottom:10 }}>
                    {dist.waPhone || 'לא הוגדר טלפון'} · {dist.emails || 'לא הוגדרו מיילים'}
                  </div>
                ); })()}
                <div style={{ display:'flex', gap:8 }}>
                  <button disabled={!pdfPrinted} onClick={() => {
                    const dist = getDist();
                    const txt = `📅 *יומן פרויקט — ${project?.name||''}*\n\n${MONTH_NAMES_HE[curMonth]} ${curYear}\n\nYBPROJECTS`;
                    const url = dist.waPhone ? `https://wa.me/${dist.waPhone.replace(/\D/,'')}?text=${encodeURIComponent(txt)}` : `https://wa.me/?text=${encodeURIComponent(txt)}`;
                    window.open(url,'_blank');
                  }} style={{ flex:1, padding:'10px', borderRadius:7, border:'none', background: pdfPrinted?'#25d366':'#e5e7eb', color: pdfPrinted?'#fff':'#9ca3af', fontSize:12, fontWeight:700, cursor: pdfPrinted?'pointer':'not-allowed', fontFamily:'inherit' }}>💬 WhatsApp</button>
                  <button disabled={!pdfPrinted} onClick={() => {
                    const dist = getDist();
                    const emails = (dist.emails||'').split(',').map(e=>e.trim()).filter(Boolean);
                    const subject = `יומן פרויקט — ${project?.name||''} — ${MONTH_NAMES_HE[curMonth]} ${curYear}`;
                    const body = `שלום,\n\nמצורף יומן הפרויקט לחודש ${MONTH_NAMES_HE[curMonth]} ${curYear}.\n\nYBPROJECTS`;
                    window.open(buildGmailUrl({to:emails,subject,body}),'_blank');
                  }} style={{ flex:1, padding:'10px', borderRadius:7, border:'none', background: pdfPrinted?'#ea4335':'#e5e7eb', color: pdfPrinted?'#fff':'#9ca3af', fontSize:12, fontWeight:700, cursor: pdfPrinted?'pointer':'not-allowed', fontFamily:'inherit' }}>✉️ Gmail</button>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}

      {/* HEADER */}
      <div className="no-print" style={{ background:'#fff', borderBottom:'1px solid #e5e7eb', padding:'10px 16px', display:'flex', alignItems:'center', gap:10, flexShrink:0, flexWrap:'wrap' }}>
        {/* Prev/Next */}
        <button onClick={viewMode==='month' ? prevMonth : prevWeek}
          style={{ width:34, height:34, borderRadius:8, border:'1px solid #e5e7eb', background:'#fff', cursor:'pointer', fontSize:16, display:'flex', alignItems:'center', justifyContent:'center' }}>‹</button>
        <span style={{ fontSize:16, fontWeight:800, color:'var(--ybp-navy)', minWidth:160, textAlign:'center' }}>
          {viewMode==='month'
            ? `${MONTH_NAMES_HE[curMonth]} ${curYear}`
            : (() => { const e = new Date(curWeekStart); e.setDate(e.getDate()+6); return `${curWeekStart.getDate()}/${curWeekStart.getMonth()+1} — ${e.getDate()}/${e.getMonth()+1}/${e.getFullYear()}`; })()
          }
        </span>
        <button onClick={viewMode==='month' ? nextMonth : nextWeek}
          style={{ width:34, height:34, borderRadius:8, border:'1px solid #e5e7eb', background:'#fff', cursor:'pointer', fontSize:16, display:'flex', alignItems:'center', justifyContent:'center' }}>›</button>
        <button onClick={goToday}
          style={{ padding:'6px 12px', borderRadius:7, border:'1px solid #e5e7eb', background:'#fff', fontSize:12, fontWeight:700, color:'var(--ybp-navy)', cursor:'pointer', fontFamily:'inherit' }}>היום</button>

        <div style={{ flex:1 }}/>

        {/* Month/Week toggle */}
        <div style={{ display:'flex', background:'#f3f4f6', borderRadius:7, padding:2, gap:1 }}>
          {[['month','חודש'],['week','שבוע']].map(([k,l]) => (
            <button key={k} onClick={() => setViewMode(k)} style={{
              padding:'5px 14px', borderRadius:5, border:'none', fontSize:12, fontWeight:700, cursor:'pointer', fontFamily:'inherit',
              background: viewMode===k ? 'var(--ybp-navy)' : 'transparent',
              color: viewMode===k ? '#fff' : '#6b7280',
            }}>{l}</button>
          ))}
        </div>

        <button onClick={() => { setPdfPrinted(false); setShowPdfModal(true); }}
          style={{ padding:'6px 14px', borderRadius:7, border:'none', background:'#b5a882', color:'var(--ybp-navy)', fontSize:12, fontWeight:700, cursor:'pointer', fontFamily:'inherit' }}>📤 שלח PDF</button>
      </div>

      {/* כותרת הדפסה — מוצגת רק ב-print */}
      <div className="print-only-header" style={{ display:'none', padding:'8px 0 12px', borderBottom:'2px solid #1a2c4a', marginBottom:8, direction:'rtl', fontFamily:'Assistant,sans-serif' }}>
        <div style={{ fontSize:18, fontWeight:800, color:'var(--ybp-navy)' }}>📅 יומן פרויקט — {project?.name}</div>
        <div style={{ fontSize:13, color:'#6b7280', marginTop:2 }}>{MONTH_NAMES_HE[curMonth]} {curYear}</div>
      </div>

      {/* DAY HEADERS */}
      <div style={{ display:'grid', gridTemplateColumns:'repeat(7,1fr)', background:'var(--ybp-panel)', flexShrink:0 }}>
        {WEEK_DAYS_HE.map((d,i) => (
          <div key={i} style={{ padding:'7px 0', textAlign:'center', fontSize:11, fontWeight:700, color: i===5||i===6 ? '#b5a882' : 'rgba(255,255,255,0.85)' }}>{d}</div>
        ))}
      </div>

      {/* CALENDAR GRID */}
      <div style={{ flex:1, overflowY:'auto' }}>
        {viewMode === 'month' ? (
          <div className="cal-grid" style={{ display:'grid', gridTemplateColumns:'repeat(7,1fr)', flex:1 }}>
            {cells.map((cell, ci) => {
              if (!cell) return <div key={'e'+ci} style={{ minHeight:CAL_CELL_MIN_H, background:'#f9fafb', borderBottom:'1px solid #e5e7eb', borderLeft:'1px solid #e5e7eb' }}/>;
              const isToday = cell.iso === todayISO2;
              const isWeekend = cell.date.getDay() === 5 || cell.date.getDay() === 6;
              const holiday = HOLIDAYS[cell.iso];
              const dayBars = barMap[cell.iso] || [];
              // sort by slot
              const sorted = [...dayBars].sort((a,b) => a.slot - b.slot);
              // max 3 bars, then "+N more"
              const MAX_BARS = 3;
              const visible = sorted.slice(0, MAX_BARS);
              const hidden = sorted.length - MAX_BARS;

              return (
                <div key={cell.iso} className="cal-cell" style={{
                  minHeight: CAL_CELL_MIN_H, borderBottom:'1px solid #e5e7eb', borderLeft:'1px solid #e5e7eb',
                  background: isToday ? '#fefce8' : isWeekend ? '#f9fafb' : '#fff',
                  padding:'4px 2px 4px', position:'relative', overflow:'hidden',
                }}>
                  {/* Day number */}
                  <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', padding:'0 4px 2px', marginBottom:2 }}>
                    <span style={{
                      width:24, height:24, borderRadius:12, display:'flex', alignItems:'center', justifyContent:'center',
                      fontSize:12, fontWeight: isToday ? 800 : 600,
                      background: isToday ? 'var(--ybp-navy)' : 'transparent',
                      color: isToday ? '#fff' : isWeekend ? '#9ca3af' : '#374151',
                    }}>{cell.day}</span>
                    {holiday && <span style={{ fontSize:9, color:'#6b7280', maxWidth:60, textAlign:'left', lineHeight:1.2, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{holiday}</span>}
                  </div>

                  {/* Bars */}
                  <div className="cal-bars" style={{ display:'flex', flexDirection:'column', gap:1 }}>
                    {visible.map(({ item, slot, isStart, isEnd, isFirstDayOfItem }) => {
                      const color = getAssigneeColor(item.assignee);
                      const isOneDayItem = item.start === item.end;
                      return (
                        <div key={item.id+'_'+slot} onClick={() => onEditItem(item)}
                          style={{
                            height:20, borderRadius: isOneDayItem ? 4 : (isStart ? '4px 0 0 4px' : isEnd ? '0 4px 4px 0' : 0),
                            background: color, color:'#fff',
                            fontSize:10, fontWeight:700,
                            display:'flex', alignItems:'center',
                            paddingRight: isStart||isOneDayItem ? 5 : 2,
                            paddingLeft: isEnd||isOneDayItem ? 5 : 0,
                            cursor:'pointer', overflow:'hidden', whiteSpace:'nowrap',
                            opacity: 0.92,
                          }}>
                          {(isStart || isOneDayItem) && (
                            <span style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', flex:1 }}>
                              {item.assignee ? `👤 ${item.name}` : item.name}
                            </span>
                          )}
                          {!isStart && !isOneDayItem && <span style={{ flex:1 }}/>}
                        </div>
                      );
                    })}
                    {/* hidden items — מוצגים בהדפסה */}
                    {sorted.slice(MAX_BARS).map(({ item, slot, isStart, isEnd }) => {
                      const color = getAssigneeColor(item.assignee);
                      const isOneDayItem = item.start === item.end;
                      return (
                        <div key={item.id+'_h_'+slot} className="cal-bar-hidden" onClick={() => onEditItem(item)}
                          style={{
                            height:20, borderRadius: isOneDayItem ? 4 : (isStart ? '4px 0 0 4px' : isEnd ? '0 4px 4px 0' : 0),
                            background: color, color:'#fff',
                            fontSize:10, fontWeight:700,
                            display:'none', alignItems:'center',
                            paddingRight: isStart||isOneDayItem ? 5 : 2,
                            paddingLeft: isEnd||isOneDayItem ? 5 : 0,
                            overflow:'hidden', whiteSpace:'nowrap', opacity:0.92,
                          }}>
                          {(isStart || isOneDayItem) && (
                            <span style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', flex:1 }}>
                              {item.assignee ? `👤 ${item.name}` : item.name}
                            </span>
                          )}
                          {!isStart && !isOneDayItem && <span style={{ flex:1 }}/>}
                        </div>
                      );
                    })}
                    {hidden > 0 && (
                      <div className="cal-more-btn" onClick={() => setDayPopup({ iso: cell.iso, items: sorted.map(b=>b.item), label: `${cell.day}/${curMonth+1}/${curYear}` })}
                        style={{ fontSize:10, color:'#4A90D9', padding:'0 5px', fontWeight:700, cursor:'pointer', textDecoration:'underline' }}>+{hidden} עוד</div>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        ) : (
          /* WEEK VIEW */
          <div style={{ display:'grid', gridTemplateColumns:'repeat(7,1fr)', height:'100%' }}>
            {weekDays.map((cell) => {
              const isToday = cell.iso === todayISO2;
              const isWeekend = cell.date.getDay() === 5 || cell.date.getDay() === 6;
              const holiday = HOLIDAYS[cell.iso];
              const dayItems = getItemsForDay(cell.iso);

              return (
                <div key={cell.iso} style={{
                  borderLeft:'1px solid #e5e7eb', background: isToday ? '#fefce8' : isWeekend ? '#f9fafb' : '#fff',
                  padding:'8px 6px', display:'flex', flexDirection:'column', gap:4, minHeight:400,
                }}>
                  {/* Day header */}
                  <div style={{ textAlign:'center', marginBottom:6 }}>
                    <div style={{
                      width:32, height:32, borderRadius:16, display:'inline-flex', alignItems:'center', justifyContent:'center',
                      background: isToday ? 'var(--ybp-navy)' : 'transparent',
                      fontSize:14, fontWeight:800, color: isToday ? '#fff' : isWeekend ? '#9ca3af' : '#374151',
                    }}>{cell.day}</div>
                    {holiday && <div style={{ fontSize:9, color:'#6b7280', marginTop:2 }}>{holiday}</div>}
                  </div>

                  {/* Items */}
                  {dayItems.map(item => {
                    const color = getAssigneeColor(item.assignee);
                    const isSpan = item.start !== item.end;
                    return (
                      <div key={item.id} onClick={() => onEditItem(item)}
                        style={{
                          background: color, color:'#fff', borderRadius:6,
                          padding:'5px 8px', fontSize:11, fontWeight:700, cursor:'pointer',
                          borderRight: isSpan ? `3px solid rgba(0,0,0,0.15)` : 'none',
                        }}>
                        <div style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                          {item.name}
                        </div>
                        {item.assignee && <div style={{ fontSize:9, opacity:0.85, marginTop:2 }}>👤 {item.assignee}</div>}
                        <div style={{ fontSize:9, opacity:0.75, marginTop:1 }}>{fmtDateHe(item.start)} — {fmtDateHe(item.end)}</div>
                      </div>
                    );
                  })}
                </div>
              );
            })}
          </div>
        )}
      </div>

      {/* DONE ITEMS SECTION */}
      {items.filter(isDone).length > 0 && (
        <DoneItemsSection items={items.filter(isDone)} onEditItem={onEditItem}/>
      )}

      {/* Day popup — כל המשימות של יום מסוים */}
      {dayPopup && (
        <div className="no-print" style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.5)', zIndex:10001, display:'flex', alignItems:'center', justifyContent:'center' }}
          onClick={e => e.target===e.currentTarget && setDayPopup(null)}>
          <div style={{ background:'#fff', borderRadius:14, width:'min(400px,95vw)', maxHeight:'80vh', overflow:'auto', direction:'rtl', fontFamily:'Assistant,sans-serif', boxShadow:'0 24px 64px rgba(0,0,0,0.3)' }}>
            <div style={{ background:'var(--ybp-panel)', padding:'13px 18px', display:'flex', alignItems:'center', gap:10, borderRadius:'14px 14px 0 0' }}>
              <span style={{ flex:1, fontSize:15, fontWeight:700, color:'#fff' }}>📅 {dayPopup.label} — {dayPopup.items.length} משימות</span>
              <button onClick={() => setDayPopup(null)} style={{ background:'transparent', border:'none', color:'var(--ybp-ink-soft)', fontSize:20, cursor:'pointer', lineHeight:1 }}>✕</button>
            </div>
            <div style={{ padding:14, display:'flex', flexDirection:'column', gap:8 }}>
              {dayPopup.items.map(item => {
                const color = getAssigneeColor(item.assignee);
                return (
                  <div key={item.id} onClick={() => { setDayPopup(null); onEditItem(item); }}
                    style={{ background: color, color:'#fff', borderRadius:8, padding:'10px 14px', cursor:'pointer', display:'flex', flexDirection:'column', gap:3 }}>
                    <div style={{ fontWeight:700, fontSize:13 }}>{item.name}</div>
                    {item.assignee && <div style={{ fontSize:11, opacity:0.85 }}>👤 {item.assignee}</div>}
                    <div style={{ fontSize:11, opacity:0.75 }}>{fmtDateHe(item.start)} — {fmtDateHe(item.end)}</div>
                    {item.stage && <div style={{ fontSize:10, opacity:0.7 }}>שלב: {item.stage}</div>}
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      )}

      {/* Print styles for calendar */}
      <style>{`
        @media print {
          @page { size: A4 landscape; margin: 10mm; }
          body { background:#fff !important; direction:rtl; }
          .no-print { display:none !important; }
          .print-only-header { display:block !important; }
          /* הצג את כל המשימות */
          .cal-more-btn { display:none !important; }
          .cal-cell { overflow:visible !important; min-height:auto !important; height:auto !important; break-inside:avoid; }
          .cal-bars { display:flex !important; flex-direction:column !important; }
          .cal-bar-hidden { display:flex !important; }
          /* grid מלא */
          .cal-grid { width:100% !important; }
        }
      `}</style>
    </div>
  );
};

// ── Done Items Section ────────────────────────────────────────────────────────
const DoneItemsSection = ({ items, onEditItem }) => {
  const [open, setOpen] = useState(false);
  return (
    <div className="no-print" style={{ borderTop:'2px solid #e5e7eb', background:'#f9fafb', flexShrink:0 }}>
      <button onClick={() => setOpen(o => !o)}
        style={{ width:'100%', padding:'10px 16px', background:'transparent', border:'none', cursor:'pointer', display:'flex', alignItems:'center', gap:8, fontFamily:'Assistant,sans-serif', direction:'rtl' }}>
        <span style={{ fontSize:13, fontWeight:700, color:'#374151' }}>✅ הושלמו ({items.length})</span>
        <span style={{ fontSize:11, color:'#9ca3af' }}>משימות שסומנו כהושלמו — אינן מוצגות ביומן</span>
        <span style={{ marginRight:'auto', fontSize:14, color:'#9ca3af' }}>{open ? '▲' : '▼'}</span>
      </button>
      {open && (
        <div style={{ padding:'0 16px 12px', display:'flex', flexWrap:'wrap', gap:6 }}>
          {items.map(item => (
            <div key={item.id} onClick={() => onEditItem(item)}
              style={{ padding:'5px 10px', borderRadius:6, background:'#e5e7eb', fontSize:11, fontWeight:600, color:'#6b7280', cursor:'pointer', display:'flex', alignItems:'center', gap:5, textDecoration:'line-through' }}>
              ✓ {item.name}
              {item.assignee && <span style={{ fontSize:10, color:'#9ca3af' }}>· {item.assignee}</span>}
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

window.GanttScreen = GanttScreen;
})();
