/* ============================================================
   RIVA — LE PLAT QUI SE CONSTRUIT SOUS LES YEUX

   On coche « Fior di latte » : des billes de mozzarella se posent sur la
   pizza. On décoche « Roquette » : les feuilles s'envolent. C'est tout
   l'objet de ce fichier.

   Deux vues, parce que deux plats qui ne se regardent pas pareil :

     • LA PIZZA se voit de dessus. Les ingrédients se répartissent sur le
       disque, chacun avec sa vraie forme — la rondelle d'olive est un
       anneau, le champignon a son pied, la roquette a ses nervures.

     • LE PANUOZZO se voit de profil, ouvert. Chaque ingrédient est une
       COUCHE qui s'intercale, et le pain du dessus monte à mesure. C'est
       exactement ce que promet la carte de la maison : « garniture
       généreuse ? nous adaptons la taille du pain ».

   Pourquoi dessiné et pas photographié : il faudrait une photo détourée
   par ingrédient et par position, soit des centaines de fichiers. Et
   pourquoi pas une banque d'images libres : les jeux CC0 mélangent les
   styles, on verrait une olive au trait à côté d'un champignon en dégradé.
   ============================================================ */

const { useMemo: useCp } = React;

const Mo = (typeof Motion !== 'undefined') ? Motion : null;
const mo = Mo ? Mo.motion : null;
const Presence = Mo ? Mo.AnimatePresence : (({ children }) => children);

/* ---------- 1. Reconnaître un ingrédient ---------- */

function sansAccent(s) {
  return String(s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
}

/* Chaque ingrédient a une FORME et une couleur. La forme est ce qui rend le
   dessin crédible : une tranche de mortadelle ne ressemble pas à une bille
   de mozzarella, et si les deux sont des ronds de couleur, personne n'y croit. */
const FORMES = [
  // [ motif, forme, couleur, couleur secondaire ]
  ['stracciatella',   'filaments', '#FBF6EC', '#EADFC8'],
  ['burrata',         'bille',     '#FBF6EC', '#E6D9C0'],
  ['fior di latte',   'bille',     '#F8F1E4', '#E3D6BE'],
  ['mozzarella',      'bille',     '#F8F1E4', '#E3D6BE'],
  ['boursin',         'noisette',  '#F6EFDE', '#DFD2B6'],
  ['chevre',          'rondin',    '#F7F2E6', '#DCD2BC'],
  ['gorgonzola',      'persille',  '#F0E8D2', '#5E6E7A'],
  ['parmigiano',      'copeau',    '#EFDFAE', '#D8C489'],
  ['maroilles',       'coulee',    '#E0A94A', '#C08E32'],
  ['creme',           'coulee',    '#F3EADA', '#DFD3BC'],

  ['truffe',          'copeau',    '#2E2720', '#1B1712'],
  ['champignon',      'champignon','#CBB28C', '#A8906B'],

  ['roquette',        'feuille',   '#5C8A3A', '#41682A'],
  ['basilic',         'feuille',   '#4E7A32', '#375824'],
  ['persil',          'brins',     '#4E7A32', '#375824'],
  ['pesto',           'coulee',    '#4E7A32', '#3B5F26'],
  ['salade',          'feuille',   '#6A9B40', '#4E7530'],
  ['herbes',          'brins',     '#4E7A32', '#375824'],

  ['tomates confites','petale',    '#B8412A', '#8E3020'],
  ['tomate',          'rondelle',  '#C0402A', '#96301F'],
  ['sauce tomate',    'coulee',    '#C0402A', '#96301F'],
  ['harissa',         'coulee',    '#A82A1C', '#7E1F14'],

  ['olive',           'anneau',    '#2F3A22', '#1C2415'],
  ['capre',           'bille',     '#5C6B3A', '#41502A'],

  ['poivron',         'lamelle',   '#D9822B', '#B0651C'],
  ['oignons confits', 'lamelle',   '#A56A2C', '#7E4E1E'],
  ['oignon',          'arc',       '#9B5FA0', '#734478'],
  ['courgette',       'rondelle',  '#7FA24A', '#5E7A33'],
  ['aubergine',       'rondelle',  '#7A5580', '#5A3D5F'],
  ['legumes grilles', 'lamelle',   '#C2762B', '#98591C'],
  ['pomme de terre',  'rondelle',  '#E8CE9B', '#C9AC76'],

  ['mortadelle',      'tranche',   '#E8A79C', '#CE8479'],
  ['bresaola',        'tranche',   '#9C3B33', '#7A2A24'],
  ['pastrami',        'tranche',   '#A5443A', '#82302A'],
  ['bacon',           'lardon',    '#C2604A', '#9A4736'],
  ['spianata',        'rondelle',  '#B03A2A', '#8A2A1E'],
  ['jambon',          'tranche',   '#E8A79C', '#CE8479'],
  ['veau effiloche',  'effiloche', '#8E5A34', '#6C4325'],
  ['veau',            'des',       '#8E4A2E', '#6B3721'],
  ['kefta',           'boulette',  '#7E4527', '#5E321B'],
  ['boeuf',           'emiette',   '#8E4A2E', '#6B3721'],
  ['viande hachee',   'emiette',   '#8E4A2E', '#6B3721'],
  ['merguez',         'troncon',   '#A83A22', '#822A17'],
  ['poulet',          'des',       '#DEBE86', '#BC9C64'],
  ['escalope',        'tranche',   '#DEBE86', '#BC9C64'],
  ['thon',            'emiette',   '#C99A6A', '#A57A4E'],
  ['anchois',         'filet',     '#9A8158', '#786239'],
  ['saumon',          'tranche',   '#E2734A', '#BD5834'],
  ['scampi',          'crevette',  '#E8845C', '#C46540'],
  ['crevette',        'crevette',  '#E8845C', '#C46540'],
  ['fruits de mer',   'crevette',  '#E8845C', '#C46540'],

  ['oeuf',            'oeuf',      '#F7F2E4', '#F0B429'],
  ['pistache',        'eclat',     '#8FA84A', '#6E8535'],
  ['miel',            'coulee',    '#E8B33C', '#C9922E'],
  ['citron',          'zeste',     '#E8CE55', '#C9AC33'],
  ['origan',          'brins',     '#6E7A46', '#515A33'],
  ['piment',          'eclat',     '#A82A1C', '#7E1F14'],
];

function profil(nom) {
  const n = sansAccent(nom);
  for (const [motif, forme, c1, c2] of FORMES) {
    if (n.includes(motif)) return { forme, c1, c2 };
  }
  return { forme: 'bille', c1: '#EFE2BC', c2: '#D3C39A' };
}

/* Des positions stables : cocher un nouvel ingrédient ne doit pas faire
   sauter les précédents ailleurs sur la pizza. */
function semis(cle, n, rayon) {
  let h = 2166136261;
  for (let i = 0; i < cle.length; i++) { h ^= cle.charCodeAt(i); h = Math.imul(h, 16777619); }
  const suite = () => { h = Math.imul(h ^ (h >>> 15), 2246822507); return ((h >>> 0) % 10000) / 10000; };
  const pts = [];
  for (let i = 0; i < n; i++) {
    const a = suite() * Math.PI * 2;
    const d = Math.sqrt(suite()) * rayon;
    pts.push({ x: Math.cos(a) * d, y: Math.sin(a) * d, r: suite() * 360, e: 0.85 + suite() * 0.3 });
  }
  return pts;
}


/* ---------- 2. Le dessin d'un morceau ---------- */

function Morceau({ forme, c1, c2 }) {
  switch (forme) {
    case 'feuille':
      return (
        <g>
          <path d="M0,-7 C5,-4 6,3 0,7 C-6,3 -5,-4 0,-7 Z" fill={c1}/>
          <path d="M0,-6 L0,6" stroke={c2} strokeWidth="0.7" fill="none"/>
          <path d="M0,-2 L3,0 M0,1 L-3,3" stroke={c2} strokeWidth="0.5" fill="none"/>
        </g>
      );
    case 'anneau':
      return (
        <g>
          <circle r="4.6" fill={c1}/>
          <circle r="2" fill="#1A1A16"/>
          <path d="M-4.6,-1 A4.6,4.6 0 0 1 -1,-4.4" stroke={c2} strokeWidth="1" fill="none" opacity=".7"/>
        </g>
      );
    case 'champignon':
      return (
        <g>
          <path d="M-6,1 A6,5 0 0 1 6,1 L6,2 A6,3 0 0 1 -6,2 Z" fill={c1}/>
          <rect x="-1.6" y="1" width="3.2" height="4.2" rx="1.2" fill={c2}/>
          <path d="M-4,0.4 A4,3.2 0 0 1 4,0.4" stroke={c2} strokeWidth="0.7" fill="none" opacity=".6"/>
        </g>
      );
    case 'bille':
      return (
        <g>
          <circle r="5.2" fill={c1}/>
          <circle cx="-1.6" cy="-1.8" r="1.8" fill="#FFFFFF" opacity=".55"/>
          <circle r="5.2" fill="none" stroke={c2} strokeWidth="0.6" opacity=".7"/>
        </g>
      );
    case 'noisette':
      return <path d="M-5,2 C-5,-4 5,-4 5,2 C5,5 -5,5 -5,2 Z" fill={c1} stroke={c2} strokeWidth="0.6"/>;
    case 'rondin':
      return (
        <g>
          <circle r="5" fill={c1}/>
          <circle r="5" fill="none" stroke={c2} strokeWidth="1.1"/>
          <circle r="2.2" fill="none" stroke={c2} strokeWidth="0.6" opacity=".6"/>
        </g>
      );
    case 'persille':
      return (
        <g>
          <circle r="5" fill={c1}/>
          <path d="M-2,-2 l1.6,1.2 M1,1 l1.4,1 M-1,2 l1.2,-0.8" stroke={c2} strokeWidth="1.3" strokeLinecap="round"/>
        </g>
      );
    case 'copeau':
      return <path d="M-7,2 C-3,-3 3,-3 7,1 C3,3 -3,4 -7,2 Z" fill={c1} stroke={c2} strokeWidth="0.5"/>;
    case 'coulee':
      return <path d="M-8,0 C-5,-3.5 -1,2.5 3,-1 C6,-3.5 8,-0.5 8,1.5 C4,3.5 -4,3.5 -8,0 Z" fill={c1} opacity=".92"/>;
    case 'filaments':
      return (
        <g stroke={c1} strokeWidth="1.5" strokeLinecap="round" fill="none">
          <path d="M-6,-2 C-2,-4 2,0 6,-2"/>
          <path d="M-6,1 C-2,-1 2,3 6,1"/>
          <path d="M-4,3.4 C-1,2 2,4.6 5,3.4"/>
        </g>
      );
    case 'rondelle':
      return (
        <g>
          <circle r="5.4" fill={c1}/>
          <circle r="5.4" fill="none" stroke={c2} strokeWidth="0.9"/>
          <circle r="2.4" fill={c2} opacity=".35"/>
        </g>
      );
    case 'petale':
      return <path d="M0,-6 C4.5,-4 4.5,4 0,6 C-4.5,4 -4.5,-4 0,-6 Z" fill={c1} stroke={c2} strokeWidth="0.5"/>;
    case 'lamelle':
      return <path d="M-8,-1.6 C-3,-3.4 3,0.6 8,-1.2 L8,1.6 C3,3.4 -3,-0.6 -8,1.2 Z" fill={c1} stroke={c2} strokeWidth="0.4"/>;
    case 'arc':
      return <path d="M-6,3 A6,6 0 0 1 6,3" stroke={c1} strokeWidth="2.2" fill="none" strokeLinecap="round"/>;
    case 'tranche':
      return (
        <g>
          <path d="M-7,-3 C-3,-5.5 3,-0.5 7,-3 C7.5,1 5,4.5 0,4.5 C-5,4.5 -7.5,1 -7,-3 Z"
                fill={c1} stroke={c2} strokeWidth="0.5"/>
          <path d="M-4,-1 C-1,-2.5 2,0.5 5,-1" stroke={c2} strokeWidth="0.6" fill="none" opacity=".6"/>
        </g>
      );
    case 'lardon':
      return (
        <g>
          <rect x="-5" y="-2" width="10" height="4" rx="1.4" fill={c1}/>
          <rect x="-5" y="-0.6" width="10" height="1.3" fill="#F2DCC8" opacity=".75"/>
        </g>
      );
    case 'effiloche':
      return (
        <g stroke={c1} strokeWidth="1.8" strokeLinecap="round" fill="none">
          <path d="M-7,-1.5 C-3,-3 2,0 7,-2"/>
          <path d="M-6,1.5 C-2,0 3,3 7,1"/>
        </g>
      );
    case 'des':
      return <rect x="-3.4" y="-3" width="6.8" height="6" rx="1.4" fill={c1} stroke={c2} strokeWidth="0.5"/>;
    case 'boulette':
      return (
        <g>
          <circle r="4.6" fill={c1}/>
          <path d="M-2.4,-1.4 l1.4,1 M1,0.6 l1.2,1" stroke={c2} strokeWidth="1" strokeLinecap="round"/>
        </g>
      );
    case 'emiette':
      return (
        <g fill={c1}>
          <circle cx="-2.4" cy="-1.4" r="2"/><circle cx="1.6" cy="0.6" r="2.3"/>
          <circle cx="-0.6" cy="2.4" r="1.6"/>
        </g>
      );
    case 'troncon':
      return <rect x="-6" y="-2.6" width="12" height="5.2" rx="2.6" fill={c1} stroke={c2} strokeWidth="0.6"/>;
    case 'filet':
      return <path d="M-7,0 C-3,-2.4 3,2.4 7,0 C3,1.8 -3,-1.8 -7,0 Z" fill={c1} stroke={c2} strokeWidth="0.4"/>;
    case 'crevette':
      return (
        <g>
          <path d="M4,-4 C-3,-4 -5,1 -1,4 C1.5,5.5 4,4.5 4.5,2.5"
                stroke={c1} strokeWidth="3.4" fill="none" strokeLinecap="round"/>
          <path d="M4,-4 C-3,-4 -5,1 -1,4" stroke={c2} strokeWidth="0.8" fill="none" opacity=".7"/>
        </g>
      );
    case 'oeuf':
      return (
        <g>
          <ellipse rx="7.5" ry="6" fill={c1}/>
          <circle r="3.1" fill={c2}/>
          <circle cx="-1" cy="-1" r="1" fill="#FFF" opacity=".5"/>
        </g>
      );
    case 'eclat':
      return (
        <g fill={c1}>
          <ellipse cx="-1.8" cy="0" rx="2" ry="1.4" transform="rotate(-20)"/>
          <ellipse cx="1.8" cy="1" rx="1.8" ry="1.2" transform="rotate(25)"/>
        </g>
      );
    case 'zeste':
      return <path d="M-5,1 C-2,-2 2,-2 5,1" stroke={c1} strokeWidth="2" fill="none" strokeLinecap="round"/>;
    case 'brins':
      return (
        <g stroke={c1} strokeWidth="1.1" strokeLinecap="round" fill="none">
          <path d="M-3,2 L-1,-2"/><path d="M0,3 L1.5,-1.5"/><path d="M2.5,2 L4,-1"/>
        </g>
      );
    default:
      return <circle r="4.6" fill={c1}/>;
  }
}


/* ---------- 3. La pizza, vue de dessus ----------

   Refaite en octobre 2026 : la première version faisait « clip-art » sous
   la vraie photo de pizza (aplats de couleur, points noirs sur la croûte
   qui passaient pour des olives, garnitures entassées au même endroit).

   Ce qui la rapproche d'une vraie napolitaine :
   • une croûte dorée, plus foncée au bord, avec de petites cloques brunes ;
   • une sauce au bord irrégulier, avec du grain (filtre de bruit) ;
   • la fior di latte FONDUE, en flaques crémeuses légèrement gratinées —
     elle est comprise sur toutes les pizzas, c'est elle qui fait la pizza ;
   • une ombre douce sous chaque morceau : il est posé, pas collé ;
   • une répartition régulière (spirale « tournesol ») propre à chaque
     ingrédient et tournée d'un angle différent : chacun couvre toute la
     pizza, et ajouter un supplément ne déplace pas ceux déjà posés. */

/* 64 emplacements répartis uniformément sur le disque (angle d'or). */
const EMPLACEMENTS = (() => {
  const out = [], n = 64, r = 58, or = Math.PI * (3 - Math.sqrt(5));
  for (let i = 0; i < n; i++) {
    const d = r * Math.sqrt((i + 0.6) / n);
    out.push({ x: Math.cos(i * or) * d, y: Math.sin(i * or) * d });
  }
  return out;
})();

/* La fior di latte n'est pas posée en billes : elle est fondue (voir plus bas). */
function estFromageFondu(nom) {
  const n = sansAccent(nom);
  return n.includes('fior di latte') || n === 'mozzarella';
}

function PizzaComposee({ base, ingredients }) {
  const G = mo ? mo.g : 'g';
  const poses = ingredients.filter(ing => !estFromageFondu(ing.nom));
  const sauceClaire = /creme|citron|maroilles/i.test(base.nom || '');
  // Fior di latte prise EN SUPPLÉMENT : deux fois plus de flaques fondues.
  const doubleFromage = ingredients.some(ing => estFromageFondu(ing.nom) && !ing.dejaLa);

  return (
    <svg viewBox="0 0 200 200" className="compo-svg" role="img"
         aria-label={`Pizza : ${ingredients.map(i => i.nom).join(', ') || 'sans garniture'}`}>
      <defs>
        <radialGradient id="compo-pate" cx="50%" cy="50%" r="50%">
          <stop offset="78%" stopColor="#E9C27F"/>
          <stop offset="92%" stopColor="#D9A55C"/>
          <stop offset="100%" stopColor="#B07A36"/>
        </radialGradient>
        <radialGradient id="compo-lumiere" cx="38%" cy="32%" r="75%">
          <stop offset="0%" stopColor="#FFFFFF" stopOpacity=".16"/>
          <stop offset="100%" stopColor="#000000" stopOpacity=".18"/>
        </radialGradient>
        {/* le grain : sauce et fromage ne sont jamais des aplats */}
        <filter id="compo-grain" x="-10%" y="-10%" width="120%" height="120%">
          <feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="7" result="bruit"/>
          <feColorMatrix in="bruit" type="matrix" values="0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 .35 0" result="ombres"/>
          <feComposite in="ombres" in2="SourceGraphic" operator="in" result="grain"/>
          <feMerge><feMergeNode in="SourceGraphic"/><feMergeNode in="grain"/></feMerge>
        </filter>
        <filter id="compo-ombre" x="-50%" y="-50%" width="200%" height="200%">
          <feDropShadow dx="0.6" dy="1.1" stdDeviation="0.9" floodColor="#2A0E05" floodOpacity=".45"/>
        </filter>
        <filter id="compo-flou"><feGaussianBlur stdDeviation="1.4"/></filter>
        <clipPath id="compo-disque"><circle cx="100" cy="100" r="71"/></clipPath>
      </defs>

      {/* ombre portée de la pizza sur la table */}
      <ellipse cx="102" cy="104" rx="88" ry="87" fill="#000" opacity=".35" filter="url(#compo-flou)"/>

      {/* la croûte */}
      <circle cx="100" cy="100" r="87" fill="url(#compo-pate)" filter="url(#compo-grain)"/>
      {semis('bord-riva', 18, 1).map((p, i) => {
        const a = (i / 18) * Math.PI * 2 + p.x * 0.3;
        const d = 79 + p.y * 3.5;
        const t = 1.2 + p.e * 2.2;
        return (
          <ellipse key={i} cx={100 + Math.cos(a) * d} cy={100 + Math.sin(a) * d}
                   rx={t} ry={t * 0.7} transform={`rotate(${p.r} ${100 + Math.cos(a) * d} ${100 + Math.sin(a) * d})`}
                   fill="#7A4A1C" opacity={0.28 + (i % 3) * 0.1}/>
        );
      })}

      {/* la sauce, bord irrégulier */}
      <path d={contourSauce()} fill={base.couleur} filter="url(#compo-grain)"/>
      <circle cx="100" cy="100" r="72" fill="none" stroke="#5A1A0A" strokeOpacity=".18" strokeWidth="2.5"/>

      <g clipPath="url(#compo-disque)">
        {/* la fior di latte fondue, comprise sur toutes les pizzas */}
        <g filter="url(#compo-grain)">
          {EMPLACEMENTS.filter((_, i) => i % 5 === 2 || (doubleFromage && i % 5 === 4)).map((e, i) => {
            const p = semis('fior' + i, 1, 1)[0];
            const t = 9 + p.e * 6;
            return (
              <g key={i} transform={`translate(${100 + e.x} ${100 + e.y}) rotate(${p.r})`}>
                <path d={`M${-t},0 C${-t},${-t * 0.8} ${t * 0.7},${-t * 0.9} ${t},${-t * 0.1} C${t * 1.1},${t * 0.7} ${-t * 0.3},${t * 0.9} ${-t},0 Z`}
                      fill={sauceClaire ? '#FFF8EA' : '#F6EBD3'} opacity=".95"/>
                <ellipse cx={t * 0.25} cy={t * 0.1} rx={t * 0.35} ry={t * 0.22} fill="#D9A55C" opacity=".35"/>
              </g>
            );
          })}
        </g>

        <Presence>
          {poses.map((ing, rang) => {
            const p = profil(ing.nom);
            const nb = Math.min(ing.pieces || 8, 8);
            // Chaque ingrédient a sa propre spirale « tournesol », tournée d'un
            // angle différent : il couvre toute la pizza, et s'intercale
            // entre les autres au lieu de s'empiler dessus.
            const rotation = rang * 2.4 + 0.5, or = Math.PI * (3 - Math.sqrt(5));
            const propsAnim = mo ? {
              initial: { opacity: 0, scale: 0.3, y: -30 },
              animate: { opacity: 1, scale: 1, y: 0 },
              exit: { opacity: 0, scale: 0.5, y: -22 },
              transition: { type: 'spring', stiffness: 380, damping: 26, mass: 0.6 },
            } : {};
            const alea = semis(ing.nom, nb, 1);
            return (
              <G key={ing.nom} {...propsAnim} style={{ transformOrigin: '100px 100px' }} filter="url(#compo-ombre)">
                {Array.from({ length: nb }, (_, j) => {
                  const d = 58 * Math.sqrt((j + 0.5 + (rang % 3) * 0.3) / nb);
                  const e = { x: Math.cos(j * or + rotation) * Math.min(d, 60), y: Math.sin(j * or + rotation) * Math.min(d, 60) };
                  const a = alea[j];
                  return (
                    <g key={j}
                       transform={`translate(${100 + e.x + a.x * 3} ${100 + e.y + a.y * 3}) rotate(${a.r}) scale(${a.e * 1.45})`}>
                      <Morceau forme={p.forme} c1={p.c1} c2={p.c2}/>
                    </g>
                  );
                })}
              </G>
            );
          })}
        </Presence>
      </g>

      {/* la lumière du four, par-dessus tout */}
      <circle cx="100" cy="100" r="87" fill="url(#compo-lumiere)" pointerEvents="none"/>
    </svg>
  );
}

/* Le bord de la sauce : un cercle légèrement ondulé, toujours le même. */
function contourSauce() {
  const pts = [], n = 36, alea = semis('sauce-riva', n, 1);
  for (let i = 0; i < n; i++) {
    const a = (i / n) * Math.PI * 2;
    const r = 72 + alea[i].x * 1.8;
    pts.push([100 + Math.cos(a) * r, 100 + Math.sin(a) * r]);
  }
  let d = `M${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)}`;
  for (let i = 0; i < n; i++) {
    const p1 = pts[(i + 1) % n], m = [(pts[i][0] + p1[0]) / 2, (pts[i][1] + p1[1]) / 2];
    d += ` Q${pts[i][0].toFixed(1)},${pts[i][1].toFixed(1)} ${m[0].toFixed(1)},${m[1].toFixed(1)}`;
  }
  return d + ' Z';
}


/* ---------- 4. Le panuozzo, de profil, en couches ---------- */

function PanuozzoCompose({ base, ingredients }) {
  const G = mo ? mo.g : 'g';

  /* Le sandwich reste dans son cadre, quoi qu'on empile.

     Naïvement, une couche de hauteur fixe par ingrédient : à douze
     ingrédients le pain du dessus sort de l'écran. Ici la garniture dispose
     d'une hauteur MAXIMALE ; au-delà de quelques ingrédients les couches se
     resserrent et se chevauchent — ce que fait un vrai sandwich trop garni,
     d'ailleurs. Le pain monte donc, mais jamais hors du cadre. */
  const n = ingredients.length;
  const SOCLE = 212;                 // dessus du pain du bas
  const HAUTEUR_MAX = 104;           // ce que la garniture peut occuper
  const monte = Math.min(HAUTEUR_MAX, n * 17);
  const pas = n ? monte / n : 0;

  return (
    <svg viewBox="0 0 360 260" className="compo-svg" role="img"
         aria-label={`Panuozzo : ${ingredients.map(i => i.nom).join(', ') || 'sans garniture'}`}>
      <defs>
        <linearGradient id="compo-pain" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#F5E4C0"/><stop offset="100%" stopColor="#D6B478"/>
        </linearGradient>
        <linearGradient id="compo-mie" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor="#F7EBD2"/><stop offset="100%" stopColor="#EBD8B2"/>
        </linearGradient>
      </defs>

      {/* pain du dessous */}
      <path d="M26,220 Q26,250 66,250 L294,250 Q334,250 334,220 Q334,208 314,206 L46,206 Q26,208 26,220 Z"
            fill="url(#compo-pain)" stroke="#BE9455" strokeWidth="2"/>
      <path d="M38,212 L322,212 Q330,216 326,222 L34,222 Q30,216 38,212 Z" fill="url(#compo-mie)"/>
      <rect x="40" y="205" width="280" height="8" rx="4" fill={base.couleur} opacity=".9"/>

      {/* la garniture, de bas en haut */}
      <Presence>
        {ingredients.map((ing, i) => {
          const p = profil(ing.nom);
          const y = SOCLE - 10 - (i + 0.5) * pas;
          const pts = semis('couche-' + ing.nom, 8, 1);
          const anim = mo ? {
            initial: { opacity: 0, x: -120, scale: 0.7 },
            animate: { opacity: 1, x: 0, scale: 1 },
            exit: { opacity: 0, x: 130, scale: 0.65 },
            transition: { type: 'spring', stiffness: 260, damping: 24, mass: 0.8 },
          } : {};
          return (
            <G key={ing.nom} {...anim}>
              {pts.map((pt, k) => (
                <g key={k}
                   transform={`translate(${56 + k * 36 + pt.x * 11} ${y + pt.y * 7})
                               rotate(${(pt.r % 46) - 23}) scale(${1.5 * pt.e})`}>
                  <Morceau forme={p.forme} c1={p.c1} c2={p.c2}/>
                </g>
              ))}
            </G>
          );
        })}
      </Presence>

      {/* Pain du dessus : il monte à mesure que la garniture s'empile,
          exactement ce que promet la carte de la maison.

          Le déplacement est fait en CSS et non par Motion : animer `y` sur
          un groupe SVG passe par des attributs que la bibliothèque gère mal
          selon les navigateurs — le pain disparaissait purement et
          simplement. Une transition CSS sur `transform` est comprise
          partout de la même façon. */}
      <g style={{
            transform: `translateY(${-monte}px)`,
            transition: 'transform .55s cubic-bezier(.22,.9,.3,1)',
          }}>
        <path d="M26,206 Q26,164 92,156 Q180,146 268,156 Q334,164 334,206 Q334,216 314,216 L46,216 Q26,216 26,206 Z"
              fill="url(#compo-pain)" stroke="#BE9455" strokeWidth="2"/>
        {[70, 118, 166, 214, 262, 300].map((x, i) => (
          <ellipse key={i} cx={x} cy={174 + (i % 2 ? 6 : 0)} rx="7" ry="4.6"
                   fill="#5A4426" opacity={0.18 + (i % 3) * 0.1}/>
        ))}
      </g>
    </svg>
  );
}


/* ---------- 5. Ce qu'on montre à côté du dessin ---------- */

const PALIERS = [
  { seuil: 0,  nom: 'À nu',       mot: 'Ajoutez ce qui vous fait envie.' },
  { seuil: 2,  nom: 'Sobre',      mot: 'Ça commence.' },
  { seuil: 4,  nom: 'Garni',      mot: 'Voilà qui ressemble à quelque chose.' },
  { seuil: 6,  nom: 'Généreux',   mot: 'Là on parle.' },
  { seuil: 9,  nom: 'Débordant',  mot: 'Le pain va devoir grandir.' },
  { seuil: 12, nom: 'Monumental', mot: 'On adapte la taille du pain.' },
];

function palierPour(n) {
  let c = PALIERS[0];
  for (const p of PALIERS) if (n >= p.seuil) c = p;
  return c;
}

/* La base du plat : la sauce, qui colore tout le fond. */
function baseDe(texte) {
  const n = sansAccent(texte);
  if (n.includes('truffe')) return { couleur: '#2E2720', nom: 'truffe' };
  if (n.includes('maroilles')) return { couleur: '#E0A94A', nom: 'Maroilles' };
  if (n.includes('poivrons')) return { couleur: '#D9822B', nom: 'poivrons' };
  if (n.includes('pesto')) return { couleur: '#697B42', nom: 'pesto' };
  if (n.includes('citron-herbes')) return { couleur: '#C8BD68', nom: 'citron-herbes' };
  if (n.includes('creme')) return { couleur: '#F3EADA', nom: 'crème blanche' };
  if (n.includes('tomate')) return { couleur: '#C0402A', nom: 'sauce tomate' };
  return { couleur: '#E8D7B4', nom: 'nature' };
}

function Composition({ forme, ingredients, base: baseNoms, texteBase }) {
  const base = useCp(() => baseDe(texteBase || ''), [texteBase]);

  /* Ce que le plat porte déjà (lu dans sa description) vient en premier, et
     ne s'anime pas : il était là avant d'ouvrir l'écran. Les suppléments
     cochés viennent par-dessus, eux avec leur entrée. On écarte les sauces
     de la liste : elles colorent le fond, elles ne se posent pas en morceaux. */
  const deposes = useCp(() => {
    const sauces = ['creme', 'sauce'];
    const vus = new Set();
    const out = [];
    (baseNoms || []).forEach(nom => {
      const n = sansAccent(nom);
      if (!nom || sauces.some(x => n.startsWith(x)) || n.startsWith('base') || n.startsWith('pain artisanal')) return;
      if (vus.has(nom)) return;
      vus.add(nom);
      out.push({ nom, dejaLa: true });
    });
    (ingredients || []).forEach(ing => {
      if (vus.has(ing.nom)) return;
      vus.add(ing.nom);
      out.push({ ...ing, dejaLa: false });
    });
    return out;
  }, [baseNoms, ingredients]);

  const n = ingredients.length;
  const palier = palierPour(n);

  return (
    <div className="compo">
      <div className="compo-scene">
        {forme === 'pizza'
          ? <PizzaComposee base={base} ingredients={deposes}/>
          : <PanuozzoCompose base={base} ingredients={deposes}/>}
      </div>

      <div className="compo-infos">
        <div className="compo-palier">{palier.nom}</div>
        <div className="compo-mot">{palier.mot}</div>
        <div className="compo-jauge">
          <div className="compo-jauge-remplie" style={{ width: `${Math.min(100, (n / 12) * 100)}%` }}/>
        </div>
        <div className="compo-liste">
          <Presence>
            {deposes.map(ing => {
              const p = profil(ing.nom);
              const E = mo ? mo.span : 'span';
              const anim = mo ? {
                initial: { opacity: 0, scale: 0.7 },
                animate: { opacity: 1, scale: 1 },
                exit: { opacity: 0, scale: 0.7 },
                transition: { duration: 0.18 },
                layout: true,
              } : {};
              return (
                <E key={ing.nom} className={`compo-pastille ${ing.dejaLa ? 'origine' : ''}`} {...anim}>
                  <span className="compo-point" style={{ background: p.c1 }}/>
                  {ing.nom}
                </E>
              );
            })}
          </Presence>
          {!deposes.length && <span className="compo-vide">Rien pour l'instant.</span>}
        </div>
      </div>
    </div>
  );
}

try { Object.assign(window, { Composition, profil, PizzaComposee, PanuozzoCompose }); } catch (e) {}
