// FOOTPASS Messenger v2 · Coffre-fort v3 · organisation « tableau de bord »
// Accueil (recherche rapide · activités récentes · à traiter · accès rapide · espace)
// → dossier entité (document clé · documents par domaine · activité)
// → domaine (liste + échéances) → accès par personne (blocs → feuille détaillée).
// Modèle : coffre-lab-model.jsx · feuilles doc/personne réutilisées de coffre-v2.jsx.
const { useState: useStateCV3, useMemo: useMemoCV3 } = React;

const CfV3Bell = ({ s = 15 }) => (
  <svg width={s} height={s} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round">
    <path d="M8 1.8a4.1 4.1 0 00-4.1 4.1v2.3L2.6 10.9h10.8l-1.3-2.7V5.9A4.1 4.1 0 008 1.8z"></path>
    <path d="M6.5 13a1.6 1.6 0 003 0"></path>
  </svg>
);

// Notifications · bidirectionnelles : la personne notifiée ET le propriétaire
const CF3_NOTIFS_INIT = [
  { id: 'n1', k: 'view', who: 'Lucas FERREIRA', verb: 'a consulté', docT: '« Contrat professionnel · AS Monaco »', when: 'hier · 21:04' },
  { id: 'n2', k: 'ok', who: 'Sira KEÏTA', verb: 'a été notifiée · Exporter accordé sur', docT: '« Facture honoraires · AS Monaco »', when: '2 juil' },
  { id: 'n3', k: 'view', who: 'Amadou MBAYE', verb: 'a ouvert', docT: '« Autorisation · co-représentation »', when: '10 juil' },
];

// Corps de documents réalistes par catégorie : v2/coffre-v3-papers.jsx (CfV3PaperBody, cf3PartB, cf3PartyA · globaux).

// ── Lecture d'un document : texte réel + état des signatures ──
function CfV3DocView({ doc, onClose, onSign, toast, dark, startExport }) {
  const signed = doc.state === 'signed' || doc.state === 'ok';
  const [exporting, setExporting] = useStateCV3(!!startExport);
  const [saving, setSaving] = useStateCV3(!!startExport); // feuille de partage façon iPhone
  const sigImg = (() => { try { return localStorage.getItem('fp_signature'); } catch (e) { return null; } })();
  return (
    <div className="cf3-signwrap" data-screen-label="Document ouvert">
      <FPStatusBar light={dark} />
      <div className="cf3-signhead">
        <button className="cf-back" onClick={onClose} aria-label="Fermer"><CFI.Chevron /></button>
        <span className="cf3-signtitle"><b>Document</b><em>{doc.title}</em></span>
        <CfPill state={doc.state} />
      </div>
      <div className="cf3-paperwrap">
        <CfV3PaperBody doc={doc}>
          {doc.cat === 'jur' ? (
          <div className="cf3-sigrow">
            <div className="cf3-sigcell">
              <i>Pour la Partie B</i>
              {signed || doc.state === 'sign' ? <span className="cf3-ink small">{cf3PartBSafe(doc)}</span> : <span className="cf3-usc"></span>}
              <u>{signed || doc.state === 'sign' ? 'Le 12/07/2026' : 'Date : __/__/____'}</u>
            </div>
            <div className="cf3-sigcell">
              <i>La Partie A</i>
              {signed ? <span className="cf3-ink small">{cfEntLabel(doc.ent)}</span> : <span className="cf3-usc"></span>}
              <u>{signed ? 'Le 13/07/2026' : 'Date : __/__/____'}</u>
            </div>
            <div className="cf3-sigcell">
              <i>Le Représentant</i>
              {signed ? (sigImg ? <img className="cf3-sigimg sm" src={sigImg} alt="Signature" /> : <span className="cf3-ink small">{FP_ME.name || 'Alexandre Da Costa'}</span>) : <span className="cf3-usc"></span>}
              <u>{window.fpUpperLast(FP_ME.name || 'Alexandre Da Costa')}{signed ? ' · le 13/07/2026' : doc.state === 'sign' ? ' · signature attendue' : ''}</u>
            </div>
          </div>
          ) : null}
        </CfV3PaperBody>
      </div>
      <div className="cf3-signacts">
        {doc.state === 'sign' && onSign ? <button className="cf-btn primary" onClick={() => onSign(doc.id)}><CFI.Pen s={14} /> Signer maintenant</button> : null}
        <button className="cf-btn" onClick={() => { setExporting(true); setSaving(true); }}><CFI.Wm s={14} /> Exporter · PDF</button>
      </div>
      {exporting ? (
        <div className="cf3-exwrap">
          <FPStatusBar light={dark} />
          <div className="cf3-signhead">
            <button className="cf-back" onClick={() => setExporting(false)} aria-label="Fermer"><CFI.Chevron /></button>
            <span className="cf3-signtitle"><b>Export · PDF</b><em>Filigrane FOOTPASS intégré · consigné au journal</em></span>
          </div>
          <div className="cf3-paperwrap">
            <div className="cf3-expage">
              <CfV3PaperBody doc={doc} />
            </div>
          </div>
          <div className="cf3-signacts">
            <button className="cf-btn primary" onClick={() => setSaving(true)}><CFI.Wm s={14} /> Enregistrer / Partager…</button>
          </div>
          {saving ? (
            <div className="cf-sheetwrap" style={{ zIndex: 60 }}>
              <button className="cf-dim" onClick={() => setSaving(false)} aria-label="Fermer"></button>
              <div className="cf-sheet">
                <span className="cf-grab"></span>
                <div className="cf3-iosdoc">
                  <span className="cf3-iosic">PDF</span>
                  <span className="cf3-iost"><b>{doc.title}.pdf</b><em>Document PDF · filigrane FOOTPASS · 1 page · 182 Ko</em></span>
                </div>
                <div className="cf3-iosgrp">
                  {[
                    ['Enregistrer dans Fichiers', CFI.FolderIc, '« ’ + PDF + ’ » enregistré dans Fichiers'],
                    ['Envoyer via Messages FOOTPASS', CFI.Share, 'Envoyé dans Messages · chiffré'],
                    ['WhatsApp', CFI.Share, 'Partagé via WhatsApp · PDF filigrané'],
                    ['Mail', CFI.Doc, 'Brouillon Mail créé avec la pièce jointe'],
                    ['Copier', CFI.Wm, 'PDF copié dans le presse-papiers'],
                  ].map(([lb, Ic, msg]) => (
                    <button key={lb} className="cf3-iosrow" onClick={() => { setSaving(false); toast(msg.replace('’ + PDF + ’', doc.title + '.pdf')); }}>
                      <span>{lb}</span><Ic s={15} />
                    </button>
                  ))}
                </div>
              </div>
            </div>
          ) : null}
        </div>
      ) : null}
    </div>
  );
}

// ── Écran de signature : aperçu du document + encre + confirmation (raccourci « à signer ») ──
// ── Pad de signature manuscrite (canvas tactile) ──
function CfV3SignPad({ onChange }) {
  const ref = React.useRef(null);
  const drawing = React.useRef(false);
  const dirty = React.useRef(false);
  const pos = (e, c) => { const r = c.getBoundingClientRect(); const t = e.touches ? e.touches[0] : e; return { x: (t.clientX - r.left) * (c.width / r.width), y: (t.clientY - r.top) * (c.height / r.height) }; };
  const start = (e) => { const c = ref.current; const ctx = c.getContext('2d'); drawing.current = true; const p = pos(e, c); ctx.beginPath(); ctx.moveTo(p.x, p.y); e.preventDefault(); };
  const move = (e) => {
    if (!drawing.current) return;
    const c = ref.current; const ctx = c.getContext('2d');
    ctx.lineWidth = 3.2; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.strokeStyle = '#14141A';
    const p = pos(e, c); ctx.lineTo(p.x, p.y); ctx.stroke(); dirty.current = true; e.preventDefault();
  };
  const end = () => { if (drawing.current && dirty.current) onChange(ref.current.toDataURL('image/png')); drawing.current = false; };
  const clear = () => { const c = ref.current; c.getContext('2d').clearRect(0, 0, c.width, c.height); dirty.current = false; onChange(null); };
  return (
    <div className="cf3-pad">
      <canvas ref={ref} width={520} height={168}
        onMouseDown={start} onMouseMove={move} onMouseUp={end} onMouseLeave={end}
        onTouchStart={start} onTouchMove={move} onTouchEnd={end}></canvas>
      <div className="cf3-padrow"><i>Dessinez votre signature dans le cadre</i><button type="button" onClick={clear}>Effacer</button></div>
    </div>
  );
}

function CfV3Signer({ doc, onClose, onSigned, dark }) {
  // La signature se dessine UNE fois : enregistrée (fp_signature), réutilisée ensuite automatiquement.
  const [sig, setSig] = useStateCV3(() => { try { return localStorage.getItem('fp_signature') || null; } catch (e) { return null; } });
  const [draft, setDraft] = useStateCV3(null);
  const [redo, setRedo] = useStateCV3(false);
  const saved = sig && !redo;
  const ready = saved || !!draft;
  const confirm = () => {
    const finalSig = saved ? sig : draft;
    if (!finalSig) return;
    try { localStorage.setItem('fp_signature', finalSig); } catch (e) { /* ignore */ }
    setSig(finalSig);
    onSigned(doc.id, finalSig);
  };
  return (
    <div className="cf3-signwrap" data-screen-label="Signature">
      <FPStatusBar light={dark} />
      <div className="cf3-signhead">
        <button className="cf-back" onClick={onClose} aria-label="Fermer"><CFI.Chevron /></button>
        <span className="cf3-signtitle"><b>Signature</b><em>{doc.title}</em></span>
        <CfPill state={doc.state} />
      </div>
      <div className="cf3-paperwrap">
        <CfV3PaperBody doc={doc}>
          {saved ? (
            <div className="cf3-signbox ok">
              <img className="cf3-sigimg" src={sig} alt="Votre signature enregistrée" />
              <i>{window.fpUpperLast(FP_ME.name || 'Alexandre Da Costa')} · Pass N° {FP_ME.passNo || '2480 1196 0049'}</i>
              <button type="button" className="cf3-relink" onClick={() => { setRedo(true); setDraft(null); }}>Redessiner ma signature</button>
            </div>
          ) : (
            <div className="cf3-signbox draw">
              <CfV3SignPad onChange={setDraft} />
              <i>{window.fpUpperLast(FP_ME.name || 'Alexandre Da Costa')} · Pass N° {FP_ME.passNo || '2480 1196 0049'} · imprimé sous la signature</i>
            </div>
          )}
        </CfV3PaperBody>
        {!saved ? <p className="cf3-signnote">Dessinée une seule fois : votre signature est enregistrée et réutilisée pour les prochains documents.</p> : null}
      </div>
      <div className="cf3-signacts">
        <button className="cf-btn primary" disabled={!ready} onClick={confirm}><CFI.Pen s={14} /> Confirmer la signature</button>
        <button className="cf-btn" onClick={onClose}>Refuser de signer</button>
      </div>
    </div>
  );
}

// ── Feuille d'import · guidée : 1 fichiers (illimités) → 2 tags → 3 catégorie ──
// Ce qu'on met dans le coffre nous APPARTIENT · pas de « propriétaire » à choisir.
// Les TAGS disent qui ou quoi est concerné (agence, joueur, club, mots libres) :
// suggérés automatiquement d'après le nom des fichiers, créables à la volée.
const CF3_ENT_OPTS = ['me', 'lucas', 'mathis', 'amadou', 'karim', 'mufc', 'david', 'marco', 'sira'];
const cf3GuessCat = (name) => {
  const n = cfDeacc(name);
  if (/contrat|mandat|avenant|accord|clause|litige|nda|juridique/.test(n)) return 'jur';
  if (/irm|medical|bilan|examen|sante|blessure|radio|vaccin/.test(n)) return 'med';
  if (/clip|video|stat|data|match|perf|gps|seance/.test(n)) return 'perf';
  if (/facture|billet|visa|passeport|admin|recu|hebergement|vol/.test(n)) return 'adm';
  if (/photo|logo|presse|com|media|visuel|interview|campagne/.test(n)) return 'com';
  return null;
};

function CfV3ImportSheet({ onClose, onAdd }) {
  const [items, setItems] = useStateCV3([]); // { f: File, tags: [{ t, v, auto? }], cat, guessed } · tags & catégorie PAR fichier
  const [selIdx, setSelIdx] = useStateCV3(0);
  const [eq, setEq] = useStateCV3('');
  const [preview, setPreview] = useStateCV3(null); // aperçu du fichier { name, url, kind }
  const [catNew, setCatNew] = useStateCV3(null); // saisie d'une nouvelle catégorie (null = fermée)
  const fmt = (b) => b >= 1048576 ? (b / 1048576).toFixed(1).replace('.', ',') + ' Mo' : Math.max(1, Math.round(b / 1024)) + ' Ko';
  // Tags auto d'après le nom DU fichier (entités reconnues)
  const autoTags = (name) => {
    const txt = cfDeacc(name);
    return CF3_ENT_OPTS.filter((k) => {
      const words = cfDeacc(k === 'me' ? 'agence' : FP_PEERS[k].name).split(' ');
      return words.some((w) => w.length > 3 && txt.includes(w));
    }).map((k) => ({ t: 'ent', v: k, auto: true }));
  };
  // Ajout de fichiers · illimité · chaque fichier reçoit ses propres tags
  const addPick = (e) => {
    const l = Array.from(e.target.files || []);
    e.target.value = '';
    if (!l.length) return;
    setItems((prev) => { setSelIdx(prev.length); return [...prev, ...l.map((f) => { const g = cf3GuessCat(f.name); return { f, tags: autoTags(f.name), cat: g, guessed: !!g }; })]; });
  };
  const cur = items[selIdx] || null;
  const setCurTags = (fn) => setItems(items.map((it, i) => (i === selIdx ? { ...it, tags: fn(it.tags) } : it)));
  const setCurCat = (k) => setItems(items.map((it, i) => (i === selIdx ? { ...it, cat: k, guessed: false } : it)));
  const toggleEnt = (k) => setCurTags((tg) => tg.some((x) => x.t === 'ent' && x.v === k) ? tg.filter((x) => !(x.t === 'ent' && x.v === k)) : [...tg, { t: 'ent', v: k }]);
  const createTag = () => {
    const v = eq.trim();
    if (!v || !cur) return;
    setCurTags((tg) => tg.some((x) => x.t === 'libre' && cfDeacc(x.v) === cfDeacc(v)) ? tg : [...tg, { t: 'libre', v }]);
    setEq('');
  };
  const removeFile = (i) => { setItems(items.filter((_, j) => j !== i)); setSelIdx((s) => Math.max(0, s > i ? s - 1 : Math.min(s, items.length - 2))); };
  const openPrev = (it) => {
    const t = it.f.type || '';
    setPreview({ name: it.f.name, url: URL.createObjectURL(it.f), kind: t.indexOf('image/') === 0 ? 'img' : t === 'application/pdf' ? 'pdf' : 'none' });
  };
  const closePrev = () => { if (preview) { try { URL.revokeObjectURL(preview.url); } catch (e) { /* ignore */ } } setPreview(null); };
  const tagLbl = (x) => (x.t === 'ent' ? cfEntLabel(x.v) : x.v);
  const neq = cfDeacc(eq.trim());
  const opts = CF3_ENT_OPTS.filter((k) => !neq || cfDeacc(cfEntLabel(k) + ' ' + cfEntSub(k)).includes(neq));
  const noCat = items.filter((it) => !it.cat).length;
  const missing = !noCat ? null : (cur && !cur.cat ? 'Choisissez la catégorie de ce fichier.' : noCat + (noCat > 1 ? ' fichiers sont' : ' fichier est') + ' sans catégorie : touchez sa ligne pour la régler.');
  // Catégorie personnalisée · ajoutée au registre CF_CATS (session), sélectionnée aussitôt
  const addCat = () => {
    const v = (catNew || '').trim();
    if (!v) { setCatNew(null); return; }
    const k = 'x_' + (cfDeacc(v).replace(/[^a-z0-9]+/g, '_').slice(0, 18) || 'cat');
    if (!CF_CATS[k]) CF_CATS[k] = { label: v, hue: 45, Icon: CFI.FolderIc };
    setCurCat(k); setCatNew(null);
  };
  return (
    <div className="cf-sheetwrap">
      <button className="cf-dim" onClick={onClose} aria-label="Fermer"></button>
      <div className="cf-sheet">
        <span className="cf-grab"></span>
        <div className="cf-sh-head">
          <span className="cf-doc-ic" style={{ width: 42, height: 42 }}><CFI.Import s={18} /></span>
          <span className="cf-sh-t"><b>Ajouter au coffre</b><em className="cf3-oneline">Tout type de fichier, chiffré et à l’abri.</em></span>
        </div>

        {/* 1 · Fichiers · illimités · toucher un fichier = le sélectionner pour le taguer · tags affichés sous chaque ligne */}
        <div className="cf-sechead cf3-shead" style={{ marginTop: 14 }}><span>1 · Fichiers</span>{items.length ? <b style={{ cursor: 'default' }}>{items.length}</b> : null}</div>
        {!items.length ? (
          <label className="cf3-pick">
            <input type="file" multiple onChange={addPick} />
            <CFI.Import s={20} />
            <b>Choisir des fichiers…</b>
            <em>PDF, images, vidéos, documents, scans : tout format est accepté.</em>
          </label>
        ) : (
          <div className="cf3-flist">
            {items.map((it, i) => (
              <div key={i} className={`cf3-fitem ${i === selIdx ? 'sel' : ''}`} onClick={() => setSelIdx(i)} role="button" tabIndex={0}>
                <div className="cf3-frow2">
                  <CFI.Doc s={14} />
                  <span className="cf3-fname">{it.f.name}</span>
                  <em>{fmt(it.f.size)}</em>
                  <button className="cf3-fbtn" onClick={(e) => { e.stopPropagation(); openPrev(it); }} aria-label="Aperçu du fichier"><CFI.Eye s={13} /></button>
                  <button className="cf3-fbtn" onClick={(e) => { e.stopPropagation(); removeFile(i); }} aria-label="Retirer ce fichier">×</button>
                </div>
                {(it.cat || it.tags.length || i === selIdx) ? (
                  <div className="cf3-ftags">
                    {it.cat ? <span className="cf3-fcat"><i style={{ background: cfCatColor(it.cat) }}></i>{CF_CATS[it.cat].label}</span> : null}
                    {it.tags.map((x, j) => (
                      <span key={j} className={`cf3-tag sm ${x.auto ? 'auto' : ''}`}>
                        {tagLbl(x)}
                        <button onClick={(e) => { e.stopPropagation(); setItems(items.map((v, vi) => (vi === i ? { ...v, tags: v.tags.filter((_, jj) => jj !== j) } : v))); }} aria-label="Retirer le tag">×</button>
                      </span>
                    ))}
                    {!it.tags.length && i === selIdx ? <span className="cf3-notag">{it.cat ? 'Aucun tag, taguez ci-dessous' : 'Taguez et catégorisez ci-dessous'}</span> : null}
                  </div>
                ) : null}
              </div>
            ))}
            <label className="cf3-addmore">
              <input type="file" multiple onChange={addPick} />
              <CFI.Plus s={13} /> Ajouter des fichiers
            </label>
          </div>
        )}

        {/* 2 · Tags DU fichier sélectionné · chaque fichier a les siens */}
        {cur ? (
          <div className="cf3-step">
            <div className="cf-sechead cf3-shead" style={{ marginTop: 18 }}><span>2 · Tags</span>{cur.tags.some((x) => x.auto) ? <b style={{ cursor: 'default' }}>suggérés automatiquement</b> : null}</div>
            <p className="cf2-hint" style={{ margin: '0 2px 8px' }}>
              {items.length > 1
                ? <React.Fragment>Vous taguez <b>{cur.f.name}</b>. Touchez un autre fichier pour changer.</React.Fragment>
                : 'Ce document vous appartient. Les tags disent qui ou quoi est concerné, pour tout retrouver en un geste.'}
            </p>
            <label className="cf-search" style={{ margin: '0 0 7px', height: 38 }}>
              <CFI.Search s={13} />
              <input value={eq} onChange={(e) => setEq(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') createTag(); }} placeholder="Taguer : joueur, club, projet, mot-clé…" />
              {eq ? <button onClick={() => setEq('')} aria-label="Effacer">×</button> : null}
            </label>
            <div className="cf3-erows scroll">
              {opts.map((k) => {
                const p = k === 'me' ? null : FP_PEERS[k];
                const on = cur.tags.some((x) => x.t === 'ent' && x.v === k);
                return (
                  <button key={k} className={`cf2-prow ${on ? 'sel' : ''}`} onClick={() => toggleEnt(k)}>
                    {p ? <FPAvatar size={28} initials={p.initials} src={p.photo} /> : <span className="cf2-orgav" style={{ width: 28, height: 28 }}>{FP_ME.initials || 'AD'}</span>}
                    <span className="cf2-prow-t"><b>{cfEntLabel(k)}</b><em>{cfEntSub(k)}</em></span>
                    <span className="cf3-echeck"><CFI.Check s={10} /></span>
                  </button>
                );
              })}
              {neq ? (
                <button className="cf2-prow" onClick={createTag}>
                  <span className="cf3-newtag"><CFI.Plus s={13} /></span>
                  <span className="cf2-prow-t"><b>Créer le tag « {eq.trim()} »</b><em>Tag libre, réutilisable pour la recherche</em></span>
                </button>
              ) : null}
              {!opts.length && !neq ? <div className="cf2-empty">Aucune entité ne correspond.</div> : null}
            </div>
          </div>
        ) : null}

        {/* 3 · Catégorie DU fichier sélectionné · suggérée d'après son nom, modifiable */}
        {cur ? (
          <div className="cf3-step">
            <div className="cf-sechead cf3-shead" style={{ marginTop: 18 }}><span>3 · Catégorie du fichier sélectionné</span></div>
            <div className="cf2-fams cf3-cats">
              {Object.keys(CF_CATS).map((k) => (
                <button key={k} className={`cf2-fam ${cur.cat === k ? 'on' : ''}`} onClick={() => setCurCat(k)}>
                  <i style={{ background: cfCatColor(k) }}></i>{CF_CATS[k].label}{cur.cat === k ? <CFI.Check s={9} /> : null}
                </button>
              ))}
              {catNew !== null ? (
                <span className="cf2-fam on cf3-catnew">
                  <input value={catNew} onChange={(e) => setCatNew(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') addCat(); }} placeholder="Nouvelle catégorie…" autoFocus />
                  <button onClick={addCat} aria-label="Créer la catégorie"><CFI.Check s={10} /></button>
                  <button onClick={() => setCatNew(null)} aria-label="Annuler">×</button>
                </span>
              ) : (
                <button className="cf2-fam cf3-catplus" onClick={() => setCatNew('')} aria-label="Ajouter une catégorie"><CFI.Plus s={11} /></button>
              )}
            </div>
          </div>
        ) : null}

        {missing ? <p className="cf3-miss">{missing}</p> : null}
        <div className="cf-sh-acts" style={{ marginTop: missing ? 6 : 14 }}>
          <button className="cf-btn primary" disabled={!items.length || noCat > 0} onClick={() => onAdd(items)}>
            <CFI.Lock s={14} /> Ajouter au coffre{items.length > 1 ? ' · ' + items.length + ' fichiers' : ''}
          </button>
        </div>
      </div>
      {preview ? (
        <div className="cf3-prev" onClick={closePrev}>
          <div className="cf3-prevcard" onClick={(e) => e.stopPropagation()}>
            <div className="cf3-prevhead"><b>{preview.name}</b><button onClick={closePrev} aria-label="Fermer l’aperçu">×</button></div>
            {preview.kind === 'img' ? <img src={preview.url} alt={preview.name} /> : preview.kind === 'pdf' ? <iframe src={preview.url} title={preview.name}></iframe> : (
              <div className="cf3-prevnone"><CFI.Doc s={26} /><span>Aperçu indisponible pour ce format · le fichier sera chiffré tel quel.</span></div>
            )}
          </div>
        </div>
      ) : null}
    </div>
  );
}

// Activités récentes (accueil) : le fil des dernières semaines, du plus récent au plus ancien
const CF3_RECENT = [
  { id: 'd4', when: 'aujourd’hui', act: 'Reçue pour signature' },
  { id: 'd2', when: 'hier', act: 'Modifié par Sira Keïta' },
  { id: 'e41', when: '6 juil', act: 'Reçu de Manchester United' },
  { id: 'e14', when: '5 juil', act: 'Brouillon relu' },
  { id: 'd9', when: '3 juil', act: '12 clips ajoutés' },
  { id: 'd11', when: '2 juil', act: 'Reçu du FC Nantes' },
  { id: 'e28', when: '2 juil', act: 'Réglée' },
  { id: 'e44', when: '1 juil', act: 'Partagé par Marco Silva' },
  { id: 'e11', when: '1 juil', act: 'Mis à jour par le kiné' },
  { id: 'e05', when: '30 juin', act: 'Ajouté par le staff' },
  { id: 'e38', when: '30 juin', act: 'Draft v2 reçu' },
  { id: 'e15', when: '28 juin', act: 'Reçu de Nike' },
  { id: 'e36', when: '25 juin', act: 'Reçue' },
  { id: 'e06', when: '24 juin', act: 'Ajoutés' },
  { id: 'e29', when: '22 juin', act: 'Ajoutée' },
  { id: 'e19', when: '20 juin', act: '9 clips ajoutés' },
  { id: 'e24', when: '15 juin', act: 'Publié' },
  { id: 'e40', when: '12 juin', act: 'Reçue · confidentielle' },
  { id: 'e09', when: '8 juin', act: 'Ajoutée' },
];

// ── Jeu de démo étendu (~55 documents au total) : filtres, tags et dossiers concrets ──
const CF3_DOCS_EXTRA = [
  { id: 'e01', title: 'Avenant sponsoring · Nike', ent: 'lucas', cat: 'jur', state: 'signed', meta: 'Signé · 12 mai 2026', size: '640 Ko', hash: '11A4 · 8B02 · C7D1 · 3E90', tags: ['Sponsoring', 'Nike'], access: [{ pid: 'sira', rights: ['view'] }] },
  { id: 'e02', title: 'Convention image · saison 25-26', ent: 'lucas', cat: 'jur', state: 'signed', meta: 'Signée · 1 août 2025', size: '710 Ko', hash: '2D19 · 44FA · 90BC · A711', tags: ['Image', 'Saison 25-26'], access: [] },
  { id: 'e03', title: 'Accord de confidentialité · transfert', ent: 'lucas', cat: 'jur', state: null, meta: 'Reçu le 12 juin', size: '380 Ko', hash: '7C02 · E1B8 · 55D0 · 9A34', tags: ['Transfert', 'Mercato 2026'], access: [{ pid: 'sira', rights: ['view'] }] },
  { id: 'e04', title: 'Procuration bancaire', ent: 'lucas', cat: 'jur', state: 'ok', meta: 'Vérifiée · notaire Monaco', size: '290 Ko', hash: 'F310 · 0C77 · 21AB · D6E2', access: [] },
  { id: 'e05', title: 'Rapport GPS · juin 2026', ent: 'lucas', cat: 'perf', state: null, meta: 'Ajouté le 30 juin', size: '3,1 Mo', hash: '8E44 · A902 · 17CD · 4B60', tags: ['Saison 25-26'], access: [{ pid: 'david', rights: ['view'] }] },
  { id: 'e06', title: 'Tests physiques · reprise', ent: 'lucas', cat: 'perf', state: null, meta: 'Ajoutés le 24 juin', size: '1,8 Mo', hash: '5A21 · C3F0 · 88D2 · E917', access: [] },
  { id: 'e07', title: 'Analyse vidéo · demi-finale', ent: 'lucas', cat: 'perf', state: null, meta: '14 clips · 12 mai', size: '420 Mo', hash: '90B3 · 6DE1 · F24A · 07C8', tags: ['Vidéo'], access: [] },
  { id: 'e08', title: 'Plan de préparation · été 2026', ent: 'lucas', cat: 'perf', state: null, meta: 'Partagé par le staff', size: '860 Ko', hash: '3F70 · 12A9 · BD05 · 66E4', access: [] },
  { id: 'e09', title: 'Échographie · mollet gauche', ent: 'lucas', cat: 'med', state: null, meta: 'Confidentiel · 8 juin', size: '2,4 Mo', hash: 'C912 · 7B40 · 03EF · A852', access: [{ pid: 'lucas', rights: ['view'] }] },
  { id: 'e10', title: 'Bilan sanguin · juin', ent: 'lucas', cat: 'med', state: null, meta: 'Vous et Lucas uniquement', size: '540 Ko', hash: '1E88 · 95C2 · D07B · 34F1', access: [{ pid: 'lucas', rights: ['view'] }] },
  { id: 'e11', title: 'Protocole récupération', ent: 'lucas', cat: 'med', state: null, meta: 'Kiné · mis à jour 1 juil', size: '410 Ko', hash: '6B05 · E7A3 · 29D8 · C140', access: [] },
  { id: 'e12', title: 'Visa Royaume-Uni', ent: 'lucas', cat: 'adm', state: 'ok', meta: 'Valable → mars 2027', size: '1,2 Mo', hash: 'A227 · 40BD · 8E13 · F5C9', tags: ['Voyage'], access: [] },
  { id: 'e13', title: 'Billets · stage Algarve', ent: 'lucas', cat: 'adm', state: null, meta: 'Aller-retour · 4 pers.', size: '380 Ko', hash: 'D84E · 19F2 · A60B · 73C5', tags: ['Stage Algarve', 'Voyage'], access: [] },
  { id: 'e14', title: 'Interview · France Football', ent: 'lucas', cat: 'com', state: null, meta: 'Brouillon relu · 5 juil', size: '96 Ko', hash: '47A0 · B3D6 · E29C · 1085', tags: ['Presse'], access: [] },
  { id: 'e15', title: 'Brief campagne équipementier', ent: 'lucas', cat: 'com', state: null, meta: 'Reçu de Nike · 28 juin', size: '5,7 Mo', hash: '92C4 · D50A · 3B78 · 6EF1', tags: ['Sponsoring', 'Nike'], access: [] },
  { id: 'e16', title: 'Accord image · ancien équipementier', ent: 'lucas', cat: 'jur', state: 'expired', meta: 'Échu le 31 déc 2025', size: '680 Ko', hash: '0D93 · A417 · C85E · 2BF0', tags: ['Image'], access: [] },
  { id: 'e17', title: 'Convention de formation · FC Nantes', ent: 'mathis', cat: 'jur', state: 'signed', meta: 'Signée par 3 parties', size: '1,1 Mo', hash: 'B160 · 5C9D · E742 · 08A3', tags: ['U19'], access: [{ pid: 'mathis', rights: ['view'] }] },
  { id: 'e18', title: 'Autorisation de représentation · Mathis', ent: 'mathis', cat: 'jur', state: 'signed', soon: 21, meta: 'Renouvellement à préparer', size: '760 Ko', hash: '78E2 · D04B · A391 · C56F', access: [{ pid: 'mathis', rights: ['view'] }] },
  { id: 'e19', title: 'Clips U19 · phase retour', ent: 'mathis', cat: 'perf', state: null, meta: '9 clips · ajoutés le 20 juin', size: '310 Mo', hash: 'E50C · 27A8 · 94BD · 13F6', tags: ['U19', 'Vidéo'], access: [] },
  { id: 'e20', title: 'Rapport · pôle espoirs', ent: 'mathis', cat: 'perf', state: null, meta: 'Reçu de la fédération', size: '1,4 Mo', hash: '39B7 · F1D0 · 62CA · 85E4', tags: ['Sélection'], access: [] },
  { id: 'e21', title: 'Certificat · surclassement', ent: 'mathis', cat: 'med', state: 'ok', meta: 'Validé médecin fédéral', size: '220 Ko', hash: 'C0A5 · 4E92 · D71B · 68F3', tags: ['U19'], access: [] },
  { id: 'e22', title: 'Carte d’identité · scan certifié', ent: 'mathis', cat: 'adm', state: 'ok', meta: 'Vérifié par Veriff', size: '1,6 Mo', hash: '52D8 · B36E · 09A4 · E7C1', access: [] },
  { id: 'e23', title: 'Autorisation parentale · déplacements', ent: 'mathis', cat: 'adm', state: 'signed', meta: 'Signée · saison 25-26', size: '340 Ko', hash: '86F4 · 1A0D · C592 · 3BE7', tags: ['U19', 'Voyage'], access: [] },
  { id: 'e24', title: 'Portrait · magazine du club', ent: 'mathis', cat: 'com', state: null, meta: 'Publié le 15 juin', size: '4,2 Mo', hash: 'A9E1 · 60C7 · D48B · 25F0', tags: ['Presse'], access: [] },
  { id: 'e25', title: 'Statuts · Classico Sport', ent: 'me', cat: 'jur', state: 'ok', meta: 'Dépôt Genève · 2023', size: '980 Ko', hash: '14C6 · 87BA · E03D · 59F2', tags: ['Agence'], access: [] },
  { id: 'e26', title: 'Contrat de travail · Sira Keïta', ent: 'me', cat: 'jur', state: 'signed', meta: 'Signé · 3 janv 2024', size: '620 Ko', hash: 'D3B0 · 2E85 · 71FC · A496', tags: ['Agence'], access: [{ pid: 'sira', rights: ['view'] }] },
  { id: 'e27', title: 'Assurance RC professionnelle', ent: 'me', cat: 'jur', state: 'signed', soon: 45, meta: 'Renouvellement · 27 août', size: '830 Ko', hash: '68A9 · F5C1 · 30DE · B274', tags: ['Agence'], access: [] },
  { id: 'e28', title: 'Facture honoraires · AS Monaco', ent: 'me', cat: 'adm', state: null, meta: 'Réglée le 2 juil', size: '180 Ko', hash: '95D2 · 0B47 · C6E8 · 13AF', tags: ['Agence'], access: [{ pid: 'sira', rights: ['view', 'export'] }] },
  { id: 'e29', title: 'Note de frais · Madrid', ent: 'me', cat: 'adm', state: null, meta: 'Déplacement · 22 juin', size: '240 Ko', hash: '3CE7 · A180 · 54B9 · D62F', tags: ['Voyage'], access: [] },
  { id: 'e30', title: 'Bail · bureaux Genève', ent: 'me', cat: 'adm', state: 'signed', meta: 'Signé → 2028', size: '1,3 Mo', hash: 'B742 · 68D5 · F90A · 21CE', tags: ['Agence'], access: [] },
  { id: 'e31', title: 'Plaquette agence · 2026', ent: 'me', cat: 'com', state: null, meta: 'Version 3 · avril', size: '12,4 Mo', hash: 'E815 · 3FA2 · 07C9 · 64BD', tags: ['Agence'], access: [] },
  { id: 'e32', title: 'Communiqué · signature Mathis', ent: 'me', cat: 'com', state: null, meta: 'Diffusé le 17 déc', size: '150 Ko', hash: '4A6D · C297 · 85E0 · F13B', tags: ['Presse', 'U19'], access: [] },
  { id: 'e33', title: 'Base scouting · export trimestriel', ent: 'me', cat: 'perf', state: null, meta: 'Interne · avril', size: '48 Mo', hash: '07F8 · 5B31 · A2D4 · 96CE', tags: ['Scouting'], access: [] },
  { id: 'e34', title: 'Autorisation échue · Bakary Cissé', ent: 'me', cat: 'jur', state: 'expired', meta: 'Échu le 30 juin 2025', size: '590 Ko', hash: '61C0 · D8A4 · 32EF · 7B95', access: [] },
  { id: 'e35', title: 'Accord de co-représentation · cadre', ent: 'amadou', cat: 'jur', state: 'signed', meta: 'Signé · 10 juil', size: '720 Ko', hash: '2B9E · 70C3 · F5A8 · D041', tags: ['Classico Sport'], access: [{ pid: 'amadou', rights: ['view'] }] },
  { id: 'e36', title: 'Facture · partenariat Dakar', ent: 'amadou', cat: 'adm', state: null, meta: 'Reçue le 25 juin', size: '210 Ko', hash: '8D52 · E406 · 19BC · A37F', tags: ['Partenariat'], access: [] },
  { id: 'e37', title: 'Annonce partenariat · brouillon', ent: 'amadou', cat: 'com', state: null, meta: 'À relire ensemble', size: '88 Ko', hash: 'C6E3 · 12D9 · 74AB · 50F8', tags: ['Partenariat', 'Presse'], access: [{ pid: 'amadou', rights: ['view'] }] },
  { id: 'e38', title: 'Protocole d’accord · FC Nantes', ent: 'karim', cat: 'jur', state: null, meta: 'Draft v2 · 30 juin', size: '540 Ko', hash: '39A7 · B0E5 · C821 · 6DF4', tags: ['Transfert'], access: [] },
  { id: 'e39', title: 'Invitation · loge VIP', ent: 'karim', cat: 'adm', state: null, meta: 'Match du 9 août', size: '120 Ko', hash: 'F04B · 57C8 · A9D2 · 83E6', tags: ['Voyage'], access: [] },
  { id: 'e40', title: 'Shortlist attaquants · U21', ent: 'karim', cat: 'perf', state: null, meta: 'Confidentiel · 12 juin', size: '2,2 Mo', hash: '6E19 · D3F7 · 04AC · B285', tags: ['Scouting', 'U19'], access: [] },
  { id: 'e41', title: 'Term sheet · prêt avec option', ent: 'mufc', cat: 'jur', state: null, meta: 'Reçu le 6 juil', size: '460 Ko', hash: '85C2 · F613 · 2A0D · 97BE', tags: ['Transfert', 'Mercato 2026'], access: [{ pid: 'sira', rights: ['view'] }] },
  { id: 'e42', title: 'Brief média · visite Manchester', ent: 'mufc', cat: 'com', state: null, meta: 'Proposé le 4 juil', size: '340 Ko', hash: '10D6 · 4CB9 · E782 · A5F3', tags: ['Presse', 'Voyage'], access: [] },
  { id: 'e43', title: 'Rapport de suivi · Real Madrid', ent: 'david', cat: 'perf', state: null, meta: 'Reçu le 15 mai', size: '1,9 Mo', hash: 'B37A · 90E4 · 5D1C · 28F6', tags: ['Scouting'], access: [] },
  { id: 'e44', title: 'Programme pré-saison · FC Porto', ent: 'marco', cat: 'perf', state: null, meta: 'Partagé le 1 juil', size: '1,1 Mo', hash: '7F20 · C58D · 36BA · E941', access: [] },
];

const CF3_STATE_LBL = { signed: 'signés', sign: 'à signer', review: 'en relecture', ok: 'vérifiés', expired: 'expirés', none: 'ajoutés' };
function cf3Summary(list) {
  const n = {};
  list.forEach((d) => { const k = d.state || 'none'; n[k] = (n[k] || 0) + 1; });
  return Object.keys(n).slice(0, 3).map((k) => n[k] + ' ' + (n[k] === 1 ? CF3_STATE_LBL[k].replace(/s$/, '') : CF3_STATE_LBL[k])).join(' · ');
}

// ── Partager un accès : contact FOOTPASS ou lien sécurisé pour NON-inscrits (croissance du réseau) ──
function CfV3ShareSheet({ doc, onClose, onPick, onInvite, toast }) {
  const [copied, setCopied] = useStateCV3(false);
  const link = 'footpass.link/d/' + (doc.hash || 'A1B2').slice(0, 4).toLowerCase() + doc.id;
  const tops = (window.FP_CONVOS || []).map((c) => c.id).filter((pid) => FP_PEERS[pid] && !doc.access.some((a) => a.pid === pid)).slice(0, 4);
  return (
    <div className="cf-sheetwrap" style={{ zIndex: 52 }}>
      <button className="cf-dim" onClick={onClose} aria-label="Fermer"></button>
      <div className="cf-sheet">
        <span className="cf-grab"></span>
        <div className="cf-sh-head">
          <span className="cf-doc-ic" style={{ width: 42, height: 42 }}><CFI.Share s={16} /></span>
          <span className="cf-sh-t"><b>Partager un accès</b><em>{doc.title}</em></span>
        </div>
        <div className="cf-sechead" style={{ marginTop: 12 }}><span>À un contact FOOTPASS</span></div>
        <div className="cf2-list-pad">
          {tops.map((pid) => {
            const p = FP_PEERS[pid];
            return (
              <button key={pid} className="cf2-prow" onClick={() => onPick(pid)}>
                <FPAvatar size={28} initials={p.initials} src={p.photo} />
                <span className="cf2-prow-t"><b>{cfName(pid)}</b><em>{p.role}{p.org ? ' · ' + p.org : ''}</em></span>
                <span className="cf3-echeck"><CFI.Check s={10} /></span>
              </button>
            );
          })}
        </div>
        <div className="cf-sechead" style={{ marginTop: 14 }}><span>Sans FOOTPASS · lien sécurisé</span></div>
        <div className="cf3-linkcard">
          <span className="cf3-link">{link}</span>
          <button className={`cf3-copybtn ${copied ? 'ok' : ''}`} onClick={() => { setCopied(true); onInvite(); }}>{copied ? <React.Fragment><CFI.Check s={11} /> Copié</React.Fragment> : 'Copier'}</button>
        </div>
        <p className="cf-accnote">La personne ouvre le document <b>sur le web, en lecture seule</b> : filigrané, révocable, consigné au journal. Pour agir (signer, répondre, conserver), elle sera invitée à <b>créer son Pass FOOTPASS</b>. Votre réseau grandit à chaque partage.</p>
      </div>
    </div>
  );
}

function CoffreV3({ onBack, onLock, dark }) {
  const [docs, setDocs] = useStateCV3(() => CF_DOCS_INIT.concat(CF3_DOCS_EXTRA));
  const [scr, setScr] = useStateCV3({ k: 'home' }); // home · ent · cat · access · alerts · activity
  const [intro, setIntro] = useStateCV3(() => { try { return !localStorage.getItem('fp_coffre_intro'); } catch (e) { return true; } });
  const [sel, setSel] = useStateCV3(null);
  const [selP, setSelP] = useStateCV3(null);
  const [importing, setImporting] = useStateCV3(false);
  const [signing, setSigning] = useStateCV3(null); // id du document en cours de signature
  const [viewing, setViewing] = useStateCV3(null); // id du document ouvert en lecture
  const [sharing, setSharing] = useStateCV3(null); // id du document en cours de partage
  const [tagsOpen, setTagsOpen] = useStateCV3(false); // feuille Tags : tous les tags, tap = filtrer, création libre
  const [customTags, setCustomTags] = useStateCV3([]); // tags créés ici, prêts à être posés à l'import
  const [newTag, setNewTag] = useStateCV3('');
  const createFreeTag = () => {
    const v = newTag.trim();
    if (!v) return;
    setCustomTags((p) => (p.some((x) => cfDeacc(x) === cfDeacc(v)) ? p : [...p, v]));
    setNewTag('');
    toast('Tag « ' + v + ' » créé · posez-le sur vos fichiers à l’import');
  };
  const [exportPick, setExportPick] = useStateCV3(false); // choix du document à exporter
  const [viewExport, setViewExport] = useStateCV3(false); // ouvre la lecture directement sur l'export
  const [grants, setGrants] = useStateCV3([{ pid: 'sira', cat: 'jur' }]); // permissions PAR CATÉGORIE : { pid, cat } = consulter toute la catégorie (ex. juriste → tout le Juridique)
  const [gPick, setGPick] = useStateCV3(null); // création d'une permission de catégorie ({} → personne → catégorie)
  const [q, setQ] = useStateCV3('');
  const [fam, setFam] = useStateCV3(null); // filtre catégorie (accueil) · même logique que les filtres de Messages
  const [famEnt, setFamEnt] = useStateCV3(null); // 2e filtre : TAG (personne, entité, mot-clé) · persistant quand on change de catégorie
  const [famSub, setFamSub] = useStateCV3(null); // 3e niveau : raffinement { t: 'state' | 'tag', v } lié à la sélection
  const [actOpen, setActOpen] = useStateCV3(false); // « Voir toute l'activité » : dépliage sur place
  const [alertsOpen, setAlertsOpen] = useStateCV3(false); // « Voir les alertes » : même dépliage sur place
  const [showStats, setShowStats] = useStateCV3(() => { try { return localStorage.getItem('fp_coffre_stats') !== '0'; } catch (e) { return true; } });
  const toggleStats = () => setShowStats((s) => { try { localStorage.setItem('fp_coffre_stats', s ? '0' : '1'); } catch (e) { /* ignore */ } return !s; });
  // ── Notifications : chaque geste notifie l'autre partie, et tout geste d'un tiers vous notifie ──
  const [notifs, setNotifs] = useStateCV3(CF3_NOTIFS_INIT);
  const [notifOpen, setNotifOpen] = useStateCV3(false);
  const [nUnread, setNUnread] = useStateCV3(1);
  const pushNotif = (n) => { setNotifs((p) => [{ id: 'n' + Date.now() + Math.random(), when: 'à l’instant', ...n }, ...p]); setNUnread((u) => u + 1); };
  const [toastMsg, setToastMsg] = useStateCV3(null);
  const toast = (m) => { setToastMsg(m); clearTimeout(toast._t); toast._t = setTimeout(() => setToastMsg(null), 2100); };
  // ── LOT 5 · pré-filtre structure : la structure est une catégorie de premier rang du coffre unique ──
  // Reçoit fp:vault-filter (émis par le panneau) · chip dorée retirable · agrège structure + joueurs/agents liés + permissions partagées.
  const [structF, setStructF] = useStateCV3(() => { try { const g = window.__fpVaultFilter; if (g) { window.__fpVaultFilter = null; return g; } } catch (e) { /* ignore */ } return null; }); // nom de la structure, ou null = tout le coffre
  React.useEffect(() => {
    const onFilter = (e) => { const name = e.detail && e.detail.structure; if (!name) return; setStructF(name); setScr({ k: 'home' }); setFam(null); setFamEnt(null); setFamSub(null); setQ(''); };
    window.addEventListener('fp:vault-filter', onFilter);
    return () => window.removeEventListener('fp:vault-filter', onFilter);
  }, []);
  const STRUCT_ENTS = ['me', 'lucas', 'mathis'];
  const inStruct = (d) => STRUCT_ENTS.includes(d.ent) || (d.access || []).some((a) => a.pid === 'sira');
  const clearStruct = () => { setStructF(null); setFam(null); setFamEnt(null); setFamSub(null); toast('Filtre structure retiré · tout le coffre'); };

  // ── Droits (mêmes règles que le labo : immédiat, révocable, consigné) ──
  const toggleRight = (docId, pid, k) => {
    setDocs(docs.map((d) => d.id !== docId ? d : { ...d, access: d.access.map((a) => a.pid !== pid ? a : { ...a, rights: a.rights.includes(k) ? a.rights.filter((r) => r !== k) : [...a.rights, k] }) }));
    const d = docs.find((x) => x.id === docId); const a = d.access.find((x) => x.pid === pid);
    const on = !a.rights.includes(k); const lbl = (CF_RIGHTS.find((r) => r.k === k) || {}).label;
    toast(cfName(pid) + ' · ' + lbl + (on ? ' accordé' : ' retiré') + ' · effet immédiat');
    pushNotif({ k: 'ok', who: cfName(pid), verb: on ? 'a été notifié(e) · ' + lbl + ' accordé sur' : 'a été notifié(e) · ' + lbl + ' retiré sur', docT: '« ' + d.title + ' »' });
    // Partage transitif : si la personne repartage votre document, vous êtes prévenu · et le réseau grandit
    if (k === 'share' && on) {
      clearTimeout(toggleRight._t);
      toggleRight._t = setTimeout(() => {
        pushNotif({ k: 'share', who: cfName(pid), verb: 'a partagé', docT: '« ' + d.title + ' » avec Maître P. BERNARD · avocat, hors de vos contacts', action: { label: 'Ajouter Maître BERNARD à mes contacts', docId } });
        toast(cfName(pid) + ' a repartagé votre document · voir Notifications');
      }, 4000);
    }
  };
  const revoke = (docId, pid) => {
    setDocs(docs.map((d) => d.id !== docId ? d : { ...d, access: d.access.filter((a) => a.pid !== pid) }));
    toast('Accès de ' + cfName(pid) + ' révoqué · effet immédiat');
    const dR = docs.find((x) => x.id === docId);
    if (dR) pushNotif({ k: 'ok', who: cfName(pid), verb: 'a été notifié(e) · accès révoqué sur', docT: '« ' + dR.title + ' »' });
  };
  // Autorisation rapide depuis la feuille document · Consulter par défaut, réglable ensuite
  const addAccess = (docId, pid) => {
    setDocs(docs.map((d) => d.id !== docId ? d : { ...d, access: [...d.access, { pid, rights: [] }] }));
    toast(cfName(pid) + ' ajouté · accordez maintenant ses droits');
    const dA = docs.find((x) => x.id === docId);
    if (dA) pushNotif({ k: 'ok', who: cfName(pid), verb: 'a été ajouté(e), sans droit pour l’instant, sur', docT: '« ' + dA.title + ' »' });
  };
  // Signature · raccourci depuis la feuille document
  const signDone = (docId) => {
    setDocs(docs.map((d) => d.id !== docId ? d : { ...d, state: 'signed', meta: 'Signé à l’instant · consigné au journal' }));
    setSigning(null);
    toast('Signé · effet immédiat, consigné au journal');
    const dS = docs.find((x) => x.id === docId);
    if (dS) pushNotif({ k: 'sign', who: 'Les parties', verb: 'ont été notifiées · votre signature est apposée sur', docT: '« ' + dS.title + ' »' });
  };
  // Invitation hors réseau : le lien force la création d'un Pass · croissance du réseau
  const inviteLink = (docId) => {
    const dI = docs.find((x) => x.id === docId);
    if (!dI) return;
    toast('Lien copié · lecture web seule, révocable');
    pushNotif({ k: 'share', who: 'Vous', verb: 'avez généré un lien de lecture web pour', docT: '« ' + dI.title + ' »' });
    clearTimeout(inviteLink._t);
    inviteLink._t = setTimeout(() => {
      pushNotif({ k: 'view', who: 'Maître P. BERNARD', verb: 'a ouvert sur le web (lecture seule, filigranée)', docT: '« ' + dI.title + ' »' });
      toast('Votre lien vient d’être ouvert sur le web · voir Notifications');
      clearTimeout(inviteLink._t2);
      inviteLink._t2 = setTimeout(() => {
        pushNotif({ k: 'share', who: 'Maître P. BERNARD', verb: 'a créé son Pass FOOTPASS pour agir sur', docT: '« ' + dI.title + ' »', action: { label: 'Ajouter Maître BERNARD à mes contacts', docId } });
        toast('Nouveau Pass créé via votre lien · votre réseau grandit');
      }, 6000);
    }, 5000);
  };
  // Permissions par catégorie : ex. Sira consulte TOUT le Juridique · matérialisées en droit Consulter, révocables d'un geste
  const addGrant = (pid, cat) => {
    setGrants((p) => (p.some((g) => g.pid === pid && g.cat === cat) ? p : [...p, { pid, cat }]));
    setDocs((prev) => prev.map((d) => (d.cat !== cat || d.access.some((a) => a.pid === pid) ? d : { ...d, access: [...d.access, { pid, rights: ['view'], viaCat: true }] })));
    setGPick(null);
    toast(cfName(pid) + ' · Consulter sur toute la catégorie ' + CF_CATS[cat].label);
    pushNotif({ k: 'ok', who: cfName(pid), verb: 'a été notifié(e) · accès Consulter sur toute la catégorie', docT: CF_CATS[cat].label });
  };
  const removeGrant = (pid, cat) => {
    setGrants((p) => p.filter((g) => !(g.pid === pid && g.cat === cat)));
    setDocs((prev) => prev.map((d) => (d.cat !== cat ? d : { ...d, access: d.access.filter((a) => !(a.pid === pid && a.viaCat)) })));
    toast('Permission retirée · effet immédiat');
    pushNotif({ k: 'ok', who: cfName(pid), verb: 'a été notifié(e) · accès de catégorie retiré sur', docT: CF_CATS[cat].label });
  };
  // Ajout au réseau depuis une notification de partage transitif
  const doNotifAction = (n) => {
    if (!n.action) return;
    if (!FP_PEERS.bernard) FP_PEERS.bernard = { id: 'bernard', name: 'Pierre Bernard', role: 'Avocat', org: 'Cabinet Bernard & Associés', initials: 'PB' };
    if (n.action.docId) setDocs((prev) => prev.map((d) => d.id !== n.action.docId ? d : (d.access.some((a) => a.pid === 'bernard') ? d : { ...d, access: [...d.access, { pid: 'bernard', rights: ['view'] }] })));
    setNotifs((prev) => prev.map((x) => x.id === n.id ? { ...x, action: null, done: 'Maître BERNARD ajouté · son accès est désormais géré par vous' } : x));
    toast('Maître BERNARD rejoint votre réseau FOOTPASS');
  };
  const removeAll = (pid) => {
    setDocs(docs.map((d) => ({ ...d, access: d.access.filter((a) => a.pid !== pid) })));
    setSelP(null);
    toast('Tous les accès de ' + cfName(pid) + ' retirés · consigné au journal');
  };
  const grant = (docId) => {
    setDocs(docs.map((d) => d.id !== docId ? d : { ...d, request: null, meta: d.meta + ' · AS Monaco 30 j', access: [...d.access, { pid: 'karim', rights: ['view'] }] }));
    toast('AS Monaco · lecture 30 jours · filigranée');
    pushNotif({ k: 'ok', who: 'AS Monaco', verb: 'a été notifié · lecture 30 jours accordée sur', docT: '« Rapport IRM · genou droit »' });
  };
  const refuse = (docId) => {
    setDocs(docs.map((d) => d.id !== docId ? d : { ...d, request: null }));
    toast('Demande refusée · AS Monaco notifié');
    pushNotif({ k: 'ok', who: 'AS Monaco', verb: 'a été notifié · demande d’accès refusée sur', docT: '« Rapport IRM · genou droit »' });
  };
  // Import · tout type de fichier · tags & catégorie PAR fichier
  const addFiles = (items) => {
    const fmt = (b) => b >= 1048576 ? (b / 1048576).toFixed(1).replace('.', ',') + ' Mo' : Math.max(1, Math.round(b / 1024)) + ' Ko';
    const hx = () => Array.from({ length: 4 }, () => Math.floor(Math.random() * 65536).toString(16).toUpperCase().padStart(4, '0')).join(' · ');
    const added = items.map(({ f, tags, cat }, i) => {
      const entTag = (tags || []).find((t) => t.t === 'ent');
      const labels = (tags || []).map((t) => (t.t === 'ent' ? cfEntLabel(t.v) : t.v));
      return {
        id: 'u' + Date.now() + i,
        title: f.name.replace(/\.[^.]+$/, ''),
        ent: entTag ? entTag.v : 'me', cat, state: null, tags: labels,
        meta: 'Ajouté à l’instant · ' + ((f.name.split('.').pop() || 'fichier').toUpperCase()) + ' · ' + fmt(f.size),
        size: fmt(f.size), hash: hx(), access: [],
      };
    });
    setDocs([...added, ...docs]);
    setImporting(false);
    toast(added.length > 1 ? added.length + ' documents chiffrés et ajoutés · vous seul' : 'Chiffré et ajouté · vous seul pouvez l’ouvrir');
  };

  // ── Dérivés ──
  const selDoc = sel ? docs.find((d) => d.id === sel) : null;
  const sdocs = structF ? docs.filter(inStruct) : docs; // jeu de travail scopé (accueil + listes) · setDocs reste sur docs
  const alive = sdocs.filter((d) => d.state !== 'expired');
  const todo = sdocs.filter((d) => d.state === 'sign' || d.state === 'review' || d.request);
  const nSign = sdocs.filter((d) => d.state === 'sign').length;
  const nReview = sdocs.filter((d) => d.state === 'review').length;
  const nReq = sdocs.filter((d) => d.request).length;
  const soonDocs = sdocs.filter((d) => d.soon);
  const people = useMemoCV3(() => {
    const m = {};
    docs.forEach((d) => d.access.forEach((a) => { (m[a.pid] = m[a.pid] || []).push({ d, a }); }));
    return Object.keys(m).map((pid) => ({ pid, items: m[pid] }));
  }, [docs]);

  const nq = cfDeacc(q.trim());
  const entMatches = nq ? CF_ENT_ORDER.filter((k) => cfDeacc(cfEntLabel(k) + ' ' + cfEntSub(k)).includes(nq)) : [];
  const docMatches = nq ? sdocs.filter((d) => cfDeacc(d.title + ' ' + d.meta + ' ' + CF_CATS[d.cat].label + ' ' + cfEntLabel(d.ent) + ' ' + ((d.tags || []).join(' '))).includes(nq)) : [];

  // ── Filtres en cascade : catégorie → personne/tag → raffinements (états + tags liés à la sélection) ──
  const cfTagEq = (a, b) => cfDeacc(a) === cfDeacc(b);
  const cfDocTags = (d) => [cfEntLabel(d.ent)].concat(d.tags || []);
  const tagScope = fam ? sdocs.filter((d) => d.cat === fam) : sdocs;
  const entChips = CF_ENT_ORDER.concat(Object.keys(FP_PEERS).filter((k) => !CF_ENT_ORDER.includes(k)))
    .filter((k) => tagScope.some((d) => d.ent === k)).slice(0, 7);
  const freeChips = (() => {
    const m = {};
    tagScope.forEach((d) => (d.tags || []).forEach((l) => {
      if (entChips.some((e) => cfTagEq(cfEntLabel(e), l))) return;
      const k = cfDeacc(l); (m[k] = m[k] || { l, n: 0 }).n++;
    }));
    return Object.values(m).sort((a, b) => b.n - a.n || a.l.localeCompare(b.l, 'fr')).map((x) => x.l).slice(0, 8);
  })();
  // Une fois une personne/un tag choisi : seuls les raffinements qui LA concernent restent
  const entScope2 = famEnt ? tagScope.filter((d) => cfDocTags(d).some((l) => cfTagEq(l, famEnt))) : tagScope;
  const subStates = [
    ['signed', 'Signés', entScope2.filter((d) => d.state === 'signed').length],
    ['todo', 'À traiter', entScope2.filter((d) => d.state === 'sign' || d.state === 'review' || d.request).length],
    ['ok', 'Vérifiés', entScope2.filter((d) => d.state === 'ok').length],
    ['expired', 'Expirés', entScope2.filter((d) => d.state === 'expired').length],
  ].filter((s) => s[2] > 0);
  const relTags = (() => {
    if (!famEnt) return [];
    const m = {};
    entScope2.forEach((d) => cfDocTags(d).forEach((l) => {
      if (cfTagEq(l, famEnt)) return;
      const k = cfDeacc(l); (m[k] = m[k] || { l, n: 0 }).n++;
    }));
    return Object.values(m).sort((a, b) => b.n - a.n || a.l.localeCompare(b.l, 'fr')).map((x) => x.l).slice(0, 6);
  })();
  const matchSub = (d) => !famSub ? true : famSub.t === 'tag' ? cfDocTags(d).some((l) => cfTagEq(l, famSub.v))
    : famSub.v === 'signed' ? d.state === 'signed'
    : famSub.v === 'todo' ? (d.state === 'sign' || d.state === 'review' || !!d.request)
    : famSub.v === 'ok' ? d.state === 'ok'
    : d.state === 'expired';
  const filtDocs = sdocs.filter((d) => (!fam || d.cat === fam) && (!famEnt || cfDocTags(d).some((l) => cfTagEq(l, famEnt))) && matchSub(d));
  // Les filtres ne remplacent PAS le tableau de bord : ils changent l'INTÉRIEUR de chaque section
  const fActive = !!(fam || famEnt || famSub);
  const fIds = new Set(filtDocs.map((d) => d.id));
  const fSign = filtDocs.filter((d) => d.state === 'sign').length;
  const fReview = filtDocs.filter((d) => d.state === 'review').length;
  const fReq = filtDocs.filter((d) => d.request).length;
  const fSoon = filtDocs.filter((d) => d.soon);
  const recentList = [...docs.filter((d) => d.id[0] === 'u').map((d) => ({ id: d.id, when: 'à l’instant', act: 'Ajouté par vous' })), ...CF3_RECENT]
    .filter((r) => ((!fActive && !structF) || fIds.has(r.id)) && docs.some((d) => d.id === r.id));

  const goHome = () => setScr({ k: 'home' });
  const back = () => {
    if (scr.k === 'home') { onBack(); return; }
    if (scr.k === 'cat' && scr.ent) { setScr({ k: 'ent', ent: scr.ent }); return; }
    if (scr.k === 'activity' && scr.ent) { setScr({ k: 'ent', ent: scr.ent }); return; }
    goHome();
  };

  // ── Atomes locaux ──
  const docCard = (d) => (
    <button key={d.id} className="cf-doc" onClick={() => setSel(d.id)}>
      <CfDocIcon cat={d.cat} />
      <span className="cf-doc-txt">
        <b>{d.title}</b>
        <em>{d.soon ? <span className="cf2-soon">Expire dans {d.soon} j</span> : null}{d.soon ? ' · ' : ''}{d.meta}</em>
      </span>
      <span className="cf-doc-side">
        <CfPill state={d.state} />
        <CfPrivacy access={d.access} />
      </span>
    </button>
  );

  const moreRow = (label, fn, up) => (
    <button className="cf3-more" onClick={fn}>{label} <span className={`cf3-morechev ${up ? 'up' : ''}`}><CFI.Chevron s={11} dir="r" /></span></button>
  );

  const activityRows = (ent) => {
    const list = CF_ACTIVITY.filter((a) => !ent || a.ent === ent);
    return (
      <div className="cf2-acts">
        {list.map((a, i) => (
          <div key={i} className="cf2-act">
            <FPAvatar size={22} initials={a.who === 'me' ? (FP_ME.initials || 'AD') : FP_PEERS[a.who].initials} src={a.who === 'me' ? FP_ME.photo : FP_PEERS[a.who].photo} />
            <span><b>{a.who === 'me' ? 'Vous' : cfName(a.who)}</b> {a.txt}</span>
            <em>{a.when}</em>
          </div>
        ))}
        {!list.length ? <div className="cf2-empty">Aucune activité récente.</div> : null}
      </div>
    );
  };

  // ═══ ACCUEIL · tableau de bord ═══
  const home = (
    <React.Fragment>
      {structF ? (
        <div className="cf3-structbar">
          <button className="cf3-structchip" onClick={clearStruct}>
            <span className="cf3-structgm">RT</span>
            <span className="cf3-structtx"><b>{structF}</b><em>Documents de la structure, joueurs & agents liés</em></span>
            <span className="cf3-structx" aria-label="Retirer le filtre structure">×</span>
          </button>
          <p className="cf3-structnote">Vue agrégée du coffre unique · inclut les permissions partagées · <b>Sira Keïta, juriste externe</b>. Touchez la puce pour revenir à tout le coffre.</p>
        </div>
      ) : null}
      {/* Bandeau 3 compteurs · masquable comme dans Autorisations */}
      {showStats ? (
        <div className="cf2-stats">
          <button className="cf2-stat ok" onClick={() => { setFam(null); setFamEnt(null); setFamSub(null); }}><b>{alive.length}</b><span>Documents protégés</span></button>
          <button className="cf2-stat warn" onClick={() => setAlertsOpen(true)}><b>{todo.length}</b><span>À traiter</span></button>
          <button className="cf2-stat warn" onClick={() => setAlertsOpen(true)}><b>{soonDocs.length}</b><span>Expirent bientôt</span></button>
        </div>
      ) : null}
      {/* Recherche rapide */}
      <div className="cf-sechead"><span>Recherche rapide</span></div>
      <label className="cf-search" style={{ margin: '0 0 8px' }}>
        <CFI.Search s={14} />
        <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Un nom, un type de document, un mot-clé…" />
        {q ? <button onClick={() => setQ('')} aria-label="Effacer">×</button> : null}
      </label>
      <div className="cf2-fams cf3-cats">
        <button className={`cf2-fam ${!fam ? 'on' : ''}`} onClick={() => setFam(null)}>Tous</button>
        {Object.keys(CF_CATS).map((k) => (
          <button key={k} className={`cf2-fam ${fam === k ? 'on' : ''}`} onClick={() => setFam(fam === k ? null : k)}>
            <i style={{ background: cfCatColor(k) }}></i>{CF_CATS[k].label}
          </button>
        ))}
      </div>
      {/* 2e rangée en cascade : sans sélection = personnes puis tags · avec sélection = la personne + ce qui LA concerne (états, tags liés) */}
      <div className="cf2-fams cf3-cats cf3-sub">
        {famEnt ? (
          <React.Fragment>
            {(() => {
              const ek = entChips.find((k) => cfTagEq(cfEntLabel(k), famEnt));
              const p = ek && ek !== 'me' ? FP_PEERS[ek] : null;
              return (
                <button className="cf2-fam cf3-pchip on" onClick={() => { setFamEnt(null); setFamSub(null); }}>
                  {p ? <FPAvatar size={14} initials={p.initials} src={p.photo} /> : ek === 'me' ? <span className="cf3-mini">{FP_ME.initials || 'AD'}</span> : null}{famEnt}<span className="cf3-x">×</span>
                </button>
              );
            })()}
            {subStates.length ? <i className="cf3-fsep"></i> : null}
            {subStates.map((s) => (
              <button key={s[0]} className={`cf2-fam ${famSub && famSub.t === 'state' && famSub.v === s[0] ? 'on' : ''}`} onClick={() => setFamSub(famSub && famSub.t === 'state' && famSub.v === s[0] ? null : { t: 'state', v: s[0] })}>{s[1]}<span className="cf3-chipn">{s[2]}</span></button>
            ))}
            {relTags.length ? <i className="cf3-fsep"></i> : null}
            {relTags.map((l) => (
              <button key={'r' + l} className={`cf2-fam ${famSub && famSub.t === 'tag' && cfTagEq(famSub.v, l) ? 'on' : ''}`} onClick={() => setFamSub(famSub && famSub.t === 'tag' && cfTagEq(famSub.v, l) ? null : { t: 'tag', v: l })}>{l}</button>
            ))}
          </React.Fragment>
        ) : (
          <React.Fragment>
            {entChips.map((k) => {
              const l = cfEntLabel(k);
              const p = k === 'me' ? null : FP_PEERS[k];
              return (
                <button key={'e' + k} className="cf2-fam cf3-pchip" onClick={() => { setFamEnt(l); setFamSub(null); }}>
                  {p ? <FPAvatar size={14} initials={p.initials} src={p.photo} /> : <span className="cf3-mini">{FP_ME.initials || 'AD'}</span>}{l}
                </button>
              );
            })}
            {entChips.length && freeChips.length ? <i className="cf3-fsep"></i> : null}
            {freeChips.map((l) => (
              <button key={'t' + l} className="cf2-fam" onClick={() => { setFamEnt(l); setFamSub(null); }}>{l}</button>
            ))}
          </React.Fragment>
        )}
      </div>

      {nq ? (
        <React.Fragment>
          {entMatches.length ? (
            <React.Fragment>
              <div className="cf-sechead"><span>Dossiers</span></div>
              <div className="cf2-list-pad" style={{ marginBottom: 4 }}>
                {entMatches.map((k) => {
                  const p = k === 'me' ? null : FP_PEERS[k];
                  const n = docs.filter((d) => d.ent === k).length;
                  return (
                    <button key={k} className="cf2-prow" onClick={() => { setQ(''); setScr({ k: 'ent', ent: k }); }}>
                      {p ? <FPAvatar size={30} initials={p.initials} src={p.photo} /> : <span className="cf2-orgav">{FP_ME.initials || 'AD'}</span>}
                      <span className="cf2-prow-t"><b>{cfEntLabel(k)}</b><em>{cfEntSub(k)} · {n} document{n > 1 ? 's' : ''}</em></span>
                      <CFI.Chevron s={11} dir="r" />
                    </button>
                  );
                })}
              </div>
            </React.Fragment>
          ) : null}
          <div className="cf-sechead"><span>Documents · {docMatches.length}</span></div>
          <div className="cf-list">{docMatches.map(docCard)}{!docMatches.length ? <div className="cf2-empty">Aucun document ne correspond.</div> : null}</div>
        </React.Fragment>
      ) : (
        <React.Fragment>
          {/* Activités récentes */}
          <div className="cf-sechead"><span>Activités récentes</span></div>
          <div className={`cf3-actsec ${actOpen ? 'open' : ''}`}>
            <div className="cf2-list-pad">
              {(actOpen ? recentList : recentList.slice(0, 3)).map(({ id, when, act }) => {
                const d = docs.find((x) => x.id === id);
                if (!d) return null;
                return (
                  <div key={id} className="cf2-prow cf3-actrow" role="button" tabIndex={0} onClick={() => setSel(id)}>
                    <CfDocIcon cat={d.cat} size={30} />
                    <span className="cf2-prow-t">
                      <span className="cf3-actline1"><b>{d.title}</b><em className="cf3-when">{when}</em></span>
                      {act ? <em className="cf3-act">{act}</em> : null}
                      <em className="cf3-actmeta"><i style={{ background: cfCatColor(d.cat) }}></i><span style={{ color: cfCatColor(d.cat) }}>{CF_CATS[d.cat].label}</span> · {cfEntLabel(d.ent)}</em>
                    </span>
                    <CFI.Chevron s={11} dir="r" />
                  </div>
                );
              })}
              {!recentList.length ? <div className="cf3-none">Aucune activité récente pour ce filtre.</div> : null}
              {actOpen ? filtDocs.filter((d) => !recentList.some((r) => r.id === d.id)).slice(0, 12).map((d) => (
                <div key={'x' + d.id} className="cf2-prow cf3-actrow" role="button" tabIndex={0} onClick={() => setSel(d.id)}>
                  <CfDocIcon cat={d.cat} size={30} />
                  <span className="cf2-prow-t">
                    <span className="cf3-actline1"><b>{d.title}</b></span>
                    <em className="cf3-act">{d.meta}</em>
                    <em className="cf3-actmeta"><i style={{ background: cfCatColor(d.cat) }}></i><span style={{ color: cfCatColor(d.cat) }}>{CF_CATS[d.cat].label}</span> · {cfEntLabel(d.ent)}</em>
                  </span>
                  <CFI.Chevron s={11} dir="r" />
                </div>
              )) : null}
            </div>
          </div>
          {recentList.length ? moreRow(actOpen ? 'Réduire l’activité' : 'Voir toute l’activité', () => setActOpen(!actOpen), actOpen) : null}

          {/* À traiter */}
          {todo.length || soonDocs.length || fActive ? (
            <React.Fragment>
              <div className="cf-sechead"><span>À traiter</span></div>
              <div className="cf3-scrollsec">
                <div className="cf2-list-pad">
                  {filtDocs.filter((d) => d.state === 'sign' || d.state === 'review' || d.request).map((d) => {
                    const kind = d.state === 'sign' ? 'sign' : d.state === 'review' ? 'review' : 'request';
                    return (
                      <button key={'a' + d.id} className="cf2-prow cf3-actrow" onClick={() => setSel(d.id)}>
                        <span className={`cf3-prowic ${kind}`}>{kind === 'sign' ? <CFI.Pen s={14} /> : kind === 'review' ? <CFI.Eye s={14} /> : <CFI.Share s={13} />}</span>
                        <span className="cf2-prow-t">
                          <span className="cf3-actline1"><b>{kind === 'request' ? d.request.org + ' demande un accès' : d.title}</b></span>
                          <em className="cf3-act">{kind === 'sign' ? 'Votre signature est attendue' : kind === 'review' ? 'En relecture chez Sira Keïta' : d.title}</em>
                          <em className="cf3-actmeta"><i style={{ background: cfCatColor(d.cat) }}></i><span style={{ color: cfCatColor(d.cat) }}>{CF_CATS[d.cat].label}</span> · {cfEntLabel(d.ent)}</em>
                        </span>
                        <span className="cf3-open cf3-openTop">{kind === 'sign' ? 'Signer' : kind === 'request' ? 'Répondre' : 'Voir'}</span>
                      </button>
                    );
                  })}
                  {!fSign && !fReview && !fReq && !fSoon.length ? <div className="cf3-none">Rien à traiter pour ce filtre.</div> : null}
                  {alertsOpen ? (
                    <React.Fragment>
                      {fSoon.map((d) => (
                        <button key={'s' + d.id} className="cf2-prow cf3-actrow" onClick={() => setSel(d.id)}>
                          <span className="cf3-prowic soon"><CFI.Journal s={13} /></span>
                          <span className="cf2-prow-t">
                            <span className="cf3-actline1"><b>{d.title}</b></span>
                            <em className="cf3-act"><span className="cf2-soon">Expire dans {d.soon} j</span></em>
                            <em className="cf3-actmeta"><i style={{ background: cfCatColor(d.cat) }}></i><span style={{ color: cfCatColor(d.cat) }}>{CF_CATS[d.cat].label}</span> · {cfEntLabel(d.ent)}</em>
                          </span>
                          <CFI.Chevron s={11} dir="r" />
                        </button>
                      ))}
                      {CF_PROCESSED.map((x) => (
                        <div key={x.id} className="cf2-prow cf3-actrow cf3-done">
                          <span className="cf3-prowic done">{x.okd ? <CFI.Check s={12} /> : <CFI.Ban s={12} />}</span>
                          <span className="cf2-prow-t">
                            <span className="cf3-actline1"><b>{x.txt}</b></span>
                            <em className="cf3-act">{x.sub}</em>
                          </span>
                        </div>
                      ))}
                    </React.Fragment>
                  ) : null}
                </div>
              </div>
              {(fSoon.length || fSign || fReview || fReq) ? moreRow(alertsOpen ? 'Réduire les alertes' : 'Voir les alertes', () => setAlertsOpen(!alertsOpen), alertsOpen) : null}
            </React.Fragment>
          ) : null}

          {/* Dossiers (entités) */}
          <div className="cf-sechead"><span>Dossiers</span></div>
          <div className="cf2-list-pad cf3-scrollsec">
            {[...CF_ENT_ORDER, ...docs.map((d) => d.ent).filter((k, i, a) => !CF_ENT_ORDER.includes(k) && a.indexOf(k) === i)].map((k) => {
              const p = k === 'me' ? null : FP_PEERS[k];
              const n = filtDocs.filter((d) => d.ent === k).length;
              if (!n) return null;
              return (
                <button key={k} className="cf2-prow" onClick={() => setScr({ k: 'ent', ent: k })}>
                  {p ? <FPAvatar size={30} initials={p.initials} src={p.photo} /> : <span className="cf2-orgav">{FP_ME.initials || 'AD'}</span>}
                  <span className="cf2-prow-t"><b>{cfEntLabel(k)}</b><em>{cfEntSub(k)}</em></span>
                  <span className="cf3-n">{n}</span>
                  <CFI.Chevron s={11} dir="r" />
                </button>
              );
            })}
            {fActive && !filtDocs.length ? <div className="cf3-none">Aucun dossier pour ce filtre.</div> : null}
          </div>

          {/* Accès rapide */}
          <div className="cf-sechead"><span>Accès rapide</span></div>
          <div className="cf3-qa">
            <button className="cf3-qab" onClick={() => setImporting(true)}><CFI.Import s={16} /><span>Importer</span></button>
            <button className="cf3-qab" onClick={() => setExportPick(true)}><CFI.Wm s={16} /><span>Exporter</span></button>
            <button className="cf3-qab" onClick={() => setTagsOpen(true)}><CFI.FolderIc s={16} /><span>Tags</span></button>
            <button className="cf3-qab" onClick={() => setScr({ k: 'access' })}><CFI.Eye s={16} /><span>Accès</span></button>
          </div>

          {/* Espace disponible */}
          <div className="cf-sechead"><span>Espace disponible</span></div>
          <div className="cf3-block cf3-store">
            <div className="cf3-bar" role="img" aria-label="3 % utilisés"><i style={{ width: '3.2%' }}></i></div>
            <div className="cf3-storetxt"><b>8,2 Go</b> utilisés sur 256 Go · Pass Pro</div>
            <div className="cf3-storebtns">
              <button onClick={() => toast('Optimisation · démo')}>Optimiser</button>
              <button onClick={() => toast('Pass Pro : 256 Go (actif) · Pass Start : 60 Go')}>Augmenter l’espace</button>
            </div>
          </div>

          <div className="cf-foot"><FPIcon.Shield s={11} c="currentColor" /> Chiffré AES-256 · hébergé en Suisse (Zurich) · les dossiers ne stockent rien.</div>
        </React.Fragment>
      )}
    </React.Fragment>
  );

  // ═══ DOSSIER ENTITÉ ═══
  const entScreen = (ent) => {
    const list = docs.filter((d) => d.ent === ent);
    const p = ent === 'me' ? null : FP_PEERS[ent];
    const cats = Object.keys(CF_CATS).map((k) => [k, list.filter((d) => d.cat === k)]).filter(([, l]) => l.length);
    // Cascade locale du dossier : catégorie (scr.cat) → tags du domaine (scr.tag), comme sur l'accueil
    const eCat = scr.cat || null;
    const eTag = scr.tag || null;
    const scope = eCat ? list.filter((d) => d.cat === eCat) : list;
    const eTags = (() => {
      const m = {};
      scope.forEach((d) => (d.tags || []).forEach((l) => { if (cfTagEq(l, cfEntLabel(ent))) return; const k2 = cfDeacc(l); (m[k2] = m[k2] || { l, n: 0 }).n++; }));
      const arr = Object.values(m).sort((a, b) => b.n - a.n || a.l.localeCompare(b.l, 'fr')).map((x) => x.l).slice(0, 8);
      if (eTag && !arr.some((l) => cfTagEq(l, eTag))) arr.unshift(eTag);
      return arr;
    })();
    const eDocs = scope.filter((d) => !eTag || cfDocTags(d).some((l) => cfTagEq(l, eTag)));
    const eIds = new Set(eDocs.map((d) => d.id));
    const eTodo = eDocs.filter((d) => d.state === 'sign' || d.state === 'review' || d.request);
    const eRecent = CF3_RECENT.filter((r) => eIds.has(r.id)).slice(0, 4);
    const nAcc = (() => { const s = new Set(); list.forEach((d) => d.access.forEach((a) => s.add(a.pid))); return s.size; })();
    return (
      <React.Fragment>
        {/* Carte du dossier : identité complète, pas un simple filtre */}
        <div className="cf3-entcard">
          {p ? <FPAvatar size={44} initials={p.initials} src={p.photo} /> : <span className="cf2-orgav" style={{ width: 44, height: 44, fontSize: 14 }}>{FP_ME.initials || 'AD'}</span>}
          <span className="cf3-entt">
            <b>Dossier · {cfEntLabel(ent)}</b>
            <em>{cfEntSub(ent)}</em>
            <i>{list.length} documents · {nAcc ? nAcc + (nAcc > 1 ? ' personnes ont accès' : ' personne a accès') : 'vous seul'}</i>
          </span>
          <button onClick={goHome} aria-label="Fermer le dossier">×</button>
        </div>

        {/* Catégories : la sélection reste, les sous-filtres (tags du domaine) apparaissent dessous */}
        <div className="cf2-fams cf3-cats">
          <button className={`cf2-fam ${!eCat ? 'on' : ''}`} onClick={() => setScr({ k: 'ent', ent })}>Tous</button>
          {cats.map(([k, l]) => (
            <button key={k} className={`cf2-fam ${eCat === k ? 'on' : ''}`} onClick={() => setScr({ k: 'ent', ent, cat: eCat === k ? null : k })}>
              <i style={{ background: cfCatColor(k) }}></i>{CF_CATS[k].label}<span className="cf3-chipn">{l.length}</span>
            </button>
          ))}
        </div>
        {eCat && eTags.length ? (
          <div className="cf2-fams cf3-cats cf3-sub">
            {eTags.map((l) => (
              <button key={l} className={`cf2-fam ${eTag && cfTagEq(eTag, l) ? 'on' : ''}`} onClick={() => setScr({ k: 'ent', ent, cat: eCat, tag: eTag && cfTagEq(eTag, l) ? null : l })}>{l}</button>
            ))}
          </div>
        ) : null}

        {/* Activités récentes · uniquement ce dossier (et le filtre en cours) */}
        <div className="cf-sechead"><span>Activités récentes</span></div>
        <div className="cf2-list-pad">
          {eRecent.map(({ id, when, act }) => {
            const d = docs.find((x) => x.id === id);
            if (!d) return null;
            return (
              <div key={id} className="cf2-prow cf3-actrow" role="button" tabIndex={0} onClick={() => setSel(id)}>
                <CfDocIcon cat={d.cat} size={30} />
                <span className="cf2-prow-t">
                  <span className="cf3-actline1"><b>{d.title}</b><em className="cf3-when">{when}</em></span>
                  {act ? <em className="cf3-act">{act}</em> : null}
                  <em className="cf3-actmeta"><i style={{ background: cfCatColor(d.cat) }}></i><span style={{ color: cfCatColor(d.cat) }}>{CF_CATS[d.cat].label}</span> · {cfEntLabel(d.ent)}</em>
                </span>
                <CFI.Chevron s={11} dir="r" />
              </div>
            );
          })}
          {!eRecent.length ? <div className="cf3-none">Aucune activité récente pour ce filtre.</div> : null}
        </div>

        {/* À traiter · uniquement ce dossier */}
        <div className="cf-sechead" style={{ marginTop: 14 }}><span>À traiter</span></div>
        <div className="cf2-list-pad">
          {eTodo.map((d) => {
            const kind = d.state === 'sign' ? 'sign' : d.state === 'review' ? 'review' : 'request';
            return (
              <button key={'a' + d.id} className="cf2-prow cf3-actrow" onClick={() => setSel(d.id)}>
                <span className={`cf3-prowic ${kind}`}>{kind === 'sign' ? <CFI.Pen s={14} /> : kind === 'review' ? <CFI.Eye s={14} /> : <CFI.Share s={13} />}</span>
                <span className="cf2-prow-t">
                  <span className="cf3-actline1"><b>{kind === 'request' ? d.request.org + ' demande un accès' : d.title}</b></span>
                  <em className="cf3-act">{kind === 'sign' ? 'Votre signature est attendue' : kind === 'review' ? 'En relecture chez Sira Keïta' : d.title}</em>
                  <em className="cf3-actmeta"><i style={{ background: cfCatColor(d.cat) }}></i><span style={{ color: cfCatColor(d.cat) }}>{CF_CATS[d.cat].label}</span> · {cfEntLabel(d.ent)}</em>
                </span>
                <span className="cf3-open cf3-openTop">{kind === 'sign' ? 'Signer' : kind === 'request' ? 'Répondre' : 'Voir'}</span>
              </button>
            );
          })}
          {!eTodo.length ? <div className="cf3-none">Rien à traiter pour ce filtre.</div> : null}
        </div>

        {/* Documents : par domaine, ou la liste du domaine sélectionné */}
        {eCat ? (
          <React.Fragment>
            <div className="cf-sechead" style={{ marginTop: 14 }}><span>Documents · {CF_CATS[eCat].label}{eTag ? ' · ' + eTag : ''}</span><b style={{ cursor: 'default' }}>{eDocs.length}</b></div>
            <div className="cf-list">{eDocs.map(docCard)}{!eDocs.length ? <div className="cf3-none">Aucun document pour ce filtre.</div> : null}</div>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <div className="cf-sechead" style={{ marginTop: 14 }}><span>Documents par domaine</span></div>
            <div className="cf2-list-pad">
              {cats.map(([k, l]) => (
                <button key={k} className="cf2-prow" onClick={() => setScr({ k: 'ent', ent, cat: k })}>
                  <CfDocIcon cat={k} size={30} />
                  <span className="cf2-prow-t"><b>{CF_CATS[k].label}</b><em>{l.length <= 2 ? l.map((d) => d.title).join(' · ') : cf3Summary(l)}</em></span>
                  <span className="cf3-n">{l.length}</span>
                  <span className="cf3-open">Ouvrir <CFI.Chevron s={10} dir="r" /></span>
                </button>
              ))}
            </div>
          </React.Fragment>
        )}

        {/* Qui a accès à ce dossier : par personne, avec ses droits · toucher = détail complet */}
        <div className="cf-sechead" style={{ marginTop: 14 }}><span>Accès à ce dossier</span></div>
        <div className="cf2-list-pad">
          {(() => {
            const m = {};
            list.forEach((d) => d.access.forEach((a) => { (m[a.pid] = m[a.pid] || { n: 0, rights: new Set() }).n++; a.rights.forEach((r) => m[a.pid].rights.add(r)); }));
            const ppl = Object.keys(m).filter((pid) => FP_PEERS[pid]);
            if (!ppl.length) return <div className="cf3-none">Vous seul avez accès à ce dossier.</div>;
            return ppl.map((pid) => {
              const p = FP_PEERS[pid];
              return (
                <button key={pid} className="cf2-prow" onClick={() => setSelP(pid)}>
                  <FPAvatar size={30} initials={p.initials} src={p.photo} />
                  <span className="cf2-prow-t"><b>{cfName(pid)}</b><em>{p.role}{p.org ? ' · ' + p.org : ''} · {m[pid].n} document{m[pid].n > 1 ? 's' : ''}</em></span>
                  <CfRightsIcons rights={[...m[pid].rights]} />
                  <CFI.Chevron s={11} dir="r" />
                </button>
              );
            });
          })()}
        </div>
      </React.Fragment>
    );
  };

  // ═══ DOMAINE (catégorie, entité optionnelle) ═══
  const catScreen = (ent, cat) => {
    const list = docs.filter((d) => d.cat === cat && (!ent || d.ent === ent));
    return (
      <React.Fragment>
        <div className="cf3-tokens">
          {ent ? (
            <div className="cf2-token"><b>{cfEntLabel(ent)}</b><button onClick={() => setScr({ k: 'cat', ent: null, cat })} aria-label="Retirer l’entité">×</button></div>
          ) : null}
          <div className="cf2-token"><b style={{ color: cfCatColor(cat) }}>{CF_CATS[cat].label}</b><button onClick={() => (ent ? setScr({ k: 'ent', ent }) : goHome())} aria-label="Retirer la catégorie">×</button></div>
        </div>
        <div className="cf-sechead"><span>Documents {CF_CATS[cat].label}{ent ? ' · ' + cfEntLabel(ent) : ''}</span><b>{list.length} document{list.length > 1 ? 's' : ''}</b></div>
        <div className="cf-list">{list.map(docCard)}{!list.length ? <div className="cf2-empty">Rien dans ce domaine.</div> : null}</div>
        {ent ? (
          <React.Fragment>
            <div className="cf-sechead" style={{ marginTop: 18 }}><span>Activité récente · {cfEntLabel(ent)}</span></div>
            {activityRows(ent)}
          </React.Fragment>
        ) : null}
      </React.Fragment>
    );
  };

  // ═══ ACCÈS PAR PERSONNE ═══
  const accessScreen = (
    <React.Fragment>
      <p className="cf2-hint">Qui peut ouvrir quoi, par personne et par document. Révocable en un geste.</p>

      {/* Permissions par CATÉGORIE : un seul endroit pour déléguer tout un domaine */}
      <div className="cf-sechead"><span>Permissions par catégorie</span></div>
      <div className="cf2-list-pad" style={{ marginBottom: 10 }}>
        {grants.map((g) => {
          const p = FP_PEERS[g.pid];
          if (!p) return null;
          return (
            <div key={g.pid + g.cat} className="cf2-prow" style={{ cursor: 'default' }}>
              <FPAvatar size={30} initials={p.initials} src={p.photo} />
              <span className="cf2-prow-t"><b>{cfName(g.pid)}</b><em><span style={{ color: cfCatColor(g.cat), fontWeight: 650 }}>{CF_CATS[g.cat].label}</span> · consulte toute la catégorie, documents à venir compris</em></span>
              <button className="cf-revoke" onClick={() => removeGrant(g.pid, g.cat)}>Retirer</button>
            </div>
          );
        })}
        {/* Suggestions selon le rôle : avocat → Juridique, coach/recruteur → Performance, médecin → Médical */}
        {(() => {
          const roleCat = [['jurist', 'jur'], ['avocat', 'jur'], ['entraineur', 'perf'], ['recruteur', 'perf'], ['preparateur', 'perf'], ['directeur sportif', 'perf'], ['medecin', 'med'], ['docteur', 'med']];
          const sugg = (window.FP_CONVOS || []).map((c) => c.id).filter((pid) => FP_PEERS[pid])
            .map((pid) => { const role = cfDeacc(FP_PEERS[pid].role || ''); const hit = roleCat.find(([w]) => role.includes(w)); return hit ? { pid, cat: hit[1] } : null; })
            .filter(Boolean).filter((s) => !grants.some((g) => g.pid === s.pid && g.cat === s.cat)).slice(0, 2);
          return sugg.map((s) => {
            const p = FP_PEERS[s.pid];
            return (
              <div key={'s' + s.pid + s.cat} className="cf2-prow" style={{ cursor: 'default' }}>
                <FPAvatar size={30} initials={p.initials} src={p.photo} />
                <span className="cf2-prow-t"><b>{cfName(s.pid)}</b><em>{p.role} : consulter toute la catégorie <span style={{ color: cfCatColor(s.cat), fontWeight: 650 }}>{CF_CATS[s.cat].label}</span> ?</em></span>
                <button className="cf3-nact" style={{ marginTop: 0 }} onClick={() => addGrant(s.pid, s.cat)}>Accorder</button>
              </div>
            );
          });
        })()}
        {gPick ? (
          <div className="cf3-block" style={{ padding: 11 }}>
            {!gPick.pid ? (
              <React.Fragment>
                <p className="cf2-hint" style={{ margin: '0 0 7px' }}>1 · Choisissez la personne</p>
                <div className="cf3-erows scroll">
                  {(window.FP_CONVOS || []).map((c) => c.id).filter((pid) => FP_PEERS[pid]).slice(0, 6).map((pid) => {
                    const p = FP_PEERS[pid];
                    return (
                      <button key={pid} className="cf2-prow" onClick={() => setGPick({ pid })}>
                        <FPAvatar size={28} initials={p.initials} src={p.photo} />
                        <span className="cf2-prow-t"><b>{cfName(pid)}</b><em>{p.role}{p.org ? ' · ' + p.org : ''}</em></span>
                        <span className="cf3-echeck"><CFI.Check s={10} /></span>
                      </button>
                    );
                  })}
                </div>
              </React.Fragment>
            ) : (
              <React.Fragment>
                <p className="cf2-hint" style={{ margin: '0 0 7px' }}>2 · {cfName(gPick.pid)} pourra consulter toute la catégorie :</p>
                <div className="cf2-fams cf3-cats">
                  {Object.keys(CF_CATS).map((k) => (
                    <button key={k} className="cf2-fam" onClick={() => addGrant(gPick.pid, k)}>
                      <i style={{ background: cfCatColor(k) }}></i>{CF_CATS[k].label}
                    </button>
                  ))}
                </div>
              </React.Fragment>
            )}
            <button className="cf2-addcancel" onClick={() => setGPick(null)}>Annuler</button>
          </div>
        ) : (
          <button className="cf2-addrow" onClick={() => setGPick({})}>
            <i><CFI.Plus s={13} /></i> Accorder une permission de catégorie
            <em>Ex. : Sira, tout le Juridique</em>
          </button>
        )}
      </div>

      <div className="cf-sechead"><span>Accès par personne</span></div>
      {people.map(({ pid, items }) => {
        const p = FP_PEERS[pid];
        if (!p) return null;
        return (
          <div key={pid} className="cf3-pblock">
            <div className="cf3-ph">
              <FPAvatar size={32} initials={p.initials} src={p.photo} />
              <span className="cf2-prow-t"><b>{cfName(pid)}</b><em>{p.role}{p.org ? ' · ' + p.org : ''}</em></span>
            </div>
            {items.slice(0, 4).map(({ d, a }) => (
              <button key={d.id} className="cf3-pline" onClick={() => setSel(d.id)}>
                <i style={{ background: cfCatColor(d.cat) }}></i>
                <span>{d.title}</span>
                <CfRightsIcons rights={a.rights} />
              </button>
            ))}
            <button className="cf3-more in" onClick={() => setSelP(pid)}>Voir tous les accès de {cfName(pid).split(' ')[0]} <CFI.Chevron s={10} dir="r" /></button>
          </div>
        );
      })}
      <p className="cf-accnote cf3-center cf3-oneline" style={{ fontSize: 9.5 }}>Ici, l’accès aux <b>documents</b> · la <b>représentation</b>, dans Autorisations.</p>
    </React.Fragment>
  );

  // ═══ ALERTES (à traiter) ═══
  const alertsScreen = (
    <React.Fragment>
      <div className="cf-sechead"><span>À traiter · {todo.length}</span></div>
      <div className="cf2-list-pad">
        {todo.map((d) => {
          const kind = d.state === 'sign' ? 'sign' : d.state === 'review' ? 'review' : 'request';
          return (
            <button key={d.id} className={`cf-todorow ${kind}`} onClick={() => setSel(d.id)}>
              <i>{kind === 'sign' ? <CFI.Pen s={15} /> : kind === 'review' ? <CFI.Eye s={15} /> : <CFI.Share s={14} />}</i>
              <span className="cf-todo-txt">
                <b>{kind === 'request' ? d.request.org + ' demande un accès' : d.title}</b>
                <em>{kind === 'sign' ? 'Votre signature est attendue' : kind === 'review' ? 'Sira Keïta relit, vous serez notifié' : d.title}</em>
              </span>
              <span className="cf-todo-act">{kind === 'sign' ? 'Signer' : kind === 'request' ? 'Répondre' : 'Voir'} <CFI.Chevron s={11} dir="r" /></span>
            </button>
          );
        })}
      </div>
      {soonDocs.length ? (
        <React.Fragment>
          <div className="cf-sechead" style={{ marginTop: 14 }}><span>Échéances · {soonDocs.length}</span></div>
          <div className="cf-list">{soonDocs.map(docCard)}</div>
        </React.Fragment>
      ) : null}
      <div className="cf-sechead" style={{ marginTop: 14 }}><span>Traitées · {CF_PROCESSED.length}</span></div>
      <div className="cf2-done">
        {CF_PROCESSED.map((x) => (
          <div key={x.id} className="cf2-donerow">
            <i className={x.okd ? 'ok' : 'no'}>{x.okd ? <CFI.Check s={11} /> : <CFI.Ban s={11} />}</i>
            <span><b>{x.txt}</b><em>{x.sub}</em></span>
          </div>
        ))}
      </div>
      <p className="cf-accnote">Échéances réelles des documents, aucun rappel artificiel.</p>
    </React.Fragment>
  );

  // ═══ ACTIVITÉ (journal) ═══
  const activityScreen = (ent) => (
    <React.Fragment>
      <div className="cf-sechead"><span>Toute l’activité{ent ? ' · ' + cfEntLabel(ent) : ''}</span></div>
      {activityRows(ent)}
      <p className="cf-accnote">Extraits du <b>journal inviolable</b> : chaque consultation, signature et export y est consigné.</p>
    </React.Fragment>
  );

  return (
    <div className={`cf ${dark ? 'dark' : 'light'}`} data-screen-label="Coffre-fort">
      <FPStatusBar light={dark} />
      <div className="cf-head">
        <button className="cf-back" onClick={back} aria-label="Retour"><CFI.Chevron /></button>
        <h1 className="cf-h1">{scr.k === 'access' ? 'Accès' : scr.k === 'alerts' ? 'À traiter' : scr.k === 'activity' ? 'Activité' : 'Coffre-fort'}</h1>
        <button className={`cf-hbtn ${showStats ? 'on' : ''}`} aria-label="Résumé" aria-pressed={showStats} onClick={toggleStats}><CFI.Stats /></button>
        <button className="cf-hbtn cf3-bell" aria-label="Notifications" aria-pressed={notifOpen} onClick={() => { setNotifOpen(!notifOpen); setNUnread(0); }}><CfV3Bell s={15} />{nUnread ? <i className="cf3-badge">{nUnread}</i> : null}</button>
        <button className="cf-hbtn" aria-label="Importer" onClick={() => setImporting(true)}><CFI.Plus /></button>
        <button className="cf-hbtn" aria-label="Verrouiller" onClick={onLock}><CFI.Lock s={15} /></button>
      </div>

      <div className="cf-scroll">
        <div className="cf-reveal">
          {intro ? (
            <CfIntro onDone={() => { try { localStorage.setItem('fp_coffre_intro', '1'); } catch (e) { /* ignore */ } setIntro(false); }} />
          ) : (
            <React.Fragment>
              {scr.k === 'home' ? home : null}
              {scr.k === 'ent' ? entScreen(scr.ent) : null}
              {scr.k === 'cat' ? catScreen(scr.ent, scr.cat) : null}
              {scr.k === 'access' ? accessScreen : null}
              {scr.k === 'alerts' ? alertsScreen : null}
              {scr.k === 'activity' ? activityScreen(scr.ent) : null}
            </React.Fragment>
          )}
        </div>
      </div>

      {selDoc ? (
        <CfV2DocSheet doc={selDoc} onClose={() => setSel(null)} toast={toast} onBack={selP ? () => setSel(null) : null}
          onToggleRight={toggleRight} onRevoke={revoke} onGrant={grant} onRefuse={refuse} onAddAccess={addAccess} onSign={(id) => setSigning(id)} onOpenDoc={(id) => setViewing(id)} onShare={(id) => setSharing(id)} />
      ) : null}
      {selP && !selDoc ? (
        <CfV2PersonSheet pid={selP} docs={docs} onClose={() => setSelP(null)} toast={toast}
          onOpenDoc={(id) => { setSel(id); }} onRemoveAll={removeAll} />
      ) : null}
      {notifOpen ? (
        <div className="cf3-npop" data-screen-label="Notifications">
          <div className="cf3-nphead">
            <CfV3Bell s={14} />
            <b>Notifications</b>
            <button onClick={() => setNotifOpen(false)} aria-label="Fermer">×</button>
          </div>
          <div className="cf3-nlist">
            {notifs.map((n) => (
              <div key={n.id} className={`cf3-nrow ${n.k}`}>
                <i>{n.k === 'view' ? <CFI.Eye s={13} /> : n.k === 'share' ? <CFI.Share s={12} /> : n.k === 'sign' ? <CFI.Pen s={13} /> : <CFI.Check s={12} />}</i>
                <span className="cf3-ntxt">
                  <span><b>{n.who}</b> {n.verb} <b>{n.docT}</b></span>
                  <em>{n.when}</em>
                  {n.action ? <button className="cf3-nact" onClick={() => doNotifAction(n)}><CFI.Plus s={11} /> {n.action.label}</button> : null}
                  {n.done ? <span className="cf3-ndone"><CFI.Check s={10} /> {n.done}</span> : null}
                </span>
              </div>
            ))}
          </div>
          <p className="cf3-npnote">Chaque geste sur vos documents prévient l’autre partie, et vous. Un repartage hors réseau vous permet d’<b>ajouter la personne</b> ou de révoquer.</p>
        </div>
      ) : null}
      {sharing ? <CfV3ShareSheet doc={docs.find((d) => d.id === sharing)} onClose={() => setSharing(null)} toast={toast}
        onPick={(pid) => { addAccess(sharing, pid); setSharing(null); }} onInvite={() => inviteLink(sharing)} /> : null}
      {tagsOpen ? (
        <div className="cf-sheetwrap">
          <button className="cf-dim" onClick={() => setTagsOpen(false)} aria-label="Fermer"></button>
          <div className="cf-sheet">
            <span className="cf-grab"></span>
            <div className="cf-sh-head">
              <span className="cf-doc-ic" style={{ width: 42, height: 42 }}><CFI.FolderIc s={17} /></span>
              <span className="cf-sh-t"><b>Tags</b><em>Tous les tags de vos documents. Touchez-en un pour filtrer le coffre.</em></span>
            </div>
            <div className="cf3-tagcloud">
              {(() => {
                const m = {};
                docs.forEach((d) => cfDocTags(d).forEach((l) => { const k2 = cfDeacc(l); (m[k2] = m[k2] || { l, n: 0 }).n++; }));
                customTags.forEach((l) => { const k2 = cfDeacc(l); if (!m[k2]) m[k2] = { l, n: 0 }; });
                return Object.values(m).sort((a, b) => b.n - a.n || a.l.localeCompare(b.l, 'fr')).map(({ l, n }) => (
                  <button key={l} className="cf2-fam" onClick={() => { setFam(null); setFamSub(null); setFamEnt(l); setTagsOpen(false); goHome(); }}>{l}{n ? <span className="cf3-chipn">{n}</span> : null}</button>
                ));
              })()}
            </div>
            <label className="cf-search" style={{ margin: '10px 0 0', height: 38 }}>
              <CFI.Plus s={13} />
              <input value={newTag} onChange={(e) => setNewTag(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') createFreeTag(); }} placeholder="Créer un tag : projet, mot-clé…" />
              {newTag.trim() ? <button onClick={createFreeTag} aria-label="Créer le tag" style={{ font: '650 11px Inter, sans-serif' }}>Créer</button> : null}
            </label>
            <p className="cf-accnote">Les tags viennent de l’import (personnes, clubs, projets, mots libres). Ceux créés ici sont prêts à être posés sur vos prochains fichiers.</p>
          </div>
        </div>
      ) : null}
      {exportPick ? (
        <div className="cf-sheetwrap">
          <button className="cf-dim" onClick={() => setExportPick(false)} aria-label="Fermer"></button>
          <div className="cf-sheet">
            <span className="cf-grab"></span>
            <div className="cf-sh-head">
              <span className="cf-doc-ic" style={{ width: 42, height: 42 }}><CFI.Wm s={16} /></span>
              <span className="cf-sh-t"><b>Exporter · PDF</b><em>Choisissez le document. L’export est filigrané et consigné.</em></span>
            </div>
            <div className="cf3-erows scroll" style={{ marginTop: 10 }}>
              {filtDocs.slice(0, 10).map((d) => (
                <button key={d.id} className="cf2-prow" onClick={() => { setExportPick(false); setViewExport(true); setViewing(d.id); }}>
                  <CfDocIcon cat={d.cat} size={28} />
                  <span className="cf2-prow-t"><b>{d.title}</b><em>{CF_CATS[d.cat].label} · {cfEntLabel(d.ent)}</em></span>
                  <CFI.Chevron s={11} dir="r" />
                </button>
              ))}
            </div>
          </div>
        </div>
      ) : null}
      {importing ? <CfV3ImportSheet onClose={() => setImporting(false)} onAdd={addFiles} /> : null}
      {viewing ? <CfV3DocView doc={docs.find((d) => d.id === viewing)} onClose={() => { setViewing(null); setViewExport(false); }} toast={toast} dark={dark} startExport={viewExport} onSign={(id) => { setViewing(null); setViewExport(false); setSigning(id); }} /> : null}
      {signing ? <CfV3Signer doc={docs.find((d) => d.id === signing)} onClose={() => setSigning(null)} onSigned={signDone} dark={dark} /> : null}
      {toastMsg ? <div className="cf-toast">{toastMsg}</div> : null}
    </div>
  );
}

Object.assign(window, { CoffreV3, CF3_DOCS_EXTRA, CfV3ImportSheet, CfV3Signer, CfV3DocView, CfV3ShareSheet, CfV3SignPad, CfV3Bell, CF3_NOTIFS_INIT, CF3_RECENT, cf3GuessCat, cf3Summary });
