// Shared logic and data for both Estación Creativa variants.
// All flow state, content data, audio cues, and the kiosk shell layout live here.

const SCREENS = ['idle', 'scan', 'category', 'catalog', 'confirm', 'printing', 'pickup'];

// El catálogo (Categoría → Tema → Lámina) vive en data/catalog.json y se administra desde
// el backoffice. Se carga desde /api/catalog al arrancar (loadCatalog, abajo), que reemplaza
// CATEGORIES y DRAWINGS. Lo que sigue hardcodeado queda solo como fallback si la API falla.
let CATEGORIES = [
  { id: 'colorear', label: 'COLOREAR', sub: 'animales y más', emoji: '🎨', color: '#F4B6C2' },
  { id: 'recortar', label: 'RECORTAR', sub: 'máscaras y muñecos', emoji: '✂️', color: '#A8DADC' },
  { id: 'juegos',   label: 'JUEGOS',   sub: 'laberintos y puntos', emoji: '🧩', color: '#FFD56B' },
  { id: 'origami',  label: 'ORIGAMI',  sub: 'plegado paso a paso', emoji: '📄', color: '#C7B8EA' },
  { id: 'cuentos',  label: 'CUENTOS',  sub: 'historias para pintar', emoji: '📖', color: '#F6A96B' },
  { id: 'mandalas', label: 'MANDALAS', sub: 'círculos mágicos', emoji: '🌀', color: '#9BD0A8' },
];

// Each drawing has a tiny inline SVG preview so the prototype doesn't depend on assets.
let DRAWINGS = {
  colorear: [
    { id: 'oso',    name: 'Oso',     draw: 'bear', variants: [
      { id: 'oso',   name: 'Osito',     draw: 'bear' },
      { id: 'oso-2', name: 'Ositos',    draw: 'bear' },
      { id: 'oso-3', name: 'Mamá osa',  draw: 'bear' },
      { id: 'oso-4', name: 'Oso polar', draw: 'bear' },
    ] },
    { id: 'gato',   name: 'Gato',    draw: 'cat', variants: [
      { id: 'gato',   name: 'Gato',      draw: 'cat' },
      { id: 'gato-2', name: 'Músico',    draw: 'cat' },
      { id: 'gato-3', name: 'Gatito',    draw: 'cat' },
      { id: 'gato-4', name: 'Navidad',   draw: 'cat' },
    ] },
    { id: 'unicor', name: 'Unicornio', draw: 'unicorn', variants: [
      { id: 'unicor',   name: 'Unicornio',  draw: 'unicorn' },
      { id: 'unicor-2', name: 'Con canasta', draw: 'unicorn' },
      { id: 'unicor-3', name: 'Castillo',   draw: 'unicorn' },
      { id: 'unicor-4', name: 'Conejito',   draw: 'unicorn' },
    ] },
    { id: 'auto',   name: 'Tren',    draw: 'car', variants: [
      { id: 'auto',   name: 'Tren',        draw: 'car' },
      { id: 'auto-2', name: 'Trencitos',   draw: 'car' },
      { id: 'auto-3', name: 'Trenes',      draw: 'car' },
      { id: 'auto-4', name: 'Locomotora',  draw: 'car' },
    ] },
    { id: 'dino',   name: 'Dino',    draw: 'dino', variants: [
      { id: 'dino',   name: 'Dino',         draw: 'dino' },
      { id: 'dino-2', name: 'Pescador',     draw: 'dino' },
      { id: 'dino-3', name: 'Pterodáctilo', draw: 'dino' },
      { id: 'dino-4', name: 'Volador',      draw: 'dino' },
    ] },
    { id: 'pez',    name: 'Pez',     draw: 'fish', variants: [
      { id: 'pez',   name: 'Pez',       draw: 'fish' },
      { id: 'pez-2', name: 'Pececito',  draw: 'fish' },
      { id: 'pez-3', name: 'Peces',     draw: 'fish' },
      { id: 'pez-4', name: 'En el lago', draw: 'fish' },
    ] },
    { id: 'capi',   name: 'Capibara', draw: 'capybara', variants: [
      { id: 'capi',        name: 'Capibara',   draw: 'capybara' },
      { id: 'capi-mate',   name: 'Merienda',   draw: 'capybara-mate' },
      { id: 'capi-cumple', name: 'Cumpleaños', draw: 'capybara' },
      { id: 'capi-playa',  name: 'En la playa', draw: 'capybara' },
      { id: 'capi-auto',   name: 'De paseo',   draw: 'capybara' },
      { id: 'capi-mono',   name: 'Con moño',   draw: 'capybara' },
    ] },
  ],
  recortar: [
    { id: 'mask-leon', name: 'León', draw: 'mask', variants: [
      { id: 'mask-leon',   name: 'Sonriente', draw: 'mask' },
      { id: 'mask-leon-2', name: 'Dormilón',  draw: 'mask' },
      { id: 'mask-leon-3', name: 'Rey',       draw: 'mask' },
    ] },
    { id: 'munieco',   name: 'Muñeco', draw: 'doll', variants: [
      { id: 'munieco',   name: 'Muñeco', draw: 'doll' },
      { id: 'munieco-2', name: 'Muñeca', draw: 'doll' },
      { id: 'munieco-3', name: 'Robot',  draw: 'doll' },
    ] },
    { id: 'corona',    name: 'Corona', draw: 'crown', variants: [
      { id: 'corona',   name: 'Princesa', draw: 'crown' },
      { id: 'corona-2', name: 'Rey',      draw: 'crown' },
      { id: 'corona-3', name: 'Mágica',   draw: 'crown' },
    ] },
    { id: 'avion',     name: 'Avión', draw: 'plane', variants: [
      { id: 'avion',   name: 'De papel',  draw: 'plane' },
      { id: 'avion-2', name: 'Avioncito', draw: 'plane' },
      { id: 'avion-3', name: 'Cohete',    draw: 'plane' },
    ] },
  ],
  juegos: [
    { id: 'laberinto', name: 'Laberinto', draw: 'maze', variants: [
      { id: 'laberinto',   name: 'Fácil',   draw: 'maze' },
      { id: 'laberinto-2', name: 'Medio',   draw: 'maze' },
      { id: 'laberinto-3', name: 'Difícil', draw: 'maze' },
    ] },
    { id: 'puntos',    name: 'Une Puntos', draw: 'dots', variants: [
      { id: 'puntos',   name: 'Estrella', draw: 'dots' },
      { id: 'puntos-2', name: 'Casa',     draw: 'dots' },
      { id: 'puntos-3', name: 'Corazón',  draw: 'dots' },
      { id: 'puntos-4', name: 'Pez',      draw: 'dots' },
    ] },
    { id: 'sopa',      name: 'Sopa de Letras', draw: 'wordsearch', variants: [
      { id: 'sopa',   name: 'Animales',   draw: 'wordsearch' },
      { id: 'sopa-2', name: 'Naturaleza', draw: 'wordsearch' },
      { id: 'sopa-3', name: 'Juguetes',   draw: 'wordsearch' },
    ] },
    { id: 'busca',     name: 'Buscá las Diferencias', draw: 'spot', variants: [
      { id: 'busca',   name: 'Casita',  draw: 'spot', fullPage: true },
      { id: 'busca-2', name: 'Mar',     draw: 'spot', fullPage: true },
      { id: 'busca-3', name: 'Espacio', draw: 'spot', fullPage: true },
      { id: 'busca-4', name: 'Granja',  draw: 'spot', fullPage: true },
    ] },
  ],
  origami: [
    { id: 'grulla', name: 'Grulla', draw: 'crane', base: 'cuadrado' },
    { id: 'rana',   name: 'Rana',   draw: 'frog',  base: 'cuadrado' },
    { id: 'barco',  name: 'Barco',  draw: 'boat',  base: 'rectangulo' },
  ],
  cuentos: [
    { id: 'caperu', name: 'Caperucita', draw: 'hood' },
    { id: 'lobito', name: 'Tres Cerditos', draw: 'pigs' },
    { id: 'gato-bota', name: 'Gato con Botas', draw: 'catboot' },
  ],
  mandalas: [
    { id: 'mandala-flor', name: 'Flor', draw: 'mandala1', variants: [
      { id: 'mandala-flor',   name: 'Flor' }, { id: 'mandala-flor-2', name: 'Flores' }, { id: 'mandala-flor-3', name: 'Floral' } ] },
    { id: 'mandala-sol', name: 'Sol', draw: 'mandala2', variants: [
      { id: 'mandala-sol',   name: 'Sol' }, { id: 'mandala-sol-2', name: 'Rayos' }, { id: 'mandala-sol-3', name: 'Solar' } ] },
    { id: 'mandala-estrella', name: 'Estrella', draw: 'mandala3', variants: [
      { id: 'mandala-estrella',   name: 'Estrella' }, { id: 'mandala-estrella-2', name: 'Brillos' }, { id: 'mandala-estrella-3', name: 'Estelar' } ] },
    { id: 'mandala-petalos', name: 'Pétalos', draw: 'mandala4', variants: [
      { id: 'mandala-petalos',   name: 'Pétalos' }, { id: 'mandala-petalos-2', name: 'Jardín' }, { id: 'mandala-petalos-3', name: 'Floreado' } ] },
    { id: 'mandala-hexagono', name: 'Hexágono', draw: 'mandala5', variants: [
      { id: 'mandala-hexagono',   name: 'Hexágono' }, { id: 'mandala-hexagono-2', name: 'Geométrico' }, { id: 'mandala-hexagono-3', name: 'Círculos' } ] },
    { id: 'mandala-loto', name: 'Loto', draw: 'mandala6', variants: [
      { id: 'mandala-loto',   name: 'Loto' }, { id: 'mandala-loto-2', name: 'Clásico' }, { id: 'mandala-loto-3', name: 'Espiral' } ] },
    { id: 'mandala-ondas', name: 'Ondas', draw: 'mandala7', variants: [
      { id: 'mandala-ondas',   name: 'Ondas' }, { id: 'mandala-ondas-2', name: 'Espirales' }, { id: 'mandala-ondas-3', name: 'Solcito' } ] },
    { id: 'mandala-diamante', name: 'Diamante', draw: 'mandala8', variants: [
      { id: 'mandala-diamante',   name: 'Diamante' }, { id: 'mandala-diamante-2', name: 'Cristal' }, { id: 'mandala-diamante-3', name: 'Animalitos' } ] },
    { id: 'mandala-caleido', name: 'Caleidoscopio', draw: 'mandala9', variants: [
      { id: 'mandala-caleido',   name: 'Caleidoscopio' }, { id: 'mandala-caleido-2', name: 'Mosaico' }, { id: 'mandala-caleido-3', name: 'Ornado' } ] },
  ],
};

// Inject the category id into every theme + variant so DrawingArt knows which
// assets/<categoria>/ subfolder to load from. (Images live in per-category folders.)
for (const [catId, themes] of Object.entries(DRAWINGS)) {
  for (const t of themes) {
    t.cat = catId;
    (t.variants || []).forEach(v => { v.cat = catId; });
  }
}

// Construye CATEGORIES (nivel 1) y DRAWINGS (temas → variantes) desde el catálogo del
// server, filtrando lo deshabilitado e inyectando `cat` en cada tema y lámina para que
// DrawingArt sepa de qué assets/<categoria>/ levantar la imagen. Las "láminas" del catálogo
// (campo items) se exponen como `variants`, que es lo que ya consumen las dos variantes.
function buildCatalogData(catalog) {
  const cats = (catalog?.categories || []).filter(c => c.enabled !== false);
  CATEGORIES = cats.map(({ id, label, sub, emoji, color }) => ({ id, label, sub, emoji, color }));
  DRAWINGS = {};
  for (const c of cats) {
    DRAWINGS[c.id] = (c.themes || [])
      .filter(t => t.enabled !== false)
      .map(t => ({
        id: t.id, name: t.name, draw: t.draw, base: t.base, cat: c.id,
        variants: (t.items || [])
          .filter(it => it.enabled !== false)
          .map(it => ({ ...it, cat: c.id })),
      }))
      .filter(t => t.variants.length > 0);   // un tema sin láminas activas no se muestra
  }
  Object.assign(window, { CATEGORIES, DRAWINGS });
}

// Trae el catálogo del backoffice. app.jsx la espera antes de renderizar el kiosko.
// Si la API falla, deja el catálogo hardcodeado de arriba como fallback.
async function loadCatalog() {
  try {
    const j = await fetch('/api/catalog').then(r => r.json());
    if (j && j.catalog) buildCatalogData(j.catalog);
  } catch (e) {
    console.warn('[catalog] no se pudo cargar, usando fallback:', e);
  }
}

// Categorías visibles en el kiosko (ya vienen filtradas a las habilitadas).
function activeCategories() { return CATEGORIES; }

// ---------- Tiny SVG illustrations (placeholder line art) ----------
// Stroke-only so they read as "for coloring". Kept simple intentionally.
function LineArt({ kind, stroke = '#1a1a1a', size = 120 }) {
  const common = { width: size, height: size, viewBox: '0 0 120 120', fill: 'none', stroke, strokeWidth: 2.5, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (kind) {
    case 'bear': return (
      <svg {...common}>
        <circle cx="60" cy="65" r="30" />
        <circle cx="38" cy="38" r="10" />
        <circle cx="82" cy="38" r="10" />
        <circle cx="50" cy="58" r="2.5" fill={stroke} />
        <circle cx="70" cy="58" r="2.5" fill={stroke} />
        <ellipse cx="60" cy="72" rx="6" ry="4" fill={stroke} />
        <path d="M52 80 Q60 86 68 80" />
      </svg>
    );
    case 'cat': return (
      <svg {...common}>
        <circle cx="60" cy="65" r="28" />
        <path d="M38 45 L32 30 L48 42 Z" />
        <path d="M82 45 L88 30 L72 42 Z" />
        <circle cx="50" cy="62" r="2.5" fill={stroke} />
        <circle cx="70" cy="62" r="2.5" fill={stroke} />
        <path d="M60 70 L57 74 L63 74 Z" fill={stroke} />
        <path d="M52 80 Q60 84 68 80" />
        <path d="M40 70 L30 68 M40 74 L30 76 M80 70 L90 68 M80 74 L90 76" />
      </svg>
    );
    case 'unicorn': return (
      <svg {...common}>
        <path d="M30 80 Q30 50 55 50 L80 50 Q95 50 95 65 L95 80" />
        <path d="M55 50 L60 30 L65 50" />
        <path d="M62 30 L58 26 M62 30 L66 26" />
        <circle cx="85" cy="62" r="2" fill={stroke} />
        <path d="M40 55 Q35 45 45 42" />
        <path d="M30 80 L30 90 M40 80 L40 90 M85 80 L85 90 M95 80 L95 90" />
      </svg>
    );
    case 'car': return (
      <svg {...common}>
        <path d="M20 70 L25 55 L45 50 L75 50 L90 65 L100 70 L100 80 L20 80 Z" />
        <circle cx="35" cy="82" r="8" />
        <circle cx="85" cy="82" r="8" />
        <path d="M48 52 L48 65 L72 65 L72 52" />
      </svg>
    );
    case 'dino': return (
      <svg {...common}>
        <path d="M25 80 Q25 50 55 50 L75 50 Q90 50 90 35 Q90 30 95 30" />
        <path d="M55 50 L52 42 L58 46 L60 38 L64 46 L70 42 L72 50" />
        <circle cx="88" cy="36" r="1.5" fill={stroke} />
        <path d="M25 80 L25 90 M40 78 L40 90 M70 78 L70 90 M85 50 L85 90" />
      </svg>
    );
    case 'fish': return (
      <svg {...common}>
        <path d="M20 60 Q35 35 70 45 Q90 50 95 60 Q90 70 70 75 Q35 85 20 60 Z" />
        <path d="M95 60 L110 45 L110 75 Z" />
        <circle cx="35" cy="55" r="2.5" fill={stroke} />
        <path d="M50 60 Q60 55 70 60" />
      </svg>
    );
    case 'capybara': return (
      <svg {...common} strokeWidth={3}>
        <path d="M18 56 Q15 47 25 46 Q28 33 48 34 Q86 33 95 62 Q98 82 88 90 Q90 96 84 96 Q80 96 79 90 L70 90 Q70 96 64 96 Q60 96 59 90 L40 89 Q26 87 22 72 Q18 66 18 56 Z" />
        <path d="M41 36 Q37 25 49 31" />
        <circle cx="33" cy="50" r="2.6" fill={stroke} />
        <ellipse cx="20" cy="54" rx="2" ry="2.6" fill={stroke} />
        <path d="M17 61 Q23 65 29 62" />
      </svg>
    );
    case 'capybara-mate': return (
      <svg {...common} strokeWidth={3}>
        <path d="M18 56 Q15 47 25 46 Q28 33 48 34 Q86 33 95 62 Q98 82 88 90 Q90 96 84 96 Q80 96 79 90 L70 90 Q70 96 64 96 Q60 96 59 90 L40 89 Q26 87 22 72 Q18 66 18 56 Z" />
        <path d="M41 36 Q37 25 49 31" />
        <circle cx="33" cy="50" r="2.6" fill={stroke} />
        <ellipse cx="20" cy="54" rx="2" ry="2.6" fill={stroke} />
        <path d="M17 61 Q23 65 29 62" />
        <path d="M9 95 Q3 95 3 86 Q3 79 12 79 Q21 79 21 86 Q21 95 15 95 Z" />
        <path d="M5 81 Q12 84 19 81" />
        <path d="M17 80 L24 70" />
        <circle cx="25" cy="69" r="1.9" fill={stroke} />
      </svg>
    );
    case 'mask': return (
      <svg {...common}>
        <path d="M30 50 Q30 30 60 30 Q90 30 90 50 L90 80 Q60 95 30 80 Z" />
        <circle cx="48" cy="55" r="5" />
        <circle cx="72" cy="55" r="5" />
        <path d="M55 70 Q60 75 65 70" />
        <path d="M30 50 L20 40 M30 60 L18 60 M90 50 L100 40 M90 60 L102 60" />
      </svg>
    );
    case 'doll': return (
      <svg {...common}>
        <circle cx="60" cy="35" r="15" />
        <path d="M45 50 L40 90 L80 90 L75 50" />
        <path d="M45 60 L25 75 M75 60 L95 75" />
        <circle cx="55" cy="33" r="1.5" fill={stroke} />
        <circle cx="65" cy="33" r="1.5" fill={stroke} />
      </svg>
    );
    case 'crown': return (
      <svg {...common}>
        <path d="M20 80 L25 40 L45 60 L60 30 L75 60 L95 40 L100 80 Z" />
        <circle cx="60" cy="40" r="2" fill={stroke} />
      </svg>
    );
    case 'plane': return (
      <svg {...common}>
        <path d="M20 70 L50 55 L70 30 L80 30 L75 55 L100 60 L100 70 L75 75 L80 95 L70 95 L50 75 Z" />
      </svg>
    );
    case 'maze': return (
      <svg {...common}>
        <rect x="20" y="20" width="80" height="80" />
        <path d="M20 40 L60 40 L60 60 L40 60 L40 80 L80 80 L80 40 L100 40 M20 60 L40 60 M60 80 L60 100" strokeWidth="2" />
      </svg>
    );
    case 'dots': return (
      <svg {...common}>
        {[[30,30],[60,25],[90,35],[100,60],[85,85],[55,90],[25,75],[20,50]].map(([x,y],i) => (
          <g key={i}>
            <circle cx={x} cy={y} r="2.5" fill={stroke} />
            <text x={x+5} y={y-3} fontSize="8" fill={stroke} stroke="none">{i+1}</text>
          </g>
        ))}
      </svg>
    );
    case 'wordsearch': return (
      <svg {...common}>
        <rect x="20" y="20" width="80" height="80" />
        {Array.from({length:5}).map((_,i)=>(<line key={'h'+i} x1="20" y1={20+i*16} x2="100" y2={20+i*16} strokeWidth="1"/>))}
        {Array.from({length:5}).map((_,i)=>(<line key={'v'+i} x1={20+i*16} y1="20" x2={20+i*16} y2="100" strokeWidth="1"/>))}
      </svg>
    );
    case 'spot': return (
      <svg {...common}>
        <rect x="15" y="30" width="40" height="60" rx="3" />
        <rect x="65" y="30" width="40" height="60" rx="3" />
        <circle cx="30" cy="50" r="4" />
        <circle cx="80" cy="50" r="4" />
        <path d="M22 70 L48 70 M72 70 L98 72" />
      </svg>
    );
    case 'crane': return (
      <svg {...common}>
        <path d="M20 60 L60 40 L100 60 L80 75 L60 60 L40 75 Z" />
        <path d="M60 40 L60 25 M60 25 L70 22" />
      </svg>
    );
    case 'frog': return (
      <svg {...common}>
        <ellipse cx="60" cy="70" rx="35" ry="20" />
        <circle cx="45" cy="50" r="8" />
        <circle cx="75" cy="50" r="8" />
        <circle cx="45" cy="50" r="2.5" fill={stroke} />
        <circle cx="75" cy="50" r="2.5" fill={stroke} />
        <path d="M45 75 Q60 82 75 75" />
      </svg>
    );
    case 'boat': return (
      <svg {...common}>
        <path d="M20 75 L100 75 L90 95 L30 95 Z" />
        <path d="M60 75 L60 25 L90 65 L60 65" />
      </svg>
    );
    case 'hood': return (
      <svg {...common}>
        <path d="M40 35 Q60 20 80 35 L80 60 Q60 70 40 60 Z" />
        <circle cx="60" cy="45" r="12" />
        <circle cx="55" cy="45" r="1.5" fill={stroke} />
        <circle cx="65" cy="45" r="1.5" fill={stroke} />
        <path d="M55 55 Q60 58 65 55" />
        <path d="M40 60 L35 95 L85 95 L80 60" />
      </svg>
    );
    case 'pigs': return (
      <svg {...common}>
        {[20,55,90].map((cx,i)=>(<g key={i}>
          <circle cx={cx} cy="60" r="12" />
          <circle cx={cx-4} cy="58" r="1.5" fill={stroke} />
          <circle cx={cx+4} cy="58" r="1.5" fill={stroke} />
          <ellipse cx={cx} cy="64" rx="3" ry="2" />
          <path d={`M${cx-8} 50 L${cx-10} 45 M${cx+8} 50 L${cx+10} 45`} />
        </g>))}
      </svg>
    );
    case 'catboot': return (
      <svg {...common}>
        <circle cx="60" cy="40" r="15" />
        <path d="M50 28 L48 22 L56 32 M70 28 L72 22 L64 32" />
        <circle cx="55" cy="40" r="1.5" fill={stroke} />
        <circle cx="65" cy="40" r="1.5" fill={stroke} />
        <path d="M48 55 L42 95 L78 95 L72 55" />
        <path d="M40 95 L40 100 L55 100 L55 95 M65 95 L65 100 L80 100 L80 95" />
      </svg>
    );
    case 'mandala1': return (
      <svg {...common}>
        <circle cx="60" cy="60" r="35" />
        <circle cx="60" cy="60" r="20" />
        <circle cx="60" cy="60" r="8" />
        {[0,45,90,135,180,225,270,315].map(a=>{
          const r = a*Math.PI/180; const x=60+Math.cos(r)*30; const y=60+Math.sin(r)*30;
          return <circle key={a} cx={x} cy={y} r="6" />;
        })}
      </svg>
    );
    case 'mandala2': return (
      <svg {...common}>
        <circle cx="60" cy="60" r="12" />
        {Array.from({length:12}).map((_,i)=>{
          const a = i*30*Math.PI/180; const x1=60+Math.cos(a)*15; const y1=60+Math.sin(a)*15;
          const x2=60+Math.cos(a)*38; const y2=60+Math.sin(a)*38;
          const x3=60+Math.cos(a)*32; const y3=60+Math.sin(a)*32;
          return <g key={i}><line x1={x1} y1={y1} x2={x2} y2={y2}/><circle cx={x3} cy={y3} r="3"/></g>;
        })}
        <circle cx="60" cy="60" r="42" strokeDasharray="3 3" />
      </svg>
    );
    case 'mandala3': return (
      <svg {...common}>
        {Array.from({length:8}).map((_,i)=>{
          const a = i*45; return (
          <path key={i} transform={`rotate(${a} 60 60)`} d="M60 60 L60 25 L65 35 L55 35 Z" />
        );})}
        <circle cx="60" cy="60" r="6" />
        <circle cx="60" cy="60" r="40" />
      </svg>
    );
    case 'mandala4': return (
      <svg {...common}>
        <circle cx="60" cy="60" r="48" />
        {Array.from({length:8}).map((_,i)=>(
          <ellipse key={i} cx="60" cy="30" rx="6" ry="18" transform={`rotate(${i*45} 60 60)`} />
        ))}
        <circle cx="60" cy="60" r="8" />
        <circle cx="60" cy="60" r="3" fill={stroke} />
      </svg>
    );
    case 'mandala5': return (
      <svg {...common}>
        <path d="M60 15 L98.97 37.5 L98.97 82.5 L60 105 L21.03 82.5 L21.03 37.5 Z" />
        <path d="M60 30 L85.98 75 L34.02 75 Z" />
        <path d="M60 90 L85.98 45 L34.02 45 Z" />
        <circle cx="60" cy="60" r="6" />
      </svg>
    );
    case 'mandala6': return (
      <svg {...common}>
        {[0,45,90,135,180,225,270,315].map(a => (
          <path key={a} transform={`rotate(${a} 60 60)`} d="M60 60 Q 50 35 60 15 Q 70 35 60 60 Z" />
        ))}
        {[22.5,112.5,202.5,292.5].map(a => (
          <path key={a} transform={`rotate(${a} 60 60)`} d="M60 60 Q 53 45 60 30 Q 67 45 60 60 Z" />
        ))}
        <circle cx="60" cy="60" r="6" />
      </svg>
    );
    case 'mandala7': return (
      <svg {...common}>
        <circle cx="60" cy="60" r="48" />
        <circle cx="60" cy="60" r="36" strokeDasharray="4 3" />
        <circle cx="60" cy="60" r="24" />
        <circle cx="60" cy="60" r="12" strokeDasharray="2 2" />
        <circle cx="60" cy="60" r="4" fill={stroke} />
        {Array.from({length:12}).map((_,i)=>{
          const a = i*30*Math.PI/180;
          const x = 60 + Math.cos(a)*42;
          const y = 60 + Math.sin(a)*42;
          return <circle key={i} cx={x} cy={y} r="2" fill={stroke} />;
        })}
      </svg>
    );
    case 'mandala8': return (
      <svg {...common}>
        {[0,30,60].map(a => (
          <rect key={a} x="30" y="30" width="60" height="60" transform={`rotate(${a} 60 60)`} />
        ))}
        <circle cx="60" cy="60" r="8" />
        <circle cx="60" cy="60" r="2" fill={stroke} />
      </svg>
    );
    case 'mandala9': return (
      <svg {...common}>
        <circle cx="60" cy="60" r="45" />
        <circle cx="60" cy="60" r="30" />
        {Array.from({length:12}).map((_,i)=>{
          const a = i*30*Math.PI/180;
          const x = 60 + Math.cos(a)*45;
          const y = 60 + Math.sin(a)*45;
          return <line key={i} x1="60" y1="60" x2={x} y2={y} />;
        })}
        {Array.from({length:6}).map((_,i)=>{
          const a = i*60*Math.PI/180;
          const cx = 60 + Math.cos(a)*22.5;
          const cy = 60 + Math.sin(a)*22.5;
          return <circle key={i} cx={cx} cy={cy} r="5" />;
        })}
        <circle cx="60" cy="60" r="8" />
      </svg>
    );
    default: return <svg {...common}><rect x="20" y="20" width="80" height="80" rx="4" /></svg>;
  }
}

// DrawingArt — picks between an external image (PNG/JPG/SVG) and the inline
// LineArt SVG fallback. Use this in screens that show the actual chosen drawing.
function DrawingArt({ drawing, stroke = '#1a1a1a', size = 120 }) {
  // Prefer a real image by convention: assets/<categoria>/<id>.png. Falls back
  // to the explicit `image` field, and finally to the code-drawn SVG (LineArt).
  const folder = drawing?.cat ? `${drawing.cat}/` : '';
  const src = drawing?.image || (drawing?.id ? `assets/${folder}${drawing.id}.png` : null);
  const [failed, setFailed] = React.useState(false);
  React.useEffect(() => { setFailed(false); }, [src]);  // retry when the drawing changes
  if (src && !failed) {
    return (
      <img src={src} alt={drawing?.name || ''}
        onError={() => setFailed(true)}
        style={{ width: size, height: size, objectFit: 'contain', display: 'block' }} />
    );
  }
  return <LineArt kind={drawing?.draw} stroke={stroke} size={size} />;
}

// ---------- Audio cues (Web Audio synthesised; no asset files needed) ----------
const audioCtx = (() => {
  try { return new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { return null; }
})();
function blip(freq = 800, dur = 0.12, type = 'sine', vol = 0.15) {
  if (!audioCtx) return;
  try {
    if (audioCtx.state === 'suspended') audioCtx.resume();
    const o = audioCtx.createOscillator(), g = audioCtx.createGain();
    o.type = type; o.frequency.value = freq;
    g.gain.value = vol;
    o.connect(g); g.connect(audioCtx.destination);
    o.start();
    g.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + dur);
    o.stop(audioCtx.currentTime + dur);
  } catch (e) {}
}
function chord(freqs, dur = 0.3) { freqs.forEach((f, i) => setTimeout(() => blip(f, dur, 'triangle', 0.1), i * 60)); }
const sfx = {
  tap:    () => blip(900, 0.08, 'square', 0.08),
  pick:   () => chord([523, 659], 0.18),
  scan:   () => chord([440, 660, 880], 0.15),
  print:  () => blip(220, 0.4, 'sawtooth', 0.05),
  done:   () => chord([523, 659, 784, 1047], 0.25),
};

// ---------- Speech / Voice (pre-recorded audio system with TTS fallback) ----------
//
// Strategy:
//  1) AUDIO_LIBRARY maps each line ID to an optional pre-recorded MP3 URL.
//     Replace these with your own ElevenLabs/Azure recordings later — that's
//     the only change needed. Filenames already organised under audio/.
//  2) If a line has no recording yet (or fails to load), we fall back to
//     Google Translate's free TTS endpoint, which sounds dramatically better
//     than the OS speechSynthesis (and is consistent across all devices).
//  3) Final fallback: window.speechSynthesis with the best available voice.
//
// All clips are cached in memory after first play, so there's no re-download.

const AUDIO_LIBRARY = {
  // line-id : { text: 'spoken phrase', src: '/path/to/recording.mp3' (optional) }
  idle:     { text: '¡Hola artista! Tocá la pantalla para empezar.',       src: 'audio/idle.mp4' },
  scan:     { text: 'Mostrá tu cupón al lector.',                          src: 'audio/scan.mp4' },
  category: { text: '¿Qué te gustaría hacer hoy?',                         src: 'audio/category.mp4' },
  catalog:  { text: 'Elegí tu dibujo favorito.',                           src: 'audio/catalog.mp4' },
  confirm:  { text: '¿Lo imprimimos?',                                     src: 'audio/confirm.mp4' },
  printing: { text: 'Estamos imprimiendo tu dibujo.',                      src: null },
  error:    { text: 'Ups, no pudimos imprimir. Probá de nuevo o pedí ayuda.', src: null },
  pickup:   { text: '¡Listo! Retirá tu dibujo de la ranura.',              src: 'audio/pickup.mp4' },
};

const _audioCache = {};        // url -> HTMLAudioElement
let _currentAudio = null;       // currently-playing element

function _gtransUrl(text) {
  // Google Translate TTS — free, high quality, ~male/female "es" voice.
  // Limited to ~200 chars per call (we're well under).
  return 'https://translate.google.com/translate_tts'
       + '?ie=UTF-8&client=tw-ob&tl=es&q=' + encodeURIComponent(text);
}

function _stopAudio() {
  if (_currentAudio) {
    try { _currentAudio.pause(); _currentAudio.currentTime = 0; } catch (e) {}
    _currentAudio = null;
  }
  if (window.speechSynthesis) {
    try { window.speechSynthesis.cancel(); } catch (e) {}
  }
}

// Tracks the most-recent URL that was blocked by autoplay policy, so we can
// retry it on first user gesture (kiosk runs Chrome with --autoplay-policy=
// no-user-gesture-required, but in dev browsers the first idle line is blocked).
let _pendingUrl = null;

function _playUrl(url, onFail) {
  let a = _audioCache[url];
  if (!a) {
    // NOTE: do NOT set crossOrigin — Google TTS doesn't return CORS headers,
    // and HTMLAudioElement playback doesn't require CORS. Setting it would
    // make the request fail to load entirely.
    a = new Audio(url);
    a.preload = 'auto';
    _audioCache[url] = a;
  }
  a.currentTime = 0;
  _currentAudio = a;
  const p = a.play();
  if (p && p.catch) {
    p.catch(err => {
      if (err && err.name === 'NotAllowedError') {
        // Autoplay blocked — queue for the first user gesture.
        _pendingUrl = url;
      } else if (onFail) {
        onFail();
      }
    });
  }
  a.onerror = () => onFail && onFail();
}

// On the first user gesture, retry whatever was queued. Re-arms itself
// so subsequent screens can also queue (rare, but cheap to support).
if (typeof window !== 'undefined') {
  const flush = () => {
    if (_pendingUrl) {
      const u = _pendingUrl;
      _pendingUrl = null;
      _playUrl(u);
    }
  };
  window.addEventListener('pointerdown', flush);
  window.addEventListener('keydown', flush);
}

// Best-available speechSynthesis voice (final fallback)
let _bestVoice = null;
function _pickBestVoice() {
  if (!window.speechSynthesis) return null;
  const voices = window.speechSynthesis.getVoices() || [];
  if (!voices.length) return null;
  let best = null, bestScore = -1;
  for (const v of voices) {
    const name = (v.name || '').toLowerCase();
    const lang = (v.lang || '').toLowerCase();
    let s = 0;
    if (lang.startsWith('es-ar') || lang.startsWith('es-419') || lang.startsWith('es-mx')) s += 60;
    else if (lang.startsWith('es')) s += 40;
    else continue;
    if (name.includes('google')) s += 30;
    if (name.includes('neural') || name.includes('online') || name.includes('natural')) s += 25;
    if (name.includes('premium') || name.includes('enhanced')) s += 20;
    if (s > bestScore) { bestScore = s; best = v; }
  }
  return best;
}
if (window.speechSynthesis) {
  const tryLoad = () => { _bestVoice = _pickBestVoice() || _bestVoice; };
  tryLoad();
  window.speechSynthesis.onvoiceschanged = tryLoad;
}

function _speakSynth(text) {
  if (!window.speechSynthesis) return;
  try {
    window.speechSynthesis.cancel();
    const u = new SpeechSynthesisUtterance(text);
    if (_bestVoice) { u.voice = _bestVoice; u.lang = _bestVoice.lang; }
    else u.lang = 'es-AR';
    u.rate = 0.92; u.pitch = 1.05;
    window.speechSynthesis.speak(u);
  } catch (e) {}
}

// Public API: speak a line by id (or arbitrary text).
// If `enabled` is false, no-op.
function speak(idOrText, enabled) {
  if (!enabled) return;
  _stopAudio();

  const entry = AUDIO_LIBRARY[idOrText];
  const text  = entry ? entry.text : idOrText;
  const src   = entry ? entry.src  : null;

  // 1) Pre-recorded MP3 if provided
  if (src) {
    _playUrl(src, () => {
      // fall back to Google TTS, then synth
      _playUrl(_gtransUrl(text), () => _speakSynth(text));
    });
    return;
  }
  // 2) Google TTS placeholder (much more natural than OS synth)
  _playUrl(_gtransUrl(text), () => _speakSynth(text));
}

// ---------- Kiosk shell — wraps any screen in the physical kiosk frame ----------
function KioskFrame({ children, brand, brandStyle, screenScale = 1 }) {
  // Screen area in design pixels — 1080×1350 portrait (matches a 15.6" touch in portrait)
  return (
    <div style={{
      width: 720, height: 1300,
      background: '#fafafa',
      borderRadius: 16,
      boxShadow: '0 30px 80px rgba(0,0,0,0.18), 0 0 0 1px rgba(0,0,0,0.06)',
      position: 'relative',
      overflow: 'hidden',
      fontFamily: "'Quicksand', system-ui, sans-serif",
    }}>
      {/* Top branding strip on the kiosk (printed on physical totem) */}
      <div style={{
        padding: '32px 40px 18px',
        textAlign: 'center',
        borderBottom: '1px dashed rgba(0,0,0,0.08)',
        ...brandStyle,
      }}>
        {brand}
      </div>

      {/* Touch screen area */}
      <div style={{
        margin: '20px',
        height: 1080,
        borderRadius: 12,
        background: '#000',
        padding: 8,
        boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.05)',
      }}>
        <div style={{
          width: '100%', height: '100%',
          borderRadius: 6,
          overflow: 'hidden',
          background: '#fff',
          position: 'relative',
        }}>
          {children}
        </div>
      </div>

      {/* QR scanner + paper slot (physical) */}
      <div style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
        padding: '0 50px',
        marginTop: 8,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ width: 36, height: 36, border: '2px solid #1a1a1a', borderRadius: 4, display: 'grid', placeItems: 'center', fontSize: 10, fontWeight: 700 }}>QR</div>
          <div style={{ fontFamily: "'Caveat', cursive", fontSize: 18 }}>escaneá tu código</div>
        </div>
      </div>
      <div style={{
        position: 'absolute',
        bottom: 30, left: 60, right: 60,
        height: 40,
        background: 'linear-gradient(180deg, #1a1a1a 0%, #2a2a2a 100%)',
        borderRadius: 4,
        boxShadow: 'inset 0 4px 8px rgba(0,0,0,0.4)',
        display: 'grid', placeItems: 'center',
        fontFamily: "'Caveat', cursive",
        fontSize: 18, color: '#fff',
      }}>
        retirá tu dibujo acá ↓
      </div>
    </div>
  );
}

// ---------- QR / barcode scanner (HID keyboard mode, e.g. Unnion BR70) ----------
// The BR70 in USB-HID mode "types" the decoded code very fast and ends with an
// Enter. We buffer keystrokes globally and fire onScan(code) on Enter. A long
// pause between keys clears the buffer (so stray keys never join a real scan).
function useScanner(onScan, { enabled = true } = {}) {
  const ref = React.useRef({ buf: '', last: 0 });
  const cb = React.useRef(onScan);
  cb.current = onScan;
  React.useEffect(() => {
    if (!enabled) return;
    const onKey = (e) => {
      const s = ref.current;
      const now = performance.now();
      if (now - s.last > 150) s.buf = '';   // pause → new scan / stray key
      s.last = now;
      if (e.key === 'Enter') {
        const code = s.buf.trim();
        s.buf = '';
        if (code) cb.current(code);
        return;
      }
      if (e.key.length === 1) s.buf += e.key;  // printable char only
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [enabled]);
}

// Coupon validation. Valid codes come from the backoffice (/api/coupons) and are
// refreshed on load. Override with ?coupon=OTHER, or ?coupon=any during testing.
const COUPON = {
  override: new URLSearchParams(location.search).get('coupon'),
  codes: new Set(['EC-DEMO-2026']),   // seeded default; refreshed from the server
};
fetch('/api/coupons')
  .then(r => r.json())
  .then(j => { if (j && j.coupons) COUPON.codes = new Set(j.coupons.map(c => c.code)); })
  .catch(() => {});
function isValidCoupon(code) {
  if (COUPON.override === 'any') return true;
  if (COUPON.override) return code === COUPON.override;
  return COUPON.codes.has(code);
}

// ---------- Trazabilidad de navegación ----------
// Cada uso del QR abre una sesión; registramos cada pantalla por la que pasa el usuario
// (incluidos los "atrás"), para reconstruir el recorrido en el backoffice (/api/navigation).
function newSessionId() {
  try { if (window.crypto && crypto.randomUUID) return crypto.randomUUID(); } catch (e) {}
  return 's-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
}
function catLabelById(id) {
  const c = CATEGORIES.find(x => x.id === id);
  return c ? (c.label || c.id) : (id || null);
}
function trackNav(payload) {
  try {
    fetch('/api/track', {
      method: 'POST', keepalive: true,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    }).catch(() => {});
  } catch (e) {}
}

Object.assign(window, {
  SCREENS,
  LineArt, DrawingArt, sfx, speak, blip, chord,
  KioskFrame, useScanner, isValidCoupon, COUPON, activeCategories, loadCatalog,
  newSessionId, catLabelById, trackNav,
});
// CATEGORIES/DRAWINGS se exponen en window dentro de buildCatalogData (recién cuando
// el catálogo llegó). loadCatalog se invoca desde app.jsx antes de renderizar.
