// ═══ PORT ACTIVITIES · Relatório diário de atividades do porto ═════════════
// Portado do IMT "Port Activities Module". O parser do Excel da empresa e os
// exports são port 1:1 — NÃO alterar sem reler a referência.
// E1: o módulo abre na LISTAGEM DE PORT CALLS (home); o detalhe de cada port
// call é a UI clássica de atividades, escopada pelo call (GET/PUT
// /api/portact/calls/:id[/report]). Port call encerrado = read-only (servidor
// bloqueia também). Fotos por atividade com compressão client-side (1600px,
// JPEG 0.8) antes do upload — o link de bordo não aguenta RAW de celular.

const PA_STATUSES = ['TO DO','DOING','DONE','CANCELADO'];
const PA_BADGE = { 'TO DO':{bg:'#eef1f6',fg:'#7a8697',bar:'#C3CDD8'},
                   'DOING':{bg:'#fdf1e0',fg:'#a8681a',bar:'#E8B87A'},
                   'DONE':{bg:'#e6f5ec',fg:'#1e7a45',bar:'#86C79A'},
                   'CANCELADO':{bg:'#fbe9e9',fg:'#b5433f',bar:'#D48A8A'} };
const PA_STATUS_MAP = {'não iniciado':'TO DO','nao iniciado':'TO DO','a iniciar':'TO DO','pendente':'TO DO',
  'em progresso':'DOING','em andamento':'DOING','iniciado':'DOING',
  'completo':'DONE','concluído':'DONE','concluido':'DONE','finalizado':'DONE','cancelado':'CANCELADO'};
const PA_META_DEFAULT = { reportNo:'', dateLabel:'', port:'AÇU PORT - RJ', vessel:'Genesis I' };

// escapa aspas também — vai para value="" de inputs e para o markup do PDF
const _paEsc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, (m) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]));

// ── SheetJS vendored (mesmo padrão do helideck/npt — NUNCA CDN) ──
const _paLoadScript = (src) => new Promise((resolve, reject) => {
  const s = document.createElement('script');
  s.src = src; s.onload = () => resolve(true); s.onerror = () => reject(new Error('load fail: ' + src));
  document.head.appendChild(s);
});
const _paEnsureXLSX = async () => {
  if (window.XLSX) return;
  await _paLoadScript('vendor/xlsx.full.min.js'); // vendored local — sem CDN (a internet de bordo derruba CDN)
};
// logo OSM Thome (base64) usado no cabeçalho do PDF — carregado sob demanda
const _paEnsureAssets = async () => {
  if (window.PA_ASSETS) return;
  await _paLoadScript('tidal/portact-assets.js?v=1');
};

// compressão client-side das fotos (canvas, máx 1600px no maior lado, JPEG 0.8)
// — fotos de celular têm 3–5MB; comprimidas saem ~300KB, viáveis no link de bordo
const _paCompress = (file) => new Promise((resolve, reject) => {
  const url = URL.createObjectURL(file);
  const img = new Image();
  img.onload = () => {
    const MAX = 1600;
    let w = img.naturalWidth || img.width, h = img.naturalHeight || img.height;
    if (!w || !h) { URL.revokeObjectURL(url); reject(new Error('imagem inválida')); return; }
    const k = Math.min(1, MAX / Math.max(w, h));
    w = Math.round(w * k); h = Math.round(h * k);
    const cv = document.createElement('canvas'); cv.width = w; cv.height = h;
    cv.getContext('2d').drawImage(img, 0, 0, w, h);
    URL.revokeObjectURL(url);
    cv.toBlob((b) => b ? resolve(b) : reject(new Error('compress fail')), 'image/jpeg', 0.8);
  };
  img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('imagem inválida')); };
  img.src = url;
});

const paUid = () => 'a' + Math.random().toString(36).slice(2, 9);
// E3: chave de dedupe da importação (seção+atividade+empresa+início, normalizadas)
const paActKey = (a) => [a.section, a.activity, a.company, a.start].map((x) => String(x == null ? '' : x).trim().toLowerCase().replace(/\s+/g, ' ')).join('|');

/* ---------- Excel parsing (port 1:1 do IMT) ---------- */
const paNormHdr = (h) => String(h == null ? '' : h).trim().toLowerCase();
const paFindCol = (map, names) => { for (let i = 0; i < names.length; i++) { if (Object.prototype.hasOwnProperty.call(map, names[i])) return map[names[i]]; } return -1; };
const paToDate = (v) => { if (v == null || v === '') return null; if (typeof v === 'number') return new Date(Math.round((v - 25569) * 86400 * 1000)); if (v instanceof Date) return v; const d = new Date(v); return isNaN(d.getTime()) ? null : d; };
const paPad = (n) => String(n).padStart(2, '0');
const paFmtDT = (d) => { if (!d) return ''; return paPad(d.getUTCDate()) + '/' + paPad(d.getUTCMonth() + 1) + ', ' + paPad(d.getUTCHours()) + ':' + paPad(d.getUTCMinutes()); };
const paFmtDur = (h) => { if (h == null || isNaN(h) || h <= 0) return ''; let m = Math.round(h * 60); const days = Math.floor(m / 1440); m -= days * 1440; const hrs = Math.floor(m / 60), min = m - hrs * 60; const p = []; if (days > 0) p.push(days + 'd'); if (hrs > 0) p.push(hrs + 'h'); if (days === 0 && min > 0) p.push(min + 'min'); return p.join(' ') || '0h'; };
const paDurHours = (cell, start, end) => { if (start && end) { const h = (end - start) / 3600000; if (h > 0) return h; } if (typeof cell === 'number') return cell * 24; const d = paToDate(cell); if (d) { const ep = Date.UTC(1899, 11, 30, 0, 0, 0); const h2 = (d.getTime() - ep) / 3600000; if (h2 > 0 && h2 < 24 * 400) return h2; } return null; };
const paMapStatus = (s, prog) => { const k = paNormHdr(s); if (PA_STATUS_MAP[k]) return PA_STATUS_MAP[k]; if (prog >= 100) return 'DONE'; if (prog > 0) return 'DOING'; return 'TO DO'; };

const paParseWorkbook = (wb) => {
  const out = { activities: [], sections: [], meta: {}, warnings: [] };
  const names = wb.SheetNames || [];
  let pick = null, hdrIdx = -1, colmap = null, rows = null;
  for (let s = 0; s < names.length; s++) {
    const rr = XLSX.utils.sheet_to_json(wb.Sheets[names[s]], { header: 1, raw: true, defval: null, blankrows: false });
    for (let r = 0; r < Math.min(rr.length, 15); r++) {
      const line = rr[r] || [], m = {};
      for (let c = 0; c < line.length; c++) m[paNormHdr(line[c])] = c;
      if (paFindCol(m, ['atividade']) >= 0 && paFindCol(m, ['empresa']) >= 0) { pick = names[s]; hdrIdx = r; colmap = m; rows = rr; break; }
    }
    if (pick) break;
  }
  if (!pick) { out.warnings.push('Nenhum cabeçalho com Empresa/Atividade encontrado.'); return out; }
  for (let r0 = 0; r0 < hdrIdx; r0++) {
    const l = rows[r0] || [];
    for (let c0 = 0; c0 < l.length; c0++) {
      const t = String(l[c0] == null ? '' : l[c0]);
      if (/Embarca/i.test(t)) out.meta.vessel = (t.replace(/.*:\s*/, '').trim()) || 'Genesis I';
      if (/Início/i.test(t) && l[c0 + 1] != null) { const d = paToDate(l[c0 + 1]); if (d) out.meta.pocStart = paFmtDT(d); }
      if (/Término/i.test(t) && l[c0 + 1] != null) { const d2 = paToDate(l[c0 + 1]); if (d2) out.meta.pocEnd = paFmtDT(d2); }
    }
  }
  const cComp = paFindCol(colmap, ['empresa']), cAct = paFindCol(colmap, ['atividade']),
    cIni = paFindCol(colmap, ['data início','data inicio','início','inicio']),
    cFim = paFindCol(colmap, ['data final','data fim','término','termino','fim']),
    cDur = paFindCol(colmap, ['duração','duracao']), cPro = paFindCol(colmap, ['progresso','progress']), cSt = paFindCol(colmap, ['status']);
  let topBand = 'B — PORT';
  for (let rb = 0; rb < hdrIdx; rb++) { const lb = rows[rb] || []; for (let cb = 0; cb < lb.length; cb++) { if (/^B\s*[-–—]\s*Port$/i.test(String(lb[cb] == null ? '' : lb[cb]).trim())) topBand = 'B — PORT'; } }
  let cur = topBand;
  const seen = [];
  for (let r = hdrIdx + 1; r < rows.length; r++) {
    const row = rows[r] || [];
    let company = cComp >= 0 ? row[cComp] : null, activity = cAct >= 0 ? row[cAct] : null;
    const start = cIni >= 0 ? paToDate(row[cIni]) : null, end = cFim >= 0 ? paToDate(row[cFim]) : null;
    if (company != null) company = String(company).replace(/\s+$/, '').trim();
    if (activity != null) activity = String(activity).trim();
    if ((!company || company === '') && (!activity || activity === '') && !start && !end) continue;
    if (activity && !company && !start && !end) { cur = activity; if (seen.indexOf(cur) === -1) seen.push(cur); continue; }
    if (!activity && !company) continue;
    const progRaw = cPro >= 0 ? row[cPro] : null;
    let progress = (progRaw == null || progRaw === '') ? 0 : Math.round((+progRaw <= 1 ? +progRaw * 100 : +progRaw));
    if (isNaN(progress)) progress = 0; progress = Math.max(0, Math.min(100, progress));
    const durH = paDurHours(cDur >= 0 ? row[cDur] : null, start, end);
    const status = paMapStatus(cSt >= 0 ? row[cSt] : null, progress);
    if (seen.indexOf(cur) === -1) seen.push(cur);
    out.activities.push({ id: paUid(),
      section: cur, company: company || '', activity: activity || '', start: start ? paFmtDT(start) : '', end: end ? paFmtDT(end) : '',
      duration: paFmtDur(durH), progress: progress, status: status });
  }
  out.sections = seen;
  return out;
};

/* ---------- Atividades ordenadas por seção (compartilhado pelos exports) ---------- */
const paOrdered = (st) => {
  const ordered = [], secs = st.sections.slice();
  st.activities.forEach((a) => { if (secs.indexOf(a.section) === -1) secs.push(a.section); });
  secs.forEach((sec) => { st.activities.forEach((a) => { if (a.section === sec) ordered.push(a); }); });
  return ordered;
};

/* ---------- EXPORT: Excel (Resumo + Atividades) — port 1:1 ---------- */
const _paExportExcel = async (st, toast, lang) => {
  try { await _paEnsureXLSX(); }
  catch (e) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'SheetJS não carregou' : 'SheetJS failed to load') }); return; }
  const meta = st.meta || {}, ord = paOrdered(st);
  if (!ord.length) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Nenhuma atividade para exportar' : 'No activities to export') }); return; }
  // Sheet 1: activities
  const rows = [['Seção','Atividade','Empresa / Resp.','Início','Término','Duração','Progresso (%)','Status']];
  ord.forEach((a) => { rows.push([a.section || '', a.activity || '', a.company || '', a.start || '', a.end || '', a.duration || '', (a.progress != null ? +a.progress : 0), a.status || '']); });
  const ws1 = XLSX.utils.aoa_to_sheet(rows);
  ws1['!cols'] = [{wch:16},{wch:46},{wch:22},{wch:14},{wch:14},{wch:10},{wch:12},{wch:12}];
  // Sheet 2: summary by status + by section
  const dist = {'TO DO':0,'DOING':0,'DONE':0,'CANCELADO':0};
  ord.forEach((a) => { dist[a.status] = (dist[a.status] || 0) + 1; });
  const sum = [['Relatório Diário de Atividades — MPSV Genesis I'],
    ['Nº do relatório', meta.reportNo || ''], ['Data', meta.dateLabel || ''], ['Porto', meta.port || ''], ['Embarcação', meta.vessel || 'Genesis I'], [],
    ['Total de atividades', ord.length],
    ['DONE (concluídas)', dist['DONE']],
    ['DOING (em andamento)', dist['DOING']],
    ['TO DO (a iniciar)', dist['TO DO']],
    ['CANCELADO', dist['CANCELADO']], [],
    ['Atividades por seção']];
  const bySec = {}; ord.forEach((a) => { bySec[a.section] = (bySec[a.section] || 0) + 1; });
  st.sections.forEach((s) => { if (bySec[s] != null) sum.push([s, bySec[s]]); });
  Object.keys(bySec).forEach((s) => { if (st.sections.indexOf(s) === -1) sum.push([s, bySec[s]]); });
  const ws2 = XLSX.utils.aoa_to_sheet(sum);
  ws2['!cols'] = [{wch:30},{wch:20}];
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws2, 'Resumo');
  XLSX.utils.book_append_sheet(wb, ws1, 'Atividades');
  const stamp = (meta.reportNo ? ('_' + String(meta.reportNo).replace(/[^\w-]/g, '')) : '');
  XLSX.writeFile(wb, 'Port_Activities_Genesis' + stamp + '.xlsx');
  toast.push({ icon: 'check', title: (lang === 'pt' ? 'Excel do Port Activities exportado' : 'Port Activities Excel exported') });
};

/* ---------- EXPORT: PDF (A4 landscape, print) — markup portado 1:1 ---------- */
const _paExportPDF = async (st, toast, lang) => {
  const meta = st.meta || {}, ord = paOrdered(st);
  if (!ord.length) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Nenhuma atividade para exportar' : 'No activities to export') }); return; }
  try { await _paEnsureAssets(); } catch (e) { /* sem logo, segue sem a imagem */ }
  const logo = (window.PA_ASSETS && window.PA_ASSETS.logo) || '';
  const PA_BADGE_PDF = {'TO DO':{bg:'#C3CDD8',fg:'#fff'},'DOING':{bg:'#E8B87A',fg:'#fff'},'DONE':{bg:'#86C79A',fg:'#fff'},'CANCELADO':{bg:'#D48A8A',fg:'#fff'}};
  const PA_BAR = {'TO DO':'#C3CDD8','DOING':'#E8B87A','DONE':'#86C79A','CANCELADO':'#D48A8A'};
  // group in section order
  const secs = [], groups = {};
  ord.forEach((a) => { if (!groups[a.section]) { groups[a.section] = []; secs.push(a.section); } groups[a.section].push(a); });
  let bodyRows = '';
  secs.forEach((sec) => {
    bodyRows += '<tr><td colspan="7" class="sec">' + _paEsc(sec) + '</td></tr>';
    groups[sec].forEach((a) => {
      const b = PA_BADGE_PDF[a.status] || PA_BADGE_PDF['TO DO'];
      const pct = Math.max(0, Math.min(100, +a.progress || 0));
      const barClr = PA_BAR[a.status] || '#C3CDD8';
      bodyRows += '<tr>'
        + '<td class="act">' + _paEsc(a.activity || '') + '</td>'
        + '<td class="comp">' + _paEsc(a.company || '') + '</td>'
        + '<td class="ctr">' + _paEsc(a.start || '—') + '</td>'
        + '<td class="ctr">' + _paEsc(a.end || '—') + '</td>'
        + '<td class="ctr">' + _paEsc(a.duration || '—') + '</td>'
        + '<td class="prog"><div class="track"><div class="fill" style="width:' + pct + '%;background:' + barClr + '"></div></div><span class="pct">' + pct + '%</span></td>'
        + '<td class="ctr"><span class="badge" style="background:' + b.bg + ';color:' + b.fg + '">' + _paEsc(a.status || '') + '</span></td>'
        + '</tr>';
    });
  });
  const dist = {'TO DO':0,'DOING':0,'DONE':0,'CANCELADO':0};
  ord.forEach((a) => { dist[a.status] = (dist[a.status] || 0) + 1; });
  const dt = new Date().toLocaleString('pt-BR');
  const w = window.open('', '_blank');
  if (!w) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Libere os pop-ups' : 'Allow pop-ups'), body: (lang === 'pt' ? 'O relatório abre numa aba nova' : 'The report opens in a new tab') }); return; }
  w.document.write('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Relatório Diário — Genesis I</title><style>'
   + '@page{size:A4 landscape;margin:8mm 10mm;}'
   + '*{box-sizing:border-box;margin:0;padding:0;}'
   + 'body{font-family:Arial,Helvetica,sans-serif;color:#2a2a33;font-size:9px;}'
   + '.hdr{display:flex;align-items:center;justify-content:space-between;border-bottom:2.5px solid #2C2849;padding-bottom:6px;margin-bottom:8px;}'
   + '.hdr img{height:34px;vertical-align:middle;margin-right:8px;}'
   + '.t1{font-size:15px;font-weight:800;color:#2C2849;}'
   + '.t1 .num{color:#c0392b;}'
   + '.t2{font-size:9px;color:#6a8090;margin-top:1px;}'
   + '.meta{text-align:right;font-size:9px;color:#444;line-height:1.5;}'
   + '.meta .big{font-size:13px;font-weight:800;color:#c0392b;}'
   + '.chips{display:flex;gap:6px;margin:6px 0 8px;flex-wrap:wrap;}'
   + '.chip{font-size:9px;font-weight:700;padding:3px 9px;border-radius:10px;color:#fff;}'
   + 'table{width:100%;border-collapse:collapse;}'
   + 'th{background:#F0F5FA;color:#5A6473;font-size:8px;text-transform:uppercase;letter-spacing:.5px;text-align:left;padding:5px 6px;border-bottom:1px solid #d8e0ea;}'
   + 'td{padding:4px 6px;border-bottom:1px solid #eef1f6;font-size:9px;vertical-align:middle;}'
   + 'td.sec{background:#eaf6fc;color:#0e8fc0;font-weight:800;font-size:10px;letter-spacing:1px;text-transform:uppercase;padding:5px 8px;}'
   + 'td.comp{color:#5A93C9;font-weight:700;}'
   + 'td.ctr{color:#6a7480;}'
   + 'td.prog{white-space:nowrap;}'
   + '.track{display:inline-block;width:78px;height:7px;background:#E9EEF3;border-radius:4px;overflow:hidden;vertical-align:middle;}'
   + '.fill{height:100%;border-radius:4px;}'
   + '.pct{font-size:8px;color:#6a7480;margin-left:5px;vertical-align:middle;}'
   + '.badge{display:inline-block;font-size:8px;font-weight:700;padding:2px 8px;border-radius:9px;}'
   + '.foot{margin-top:10px;padding-top:5px;border-top:1px solid #d8e0ea;display:flex;justify-content:space-between;font-size:8px;color:#8593a8;}'
   + '</style></head><body>'
   + '<div class="hdr">'
   + '<div style="display:flex;align-items:center">' + (logo ? '<img src="' + logo + '">' : '') + '<div><div class="t1">Relatório Diário <span class="num">#' + _paEsc(meta.reportNo || '—') + '</span></div><div class="t2">MPSV Genesis I · ' + _paEsc(meta.vessel || 'Genesis I') + ' · Port Activities</div></div></div>'
   + '<div class="meta"><div class="big">' + _paEsc(meta.dateLabel || '') + '</div>' + _paEsc(meta.port || '') + '</div>'
   + '</div>'
   + '<div class="chips">'
   + '<span class="chip" style="background:#86C79A">DONE ' + dist['DONE'] + '</span>'
   + '<span class="chip" style="background:#E8B87A">DOING ' + dist['DOING'] + '</span>'
   + '<span class="chip" style="background:#C3CDD8;color:#4a5560">TO DO ' + dist['TO DO'] + '</span>'
   + (dist['CANCELADO'] ? '<span class="chip" style="background:#D48A8A">CANCELADO ' + dist['CANCELADO'] + '</span>' : '')
   + '<span class="chip" style="background:#2C2849">TOTAL ' + ord.length + '</span>'
   + '</div>'
   + '<table><thead><tr><th style="width:30%">Atividade</th><th style="width:14%">Empresa / Resp.</th><th style="width:11%">Início</th><th style="width:11%">Término</th><th style="width:8%">Duração</th><th style="width:14%">Progresso</th><th style="width:10%">Status</th></tr></thead>'
   + '<tbody>' + bodyRows + '</tbody></table>'
   + '<div class="foot"><span>MPSV Genesis I · Relatório Diário de Atividades do Porto</span><span>Gerado: ' + dt + '</span></div>'
   + '<scr' + 'ipt>window.onload=function(){setTimeout(function(){window.print();},350);}</scr' + 'ipt>'
   + '</body></html>');
  w.document.close();
};

/* ════════════════════════════════════════════════════════════════════════
   RELATÓRIOS DPR-STYLE — diário emitido (snapshot) + geral do port call
   HTML autossuficiente aberto em janela de impressão (padrão do logbook DPR)
   ════════════════════════════════════════════════════════════════════════ */
const _paRepCss = (orient) => `
  *{box-sizing:border-box;margin:0;padding:0}
  @page{size:A4${orient === 'landscape' ? ' landscape' : ''};margin:10mm 12mm}
  body{font-family:'Inter',-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;color:#16232B;background:#fff;font-size:12px;line-height:1.5}
  .mono{font-family:'JetBrains Mono',ui-monospace,Menlo,monospace}
  .dhead{border-bottom:3px solid #16324A;padding-bottom:14px;margin-bottom:16px;display:flex;justify-content:space-between;align-items:flex-end;gap:20px}
  .dhead h1{font-size:19px;letter-spacing:-0.3px;color:#16324A}
  .dhead .sub{font-family:'JetBrains Mono',monospace;font-size:9.5px;letter-spacing:2px;text-transform:uppercase;color:#5E6E7A;margin-top:3px}
  .dhead .num{font-family:'JetBrains Mono',monospace;font-size:11px;color:#16324A;text-align:right;white-space:nowrap}
  .dmeta{display:grid;grid-template-columns:repeat(4,1fr);border:1px solid #D4DCE2;border-radius:6px;overflow:hidden;margin-bottom:16px}
  .dmeta>div{padding:8px 12px;border-right:1px solid #E4EAEE}
  .dmeta>div:last-child{border-right:none}
  .dmeta span{display:block;font-family:'JetBrains Mono',monospace;font-size:8.5px;letter-spacing:1.5px;text-transform:uppercase;color:#7C8A94}
  .dmeta b{font-size:12.5px;font-weight:600;color:#16232B}
  h2.dsec{font-size:11px;font-family:'JetBrains Mono',monospace;letter-spacing:2px;text-transform:uppercase;color:#16324A;border-bottom:1px solid #D4DCE2;padding-bottom:5px;margin:20px 0 10px}
  .dstrip{display:grid;gap:10px;margin:14px 0}
  .dstrip>div{border:1px solid #D4DCE2;border-radius:6px;padding:10px 12px;text-align:center}
  .dstrip b{display:block;font-size:20px;font-weight:700;color:#16324A;font-family:'JetBrains Mono',monospace}
  .dstrip span{font-family:'JetBrains Mono',monospace;font-size:8.5px;letter-spacing:1.2px;text-transform:uppercase;color:#7C8A94}
  table.dt{width:100%;border-collapse:collapse;font-size:11px}
  table.dt th{font-family:'JetBrains Mono',monospace;font-size:8.5px;letter-spacing:1.2px;text-transform:uppercase;color:#5E6E7A;text-align:left;padding:6px 8px;border-bottom:1.5px solid #16324A;background:#F4F7F9}
  table.dt td{padding:6px 8px;border-bottom:1px solid #E9EEF1;vertical-align:top}
  table.dt tr:nth-child(even) td{background:#FAFBFC}
  table.dt tr.secb td{background:#EAF1F6;font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:1.5px;text-transform:uppercase;color:#16324A;font-weight:700}
  .dchip{display:inline-block;font-family:'JetBrains Mono',monospace;font-size:8px;font-weight:700;letter-spacing:1px;padding:2px 8px;border-radius:8px;text-transform:uppercase;vertical-align:1px}
  .pact{font-size:12.5px;font-weight:700;color:#16232B;margin:16px 0 2px}
  .pcomp{font-family:'JetBrains Mono',monospace;font-size:9.5px;color:#5A93C9;margin-bottom:6px}
  .gcards{display:grid;grid-template-columns:repeat(${orient === 'landscape' ? 3 : 2},1fr);gap:12px}
  .gcard{border:1px solid #D4DCE2;border-radius:6px;overflow:hidden;page-break-inside:avoid}
  .gcard img{width:100%;height:${orient === 'landscape' ? 170 : 190}px;object-fit:cover;display:block}
  .gcard .cap{padding:6px 9px;font-size:10.5px;color:#3A4650;min-height:14px}
  .gcard .who{font-family:'JetBrains Mono',monospace;font-size:8.5px;color:#7C8A94;padding:0 9px 7px;text-transform:uppercase;letter-spacing:1px}
  .dsign{display:grid;grid-template-columns:1fr 1fr;gap:34px;margin-top:38px}
  .dsign>div{border-top:1px solid #16232B;padding-top:6px;font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:1px;text-transform:uppercase;color:#5E6E7A;text-align:center}
  .dfoot{margin-top:26px;padding-top:10px;border-top:1px solid #D4DCE2;display:flex;justify-content:space-between;font-family:'JetBrains Mono',monospace;font-size:8.5px;color:#7C8A94}
  .cover{background:linear-gradient(135deg,#16324A,#1F2A2E);color:#fff;padding:70px 54px;min-height:880px;page-break-after:always;display:flex;flex-direction:column}
  .cover .brand{font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:3px;text-transform:uppercase;color:#C9B79C}
  .cover h1{font-size:40px;font-weight:700;letter-spacing:-0.5px;margin:14px 0 6px}
  .cover .csub{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:2px;text-transform:uppercase;color:#9FB4C4}
  .cover .cmeta{margin-top:auto;display:grid;grid-template-columns:1fr 1fr;gap:16px 30px;border-top:1px solid rgba(255,255,255,0.25);padding-top:20px}
  .cover .cmeta span{display:block;font-family:'JetBrains Mono',monospace;font-size:8.5px;letter-spacing:1.5px;text-transform:uppercase;color:#9FB4C4}
  .cover .cmeta b{font-size:14px;font-weight:600;color:#fff}
  @media print {
    *{-webkit-print-color-adjust:exact;print-color-adjust:exact}
    h2.dsec{page-break-after:avoid}
    .pact{page-break-after:avoid}
    table.dt{page-break-inside:auto}
    tr{page-break-inside:avoid}
  }
`;
const _paRepChip = (stt) => {
  const map = { 'DONE': ['#86C79A', '#123B22'], 'DOING': ['#E8B87A', '#5C3A0E'], 'TO DO': ['#C3CDD8', '#3A4650'], 'CANCELADO': ['#D48A8A', '#5C1F1F'] };
  const c = map[stt] || map['TO DO'];
  return '<span class="dchip" style="background:' + c[0] + ';color:' + c[1] + '">' + _paEsc(stt || 'TO DO') + '</span>';
};
// tabela única com linha de seção (ordem: sections ∪ activities.section)
const _paRepTable = (sections, activities, lang) => {
  const pt = lang === 'pt';
  const secs = (sections || []).slice();
  (activities || []).forEach((a) => { if (secs.indexOf(a.section) === -1) secs.push(a.section); });
  let rows = '';
  secs.forEach((sec) => {
    const acts = (activities || []).filter((a) => a.section === sec);
    if (!acts.length) return;
    rows += '<tr class="secb"><td colspan="7">' + _paEsc(sec) + '</td></tr>';
    acts.forEach((a) => {
      rows += '<tr>'
        + '<td>' + _paEsc(a.activity || '') + '</td>'
        + '<td style="color:#5A93C9;font-weight:600">' + _paEsc(a.company || '—') + '</td>'
        + '<td class="mono" style="font-size:10px">' + _paEsc(a.start || '—') + '</td>'
        + '<td class="mono" style="font-size:10px">' + _paEsc(a.end || '—') + '</td>'
        + '<td class="mono" style="font-size:10px">' + _paEsc(a.duration || '—') + '</td>'
        + '<td class="mono" style="font-size:10px">' + (a.progress != null ? a.progress : 0) + '%</td>'
        + '<td>' + _paRepChip(a.status) + '</td>'
        + '</tr>';
    });
  });
  if (!rows) return '<div style="font-size:11.5px;color:#5E6E7A;padding:6px 2px">' + (pt ? 'Nenhuma atividade registrada.' : 'No activities recorded.') + '</div>';
  return '<table class="dt"><thead><tr><th>' + (pt ? 'Atividade' : 'Activity') + '</th><th>' + (pt ? 'Empresa' : 'Company') + '</th><th>' + (pt ? 'Início' : 'Start') + '</th><th>' + (pt ? 'Término' : 'End') + '</th><th>' + (pt ? 'Duração' : 'Duration') + '</th><th>%</th><th>Status</th></tr></thead><tbody>' + rows + '</tbody></table>';
};
// grupos de fotos por atividade (na ordem das atividades); órfãs no final
const _paRepGroups = (activities, photos, lang) => {
  const groups = [];
  const used = {};
  (activities || []).forEach((a) => {
    const ps = (photos || []).filter((p) => a.dbId && p.activityId === a.dbId);
    if (!ps.length) return;
    ps.forEach((p) => { used[p.id] = 1; });
    groups.push({ title: a.activity || '', company: a.company || '', status: a.status, photos: ps });
  });
  const orfas = (photos || []).filter((p) => !used[p.id]);
  if (orfas.length) groups.push({ title: (lang === 'pt' ? 'Sem atividade vinculada' : 'Not linked to an activity'), company: '', status: null, photos: orfas });
  return groups;
};
const _paRepPhotos = (groups, lang) => {
  if (!groups || !groups.length) return '';
  return groups.map((g) => {
    const cards = g.photos.map((p) => '<div class="gcard"><img src="' + _paEsc(p.url) + '" alt=""/>'
      + '<div class="cap">' + _paEsc(p.caption || '') + '</div>'
      + '<div class="who">' + _paEsc(p.uploadedBy || '') + '</div></div>').join('');
    return '<div class="pact">' + _paEsc(g.title) + '</div>'
      + '<div class="pcomp">' + _paEsc(g.company || '') + (g.status ? ' ' + _paRepChip(g.status) : '') + '</div>'
      + '<div class="gcards">' + cards + '</div>';
  }).join('');
};
// diário: renderiza SEMPRE do snapshot congelado (read-only por construção)
const _paRepDailyHtml = (snap, lang, orient) => {
  const pt = lang === 'pt';
  const loc = pt ? 'pt-BR' : 'en-US';
  const dataLonga = new Date(snap.data + 'T12:00:00').toLocaleDateString(loc, { weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' });
  const acts = snap.activities || [];
  const cnt = (s) => acts.filter((a) => a.status === s).length;
  const diaFotos = (snap.photos || []).filter((p) => String(p.createdAt || '').slice(0, 10) === snap.data);
  const empresas = [];
  acts.forEach((a) => { const c = (a.company || '').trim(); if (c && empresas.indexOf(c) === -1) empresas.push(c); });
  const kpis = [[cnt('DONE'), 'DONE'], [cnt('DOING'), 'DOING'], [cnt('TO DO'), 'TO DO']];
  if (cnt('CANCELADO')) kpis.push([cnt('CANCELADO'), pt ? 'CANCELADAS' : 'CANCELLED']);
  kpis.push([diaFotos.length, pt ? 'Fotos do dia' : 'Photos of the day']);
  kpis.push([empresas.length, pt ? 'Empresas' : 'Companies']);
  const strip = '<div class="dstrip" style="grid-template-columns:repeat(' + kpis.length + ',1fr)">'
    + kpis.map((k) => '<div><b>' + k[0] + '</b><span>' + k[1] + '</span></div>').join('') + '</div>';
  const groups = _paRepGroups(acts, diaFotos, lang);
  const vessel = (snap.call && snap.call.vessel) || 'MPSV Genesis I';
  return '<!doctype html><html><head><meta charset="utf-8"><title>' + _paEsc(snap.num) + '</title><style>' + _paRepCss(orient) + '</style></head><body>'
    + '<div class="dhead"><div><h1>' + (pt ? 'Port Call Report — Diário' : 'Daily Port Call Report') + '</h1>'
    + '<div class="sub">' + _paEsc(vessel) + ' · Port Activities</div></div>'
    + '<div class="num">' + _paEsc(snap.num) + '<br>' + _paEsc(dataLonga) + '</div></div>'
    + '<div class="dmeta">'
    + '<div><span>Vessel</span><b>' + _paEsc(vessel) + '</b></div>'
    + '<div><span>' + (pt ? 'Porto' : 'Port') + '</span><b>' + _paEsc((snap.call && snap.call.port) || (snap.meta && snap.meta.port) || '—') + '</b></div>'
    + '<div><span>' + (pt ? 'Berço' : 'Berth') + '</span><b>' + _paEsc((snap.call && snap.call.berth) || '—') + '</b></div>'
    + '<div><span>' + (pt ? 'Cliente' : 'Client') + '</span><b>' + _paEsc((snap.call && snap.call.client) || '—') + '</b></div>'
    + '</div>'
    + strip
    + '<h2 class="dsec">' + (pt ? '1 · Atividades' : '1 · Activities') + '</h2>'
    + _paRepTable(snap.sections, acts, lang)
    + (groups.length ? '<h2 class="dsec">' + (pt ? '2 · Registro fotográfico do dia' : '2 · Photographic record of the day') + '</h2>' + _paRepPhotos(groups, lang) : '')
    + '<div class="dsign"><div>' + (pt ? 'OIM / Capitão' : 'OIM / Master') + '</div><div>' + (pt ? 'Representante do cliente' : 'Client Representative') + '</div></div>'
    + '<div class="dfoot"><span>' + _paEsc(vessel) + ' · ' + _paEsc(snap.num) + '</span><span>' + (pt ? 'gerado por ' : 'generated by ') + _paEsc(snap.geradoPor || 'Portal Genesis') + (snap.geradoEm ? ' · ' + _paEsc(new Date(snap.geradoEm).toLocaleString(loc)) : '') + '</span></div>'
    + '</body></html>';
};
// geral: renderiza do estado atual (capa + tudo) — disponível em qualquer status
const _paRepGeneralHtml = (call, st, photos, dailyCount, lang, orient) => {
  const pt = lang === 'pt';
  const loc = pt ? 'pt-BR' : 'en-US';
  const acts = st.activities || [];
  const total = acts.length;
  const done = acts.filter((a) => a.status === 'DONE').length;
  const pct = total ? Math.round(done / total * 100) : 0;
  const ini = call.startedAt ? new Date(call.startedAt) : null;
  const fim = call.closedAt ? new Date(call.closedAt) : new Date();
  const dias = ini ? Math.max(1, Math.round((fim - ini) / 86400000) + 1) : 1;
  const empresas = [];
  acts.forEach((a) => { const c = (a.company || '').trim(); if (c && empresas.indexOf(c) === -1) empresas.push(c); });
  const periodo = (ini ? ini.toLocaleDateString(loc) : '—') + ' → ' + (call.closedAt ? fim.toLocaleDateString(loc) : (pt ? 'em aberto' : 'open'));
  const vessel = call.vessel || 'MPSV Genesis I';
  const cover = '<div class="cover">'
    + '<div class="brand">PORT CALL REPORT</div>'
    + '<h1>' + _paEsc(vessel) + '</h1>'
    + '<div class="csub">' + _paEsc(call.titulo || call.port || '') + '</div>'
    + '<div class="cmeta">'
    + '<div><span>' + (pt ? 'Porto' : 'Port') + '</span><b>' + _paEsc(call.port || '—') + '</b></div>'
    + '<div><span>' + (pt ? 'Berço' : 'Berth') + '</span><b>' + _paEsc(call.berth || '—') + '</b></div>'
    + '<div><span>' + (pt ? 'Cliente' : 'Client') + '</span><b>' + _paEsc(call.client || '—') + '</b></div>'
    + '<div><span>' + (pt ? 'Período' : 'Period') + '</span><b>' + _paEsc(periodo) + '</b></div>'
    + '<div><span>' + (pt ? 'Relatórios diários emitidos' : 'Daily reports issued') + '</span><b>' + dailyCount + '</b></div>'
    + '<div><span>Status</span><b>' + (call.status === 'open' ? (pt ? 'Em aberto' : 'Open') : (pt ? 'Encerrado' : 'Closed')) + '</b></div>'
    + '</div></div>';
  const kpis = [[total, pt ? 'Atividades' : 'Activities'], [pct + '%', pt ? 'Concluído' : 'Complete'], [dias, pt ? 'Dias de porto' : 'Days in port'],
    [(photos || []).length, pt ? 'Fotos' : 'Photos'], [empresas.length, pt ? 'Empresas' : 'Companies'], [dailyCount, pt ? 'Diários emitidos' : 'Daily reports']];
  const strip = '<div class="dstrip" style="grid-template-columns:repeat(3,1fr)">'
    + kpis.map((k) => '<div><b>' + k[0] + '</b><span>' + k[1] + '</span></div>').join('') + '</div>';
  const groups = _paRepGroups(acts, photos, lang);
  return '<!doctype html><html><head><meta charset="utf-8"><title>Port Call Report — ' + _paEsc(call.titulo || call.port || ('#' + call.id)) + '</title><style>' + _paRepCss(orient) + '</style></head><body>'
    + cover
    + '<div class="dhead"><div><h1>' + (pt ? 'Port Call Report — Geral' : 'General Port Call Report') + '</h1>'
    + '<div class="sub">' + _paEsc(vessel) + ' · Port Activities</div></div>'
    + '<div class="num">' + _paEsc(call.titulo || call.port || '') + '<br>' + _paEsc(periodo) + '</div></div>'
    + '<div class="dmeta">'
    + '<div><span>Vessel</span><b>' + _paEsc(vessel) + '</b></div>'
    + '<div><span>' + (pt ? 'Porto' : 'Port') + '</span><b>' + _paEsc(call.port || '—') + '</b></div>'
    + '<div><span>' + (pt ? 'Berço' : 'Berth') + '</span><b>' + _paEsc(call.berth || '—') + '</b></div>'
    + '<div><span>' + (pt ? 'Cliente' : 'Client') + '</span><b>' + _paEsc(call.client || '—') + '</b></div>'
    + '</div>'
    + strip
    + '<h2 class="dsec">' + (pt ? '1 · Atividades do port call' : '1 · Port call activities') + '</h2>'
    + _paRepTable(st.sections, acts, lang)
    + (groups.length ? '<h2 class="dsec">' + (pt ? '2 · Registro fotográfico' : '2 · Photographic record') + '</h2>' + _paRepPhotos(groups, lang) : '')
    + '<div class="dsign"><div>' + (pt ? 'OIM / Capitão' : 'OIM / Master') + '</div><div>' + (pt ? 'Representante do cliente' : 'Client Representative') + '</div></div>'
    + '<div class="dfoot"><span>' + _paEsc(vessel) + ' · Port Call Report</span><span>' + (pt ? 'gerado pelo Portal Genesis · ' : 'generated by Portal Genesis · ') + _paEsc(new Date().toLocaleString(loc)) + '</span></div>'
    + '</body></html>';
};
// abre o HTML numa janela de impressão (padrão logbook.jsx)
const _paOpenHtml = (html, toast, lang) => {
  const win = window.open('', '_blank');
  if (!win) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Libere os pop-ups' : 'Allow pop-ups'), body: (lang === 'pt' ? 'O relatório abre numa aba nova.' : 'The report opens in a new tab.') }); return; }
  win.document.write(html); win.document.close(); win.focus();
  setTimeout(() => { try { win.print(); } catch (e) {} }, 800);
};

/* ════════════════════════════════════════════════════════════════════════
   COMPONENTE REACT (padrão Tidal)
   home = lista de port calls · detail = atividades do port call + fotos
   ════════════════════════════════════════════════════════════════════════ */
const PortAct = ({state, setState, setPage}) => {
  const _tt = useT();
  const lang = _tt.lang;
  const toast = useToast();
  const isMobile = useMobile();
  const token = () => localStorage.getItem('genesis_token');
  const me = state.currentUser || {};
  const canManage = me.role === 'admin' || me.is_staff === true || (me.permissoes || []).indexOf('portact.gerir') >= 0;

  const [view, setView] = React.useState('home');      // 'home' | 'detail'
  const [calls, setCalls] = React.useState(null);      // null = carregando
  const [busca, setBusca] = React.useState('');
  const [sort, setSort] = React.useState({ key: null, dir: 1 });
  const [newCall, setNewCall] = React.useState(null);  // objeto = modal aberto
  const [call, setCall] = React.useState(null);        // port call aberto no detalhe
  const [st, setSt] = React.useState(null);            // relatório do port call
  const [photos, setPhotos] = React.useState([]);
  const [status, setStatus] = React.useState(null);    // {msg, kind:'ok'|'err'|null}
  const [busy, setBusy] = React.useState(false);       // gerando PPTX
  const [drag, setDrag] = React.useState(false);
  const [galAct, setGalAct] = React.useState(null);    // atividade da galeria
  const [upState, setUpState] = React.useState({});    // key -> 'comprimindo…'|'enviando…'|'erro'
  const [zoom, setZoom] = React.useState(null);        // foto ampliada (lightbox)
  const [repOrient, setRepOrient] = React.useState('portrait'); // orientação dos relatórios DPR-style
  const [dailyList, setDailyList] = React.useState([]);         // diários emitidos (seq desc)
  const [repBusy, setRepBusy] = React.useState(false);          // emitindo diário
  const [saveState, setSaveState] = React.useState('idle');     // 'idle'|'saving'|'saved' (indicador na meta bar)
  const [savedAt, setSavedAt] = React.useState(null);           // updated_at do último PUT ok
  const [importAsk, setImportAsk] = React.useState(null);       // {res, fileName} = modal de importação aberto
  const [dropOpen, setDropOpen] = React.useState(false);        // drop zone expandido com atividades presentes
  const fileRef = React.useRef(null);
  const photoRef = React.useRef(null);
  const stRef = React.useRef(null);
  const saveT = React.useRef(null);

  const H = () => ({'Authorization':`Bearer ${token()}`});
  const canEdit = canManage && call && call.status === 'open';

  /* ---------- home: lista de port calls ---------- */
  const loadCalls = () => fetch('/api/portact/calls', { headers: H() })
    .then((r) => r.ok ? r.json() : [])
    .then((d) => setCalls(Array.isArray(d) ? d : []))
    .catch(() => setCalls([]));
  React.useEffect(() => { loadCalls(); }, []);

  /* ---------- detalhe ---------- */
  const loadReports = (cid) => fetch('/api/portact/calls/' + cid + '/daily-reports', { headers: H() })
    .then((r) => r.ok ? r.json() : [])
    .then((d) => setDailyList(Array.isArray(d) ? d : []))
    .catch(() => setDailyList([]));
  const openCall = (id) => fetch('/api/portact/calls/' + id, { headers: H() })
    .then((r) => r.ok ? r.json() : null)
    .then((d) => {
      if (!d || !d.call) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao abrir o port call' : 'Could not open the port call') }); return; }
      const s = { meta: { ...PA_META_DEFAULT, ...(d.meta || {}) },
        sections: Array.isArray(d.sections) ? d.sections : [],
        activities: Array.isArray(d.activities) ? d.activities : [] };
      // E3: meta nasce do cadastro do port call quando o relatório ainda está vazio
      if (!s.meta.port && d.call.port) s.meta.port = d.call.port;
      if (!s.meta.vessel && d.call.vessel) s.meta.vessel = d.call.vessel;
      if (!s.meta.dateLabel) { const h = new Date(); s.meta.dateLabel = String(h.getDate()).padStart(2,'0') + '/' + String(h.getMonth()+1).padStart(2,'0') + '/' + h.getFullYear(); }
      s.activities.forEach((a) => { if (!a.id) a.id = paUid(); });
      stRef.current = s; setSt(s);
      setCall(d.call); setPhotos(Array.isArray(d.photos) ? d.photos : []);
      setStatus(null); setGalAct(null); setUpState({}); setView('detail');
      loadReports(id);
    })
    .catch(() => toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao abrir o port call' : 'Could not open the port call') }));
  const backHome = () => { setView('home'); setCall(null); setSt(null); setPhotos([]); setDailyList([]); loadCalls(); };

  /* ---------- persistência (PUT do relatório do port call) ---------- */
  const save = (s) => {
    const cur = s || stRef.current; if (!cur || !call) return;
    setSaveState('saving');
    fetch('/api/portact/calls/' + call.id + '/report', { method: 'PUT', headers: { ...H(), 'Content-Type': 'application/json' },
      body: JSON.stringify({ meta: cur.meta, sections: cur.sections, activities: cur.activities }) })
      .then((r) => r.json().catch(() => null).then((d) => ({ ok: r.ok, d })))
      .then(({ ok, d }) => {
        if (!ok || !d || !d.success) { setSaveState('idle'); toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao salvar' : 'Save failed'), body: (d && d.error) || undefined }); return; }
        if (d.idMap && stRef.current) { // preenche dbId sem reload — libera upload de fotos na hora
          stRef.current.activities.forEach((a) => { if (d.idMap[a.id]) a.dbId = d.idMap[a.id]; });
          setSt({ ...stRef.current });
        }
        setSavedAt(d.updatedAt ? new Date(d.updatedAt) : new Date());
        setSaveState('saved');
      })
      .catch(() => { setSaveState('idle'); toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao salvar' : 'Save failed') }); });
  };
  const saveSoon = () => { setSaveState('saving'); clearTimeout(saveT.current); saveT.current = setTimeout(() => save(), 400); }; // digitação: debounce do write do estado inteiro
  const commit = (next, immediate) => { stRef.current = next; setSt(next); if (immediate) save(next); else saveSoon(); };

  /* ---------- ciclo de vida do port call ---------- */
  const paCloseCall = () => {
    if (!call || !window.confirm(lang === 'pt' ? 'Encerrar este port call? Ele fica somente leitura (dá para reabrir depois).' : 'Close this port call? It becomes read-only (you can reopen it later).')) return;
    fetch('/api/portact/calls/' + call.id + '/close', { method: 'POST', headers: H() })
      .then((r) => r.ok ? r.json() : null)
      .then((d) => {
        if (!d || !d.success) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao encerrar' : 'Close failed') }); return; }
        toast.push({ icon: 'check', title: (lang === 'pt' ? 'Port call encerrado.' : 'Port call closed.') });
        openCall(call.id);
      });
  };
  const paReopenCall = () => {
    if (!call) return;
    fetch('/api/portact/calls/' + call.id + '/reopen', { method: 'POST', headers: H() })
      .then((r) => r.ok ? r.json() : null)
      .then((d) => {
        if (!d || !d.success) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao reabrir' : 'Reopen failed') }); return; }
        toast.push({ icon: 'check', title: (lang === 'pt' ? 'Port call reaberto.' : 'Port call reopened.') });
        openCall(call.id);
      });
  };
  const paCreateCall = () => {
    const b = newCall || {};
    fetch('/api/portact/calls', { method: 'POST', headers: { ...H(), 'Content-Type': 'application/json' }, body: JSON.stringify(b) })
      .then((r) => r.ok ? r.json() : null)
      .then((d) => {
        if (!d || !d.id) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao criar o port call' : 'Could not create the port call') }); return; }
        setNewCall(null); loadCalls(); openCall(d.id);
      });
  };

  /* ---------- Editing ops (port 1:1 do IMT) ---------- */
  const paSetMeta = (field, val) => commit({ ...st, meta: { ...st.meta, [field]: val } });
  const paSetField = (id, field, val) => {
    const acts = st.activities.map((a) => {
      if (a.id !== id) return a;
      const na = { ...a, [field]: val };
      if (field === 'progress') na.progress = Math.max(0, Math.min(100, parseInt(val) || 0));
      return na;
    });
    commit({ ...st, activities: acts });
  };
  const paSetStatusVal = (id, val) => {
    const acts = st.activities.map((a) => {
      if (a.id !== id) return a;
      const na = { ...a, status: val };
      if (val === 'DONE' && na.progress < 100) na.progress = 100;
      if (val === 'TO DO') na.progress = 0;
      return na;
    });
    commit({ ...st, activities: acts }, true);
  };
  const paAddActivity = (section) => commit({ ...st, activities: st.activities.concat([{ id: paUid(), section: section, company: '', activity: (lang === 'pt' ? 'Nova atividade' : 'New activity'), start: '', end: '', duration: '', progress: 0, status: 'TO DO' }]) }, true);
  const paDelActivity = (id) => {
    if (!window.confirm(lang === 'pt' ? 'Remover esta atividade?' : 'Remove this activity?')) return;
    commit({ ...st, activities: st.activities.filter((a) => a.id !== id) }, true);
  };
  const paAddSection = () => {
    let name = window.prompt(lang === 'pt' ? 'Nome da nova seção:' : 'New section name:', 'NOVA SEÇÃO');
    if (!name) return; name = name.trim(); if (!name) return;
    const secs = st.sections.indexOf(name) === -1 ? st.sections.concat([name]) : st.sections.slice();
    commit({ ...st, sections: secs }, true);
  };
  const paRenameSection = (old) => {
    let name = window.prompt(lang === 'pt' ? 'Renomear seção:' : 'Rename section:', old);
    if (!name) return; name = name.trim(); if (!name || name === old) return;
    const secs = st.sections.map((s) => s === old ? name : s);
    const acts = st.activities.map((a) => a.section === old ? { ...a, section: name } : a);
    commit({ ...st, sections: secs, activities: acts }, true);
  };
  const paDelSection = (name) => {
    const n = st.activities.filter((a) => a.section === name).length;
    if (!window.confirm((lang === 'pt' ? 'Remover a seção "' : 'Remove section "') + name + '"' + (n ? (lang === 'pt' ? ' e suas ' : ' and its ') + n + (lang === 'pt' ? ' atividade(s)' : ' activitie(s)') : '') + '?')) return;
    commit({ ...st, sections: st.sections.filter((s) => s !== name), activities: st.activities.filter((a) => a.section !== name) }, true);
  };
  const paMoveSection = (name, dir) => {
    const i = st.sections.indexOf(name); if (i < 0) return;
    const j = i + dir; if (j < 0 || j >= st.sections.length) return;
    const secs = st.sections.slice(); const t = secs[i]; secs[i] = secs[j]; secs[j] = t;
    commit({ ...st, sections: secs }, true);
  };
  const paMoveActivity = (id, dir) => {
    const a = st.activities.find((x) => x.id === id); if (!a) return;
    const sameSec = st.activities.filter((x) => x.section === a.section);
    const pos = sameSec.indexOf(a); const swapWith = sameSec[pos + dir]; if (!swapWith) return;
    const acts = st.activities.slice();
    const i1 = acts.indexOf(a), i2 = acts.indexOf(swapWith);
    const t = acts[i1]; acts[i1] = acts[i2]; acts[i2] = t;
    commit({ ...st, activities: acts }, true);
  };
  // E3: duplicar — cópia logo abaixo, novo client_id; fotos NÃO são copiadas (pertencem à original)
  const paDupActivity = (id) => {
    const i = st.activities.findIndex((x) => x.id === id); if (i < 0) return;
    const copy = { ...st.activities[i], id: paUid(), dbId: undefined };
    const acts = st.activities.slice(); acts.splice(i + 1, 0, copy);
    commit({ ...st, activities: acts }, true);
  };
  const paClearAll = () => {
    if (!window.confirm(lang === 'pt' ? 'Limpar TODAS as atividades e seções?' : 'Clear ALL activities and sections?')) return;
    commit({ meta: { ...PA_META_DEFAULT }, sections: [], activities: [] }, true);
  };

  /* ---------- Import do Excel da empresa (port 1:1 do parser; E3: modal de 3 botões) ---------- */
  const paHandleFile = async (file) => {
    if (!file) return;
    try { await _paEnsureXLSX(); }
    catch (e) { setStatus({ msg: (lang === 'pt' ? 'Leitor de Excel indisponível (SheetJS não carregou).' : 'Excel reader unavailable (SheetJS did not load).'), kind: 'err' }); return; }
    setStatus({ msg: (lang === 'pt' ? 'Lendo ' : 'Reading ') + file.name + '…', kind: null });
    const reader = new FileReader();
    reader.onerror = () => setStatus({ msg: (lang === 'pt' ? 'Não consegui ler o arquivo.' : 'Could not read the file.'), kind: 'err' });
    reader.onload = (e) => {
      try {
        const wb = XLSX.read(new Uint8Array(e.target.result), { type: 'array' });
        const res = paParseWorkbook(wb);
        if (!res.activities.length) { setStatus({ msg: (lang === 'pt' ? 'Nenhuma atividade encontrada. Confira as colunas Empresa/Atividade/Status.' : 'No activities found. Check the Company/Activity/Status columns.'), kind: 'err' }); return; }
        if (stRef.current.activities.length) { // lista não-vazia: pergunta o modo num modal de verdade
          setImportAsk({ res, fileName: file.name });
          setStatus(null);
          return;
        }
        paApplyImport({ res, fileName: file.name }, 'replace'); // lista vazia: substitui direto
      } catch (err) { console.error(err); setStatus({ msg: (lang === 'pt' ? 'Erro ao processar: ' : 'Processing error: ') + err.message, kind: 'err' }); }
    };
    reader.readAsArrayBuffer(file);
  };
  // mode 'replace' | 'append' — no append, dedupe por (seção+atividade+empresa+início)
  const paApplyImport = (p, mode) => {
    const cur = stRef.current;
    if (!cur || !p) return;
    let next, added = p.res.activities.length, skipped = 0;
    if (mode === 'replace' || !cur.activities.length) {
      next = { ...cur, activities: p.res.activities, sections: p.res.sections.slice() };
    } else {
      const keys = {};
      cur.activities.forEach((a) => { keys[paActKey(a)] = 1; });
      const novas = [];
      p.res.activities.forEach((a) => { const k = paActKey(a); if (keys[k]) skipped++; else { keys[k] = 1; novas.push(a); } });
      added = novas.length;
      const secs = cur.sections.slice();
      p.res.sections.forEach((s) => { if (secs.indexOf(s) === -1) secs.push(s); });
      next = { ...cur, sections: secs, activities: cur.activities.concat(novas) };
    }
    if (!next.meta.vessel && p.res.meta.vessel) next.meta = { ...next.meta, vessel: p.res.meta.vessel };
    commit(next, true);
    setImportAsk(null); setDropOpen(false);
    setStatus({ msg: added + (lang === 'pt' ? ' atividade(s) · ' : ' activitie(s) · ') + p.res.sections.length + (lang === 'pt' ? ' seção(ões) importada(s) de ' : ' section(s) imported from ') + p.fileName + '.', kind: 'ok' });
    toast.push({ icon: 'check', title: '✓ ' + added + (lang === 'pt' ? ' atividade(s) importada(s).' : ' activitie(s) imported.'),
      body: skipped ? (skipped + (lang === 'pt' ? ' ignorada(s) como duplicada(s)' : ' skipped as duplicate(s)')) : undefined });
  };

  /* ---------- Generate PPTX (server) ---------- */
  const paGeneratePPTX = async () => {
    if (!st.activities.length) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Nenhuma atividade para gerar' : 'No activities to generate') }); return; }
    setBusy(true);
    try {
      const ordered = paOrdered(st);
      const payload = { meta: st.meta, activities: ordered.map((a) => ({ section: a.section, company: a.company, activity: a.activity, start: a.start, end: a.end, duration: a.duration, progress: a.progress, status: a.status })) };
      const r = await fetch('/api/portact/pptx', { method: 'POST', headers: { ...H(), 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
      const ct = (r.headers.get('Content-Type') || '');
      if (!r.ok || ct.indexOf('presentation') === -1) {
        toast.push({ icon: 'x', title: (lang === 'pt' ? 'Não foi possível gerar o PowerPoint' : 'Could not generate the PowerPoint') });
        setBusy(false); return;
      }
      const blob = await r.blob();
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a'); a.href = url;
      a.download = 'Relatorio_Diario_Genesis' + (st.meta.reportNo ? ('_' + st.meta.reportNo) : '') + '.pptx';
      document.body.appendChild(a); a.click(); a.remove();
      setTimeout(() => { URL.revokeObjectURL(url); }, 1500);
      toast.push({ icon: 'check', title: (lang === 'pt' ? '✓ Relatório gerado.' : '✓ Report generated.') });
    } catch (e) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Não foi possível gerar o PowerPoint' : 'Could not generate the PowerPoint'), body: String(e && e.message ? e.message : e) }); }
    setBusy(false);
  };

  /* ---------- fotos ---------- */
  const photosByAct = {};
  photos.forEach((p) => { const k = p.activityId || 0; if (!photosByAct[k]) photosByAct[k] = []; photosByAct[k].push(p); });

  const paUploadPhotos = async (files) => {
    if (!galAct || !galAct.dbId || !call) return;
    const list = Array.from(files || []);
    for (const f of list) {
      const key = (f.name || 'foto') + '_' + f.size + '_' + (f.lastModified || 0);
      try {
        setUpState((m) => ({ ...m, [key]: (lang === 'pt' ? 'comprimindo…' : 'compressing…') }));
        const blob = await _paCompress(f);
        setUpState((m) => ({ ...m, [key]: (lang === 'pt' ? 'enviando…' : 'uploading…') }));
        const fd = new FormData();
        fd.append('photo', blob, (f.name || 'foto').replace(/\.\w+$/, '') + '.jpg');
        fd.append('activity_id', String(galAct.dbId));
        fd.append('caption', '');
        const r = await fetch('/api/portact/calls/' + call.id + '/photos', { method: 'POST', headers: H(), body: fd });
        const d = await r.json().catch(() => null);
        if (!r.ok || !d || !d.photo) throw new Error((d && d.error) || 'falha');
        setPhotos((ps) => ps.concat([d.photo]));
        setUpState((m) => { const n = { ...m }; delete n[key]; return n; });
      } catch (e) { setUpState((m) => ({ ...m, [key]: (lang === 'pt' ? 'erro — tente de novo' : 'error — try again') })); }
    }
  };
  const paSaveCaption = (p, val) => {
    fetch('/api/portact/photos/' + p.id, { method: 'PATCH', headers: { ...H(), 'Content-Type': 'application/json' }, body: JSON.stringify({ caption: val }) })
      .then((r) => { if (r.ok) setPhotos((ps) => ps.map((x) => x.id === p.id ? { ...x, caption: val } : x)); });
  };
  const paDelPhoto = (p) => {
    if (!window.confirm(lang === 'pt' ? 'Excluir esta foto?' : 'Delete this photo?')) return;
    fetch('/api/portact/photos/' + p.id, { method: 'DELETE', headers: H() })
      .then((r) => {
        if (r.ok) { setPhotos((ps) => ps.filter((x) => x.id !== p.id)); if (zoom && zoom.id === p.id) setZoom(null); }
        else toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao excluir a foto' : 'Could not delete the photo') });
      });
  };

  /* ---------- relatórios DPR-style (diário emitido + geral) ---------- */
  const paGenDaily = () => {
    if (!call || repBusy) return;
    setRepBusy(true);
    fetch('/api/portact/calls/' + call.id + '/daily-report', { method: 'POST', headers: H() })
      .then((r) => r.json().catch(() => null).then((d) => ({ ok: r.ok, d })))
      .then(({ ok, d }) => {
        setRepBusy(false);
        if (!ok || !d || !d.success) { toast.push({ icon: 'x', title: (d && d.error) || (lang === 'pt' ? 'Falha ao emitir o diário' : 'Could not issue the daily report') }); return; }
        toast.push({ icon: 'check', title: (lang === 'pt' ? '✓ Diário #' : '✓ Daily #') + d.seq + (lang === 'pt' ? ' emitido.' : ' issued.') });
        loadReports(call.id);
        _paOpenHtml(_paRepDailyHtml(d.snapshot, lang, repOrient), toast, lang);
      })
      .catch(() => { setRepBusy(false); toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao emitir o diário' : 'Could not issue the daily report') }); });
  };
  const paOpenDaily = (rid) => {
    fetch('/api/portact/daily-reports/' + rid, { headers: H() })
      .then((r) => r.ok ? r.json() : null)
      .then((d) => {
        if (!d || !d.snapshot) { toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao abrir o diário' : 'Could not open the daily report') }); return; }
        _paOpenHtml(_paRepDailyHtml(d.snapshot, lang, repOrient), toast, lang);
      })
      .catch(() => toast.push({ icon: 'x', title: (lang === 'pt' ? 'Falha ao abrir o diário' : 'Could not open the daily report') }));
  };
  const paGenGeneral = () => {
    if (!call || !st) return;
    _paOpenHtml(_paRepGeneralHtml(call, st, photos, dailyList.length, lang, repOrient), toast, lang);
  };

  /* ---------- estilos compartilhados ---------- */
  const lbl = { display:'block', fontSize:9, letterSpacing:1, textTransform:'uppercase', color:'var(--muted)', marginBottom:3 };
  const metaInp = { padding:'6px 8px', border:'1px solid var(--line-soft)', borderRadius:6, fontSize:13, background:'var(--surface)', color:'var(--ink)' };
  const overlay = { position:'fixed', inset:0, zIndex:210, background:'rgba(31,42,46,0.55)', backdropFilter:'blur(3px)', display:'flex', alignItems:'center', justifyContent:'center', padding:16 };
  const fmtD = (iso) => { if (!iso) return ''; const d = new Date(iso); return String(d.getDate()).padStart(2,'0') + '/' + String(d.getMonth()+1).padStart(2,'0') + '/' + String(d.getFullYear()).slice(2); };
  const statusChip = (stt) => stt === 'open'
    ? <span style={{fontFamily:'var(--fm)',fontSize:9,fontWeight:700,padding:'3px 9px',borderRadius:9,background:'#e6f5ec',color:'#1e7a45'}}>{lang==='pt'?'ABERTO':'OPEN'}</span>
    : <span style={{fontFamily:'var(--fm)',fontSize:9,fontWeight:700,padding:'3px 9px',borderRadius:9,background:'var(--cream-3)',color:'var(--muted)'}}>{lang==='pt'?'ENCERRADO':'CLOSED'}</span>;

  /* ══════════════ RENDER: HOME (lista de port calls) ══════════════ */
  if (view === 'home') {
    const gridHome = isMobile ? '1.6fr 1fr 0.9fr' : '1.9fr 0.8fr 1fr 1.2fr 1.2fr 0.55fr 0.6fr 0.8fr';
    const th = (key, label) => (
      <div key={key} onClick={() => setSort((s) => ({ key, dir: s.key === key ? -s.dir : 1 }))}
        style={{cursor:'pointer',fontSize:9,letterSpacing:1,textTransform:'uppercase',color:'var(--muted)',userSelect:'none',fontFamily:'var(--fm)'}}>
        {label}{sort.key === key ? (sort.dir === 1 ? ' ▲' : ' ▼') : ''}
      </div>
    );
    let rows = (calls || []).slice();
    if (busca.trim()) {
      const q = busca.trim().toLowerCase();
      rows = rows.filter((c) => ((c.titulo + ' ' + c.port + ' ' + c.berth + ' ' + c.client).toLowerCase().indexOf(q) >= 0));
    }
    if (sort.key) {
      const val = (c) => sort.key === 'prog' ? (c.total ? c.done / c.total : 0)
        : sort.key === 'fotos' ? c.photos
        : sort.key === 'rel' ? (c.reportSeq || 0)
        : sort.key === 'periodo' ? String(c.startedAt || '')
        : String(c[sort.key] || '').toLowerCase();
      rows.sort((a, b) => { const x = val(a), y = val(b); return (x < y ? -1 : x > y ? 1 : 0) * sort.dir; });
    }
    return (
      <main style={{maxWidth:1080,margin:'0 auto',padding:isMobile?'16px 14px 60px':'26px 30px 80px'}}>
        <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',gap:10,flexWrap:'wrap',marginBottom:14}}>
          <div style={{fontFamily:'var(--fs)',fontSize:isMobile?22:26,letterSpacing:'-0.5px'}}>⚓ Port Activities</div>
          <div style={{display:'flex',gap:8,alignItems:'center',flexWrap:'wrap'}}>
            <input value={busca} onChange={(e)=>setBusca(e.target.value)} placeholder={lang==='pt'?'Buscar porto, berço, cliente…':'Search port, berth, client…'}
              style={{padding:'7px 10px',border:'1px solid var(--line-soft)',borderRadius:8,fontSize:12,background:'var(--surface)',color:'var(--ink)',width:isMobile?150:220}}/>
            {canManage && <button className="btn btn-sm" onClick={()=>setNewCall({port:'',berth:'',client:'',titulo:''})}>{lang==='pt'?'＋ Novo port call':'＋ New port call'}</button>}
          </div>
        </div>
        <div style={{background:'var(--surface)',border:'1px solid var(--line-soft)',borderRadius:14,overflow:'hidden',boxShadow:'var(--shadow-sm)'}}>
          <div style={{display:'grid',gridTemplateColumns:gridHome,gap:8,padding:'8px 14px',borderBottom:'1px solid var(--line-soft)'}}>
            {th('titulo', lang==='pt'?'Port call':'Port call')}
            {th('berth', lang==='pt'?'Berço':'Berth')}
            {!isMobile && th('client', lang==='pt'?'Cliente':'Client')}
            {th('periodo', lang==='pt'?'Período':'Period')}
            {!isMobile && th('prog', lang==='pt'?'Progresso':'Progress')}
            {!isMobile && th('fotos', lang==='pt'?'Fotos':'Photos')}
            {!isMobile && th('rel', lang==='pt'?'Relatórios':'Reports')}
            {th('status', 'Status')}
          </div>
          {calls === null && <div style={{padding:20,fontFamily:'var(--fm)',fontSize:12,color:'var(--muted)'}}>{lang==='pt'?'Carregando…':'Loading…'}</div>}
          {calls !== null && !rows.length && (
            <div style={{padding:'26px 16px',textAlign:'center',fontSize:13,color:'var(--muted)',lineHeight:1.7}}>
              {busca.trim() ? (lang==='pt'?'Nada encontrado para essa busca.':'Nothing found for that search.')
                : (lang==='pt'?'Nenhum port call ainda.':'No port calls yet.')}
              {!busca.trim() && canManage && <><br/>{lang==='pt'?'Crie o primeiro com ＋ Novo port call.':'Create the first one with ＋ New port call.'}</>}
            </div>
          )}
          {rows.map((c, i) => {
            const pct = c.total ? Math.round(c.done / c.total * 100) : 0;
            return (
              <div key={c.id} onClick={()=>openCall(c.id)}
                style={{display:'grid',gridTemplateColumns:gridHome,gap:8,padding:'9px 14px',alignItems:'center',cursor:'pointer',
                  borderBottom:'1px solid var(--line-soft)',
                  background:c.status==='open'?(i%2?'rgba(0,0,0,.015)':'transparent'):'var(--surface-2)',
                  opacity:c.status==='open'?1:0.72}}>
                <div>
                  <div style={{fontWeight:700,fontSize:13,color:'var(--ink)'}}>{c.titulo || c.port || ('#' + c.id)}</div>
                  <div style={{fontFamily:'var(--fm)',fontSize:10,color:'var(--muted)'}}>{c.port}{c.vessel ? ' · ' + c.vessel : ''}</div>
                </div>
                <div style={{fontSize:12,color:'var(--ink)'}}>{c.berth || '—'}</div>
                {!isMobile && <div style={{fontSize:12,color:'var(--ink)'}}>{c.client || '—'}</div>}
                <div style={{fontFamily:'var(--fm)',fontSize:10.5,color:'var(--muted)'}}>{fmtD(c.startedAt)} → {c.closedAt ? fmtD(c.closedAt) : (lang==='pt'?'em aberto':'open')}</div>
                {!isMobile && (
                  <div style={{display:'flex',alignItems:'center',gap:6}}>
                    <div style={{flex:1,height:6,background:'var(--cream-3)',borderRadius:4,overflow:'hidden'}}><div style={{width:pct+'%',height:'100%',background:'#22c55e',borderRadius:4}}/></div>
                    <span style={{fontFamily:'var(--fm)',fontSize:10,color:'var(--muted)',whiteSpace:'nowrap'}}>{c.done}/{c.total}</span>
                  </div>
                )}
                {!isMobile && <div style={{fontFamily:'var(--fm)',fontSize:11,color:'var(--muted)'}}>📷 {c.photos}</div>}
                {!isMobile && <div style={{fontFamily:'var(--fm)',fontSize:11,color:'var(--muted)'}} title={lang==='pt'?'Diários emitidos':'Daily reports issued'}>🗓 {c.reportSeq || 0}</div>}
                <div>{statusChip(c.status)}</div>
              </div>
            );
          })}
        </div>

        {/* modal: novo port call */}
        {newCall && (
          <div onClick={()=>setNewCall(null)} style={overlay}>
            <div onClick={(e)=>e.stopPropagation()} style={{background:'var(--surface)',borderRadius:16,padding:'20px 22px',width:400,maxWidth:'100%',boxShadow:'var(--shadow-md)'}}>
              <div className="t-eyebrow" style={{marginBottom:12}}>{lang==='pt'?'Novo port call':'New port call'}</div>
              {[['port',lang==='pt'?'Porto':'Port','AÇU PORT - RJ'],
                ['berth',lang==='pt'?'Berço':'Berth','Berth 1'],
                ['client',lang==='pt'?'Cliente / empresa principal':'Client / main company',''],
                ['titulo',lang==='pt'?'Título (vazio = automático)':'Title (empty = automatic)',(lang==='pt'?'Açu — ago/2026':'Açu — Aug/2026')]].map(([f,l2,ph])=>(
                <div key={f} style={{marginBottom:10}}>
                  <label style={lbl}>{l2}</label>
                  <input value={newCall[f]||''} onChange={(e)=>setNewCall({...newCall,[f]:e.target.value})} placeholder={ph} style={{...metaInp,width:'100%'}}/>
                </div>
              ))}
              <div style={{display:'flex',gap:8,justifyContent:'flex-end',marginTop:14}}>
                <button className="btn btn-sm" onClick={()=>setNewCall(null)}>{lang==='pt'?'Cancelar':'Cancel'}</button>
                <button className="btn btn-sm" onClick={paCreateCall} disabled={!((newCall.port||'').trim())}>{lang==='pt'?'Criar e abrir':'Create & open'}</button>
              </div>
            </div>
          </div>
        )}
      </main>
    );
  }

  /* ══════════════ RENDER: DETAIL (atividades do port call) ══════════════ */
  if (!call || st === null) return <main style={{maxWidth:1080,margin:'0 auto',padding:isMobile?'16px 14px 60px':'26px 30px 80px'}}><div style={{fontFamily:'var(--fm)',fontSize:13,color:'var(--muted)',padding:20}}>{lang==='pt'?'Carregando…':'Loading…'}</div></main>;

  const total = st.activities.length;
  const dist = {'TO DO':0,'DOING':0,'DONE':0,'CANCELADO':0};
  st.activities.forEach((a) => { dist[a.status] = (dist[a.status] || 0) + 1; });
  // atividade cuja seção não está na lista ordenada -> a seção entra no fim (só na render)
  const secs = st.sections.slice();
  st.activities.forEach((a) => { if (secs.indexOf(a.section) === -1) secs.push(a.section); });

  const gridCols = '2.6fr 1.1fr 1fr 1fr 0.7fr 1.3fr 1.2fr 0.8fr';
  const inp = { width:'100%', padding:'4px 6px', border:'1px solid transparent', borderRadius:4, fontSize:11, background:'transparent', color:'var(--ink)' };
  const inpFocus = (e) => { e.target.style.borderColor = 'var(--line-soft)'; e.target.style.background = 'var(--surface)'; };
  const inpBlur = (e) => { e.target.style.borderColor = 'transparent'; e.target.style.background = 'transparent'; };
  const galPhotos = galAct && galAct.dbId ? (photosByAct[galAct.dbId] || []) : [];
  // E3: datalist de empresas (união das já usadas no port call) — sugere sem travar texto livre
  const empresasList = [];
  st.activities.forEach((a) => { const c = (a.company || '').trim(); if (c && empresasList.indexOf(c) === -1) empresasList.push(c); });
  // E3: badge de salvo na meta bar
  const savedHm = savedAt ? String(savedAt.getHours()).padStart(2,'0') + ':' + String(savedAt.getMinutes()).padStart(2,'0') : '';
  const saveBadge = saveState === 'saving'
    ? (lang==='pt' ? 'salvando…' : 'saving…')
    : (saveState === 'saved' && savedHm ? (lang==='pt' ? '✓ salvo ' : '✓ saved ') + savedHm : '');

  return (
    <main style={{maxWidth:1080,margin:'0 auto',padding:isMobile?'16px 14px 60px':'26px 30px 80px'}}>
      <datalist id="pa-empresas">{empresasList.map((c) => <option key={c} value={c}/>)}</datalist>

      {/* header do detalhe: breadcrumb + título + ciclo de vida */}
      <div style={{display:'flex',alignItems:'center',gap:10,flexWrap:'wrap',marginBottom:12}}>
        <button className="btn btn-sm" onClick={backHome}>‹ Port calls</button>
        <div style={{flex:1,minWidth:200}}>
          <div style={{fontFamily:'var(--fs)',fontSize:isMobile?20:24,letterSpacing:'-0.5px'}}>{call.titulo || call.port || ('#' + call.id)}</div>
          <div style={{fontFamily:'var(--fm)',fontSize:10.5,color:'var(--muted)'}}>
            {[call.port, call.berth, call.client].filter(Boolean).join(' · ')}{call.port || call.berth || call.client ? ' · ' : ''}{fmtD(call.startedAt)} → {call.closedAt ? fmtD(call.closedAt) : (lang==='pt'?'em aberto':'open')}
          </div>
        </div>
        {statusChip(call.status)}
        {canEdit && <button className="btn btn-sm" onClick={paCloseCall}>⛔ {lang==='pt'?'Encerrar':'Close'}</button>}
        {canManage && call.status !== 'open' && <button className="btn btn-sm" onClick={paReopenCall}>↩ {lang==='pt'?'Reabrir':'Reopen'}</button>}
      </div>

      {/* aviso de encerrado */}
      {call.status !== 'open' && (
        <div style={{background:'var(--cream-2)',border:'1px solid var(--line-soft)',borderRadius:10,padding:'9px 14px',marginBottom:12,fontFamily:'var(--fm)',fontSize:11,color:'var(--muted)'}}>
          🔒 {lang==='pt'?'Port call encerrado — somente leitura.':'Port call closed — read-only.'}
          {call.closedBy ? ' ' + (lang==='pt'?'Encerrado por':'Closed by') + ' ' + call.closedBy + (call.closedAt ? ' ' + (lang==='pt'?'em':'on') + ' ' + fmtD(call.closedAt) : '') + '.' : ''}
        </div>
      )}

      {/* toolbar */}
      <div style={{display:'flex',gap:6,alignItems:'center',flexWrap:'wrap',marginBottom:12}}>
        {canEdit && <button className="btn btn-sm" onClick={paAddSection}>{lang==='pt'?'＋ Seção':'＋ Section'}</button>}
        {canManage && <button className="btn btn-sm" onClick={paGeneratePPTX} disabled={busy}>{busy ? '⏳ PPTX…' : '📊 PPTX'}</button>}
        {canManage && <button className="btn btn-sm" onClick={()=>_paExportPDF(st, toast, lang)}>📄 PDF</button>}
        {canManage && <button className="btn btn-sm" onClick={()=>_paExportExcel(st, toast, lang)}>📗 Excel</button>}
        {canEdit && total > 0 && <button className="btn btn-sm" onClick={paClearAll}>↺ {lang==='pt'?'Limpar':'Clear'}</button>}
      </div>

      {/* meta bar (relatório # / data / porto / embarcação) */}
      <div style={{display:'flex',gap:14,flexWrap:'wrap',alignItems:'flex-end',background:'var(--surface)',border:'1px solid var(--line-soft)',borderRadius:10,padding:'12px 16px',marginBottom:12,boxShadow:'var(--shadow-sm)'}}>
        <div><label style={lbl}>{lang==='pt'?'Relatório Diário #':'Daily Report #'}</label>
          <input type="text" value={st.meta.reportNo||''} readOnly={!canEdit} onChange={(e)=>paSetMeta('reportNo', e.target.value)} placeholder="001" style={{...metaInp,width:90,fontWeight:700}}/></div>
        <div><label style={lbl}>{lang==='pt'?'Data':'Date'}</label>
          <input type="text" value={st.meta.dateLabel||''} readOnly={!canEdit} onChange={(e)=>paSetMeta('dateLabel', e.target.value)} placeholder="04/08/2026" style={{...metaInp,width:120}}/></div>
        <div><label style={lbl}>{lang==='pt'?'Porto':'Port'}</label>
          <input type="text" value={st.meta.port||''} readOnly={!canEdit} onChange={(e)=>paSetMeta('port', e.target.value)} placeholder="AÇU PORT - RJ" style={{...metaInp,width:180}}/></div>
        <div><label style={lbl}>{lang==='pt'?'Embarcação':'Vessel'}</label>
          <input type="text" value={st.meta.vessel||''} readOnly={!canEdit} onChange={(e)=>paSetMeta('vessel', e.target.value)} placeholder="Genesis I" style={{...metaInp,width:140}}/></div>
        <div style={{flex:1,textAlign:'right',fontSize:11,color:'var(--muted)',alignSelf:'center',minWidth:180}}>
          {saveBadge && <span style={{fontFamily:'var(--fm)',fontSize:10,marginRight:10,color:saveState==='saved'?'#1e7a45':'var(--muted)'}}>{saveBadge}</span>}
          {total} {lang==='pt'?'atividade(s)':'activitie(s)'} · <span style={{color:'#1e7a45'}}>{dist['DONE']} DONE</span> · <span style={{color:'#a8681a'}}>{dist['DOING']} DOING</span> · <span style={{color:'#7a8697'}}>{dist['TO DO']} TO DO</span>{dist['CANCELADO'] ? <span> · <span style={{color:'#b5433f'}}>{dist['CANCELADO']} CANC</span></span> : null}
        </div>
      </div>

      {/* drop zone (só com o port call aberto); E3: com atividades, colapsa numa linha compacta */}
      {canEdit && (
        <React.Fragment>
          {(total === 0 || dropOpen) ? (
            <div onClick={()=>fileRef.current && fileRef.current.click()}
                 onDragEnter={(e)=>{e.preventDefault();setDrag(true);}} onDragOver={(e)=>{e.preventDefault();setDrag(true);}}
                 onDragLeave={(e)=>{e.preventDefault();setDrag(false);}}
                 onDrop={(e)=>{e.preventDefault();setDrag(false);const f=e.dataTransfer.files&&e.dataTransfer.files[0];paHandleFile(f);}}
                 style={{border:'1.5px dashed '+(drag?'#5A93C9':'var(--line-soft)'),borderRadius:10,background:'var(--surface)',padding:18,textAlign:'center',cursor:'pointer',transition:'all .15s',marginBottom:6}}>
              <div style={{fontSize:22,marginBottom:4}}>📄</div>
              <div style={{fontWeight:600,fontSize:13,marginBottom:3}}>{lang==='pt'?'Solte aqui a planilha de atividades do porto (Excel da empresa), ou clique para escolher':'Drop the port activities spreadsheet here (company Excel), or click to choose'}</div>
              <div style={{fontSize:11,color:'var(--muted)'}}>{lang==='pt'?'.xls / .xlsx · lê as colunas Empresa, Atividade, Data Início/Final, Duração, Progresso e Status · seções detectadas automaticamente':'.xls / .xlsx · reads Company, Activity, Start/End Date, Duration, Progress and Status columns · sections auto-detected'}</div>
              <input type="file" ref={fileRef} accept=".xls,.xlsx" style={{display:'none'}} onChange={(e)=>{paHandleFile(e.target.files[0]);e.target.value='';}}/>
            </div>
          ) : (
            <div onClick={()=>setDropOpen(true)}
                 style={{border:'1px solid var(--line-soft)',borderRadius:10,background:'var(--surface)',padding:'8px 14px',cursor:'pointer',marginBottom:6,display:'flex',alignItems:'center',gap:8,fontSize:12,color:'var(--muted)'}}>
              <span style={{fontWeight:700,color:'var(--ink)'}}>＋</span>
              <span>{lang==='pt'?'Importar planilha (Excel da empresa)':'Import spreadsheet (company Excel)'}</span>
              <span style={{fontFamily:'var(--fm)',fontSize:10}}>{lang==='pt'?'clique para expandir':'click to expand'}</span>
            </div>
          )}
          <div style={{fontFamily:'var(--fm)',fontSize:11,minHeight:15,marginBottom:14,color:status?(status.kind==='err'?'#b03030':status.kind==='ok'?'#2a7a4a':'var(--muted)'):'var(--muted)'}}>{status?status.msg:''}</div>
        </React.Fragment>
      )}

      {/* relatórios DPR-style: diário emitido (snapshot congelado) + geral */}
      {canManage && (
        <div style={{background:'var(--surface)',border:'1px solid var(--line-soft)',borderRadius:10,padding:'12px 16px',marginBottom:12,boxShadow:'var(--shadow-sm)'}}>
          <div style={{display:'flex',alignItems:'center',gap:10,flexWrap:'wrap'}}>
            <div className="t-eyebrow" style={{flex:1,minWidth:140}}>{lang==='pt'?'Relatórios do port call':'Port call reports'}</div>
            <div style={{display:'flex',background:'var(--cream-2)',borderRadius:12,padding:4,gap:4}}>
              {[['portrait',lang==='pt'?'▯ Retrato':'▯ Portrait'],['landscape',lang==='pt'?'▭ Paisagem':'▭ Landscape']].map(([o,l2])=>(
                <button key={o} onClick={()=>setRepOrient(o)}
                  style={{border:'none',cursor:'pointer',borderRadius:9,padding:'4px 10px',fontFamily:'var(--fm)',fontSize:10,fontWeight:700,
                    background:repOrient===o?'var(--surface)':'transparent',color:repOrient===o?'var(--ink)':'var(--muted)',
                    boxShadow:repOrient===o?'var(--shadow-sm)':'none'}}>{l2}</button>
              ))}
            </div>
            {canEdit && <button className="btn btn-sm" onClick={paGenDaily} disabled={repBusy}>{repBusy ? '⏳…' : '🗓 ' + (lang==='pt'?'Relatório do dia':'Daily report')}</button>}
            <button className="btn btn-sm" onClick={paGenGeneral}>📕 {lang==='pt'?'Relatório geral':'General report'}</button>
          </div>
          {dailyList.length > 0 && (
            <div style={{marginTop:10,borderTop:'1px solid var(--line-soft)',paddingTop:6}}>
              {dailyList.map((r)=>(
                <div key={r.id} style={{display:'flex',alignItems:'center',gap:8,padding:'4px 0',fontFamily:'var(--fm)',fontSize:11,color:'var(--ink)'}}>
                  <span style={{fontWeight:700}}>#{r.seq}</span>
                  <span style={{color:'var(--muted)'}}>{fmtD(r.data)}{r.geradoPor ? ' · ' + (lang==='pt'?'por ':'by ') + r.geradoPor : ''}</span>
                  <span style={{flex:1}}/>
                  <button className="btn btn-sm" style={{padding:'1px 8px',fontSize:10}} onClick={()=>paOpenDaily(r.id)}>↺ {lang==='pt'?'Reabrir':'Reopen'}</button>
                </div>
              ))}
            </div>
          )}
          {!dailyList.length && (
            <div style={{marginTop:6,fontFamily:'var(--fm)',fontSize:10.5,color:'var(--muted)'}}>{lang==='pt'?'Nenhum diário emitido ainda — emitir congela um snapshot numerado do dia.':'No daily reports issued yet — issuing freezes a numbered snapshot of the day.'}</div>
          )}
        </div>
      )}

      {/* seções + atividades */}
      {!total && !secs.length ? (
        <div style={{background:'var(--surface)',border:'1px solid var(--line-soft)',borderRadius:14,padding:'28px 20px',textAlign:'center',color:'var(--muted)',fontSize:13,lineHeight:1.7}}>
          <strong>{lang==='pt'?'Nenhuma atividade ainda.':'No activities yet.'}</strong><br/>
          {lang==='pt'?(canEdit?'Solte a planilha da empresa acima, ou use ＋ Seção para montar manualmente.':'Aguarde um gestor importar a planilha da empresa.'):(canEdit?'Drop the company spreadsheet above, or use ＋ Section to build it manually.':'Wait for a manager to import the company spreadsheet.')}
        </div>
      ) : secs.map((sec) => {
        const acts = st.activities.filter((a) => a.section === sec);
        return (
          <div key={sec} style={{marginBottom:16,border:'1px solid var(--line-soft)',borderRadius:10,overflow:'hidden',background:'var(--surface)',boxShadow:'var(--shadow-sm)'}}>
            {/* section header */}
            <div style={{display:'flex',alignItems:'center',gap:8,background:'var(--surface-2)',padding:'8px 12px'}}>
              <span style={{fontFamily:'var(--fs)',fontSize:14,fontWeight:800,letterSpacing:1,color:'#0e8fc0',textTransform:'uppercase',flex:1}}>{sec}</span>
              <span style={{fontSize:10,color:'var(--muted)'}}>{acts.length} item(s)</span>
              {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Subir seção':'Move section up'} onClick={()=>paMoveSection(sec,-1)} style={{padding:'2px 7px'}}>▲</button>}
              {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Descer seção':'Move section down'} onClick={()=>paMoveSection(sec,1)} style={{padding:'2px 7px'}}>▼</button>}
              {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Renomear':'Rename'} onClick={()=>paRenameSection(sec)} style={{padding:'2px 7px'}}>✎</button>}
              {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Adicionar atividade':'Add activity'} onClick={()=>paAddActivity(sec)} style={{padding:'2px 8px'}}>＋</button>}
              {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Remover seção':'Remove section'} onClick={()=>paDelSection(sec)} style={{padding:'2px 7px',color:'#b03030'}}>✕</button>}
            </div>
            {/* column header */}
            <div style={{display:'grid',gridTemplateColumns:gridCols,gap:6,padding:'5px 12px',borderBottom:'1px solid var(--line-soft)',fontSize:9,letterSpacing:.5,textTransform:'uppercase',color:'var(--muted)'}}>
              <div>{lang==='pt'?'Atividade':'Activity'}</div><div>{lang==='pt'?'Empresa':'Company'}</div><div>{lang==='pt'?'Início':'Start'}</div><div>{lang==='pt'?'Término':'End'}</div><div>{lang==='pt'?'Duração':'Duration'}</div><div>{lang==='pt'?'Progresso':'Progress'}</div><div>Status</div><div></div>
            </div>
            {!acts.length && <div style={{padding:'10px 12px',fontSize:11,color:'var(--muted)'}}>{lang==='pt'?'Sem atividades. Use ＋ para adicionar.':'No activities. Use ＋ to add.'}</div>}
            {acts.map((a, ai) => {
              const b = PA_BADGE[a.status] || PA_BADGE['TO DO'];
              const nFotos = a.dbId ? (photosByAct[a.dbId] || []).length : 0;
              return (
                <div key={a.id} style={{display:'grid',gridTemplateColumns:gridCols,gap:6,padding:'6px 12px',alignItems:'center',borderBottom:'1px solid var(--line-soft)',background:ai%2?'rgba(0,0,0,.015)':'transparent'}}>
                  <input value={a.activity} aria-label={lang==='pt'?'Atividade':'Activity'} readOnly={!canEdit} onChange={(e)=>paSetField(a.id,'activity',e.target.value)} onFocus={inpFocus} onBlur={inpBlur} style={inp}/>
                  <input value={a.company} aria-label={lang==='pt'?'Empresa':'Company'} list="pa-empresas" readOnly={!canEdit} onChange={(e)=>paSetField(a.id,'company',e.target.value)} onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontWeight:600,color:'#5A93C9'}}/>
                  <input value={a.start} aria-label={lang==='pt'?'Início':'Start'} readOnly={!canEdit} onChange={(e)=>paSetField(a.id,'start',e.target.value)} placeholder="—" onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontSize:10,color:'var(--muted)'}}/>
                  <input value={a.end} aria-label={lang==='pt'?'Término':'End'} readOnly={!canEdit} onChange={(e)=>paSetField(a.id,'end',e.target.value)} placeholder="—" onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontSize:10,color:'var(--muted)'}}/>
                  <input value={a.duration} aria-label={lang==='pt'?'Duração':'Duration'} readOnly={!canEdit} onChange={(e)=>paSetField(a.id,'duration',e.target.value)} placeholder="—" onFocus={inpFocus} onBlur={inpBlur} style={{...inp,fontSize:10,color:'var(--muted)'}}/>
                  <div style={{display:'flex',alignItems:'center',gap:6}}>
                    <div style={{flex:1,height:7,background:'#E9EEF3',borderRadius:4,overflow:'hidden'}}>
                      <div style={{width:a.progress+'%',height:'100%',background:b.bar,borderRadius:4}}></div>
                    </div>
                    <input type="number" min="0" max="100" value={a.progress} aria-label={lang==='pt'?'Progresso (%)':'Progress (%)'} readOnly={!canEdit} onChange={(e)=>paSetField(a.id,'progress',e.target.value)} style={{width:44,padding:'3px 4px',border:'1px solid var(--line-soft)',borderRadius:4,fontSize:10,textAlign:'center',background:'var(--surface)',color:'var(--ink)'}}/>
                  </div>
                  <select value={a.status} aria-label="Status" disabled={!canEdit} onChange={(e)=>paSetStatusVal(a.id,e.target.value)} style={{width:'100%',padding:'4px 6px',border:'1px solid var(--line-soft)',borderRadius:5,fontSize:10,fontWeight:700,background:b.bg,color:b.fg}}>
                    {PA_STATUSES.map((s)=><option key={s} value={s}>{s}</option>)}
                  </select>
                  <div style={{display:'flex',gap:2,justifyContent:'flex-end',alignItems:'center'}}>
                    <button className="btn btn-sm" title={lang==='pt'?'Fotos da atividade':'Activity photos'}
                      onClick={()=>{ const fresh = (stRef.current && stRef.current.activities.find((x)=>x.id===a.id)) || a;
                        if (!fresh.dbId) { toast.push({ icon:'x', title:(lang==='pt'?'Aguarde o salvamento…':'Wait for the save…'), body:(lang==='pt'?'A atividade precisa estar salva para receber fotos.':'The activity must be saved before it can hold photos.') }); return; }
                        setGalAct(fresh); }}
                      style={{padding:'1px 4px',fontSize:9}}>📷{nFotos ? ' ' + nFotos : ''}</button>
                    {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Duplicar (fotos não são copiadas)':'Duplicate (photos are not copied)'} onClick={()=>paDupActivity(a.id)} style={{padding:'1px 4px',fontSize:9}}>⧉</button>}
                    {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Subir':'Up'} onClick={()=>paMoveActivity(a.id,-1)} style={{padding:'1px 4px',fontSize:9}}>▲</button>}
                    {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Descer':'Down'} onClick={()=>paMoveActivity(a.id,1)} style={{padding:'1px 4px',fontSize:9}}>▼</button>}
                    {canEdit && <button className="btn btn-sm" title={lang==='pt'?'Remover':'Remove'} onClick={()=>paDelActivity(a.id)} style={{padding:'1px 5px',fontSize:9,color:'#b03030'}}>✕</button>}
                  </div>
                </div>
              );
            })}
          </div>
        );
      })}

      {/* modal: importação da planilha — 3 botões reais (E3) */}
      {importAsk && (
        <div onClick={()=>setImportAsk(null)} style={overlay}>
          <div onClick={(e)=>e.stopPropagation()} style={{background:'var(--surface)',borderRadius:16,padding:'20px 22px',width:430,maxWidth:'100%',boxShadow:'var(--shadow-md)'}}>
            <div className="t-eyebrow" style={{marginBottom:10}}>{lang==='pt'?'Importar planilha':'Import spreadsheet'}</div>
            <div style={{fontSize:13,color:'var(--ink)',lineHeight:1.6,marginBottom:6}}>
              <strong>{importAsk.res.activities.length}</strong> {lang==='pt'?'atividade(s) encontrada(s) em':'activitie(s) found in'} <span style={{fontFamily:'var(--fm)',fontSize:11}}>{importAsk.fileName}</span>.
            </div>
            <div style={{fontSize:12,color:'var(--muted)',lineHeight:1.6,marginBottom:14}}>
              {lang==='pt'
                ? 'A lista atual tem ' + st.activities.length + ' atividade(s). Substituir descarta a lista atual; adicionar mantém e inclui só as novas (duplicadas são ignoradas).'
                : 'The current list has ' + st.activities.length + ' activitie(s). Replace discards the current list; append keeps it and adds only the new ones (duplicates are skipped).'}
            </div>
            <div style={{display:'flex',gap:8,justifyContent:'flex-end',flexWrap:'wrap'}}>
              <button className="btn btn-sm" onClick={()=>setImportAsk(null)}>{lang==='pt'?'Cancelar':'Cancel'}</button>
              <button className="btn btn-sm" onClick={()=>paApplyImport(importAsk,'append')}>{lang==='pt'?'Adicionar à lista':'Append to list'}</button>
              <button className="btn btn-sm" onClick={()=>paApplyImport(importAsk,'replace')} style={{color:'#b03030'}}>{lang==='pt'?'Substituir tudo':'Replace all'}</button>
            </div>
          </div>
        </div>
      )}

      {/* modal: galeria de fotos da atividade */}
      {galAct && (
        <div onClick={()=>{setGalAct(null);setUpState({});}} style={overlay}>
          <div onClick={(e)=>e.stopPropagation()} style={{background:'var(--surface)',borderRadius:16,padding:'18px 20px',width:660,maxWidth:'100%',maxHeight:'86vh',overflowY:'auto',boxShadow:'var(--shadow-md)'}}>
            <div style={{display:'flex',justifyContent:'space-between',alignItems:'flex-start',gap:10,marginBottom:10}}>
              <div style={{minWidth:0}}>
                <div className="t-eyebrow">{lang==='pt'?'Fotos da atividade':'Activity photos'}</div>
                <div style={{fontSize:13,fontWeight:600,color:'var(--ink)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{galAct.activity}</div>
              </div>
              <button className="btn btn-sm" onClick={()=>{setGalAct(null);setUpState({});}}>✕</button>
            </div>
            {canEdit && (
              <div style={{marginBottom:12,display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}}>
                <button className="btn btn-sm" onClick={()=>photoRef.current && photoRef.current.click()}>📷 {lang==='pt'?'Adicionar fotos':'Add photos'}</button>
                <span style={{fontFamily:'var(--fm)',fontSize:10,color:'var(--muted)'}}>{lang==='pt'?'comprimidas p/ ~1600px antes do envio':'compressed to ~1600px before upload'}</span>
                <input type="file" ref={photoRef} accept="image/*" multiple style={{display:'none'}} onChange={(e)=>{paUploadPhotos(e.target.files);e.target.value='';}}/>
              </div>
            )}
            {Object.keys(upState).map((k)=>(
              <div key={k} style={{fontFamily:'var(--fm)',fontSize:10,color:upState[k].indexOf('erro')>=0||upState[k].indexOf('error')>=0?'#b03030':'var(--muted)',marginBottom:3}}>{k.split('_')[0]} — {upState[k]}</div>
            ))}
            {galPhotos.length > 0 && (
              <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(150px,1fr))',gap:10}}>
                {galPhotos.map((p)=>(
                  <div key={p.id} style={{border:'1px solid var(--line-soft)',borderRadius:10,overflow:'hidden',background:'var(--surface-2)'}}>
                    <img src={p.url} onClick={()=>setZoom(p)} alt={p.caption||''} style={{width:'100%',height:104,objectFit:'cover',cursor:'zoom-in',display:'block'}}/>
                    <div style={{padding:'6px 8px'}}>
                      {canEdit
                        ? <input defaultValue={p.caption} placeholder={lang==='pt'?'legenda…':'caption…'} onBlur={(e)=>{ if (e.target.value !== p.caption) paSaveCaption(p, e.target.value); }}
                            style={{width:'100%',padding:'3px 5px',border:'1px solid var(--line-soft)',borderRadius:5,fontSize:10,background:'var(--surface)',color:'var(--ink)'}}/>
                        : <div style={{fontSize:10,color:'var(--muted)',minHeight:14}}>{p.caption || ''}</div>}
                      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginTop:4}}>
                        <span style={{fontFamily:'var(--fm)',fontSize:9,color:'var(--muted)'}}>{p.uploadedBy || ''}</span>
                        {canEdit && <button className="btn btn-sm" style={{padding:'0 5px',fontSize:9,color:'#b03030'}} onClick={()=>paDelPhoto(p)}>✕</button>}
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            )}
            {!galPhotos.length && !Object.keys(upState).length && (
              <div style={{padding:'18px 10px',textAlign:'center',fontSize:12,color:'var(--muted)'}}>{lang==='pt'?'Nenhuma foto ainda.':'No photos yet.'}</div>
            )}
          </div>
        </div>
      )}

      {/* lightbox */}
      {zoom && (
        <div onClick={()=>setZoom(null)} style={{position:'fixed',inset:0,zIndex:220,background:'rgba(0,0,0,0.85)',display:'flex',alignItems:'center',justifyContent:'center',cursor:'zoom-out',padding:20}}>
          <img src={zoom.url} alt={zoom.caption||''} style={{maxWidth:'100%',maxHeight:'100%',objectFit:'contain',borderRadius:8}}/>
          {zoom.caption && <div style={{position:'absolute',bottom:18,left:0,right:0,textAlign:'center',color:'#fff',fontSize:12,textShadow:'0 1px 3px rgba(0,0,0,.7)'}}>{zoom.caption}</div>}
        </div>
      )}
    </main>
  );
};
