{"id":4295,"date":"2025-07-18T13:12:10","date_gmt":"2025-07-18T12:12:10","guid":{"rendered":"https:\/\/zabalimp.es\/?page_id=4295"},"modified":"2025-12-10T13:46:22","modified_gmt":"2025-12-10T12:46:22","slug":"juegos","status":"publish","type":"page","link":"https:\/\/zabalimp.es\/eu\/juegos\/","title":{"rendered":"Juegos"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"4295\" class=\"elementor elementor-4295\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7f6338d e-flex e-con-boxed e-con e-parent\" data-id=\"7f6338d\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6f4ae75 elementor-widget elementor-widget-html\" data-id=\"6f4ae75\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<button id=\"btn-install-pwa\" class=\"z-btn z-btn-secondary\" style=\"display:none;margin-left:8px;\">\r\n  Instalar app\r\n<\/button>\r\n\r\n<script>\r\n(function () {\r\n  const btn = document.getElementById('btn-install-pwa');\r\n  if (!btn) return;\r\n\r\n  let deferredPrompt = null;\r\n  const isStandalone =\r\n    window.matchMedia('(display-mode: standalone)').matches ||\r\n    window.navigator.standalone;\r\n  const isIOS = \/iphone|ipad|ipod\/i.test(navigator.userAgent);\r\n\r\n  const show = () => (btn.style.display = '');\r\n  const hide = () => (btn.style.display = 'none');\r\n\r\n  \/\/ \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u0434\u0451\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u043f\u0440\u043e\u043c\u043f\u0442\r\n  window.addEventListener('beforeinstallprompt', (e) => {\r\n    e.preventDefault();\r\n    deferredPrompt = e;\r\n    if (!isStandalone) show();\r\n  });\r\n\r\n  \/\/ \u041f\u043e\u0434\u0441\u0442\u0440\u0430\u0445\u043e\u0432\u043a\u0430: \u043a\u043e\u0433\u0434\u0430 SW \u0433\u043e\u0442\u043e\u0432 (Android\/Chrome) \u2014 \u0442\u043e\u0436\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\r\n  if (navigator.serviceWorker && navigator.serviceWorker.ready) {\r\n    navigator.serviceWorker.ready.then(() => {\r\n      if (!isStandalone && !isIOS) show();\r\n    });\r\n  }\r\n\r\n  \/\/ \u041a\u043b\u0438\u043a \u043f\u043e \u043a\u043d\u043e\u043f\u043a\u0435\r\n  btn.addEventListener('click', async () => {\r\n    if (deferredPrompt) {\r\n      deferredPrompt.prompt();\r\n      const choice = await deferredPrompt.userChoice.catch(() => ({ outcome: 'dismissed' }));\r\n      deferredPrompt = null;\r\n      if (choice && choice.outcome === 'accepted') hide();\r\n      return;\r\n    }\r\n    if (isIOS) {\r\n      alert('En iPhone: \u201cCompartir\u201d \u2192 \u201cA\u00f1adir a pantalla de inicio\u201d.');\r\n    } else {\r\n      alert('Abre el men\u00fa del navegador y elige \u201cA\u00f1adir a la pantalla principal\u201d.');\r\n    }\r\n  });\r\n\r\n  window.addEventListener('appinstalled', hide);\r\n})();\r\n<\/script>\r\n\r\n\r\n<div id=\"zabalimp-menu\">\r\n<div id=\"zabalimp-acceso\" class=\"z-auth\">\r\n  <h2 class=\"z-auth-title\">Bienvenido\/a a Zaba Limp<\/h2>\r\n\r\n  <div class=\"z-auth-card\">\r\n    <label class=\"z-label\" for=\"nombre-usuario\">Tu nombre<\/label>\r\n    <input type=\"text\" id=\"nombre-usuario\" class=\"z-input\" placeholder=\"Escribe tu nombre\">\r\n\r\n    <button id=\"btn-acceder\" class=\"z-btn z-btn-primary z-btn-full\">Entrar<\/button>\r\n    <div id=\"error-acceso\" class=\"z-error\" style=\"display:none;\"><\/div>\r\n  <\/div>\r\n<\/div>\r\n\r\n\r\n<div id=\"zabalimp-juegos\" style=\"display:none;max-width:1100px;margin:0 auto;\">\r\n  <h2 id=\"bienvenida-juegos\" style=\"text-align:center;margin:12px 0 20px;\"><\/h2>\r\n\r\n  <!-- \u041f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f -->\r\n  <div class=\"z-toolbar\">\r\n    <div class=\"z-tabs\" id=\"z-tabs\"><\/div>\r\n    <div class=\"z-search\">\r\n      <input id=\"z-search-input\" type=\"search\" placeholder=\"Buscar juego...\" aria-label=\"Buscar juego\">\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <!-- \u0421\u0435\u0442\u043a\u0430 \u043a\u0430\u0440\u0442\u043e\u0447\u0435\u043a -->\r\n  <div id=\"menu-juegos\" class=\"z-grid\" aria-live=\"polite\"><\/div>\r\n\r\n  <!-- \u041f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 (\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e) -->\r\n  <div id=\"progreso-usuario\" class=\"z-progress\"><\/div>\r\n\r\n  <div class=\"z-footer\">\r\n    <button id=\"btn-cambiar-usuario\" class=\"z-btn z-btn-secondary\" style=\"display:none;\">Cambiar usuario<\/button>\r\n  <\/div>\r\n<\/div>\r\n\r\n<!-- \u041a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438\u0433\u0440\u044b (\u043f\u043e\u043b\u043d\u043e\u044d\u043a\u0440\u0430\u043d\u043d\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0443 \u0442\u0435\u0431\u044f) -->\r\n<div id=\"zabalimp-juego\" style=\"display:none;\"><\/div>\r\n<\/div>\r\n<script>\r\n\r\nwindow.addEventListener('DOMContentLoaded', function() {\r\n  let storedId = localStorage.getItem('zabalimp_user_id');\r\n  let storedNombre = localStorage.getItem('zabalimp_user_nombre');\r\n  if (storedId && storedNombre) {\r\n    usuarioId = storedId;\r\n    usuarioNombre = storedNombre;\r\n    mostrarMenuJuegos();\r\n  }\r\n});\r\n\r\n\r\n\/\/ ======== \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 ========\r\nconst AJAX_URL = '\/wp-admin\/admin-ajax.php'; \/\/ \u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e \u0434\u043b\u044f WP\r\n\r\n\/\/ ======== 1. \u0412\u0445\u043e\u0434 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f ========\r\nlet usuarioId = null, usuarioNombre = '';\r\n\r\n\/\/ submit \u043f\u043e Enter\r\ndocument.getElementById('nombre-usuario').addEventListener('keydown', e=>{\r\n  if(e.key === 'Enter') document.getElementById('btn-acceder').click();\r\n});\r\n\r\ndocument.getElementById('btn-acceder').onclick = function() {\r\n  const btn = this;\r\n  const nombre = document.getElementById('nombre-usuario').value.trim();\r\n  const err = document.getElementById('error-acceso');\r\n\r\n  if (nombre.length < 2){\r\n    err.textContent = 'Introduce tu nombre';\r\n    err.style.display = 'block';\r\n    return;\r\n  }\r\n  err.style.display = 'none';\r\n\r\n  btn.disabled = true; btn.textContent = 'Entrando\u2026';\r\n\r\n  fetch(AJAX_URL, {\r\n    method: 'POST',\r\n    body: new URLSearchParams({ action:'zabalimp_acceso_usuario', nombre })\r\n  })\r\n  .then(r=>r.json())\r\n  .then(data=>{\r\n    if (data.success){\r\n      usuarioId = data.id;\r\n      usuarioNombre = nombre;\r\n      localStorage.setItem('zabalimp_user_id', usuarioId);\r\n      localStorage.setItem('zabalimp_user_nombre', usuarioNombre);\r\n      mostrarMenuJuegos();\r\n    } else {\r\n      err.textContent = 'Error en el acceso';\r\n      err.style.display = 'block';\r\n    }\r\n  })\r\n  .catch(()=>{ err.textContent = 'Sin conexi\u00f3n. Int\u00e9ntalo de nuevo.'; err.style.display = 'block'; })\r\n  .finally(()=>{ btn.disabled = false; btn.textContent = 'Entrar'; });\r\n};\r\n\r\n\/\/ === \u0424\u0438\u043b\u044c\u0442\u0440\u044b \u0441\u043f\u0438\u0441\u043a\u0430 \u0438\u0433\u0440 (\u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0435) ===\r\nlet zActiveCategory = 'all';\r\nlet zSearchTerm = '';\r\n\r\n\/\/ \u041f\u043e\u0438\u0441\u043a: \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440 \u043f\u0440\u0438 \u0432\u0432\u043e\u0434\u0435\r\nconst zSearchInput = document.getElementById('z-search-input');\r\nif (zSearchInput) {\r\n  zSearchInput.addEventListener('input', () => {\r\n    zSearchTerm = zSearchInput.value.trim().toLowerCase();\r\n    zRenderCards(window.__Z_GAMES_CACHE || []);\r\n  });\r\n}\r\n\r\n\r\n\r\n\r\n\r\nfunction mostrarMenuJuegos(){\r\n  document.getElementById('zabalimp-acceso').style.display = 'none';\r\n  document.getElementById('zabalimp-juegos').style.display = '';\r\n  document.getElementById('bienvenida-juegos').innerHTML = `\u00a1Hola, <b>${usuarioNombre}<\/b>!<br>Elige un juego para empezar.`;\r\n  cargarJuegos();\r\n  document.getElementById('btn-cambiar-usuario').style.display = '';\r\n    document.getElementById('btn-cambiar-usuario').onclick = function(){\r\n      usuarioId = null; usuarioNombre = '';\r\n      \/\/ \u041e\u0447\u0438\u0449\u0430\u0435\u043c localStorage\r\n      localStorage.removeItem('zabalimp_user_id');\r\n      localStorage.removeItem('zabalimp_user_nombre');\r\n      document.getElementById('zabalimp-juegos').style.display = 'none';\r\n      document.getElementById('zabalimp-acceso').style.display = '';\r\n      document.getElementById('nombre-usuario').value = '';\r\n    }\r\n\r\n}\r\n\r\n\/\/ \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0443 \u0438 \u043c\u043e\u0434\u0430\u043b\u043a\u0443 \u0440\u0435\u0439\u0442\u0438\u043d\u0433\u0430 (\u043e\u0434\u0438\u043d \u0440\u0430\u0437)\r\nfunction zEnsureRankingButton(){\r\n  const bar = document.querySelector('.z-toolbar');\r\n  if (!bar || bar.querySelector('#z-btn-ranking')) return;\r\n\r\n  const btn = document.createElement('button');\r\n  btn.id='z-btn-ranking';\r\n  btn.className='z-btn z-btn-secondary';\r\n  btn.style.marginLeft = '8px';\r\n  btn.textContent = '\ud83c\udfc6 Ranking';\r\n  \/\/ \u0432\u0441\u0442\u0430\u0432\u0438\u043c \u043f\u0435\u0440\u0435\u0434 \u043f\u043e\u0438\u0441\u043a\u043e\u043c\r\n  const search = bar.querySelector('.z-search');\r\n  bar.insertBefore(btn, search);\r\n\r\n  btn.onclick = ()=> zShowRankingModal();\r\n}\r\n\r\nfunction zShowRankingModal(){\r\n  let host = document.getElementById('z-ranking');\r\n  if (!host){\r\n    host = document.createElement('div');\r\n    host.id = 'z-ranking';\r\n    host.innerHTML = `\r\n      <style>\r\n        #z-ranking{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:100000}\r\n        #z-ranking .bg{position:absolute;inset:0;background:rgba(0,0,0,.45);}\r\n        #z-ranking .dlg{\r\n          position:relative;background:#fff;border-radius:18px;box-shadow:0 12px 48px rgba(0,0,0,.28);\r\n          width:min(980px,96vw);max-height:90vh;display:flex;flex-direction:column;overflow:hidden;\r\n          font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;\r\n        }\r\n        #z-ranking header{display:flex;gap:8px;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid #e5e7eb}\r\n        #z-ranking h3{margin:0;font-size:1.15rem;font-weight:900;color:#0f172a}\r\n        #z-ranking .controls{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\r\n        #z-ranking .controls input, #z-ranking .controls select{\r\n          border:1px solid #d1d5db;border-radius:10px;padding:6px 10px;font-weight:600;\r\n        }\r\n        #z-ranking .tbl-wrap{overflow:auto}\r\n        #z-ranking table{width:100%;border-collapse:collapse}\r\n        #z-ranking thead th{\r\n          position:sticky;top:0;background:#f8fafc;border-bottom:1px solid #e5e7eb;padding:10px;font-weight:800;\r\n          text-align:left;cursor:pointer;user-select:none;\r\n        }\r\n        #z-ranking tbody td{padding:10px;border-bottom:1px solid #f1f5f9}\r\n        #z-ranking .me{background:#fff7ed}\r\n        #z-ranking .close{border:none;background:#eef2ff;border-radius:10px;padding:6px 10px;font-weight:800;cursor:pointer}\r\n        #z-ranking .empty{padding:28px;text-align:center;color:#64748b}\r\n      <\/style>\r\n      <div class=\"bg\"><\/div>\r\n      <div class=\"dlg\" role=\"dialog\" aria-modal=\"true\">\r\n        <header>\r\n          <h3>Ranking global<\/h3>\r\n          <div class=\"controls\">\r\n            <select id=\"rk-game\"><\/select>\r\n            <input id=\"rk-search\" type=\"search\" placeholder=\"Buscar jugador o juego\u2026\">\r\n            <button class=\"close\" id=\"rk-close\">Cerrar<\/button>\r\n          <\/div>\r\n        <\/header>\r\n        <div class=\"tbl-wrap\">\r\n          <table>\r\n            <thead>\r\n              <tr>\r\n                <th data-sort=\"jugador\">Jugador<\/th>\r\n                <th data-sort=\"juego\">Juego<\/th>\r\n                <th data-sort=\"record\">R\u00e9cord<\/th>\r\n                <th data-sort=\"fecha\">Fecha<\/th>\r\n              <\/tr>\r\n            <\/thead>\r\n            <tbody id=\"rk-body\"><tr><td class=\"empty\" colspan=\"4\">Cargando\u2026<\/td><\/tr><\/tbody>\r\n          <\/table>\r\n        <\/div>\r\n      <\/div>`;\r\n    document.body.appendChild(host);\r\n\r\n    host.querySelector('#rk-close').onclick = ()=> host.remove();\r\n    host.querySelector('.bg').onclick = ()=> host.remove();\r\n\r\n    \/\/ \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430 \u043f\u043e \u043a\u043b\u0438\u043a\u0443 \u043d\u0430 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438\r\n    host.querySelectorAll('th[data-sort]').forEach(th=>{\r\n      th.onclick = ()=>{\r\n        const s = host.dataset.sort || 'record';\r\n        let dir = host.dataset.dir || 'desc';\r\n        const now = th.dataset.sort;\r\n        if (s === now) dir = (dir==='asc'?'desc':'asc'); else dir = (now==='record'?'desc':'asc');\r\n        host.dataset.sort = now; host.dataset.dir = dir;\r\n        zUpdateRankingModal();\r\n      };\r\n    });\r\n\r\n    \/\/ \u0441\u0435\u043b\u0435\u043a\u0442 \u0438\u0433\u0440\r\n    const sel = host.querySelector('#rk-game');\r\n    sel.innerHTML = `<option value=\"\">Todas las juegos<\/option>` +\r\n      (window.__Z_GAMES_CACHE||[]).map(g=>`<option value=\"${g.id}\">${g.nombre}<\/option>`).join('');\r\n    sel.onchange = ()=> zUpdateRankingModal();\r\n\r\n    \/\/ \u043f\u043e\u0438\u0441\u043a\r\n    host.querySelector('#rk-search').oninput = ()=> {\r\n      clearTimeout(host._deb); host._deb = setTimeout(zUpdateRankingModal, 250);\r\n    };\r\n\r\n    host.dataset.sort = 'record';\r\n    host.dataset.dir  = 'desc';\r\n  }\r\n  zUpdateRankingModal();\r\n}\r\n\r\nasync function zUpdateRankingModal(){\r\n  const host = document.getElementById('z-ranking'); if (!host) return;\r\n  const game_id = host.querySelector('#rk-game').value;\r\n  const q       = host.querySelector('#rk-search').value.trim();\r\n  const sort    = host.dataset.sort || 'record';\r\n  const dir     = host.dataset.dir  || 'desc';\r\n\r\n  const rows = await zLoadRanking({ game_id, q, sort, dir, limit:200 });\r\n\r\n  const tb = host.querySelector('#rk-body');\r\n  if (!rows.length){\r\n    tb.innerHTML = `<tr><td class=\"empty\" colspan=\"4\">No hay datos para tu filtro.<\/td><\/tr>`;\r\n    return;\r\n  }\r\n  const me = (usuarioNombre||'').toLowerCase();\r\n  const fmtDate = d => (d||'').split('T')[0] || d || '';\r\n\r\n  tb.innerHTML = rows.map(r=>`\r\n    <tr class=\"${r.usuario?.toLowerCase()===me?'me':''}\">\r\n      <td>${r.usuario||'-'}<\/td>\r\n      <td>${r.juego_nombre||('Juego '+(r.juego_id||''))}<\/td>\r\n      <td><b>${r.record??0}<\/b><\/td>\r\n      <td>${fmtDate(r.fecha)}<\/td>\r\n    <\/tr>\r\n  `).join('');\r\n}\r\n\r\n\r\n\/\/ ===== \u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438 (\u0443\u043a\u0440\u0443\u043f\u043d\u0451\u043d\u043d\u044b\u0435) =====\r\n\/\/ 4 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u0435 \u0433\u0440\u0443\u043f\u043f\u044b \u0441 \u043a\u0440\u0443\u043f\u043d\u044b\u043c\u0438 \u0438\u043a\u043e\u043d\u043a\u0430\u043c\u0438\r\nconst Z_CATEGORIES_MERGED = {\r\n  memoria:   { id:'memoria',   name:'Memoria',                emoji:'\ud83e\udde0' }, \/\/ \u043f\u0430\u043c\u044f\u0442\u044c\r\n  atencion:  { id:'atencion',  name:'Atenci\u00f3n y Reacci\u00f3n',    emoji:'\u26a1' }, \/\/ \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435\/\u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c\r\n  pensamiento:{id:'pensamiento',name:'Pensamiento y Palabras',emoji:'\ud83e\udde9' }, \/\/ \u043b\u043e\u0433\u0438\u043a\u0430+\u044f\u0437\u044b\u043a\r\n  creatividad:{id:'creatividad',name:'M\u00fasica y Creatividad',  emoji:'\ud83c\udfb5' }  \/\/ \u043c\u0443\u0437\u044b\u043a\u0430+\u043f\u0440\u043e\u0447\u0435\u0435\r\n};\r\n\r\n\/\/ \u0411\u0430\u0437\u043e\u0432\u0430\u044f (\u0442\u043e\u043d\u043a\u0430\u044f) \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e id (\u043a\u0430\u043a \u0431\u044b\u043b\u043e) \u2014 \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0442\u043e\u043c \u00ab\u0441\u043b\u043e\u0436\u0438\u0442\u044c\u00bb \u0432 \u0443\u043a\u0440\u0443\u043f\u043d\u0451\u043d\u043d\u044b\u0435\r\nfunction inferirCategoriaBasePorId(id){\r\n  id = Number(id);\r\n  if ([1,2,3,4,5].includes(id)) return 'memoria';\r\n  if ([6,14,15].includes(id))  return 'velocidad';\r\n  if ([7,17].includes(id))     return 'musica';\r\n  if ([8,9].includes(id))      return 'atencion';\r\n  if ([10,11,12].includes(id)) return 'logica';\r\n  if ([18,19,20].includes(id)) return 'lenguaje';\r\n  return 'otras';\r\n}\r\n\r\n\/\/ \u041c\u0430\u043f\u043f\u0438\u043d\u0433 \u0431\u0430\u0437\u043e\u0432\u044b\u0445 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0439 -> \u0443\u043a\u0440\u0443\u043f\u043d\u0451\u043d\u043d\u044b\u0435\r\nfunction mergeCat(base){\r\n  switch (base){\r\n    case 'memoria':               return 'memoria';\r\n    case 'atencion':\r\n    case 'velocidad':             return 'atencion';\r\n    case 'logica':\r\n    case 'lenguaje':              return 'pensamiento';\r\n    case 'musica':\r\n    case 'otras':\r\n    default:                      return 'creatividad';\r\n  }\r\n}\r\n\r\nfunction emojiForMerged(catKey){ return Z_CATEGORIES_MERGED[catKey]?.emoji || '\ud83c\udfae'; }\r\n\r\n\/\/ ----- \u0420\u0435\u043d\u0434\u0435\u0440 \u0432\u043a\u043b\u0430\u0434\u043e\u043a (\u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u0430\u044f \u043b\u0435\u043d\u0442\u0430 \u0441 \u0431\u043e\u043b\u044c\u0448\u0438\u043c\u0438 \u0437\u043d\u0430\u0447\u043a\u0430\u043c\u0438) -----\r\nfunction zRenderTabs(categoryCounts){\r\n  const tabs = [\r\n    {id:'all', label:'Todo', emoji:'\ud83c\udf10', count:categoryCounts.all || 0},\r\n    ...Object.values(Z_CATEGORIES_MERGED).map(c=>({\r\n      id:c.id, label:c.name, emoji:c.emoji, count:categoryCounts[c.id] || 0\r\n    }))\r\n  ];\r\n  const $tabs = document.getElementById('z-tabs');\r\n  $tabs.innerHTML = tabs.map(t=>`\r\n    <button class=\"z-tab\" role=\"tab\" aria-selected=\"${t.id===zActiveCategory}\" data-id=\"${t.id}\">\r\n      <div class=\"z-tab-icon\">${t.emoji}<\/div>\r\n      <div class=\"z-tab-label\">${t.label}<\/div>\r\n      <div class=\"z-tab-count\">${t.count}<\/div>\r\n    <\/button>\r\n  `).join('');\r\n\r\n  $tabs.querySelectorAll('.z-tab').forEach(btn=>{\r\n    btn.onclick = ()=>{\r\n      zActiveCategory = btn.dataset.id;\r\n      $tabs.querySelectorAll('.z-tab').forEach(b=>b.setAttribute('aria-selected','false'));\r\n      btn.setAttribute('aria-selected','true');\r\n      zRenderCards(window.__Z_GAMES_CACHE || []);\r\n\r\n      \/\/ \u0410\u0432\u0442\u043e\u0441\u043a\u0440\u043e\u043b\u043b \u043a \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430\u043c \u043f\u043e\u0441\u043b\u0435 \u0432\u044b\u0431\u043e\u0440\u0430\r\n      const grid = document.getElementById('menu-juegos');\r\n      if (grid) grid.scrollIntoView({behavior:'smooth', block:'start'});\r\n    };\r\n  });\r\n}\r\n\r\n\/\/ ----- \u041f\u0435\u0440\u0435\u0440\u0438\u0441\u043e\u0432\u043a\u0430 \u043a\u0430\u0440\u0442\u043e\u0447\u0435\u043a \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u0443\u043a\u0440\u0443\u043f\u043d\u0451\u043d\u043d\u043e\u0439 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438 -----\r\nfunction zRenderCards(juegos){\r\n  const grid = document.getElementById('menu-juegos');\r\n  const term = (zSearchTerm || '').toLowerCase();\r\n\r\n  const counts = { all: juegos.length };\r\n  Object.values(Z_CATEGORIES_MERGED).forEach(c=>counts[c.id]=0);\r\n  juegos.forEach(j=>{ counts[j._catMerged] = (counts[j._catMerged]||0)+1; });\r\n\r\n  const filtered = juegos.filter(j=>{\r\n    const okCat = (zActiveCategory==='all') || (j._catMerged===zActiveCategory);\r\n    const okSearch = !term || (j.nombre?.toLowerCase().includes(term));\r\n    return okCat && okSearch;\r\n  });\r\n\r\n  if (!filtered.length){\r\n    grid.innerHTML = `\r\n      <div class=\"z-card\" style=\"align-items:center;justify-content:center;text-align:center\">\r\n        <div class=\"z-emoji\">\ud83d\udd0e<\/div>\r\n        <div class=\"z-title\" style=\"margin-top:8px;\">No hay juegos para tu filtro<\/div>\r\n        <div class=\"z-meta\">Cambia la categor\u00eda o limpia la b\u00fasqueda.<\/div>\r\n      <\/div>`;\r\n  } else {\r\n    grid.innerHTML = filtered.map(j=>{\r\n      const emoji = emojiForMerged(j._catMerged);\r\n      const tagName = Z_CATEGORIES_MERGED[j._catMerged]?.name || 'Juego';\r\n      return `\r\n        <article class=\"z-card\" data-id=\"${j.id}\">\r\n          <header class=\"z-card-head\">\r\n            <div class=\"z-emoji\" aria-hidden=\"true\">${emoji}<\/div>\r\n            <div class=\"z-title\">${j.nombre}<\/div>\r\n          <\/header>\r\n          <div class=\"z-meta\">${j.descripcion || ''}<\/div>\r\n\r\n          <div class=\"z-tags\">\r\n            <span class=\"z-tag\">${tagName}<\/span>\r\n            ${j.dificultad ? `<span class=\"z-tag\">Nivel: ${j.dificultad}<\/span>` : ''}\r\n          <\/div>\r\n\r\n          <!-- \u041f\u043b\u0435\u0439\u0441\u0445\u043e\u043b\u0434\u0435\u0440 \u0440\u0435\u043a\u043e\u0440\u0434\u043e\u0432 -->\r\n          <div class=\"z-tags\" data-rec-game=\"${j.id}\">\r\n            <span class=\"z-tag\">T\u00fa R\u00e9cord: \u2014<\/span>\r\n            <span class=\"z-tag\">Top R\u00e9cord: \u2014<\/span>\r\n          <\/div>\r\n\r\n          <footer class=\"z-card-footer\">\r\n            <button class=\"z-btn z-btn-primary\" data-play=\"${j.id}\">Jugar<\/button>\r\n            <span class=\"z-meta\">ID: ${j.id}<\/span>\r\n          <\/footer>\r\n        <\/article>`;\r\n    }).join('');\r\n  }\r\n\r\n  grid.querySelectorAll('[data-play]').forEach(btn=>{\r\n    btn.onclick = ()=>{\r\n      const id = Number(btn.dataset.play);\r\n      const nombre = (window.__Z_GAMES_CACHE || []).find(x=>Number(x.id)===id)?.nombre || `Juego ${id}`;\r\n      iniciarJuego(id, nombre);\r\n    };\r\n  });\r\n\r\n  \/\/ \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u0431\u044b\r\n  zRenderTabs(counts);\r\n\r\n  \/\/ \u0421\u0440\u0430\u0437\u0443 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 \u043a\u044d\u0448\u0435...\r\n  zPaintCardRecords(filtered.map(x=>x.id));\r\n  \/\/ ...\u0438 \u0434\u043e\u0442\u044f\u043d\u0443\u0442\u044c \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u044e\u0449\u0435\u0435\r\n  zHydrateRecords(filtered.map(x=>x.id));\r\n}\r\n\r\n\/\/ \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u043f\u0438\u0441\u043a\u0430 \u0438\u0433\u0440: \u0441\u0440\u0430\u0437\u0443 \u0444\u043e\u043d\u043e\u0432\u043e \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u044d\u0448 \u043d\u0430 \u0432\u0441\u0435 \u0438\u0433\u0440\u044b\r\nconst _origCargar = window.cargarJuegos;\r\nwindow.cargarJuegos = function(){\r\n  fetch(AJAX_URL, {\r\n    method: 'POST',\r\n    body: new URLSearchParams({action:'zabalimp_lista_juegos'})\r\n  })\r\n  .then(r=>r.json())\r\n  .then(data=>{\r\n    if(!data?.success) return;\r\n    const juegos = (data.juegos || []).map(j=>{\r\n      const base = j.categoria_base || inferirCategoriaBasePorId(j.id);\r\n      const merged = mergeCat(base);\r\n      return {\r\n        id: Number(j.id),\r\n        nombre: j.nombre || `Juego ${j.id}`,\r\n        descripcion: j.descripcion || '',\r\n        dificultad: j.dificultad || '',\r\n        _catBase: base,\r\n        _catMerged: merged\r\n      };\r\n    });\r\n    window.__Z_GAMES_CACHE = juegos;\r\n    zActiveCategory = 'all';\r\n    zRenderCards(juegos);\r\n\r\n    \/\/ \u0444\u043e\u043d\u043e\u0432\u043e \u043f\u043e\u0434\u0442\u044f\u043d\u0443\u0442\u044c \u0440\u0435\u043a\u043e\u0440\u0434\u044b \u043f\u043e \u0432\u0441\u0435\u043c \u0438\u0433\u0440\u0430\u043c, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438 \u0441\u043c\u0435\u043d\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u0430 \u0432\u0441\u0451 \u0431\u044b\u043b\u043e \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e\r\n    const ids = juegos.map(j=>j.id);\r\n    zHydrateRecords(ids);\r\n    \r\n    const btnCambiar = document.getElementById('btn-cambiar-usuario');\r\n    if (btnCambiar) btnCambiar.style.display = '';\r\n  });\r\n};\r\n\r\n\/\/ ----- \u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0438\u0433\u0440: \u0441\u0442\u0430\u0432\u0438\u043c \u043e\u0431\u0435 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u0438 (base + merged) -----\r\nwindow.cargarJuegos = function(){\r\n  fetch(AJAX_URL, {\r\n    method: 'POST',\r\n    body: new URLSearchParams({action:'zabalimp_lista_juegos'})\r\n  })\r\n  .then(r=>r.json())\r\n  .then(data=>{\r\n    if(!data?.success) return;\r\n    const juegos = (data.juegos || []).map(j=>{\r\n      const base = j.categoria_base || inferirCategoriaBasePorId(j.id);\r\n      const merged = mergeCat(base);\r\n      return {\r\n        id: Number(j.id),\r\n        nombre: j.nombre || `Juego ${j.id}`,\r\n        descripcion: j.descripcion || '',\r\n        dificultad: j.dificultad || '',\r\n        _catBase: base,\r\n        _catMerged: merged\r\n      };\r\n    });\r\n    window.__Z_GAMES_CACHE = juegos;\r\n    zActiveCategory = 'all'; \/\/ \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0432\u0441\u0451\r\n    zRenderCards(juegos);\r\n    const btnCambiar = document.getElementById('btn-cambiar-usuario');\r\n    if (btnCambiar) btnCambiar.style.display = '';\r\n  });\r\n};\r\n\r\n\r\n\r\nfunction iniciarJuego(juegoId, juegoNombre){\r\n  \/\/ \u0441\u043f\u0440\u044f\u0442\u0430\u0442\u044c \u043c\u0435\u043d\u044e, \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438\u0433\u0440\u044b\r\n  const ctn = document.getElementById('zabalimp-juego');\r\n  document.getElementById('zabalimp-menu').style.display = 'none';\r\n  ctn.style.display = 'block';\r\n  ctn.innerHTML = ''; \/\/ \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043d\u0430\u043a\u0430\u043f\u043b\u0438\u0432\u0430\u043b\u043e\u0441\u044c\r\n\r\n  switch (Number(juegoId)) {\r\n    case 1:  iniciarJuego_MemoriaSecuencia(); break;\r\n    case 2:  iniciarJuego_Pares();            break;\r\n    case 3:  iniciarJuego_QueCambio();        break;\r\n    case 4:  iniciarJuego_Asociaciones();     break;\r\n    case 5:  iniciarJuego_Diferencias();      break;\r\n    case 6:  iniciarJuego_AtrapaObjeto();     break;\r\n    case 7:  iniciarJuego_EscuchaRepite();    break;\r\n    case 8:  iniciarJuego_SigueLinea();       break;\r\n    case 9:  iniciarJuego_DedosRapidos();     break;\r\n    case 10: iniciarJuego_Puzles();           break;\r\n    case 11: iniciarJuego_SudokuMulti();      break;\r\n    case 12: iniciarJuego_SecuenciasLogicas();break;\r\n    case 13: iniciarJuego_Clasificacion();    break;\r\n    case 14: iniciarJuego_ReaccionRapida();   break;\r\n    case 15: iniciarJuego_NumerosRapidos();   break;\r\n    case 16: iniciarJuego_Dibuja();           break;\r\n    case 17: iniciarJuego_NotasMusicales();   break;\r\n    case 18: iniciarJuego_Quiz();             break;\r\n    case 19: iniciarJuego_FormaPalabra();     break;\r\n    case 20: iniciarJuego_Refranes();         break;\r\n\r\n    default:\r\n      ctn.innerHTML = `\r\n        <div style=\"padding:60px;text-align:center;font-size:2em;\">\r\n          ${juegoNombre}<br><br>\r\n          <span style=\"font-size:0.6em;color:#666;\">(En desarrollo)<\/span><br>\r\n          <button onclick=\"salirDeJuego()\" style=\"margin-top:30px;font-size:1.2em;\">Volver<\/button>\r\n        <\/div>`;\r\n  }\r\n}\r\n\r\nfunction salirDeJuego(){\r\n  const ctn = document.getElementById('zabalimp-juego');\r\n  ctn.style.display = 'none';\r\n  ctn.innerHTML = '';            \/\/ \u043e\u0447\u0438\u0441\u0442\u0438\u0442\u044c, \u0447\u0442\u043e\u0431\u044b \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0442\u043e\u0440\u0447\u0430\u043b\u043e \u0432\u043d\u0438\u0437\u0443\r\n  document.getElementById('zabalimp-menu').style.display = 'block';\r\n  window.scrollTo({ top: 0, behavior: 'smooth' });\r\n}\r\n\r\n\r\nfunction salirDeJuegoFull() {\r\n  \/\/ \u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0447\u0438\u043a\r\n  let traductor = document.getElementById('trp-floater-ls-current-language');\r\n  if(traductor) traductor.style.display = '';\r\n\r\n  \/\/ \u0423\u0431\u0440\u0430\u0442\u044c fullscreen \u0438\u0433\u0440\u0443\r\n  let gameContainer = document.getElementById('zabalimp-juego-fullscreen');\r\n  if (gameContainer) gameContainer.remove();\r\n\r\n  \/\/ \u0412\u0435\u0440\u043d\u0443\u0442\u044c \u043c\u0435\u043d\u044e \u0438\u043b\u0438 \u043a\u0443\u0434\u0430 \u043d\u0443\u0436\u043d\u043e\r\n  document.getElementById('zabalimp-menu').style.display = '';\r\n}\r\n\r\n\r\n\r\n<\/script>\r\n\r\n\r\n\r\n<script>\r\n\/* ========================= RANKING (\u043a\u043b\u0438\u0435\u043d\u0442) ========================= *\/\r\n(function(){\r\n  const BTN_ID = 'btn-ranking';\r\n  const MODAL_ID = 'z-ranking-modal';\r\n\r\n  \/\/ 1) \u041a\u043d\u043e\u043f\u043a\u0430 \u0432 \u0442\u0443\u043b\u0431\u0430\u0440\u0435 (\u0441\u043f\u0440\u0430\u0432\u0430 \u043e\u0442 \u043f\u043e\u0438\u0441\u043a\u0430)\r\n  const toolbar = document.querySelector('.z-toolbar');\r\n  if (toolbar && !document.getElementById(BTN_ID)){\r\n    const btn = document.createElement('button');\r\n    btn.id = BTN_ID;\r\n    btn.className = 'z-btn z-btn-secondary';\r\n    btn.style.marginLeft = '8px';\r\n    btn.textContent = '\ud83c\udfc6 Ranking';\r\n    btn.onclick = openRanking;\r\n    toolbar.appendChild(btn);\r\n  }\r\n    \/\/ \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0438 \u0442\u0443\u043b\u0431\u0430\u0440\u0430:\r\nconst bar = document.querySelector('.z-toolbar');\r\nif (bar && !document.getElementById('btn-install-pwa')) {\r\n  const b = document.createElement('button');\r\n  b.id = 'btn-install-pwa';\r\n  b.className = 'z-btn z-btn-secondary';\r\n  b.style.marginLeft = '8px';\r\n  b.textContent = 'Instalar app';\r\n  const search = bar.querySelector('.z-search');\r\n  bar.insertBefore(b, search); \/\/ \u0438\u043b\u0438 appendChild(b)\r\n}\r\n\r\n\r\n\r\n  \/\/ 2) \u041c\u043e\u0434\u0430\u043b\u043a\u0430\r\n  function ensureModal(){\r\n    let m = document.getElementById(MODAL_ID);\r\n    if (m) return m;\r\n    m = document.createElement('div');\r\n    m.id = MODAL_ID;\r\n    m.style.cssText = `\r\n      position:fixed; inset:0; background:rgba(0,0,0,.35); z-index:9999; display:none;\r\n      backdrop-filter:saturate(120%) blur(2px);\r\n    `;\r\n    m.innerHTML = `\r\n      <div style=\"\r\n        position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);\r\n        width:min(980px,96vw); max-height:88vh; overflow:auto;\r\n        background:#fff; border-radius:16px; box-shadow:0 10px 30px #0003; padding:14px;\">\r\n        <div style=\"display:flex; align-items:center; gap:8px; flex-wrap:wrap;\">\r\n          <div style=\"font-weight:900; font-size:1.1rem;\">\ud83c\udfc6 Ranking<\/div>\r\n          <div style=\"margin-left:auto;\"><\/div>\r\n          <select id=\"rk-game\" class=\"z-input\" style=\"min-width:180px\"><\/select>\r\n          <input id=\"rk-search\" class=\"z-input\" type=\"search\" placeholder=\"Buscar jugador o juego\u2026\" style=\"min-width:220px\">\r\n          <button id=\"rk-close\" class=\"z-btn z-btn-secondary\">Cerrar<\/button>\r\n        <\/div>\r\n        <div style=\"margin-top:8px; overflow:auto;\">\r\n          <table id=\"rk-table\" style=\"width:100%; border-collapse:separate; border-spacing:0 6px;\">\r\n            <thead>\r\n              <tr>\r\n                <th data-col=\"rank\"  style=\"text-align:left;padding:8px 10px;\">#<\/th>\r\n                <th data-col=\"user\"  style=\"text-align:left;padding:8px 10px;cursor:pointer;\">Jugador<\/th>\r\n                <th data-col=\"game\"  style=\"text-align:left;padding:8px 10px;cursor:pointer;\">Juego<\/th>\r\n                <th data-col=\"score\" style=\"text-align:right;padding:8px 10px;cursor:pointer;\">Mejor puntaje<\/th>\r\n                <th data-col=\"time\"  style=\"text-align:right;padding:8px 10px;cursor:pointer;\">Tiempo total<\/th>\r\n                <th data-col=\"fecha\" style=\"text-align:left;padding:8px 10px;cursor:pointer;\">\u00daltima vez<\/th>\r\n              <\/tr>\r\n            <\/thead>\r\n            <tbody><\/tbody>\r\n          <\/table>\r\n          <div id=\"rk-empty\" style=\"display:none; text-align:center; padding:18px; color:#475569;\">Sin resultados\u2026<\/div>\r\n        <\/div>\r\n      <\/div>\r\n    `;\r\n    document.body.appendChild(m);\r\n\r\n    \/\/ \u0421\u043e\u0431\u044b\u0442\u0438\u044f\r\n    m.querySelector('#rk-close').onclick = ()=> m.style.display='none';\r\n    m.addEventListener('click', (e)=>{ if (e.target===m) m.style.display='none'; });\r\n\r\n    \/\/ \u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438-\u043a\u043e\u043b\u043e\u043d\u043a\u0438 \u0434\u043b\u044f \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0438\r\n    m.querySelectorAll('th[data-col]').forEach(th=>{\r\n      if (th.dataset.col==='rank') return;\r\n      th.onclick = ()=>{\r\n        if (state.sort === th.dataset.col){\r\n          state.dir = (state.dir==='DESC' ? 'ASC' : 'DESC');\r\n        } else {\r\n          state.sort = th.dataset.col;\r\n          state.dir  = (th.dataset.col==='score' ? 'DESC' : 'ASC');\r\n        }\r\n        fetchAndRender();\r\n      };\r\n    });\r\n\r\n    \/\/ \u041f\u043e\u0438\u0441\u043a\r\n    m.querySelector('#rk-search').addEventListener('input', ()=>{\r\n      state.search = m.querySelector('#rk-search').value.trim();\r\n      fetchAndRender();\r\n    });\r\n\r\n    return m;\r\n  }\r\n\r\n  \/\/ 3) \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\r\n  const state = {\r\n    game: 0,               \/\/ 0 = todos\r\n    search: '',\r\n    sort: 'score',         \/\/ score|user|game|time|fecha\r\n    dir: 'DESC',\r\n    limit: 200\r\n  };\r\n\r\n  async function openRanking(){\r\n    const modal = ensureModal();\r\n    \/\/ \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u0435\u043b\u0435\u043a\u0442 \u0438\u0433\u0440\r\n    const sel = modal.querySelector('#rk-game');\r\n    if (!sel._filled){\r\n      const optAll = document.createElement('option'); optAll.value='0'; optAll.textContent='Todos los juegos';\r\n      sel.appendChild(optAll);\r\n      (window.__Z_GAMES_CACHE||[]).forEach(g=>{\r\n        const o=document.createElement('option');\r\n        o.value = g.id; o.textContent = g.nombre;\r\n        sel.appendChild(o);\r\n      });\r\n      sel._filled = true;\r\n      sel.onchange = ()=>{ state.game = Number(sel.value||0); fetchAndRender(); };\r\n    }\r\n    sel.value = String(state.game||0);\r\n\r\n    modal.querySelector('#rk-search').value = state.search;\r\n    modal.style.display = 'block';\r\n    fetchAndRender();\r\n  }\r\n\r\n  \/\/ 4) \u0417\u0430\u043f\u0440\u043e\u0441 \u0438 \u043e\u0442\u0440\u0438\u0441\u043e\u0432\u043a\u0430\r\n  async function fetchAndRender(){\r\n    const modal = ensureModal();\r\n    const tbody = modal.querySelector('#rk-table tbody');\r\n    const empty = modal.querySelector('#rk-empty');\r\n\r\n    tbody.innerHTML = `<tr><td colspan=\"6\" style=\"padding:18px;text-align:center;color:#64748b;\">Cargando\u2026<\/td><\/tr>`;\r\n\r\n    const body = new URLSearchParams({\r\n      action:'zabalimp_ranking',\r\n      game_id: state.game ? String(state.game) : '',\r\n      search: state.search || '',\r\n      sort:   state.sort,\r\n      dir:    state.dir,\r\n      limit:  String(state.limit)\r\n    });\r\n\r\n    let rows = [];\r\n    try{\r\n      const r = await fetch(AJAX_URL, { method:'POST', body });\r\n      const j = await r.json();\r\n      rows = j?.rows || [];\r\n    }catch(_){}\r\n\r\n    if (!rows.length){\r\n      tbody.innerHTML = '';\r\n      empty.style.display = '';\r\n      return;\r\n    }\r\n    empty.style.display = 'none';\r\n\r\n    \/\/ helper \u0444\u043e\u0440\u043c\u0430\u0442\r\n    const fmtTime = s=>{\r\n      s = Math.max(0, Math.floor(Number(s||0)));\r\n      const m = String(Math.floor(s\/60)).padStart(2,'0');\r\n      const sec = String(s%60).padStart(2,'0');\r\n      return `${m}:${sec}`;\r\n    };\r\n\r\n    \/\/ \u0440\u0435\u043d\u0434\u0435\u0440\r\n    tbody.innerHTML = rows.map((r,i)=>`\r\n      <tr style=\"background:#f8fafc;\">\r\n        <td style=\"padding:8px 10px;border-radius:10px 0 0 10px;\">${i+1}<\/td>\r\n        <td style=\"padding:8px 10px;font-weight:800;\">${r.user || ''}<\/td>\r\n        <td style=\"padding:8px 10px;\">${r.game || ''}<\/td>\r\n        <td style=\"padding:8px 10px;text-align:right;font-weight:900;\">${r.best_score || 0}<\/td>\r\n        <td style=\"padding:8px 10px;text-align:right;\">${fmtTime(r.time_total)}<\/td>\r\n        <td style=\"padding:8px 10px;border-radius:0 10px 10px 0;\">${r.last_play || ''}<\/td>\r\n      <\/tr>\r\n    `).join('');\r\n  }\r\n})();\r\n<\/script>\r\n\r\n\r\n<script>\r\n\/\/ --- \u041a\u042d\u0428 ---\r\nconst Z_REC = { user: Object.create(null), global: Object.create(null) };\r\n\r\n\/\/ --- \u0417\u0410\u0413\u0420\u0423\u0417\u041a\u0410 \u0420\u0415\u041a\u041e\u0420\u0414\u041e\u0412 \u041f\u041e\u041b\u042c\u0417\u041e\u0412\u0410\u0422\u0415\u041b\u042f (bulk -> fallback \u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443) ---\r\nasync function zLoadUserRecordsBulk(gameIds){\r\n  if (!usuarioId) return;\r\n  const missing = gameIds.filter(id => !(id in Z_REC.user));\r\n  if (!missing.length) return;\r\n\r\n  \/\/ 1) \u0431\u044b\u0441\u0442\u0440\u044b\u0439 \u043f\u0443\u0442\u044c (bulk)\r\n  try{\r\n    const body = new URLSearchParams({\r\n      action:'zabalimp_user_records',\r\n      user_id:String(usuarioId),\r\n      game_ids:missing.join(',')\r\n    });\r\n    const r = await fetch(AJAX_URL,{method:'POST', body});\r\n    const j = await r.json();\r\n    if (j?.rows){\r\n      j.rows.forEach(row => { Z_REC.user[Number(row.game_id)] = Number(row.record||0); });\r\n      \/\/ \u043d\u0430 \u0438\u0433\u0440\u044b, \u0433\u0434\u0435 \u043e\u0442\u0432\u0435\u0442\u0430 \u043d\u0435\u0442 \u2014 \u0441\u0447\u0438\u0442\u0430\u0435\u043c 0 (\u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0434\u0451\u0440\u0433\u0430\u0442\u044c \u043f\u043e\u0442\u043e\u043c)\r\n      missing.forEach(id=>{ if(!(id in Z_REC.user)) Z_REC.user[id]=0; });\r\n      return;\r\n    }\r\n  }catch(_){}\r\n\r\n  \/\/ 2) \u0444\u043e\u043b\u043b\u0431\u044d\u043a: \u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 handler zabalimp_comprobar_record)\r\n  await Promise.all(missing.map(async id=>{\r\n    try{\r\n      const body = new URLSearchParams({action:'zabalimp_comprobar_record', user_id:usuarioId, game_id:id});\r\n      const r = await fetch(AJAX_URL,{method:'POST', body});\r\n      const j = await r.json();\r\n      Z_REC.user[id] = Number(j?.record||0);\r\n    }catch(_){ Z_REC.user[id]=0; }\r\n  }));\r\n}\r\n\r\n\/\/ --- \u0417\u0410\u0413\u0420\u0423\u0417\u041a\u0410 \u0413\u041b\u041e\u0411\u0410\u041b\u042c\u041d\u041e\u0413\u041e \u0420\u0415\u041a\u041e\u0420\u0414\u0410 \u041f\u041e \u0418\u0413\u0420\u0410\u041c (bulk -> fallback \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 ranking) ---\r\nasync function zLoadGlobalBestBulk(gameIds){\r\n  const missing = gameIds.filter(id => !(id in Z_REC.global));\r\n  if (!missing.length) return;\r\n\r\n  \/\/ 1) \u0431\u044b\u0441\u0442\u0440\u044b\u0439 \u043f\u0443\u0442\u044c (bulk)\r\n  try{\r\n    const body = new URLSearchParams({\r\n      action:'zabalimp_best_by_game',\r\n      game_ids:missing.join(',')\r\n    });\r\n    const r = await fetch(AJAX_URL,{method:'POST', body});\r\n    const j = await r.json();\r\n    if (j?.rows){\r\n      j.rows.forEach(row=>{\r\n        Z_REC.global[Number(row.game_id)] = {\r\n          record: Number(row.record||0),\r\n          usuario: row.usuario || ''\r\n        };\r\n      });\r\n      missing.forEach(id=>{ if(!(id in Z_REC.global)) Z_REC.global[id]={record:0,usuario:''}; });\r\n      return;\r\n    }\r\n  }catch(_){}\r\n\r\n  \/\/ 2) \u0444\u043e\u043b\u043b\u0431\u044d\u043a: \u043f\u043e \u043e\u0434\u043d\u043e\u0439 (\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0442\u0432\u043e\u0439 ranking handler \u0441 limit=1)\r\n  await Promise.all(missing.map(async id=>{\r\n    try{\r\n      const body = new URLSearchParams({\r\n        action:'zabalimp_ranking',\r\n        game_id:String(id),\r\n        sort:'score',\r\n        dir:'DESC',\r\n        limit:'1'\r\n      });\r\n      const r = await fetch(AJAX_URL,{method:'POST', body});\r\n      const j = await r.json();\r\n      const row = (j?.rows||[])[0];\r\n      Z_REC.global[id] = {\r\n        record: Number(row?.best_score||0),\r\n        usuario: row?.user || ''\r\n      };\r\n    }catch(_){ Z_REC.global[id]={record:0,usuario:''}; }\r\n  }));\r\n}\r\n\r\n\/\/ --- \u0420\u0418\u0421\u0423\u0415\u041c \u0420\u0415\u041a\u041e\u0420\u0414\u042b \u0412 \u041a\u0410\u0420\u0422\u041e\u0427\u041a\u0418 ---\r\nfunction zPaintCardRecords(gameIds){\r\n  gameIds.forEach(id=>{\r\n    document.querySelectorAll(`[data-rec-game=\"${id}\"]`).forEach(box=>{\r\n      const me = Z_REC.user[id] ?? 0;\r\n      const g  = Z_REC.global[id]?.record ?? 0;\r\n      const gu = Z_REC.global[id]?.usuario || '';\r\n      box.innerHTML = `\r\n        <span class=\"z-tag\">T\u00fa R\u00e9cord: <b>${me}<\/b><\/span>\r\n        <span class=\"z-tag\">Top R\u00e9cord: <b>${g}<\/b>${gu?` \u00b7 ${gu}`:''}<\/span>\r\n      `;\r\n    });\r\n  });\r\n}\r\n\r\n\/\/ --- \u0413\u0418\u0414\u0420\u0410\u0422\u041e\u0420 (\u0433\u0440\u0443\u0437\u0438\u0442 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u044e\u0449\u0435\u0435 \u0438 \u0434\u043e\u0440\u0438\u0441\u043e\u0432\u044b\u0432\u0430\u0435\u0442) ---\r\nasync function zHydrateRecords(gameIds){\r\n  await Promise.all([\r\n    zLoadUserRecordsBulk(gameIds),\r\n    zLoadGlobalBestBulk(gameIds)\r\n  ]);\r\n  zPaintCardRecords(gameIds);\r\n}\r\n<\/script>\r\n\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5167ac8 elementor-widget elementor-widget-html\" data-id=\"5167ac8\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_MemoriaSecuencia(){\r\n  const game = crearMarcoJuego({ gameId: 1, titulo:'Memoria de Secuencia', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u043a\u0440\u0443\u043f\u043d\u043e \u0432 \u0446\u0435\u043d\u0442\u0440\u0435\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Mira la <b>secuencia de colores<\/b> y luego rep\u00edtela tocando los botones en el mismo orden.<br>\r\n    Cada ronda <b>a\u00f1ade un color<\/b> a la secuencia. Acierto: <b>+5 pts<\/b>.<br>\r\n    Pulsa <b>Empezar<\/b> cuando est\u00e9s listo.\r\n  `);\r\n\r\n  \/\/ \u042f\u0434\u0440\u043e\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .ms-wrap{\r\n        background:#fff;border-radius:22px;box-shadow:0 0 24px 2px #ddd;\r\n        padding:16px;max-width:560px;margin:0 auto;display:none;position:relative\r\n      }\r\n      .ms-grid{display:grid;grid-template-columns:repeat(2,minmax(120px,1fr));gap:16px;justify-items:stretch}\r\n      .ms-btn{\r\n        height:120px;border-radius:20px;border:5px solid #334155;box-shadow:0 0 18px #0001;\r\n        opacity:.75; cursor:pointer;\r\n        -webkit-tap-highlight-color:transparent; touch-action:manipulation; user-select:none;\r\n        transition:opacity .12s, filter .12s, transform .0s;\r\n      }\r\n      .ms-btn[disabled]{cursor:default;filter:grayscale(.1);opacity:.55}\r\n      .ms-btn.flash{opacity:1; filter:brightness(1.05)}\r\n      .ms-count{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none}\r\n      .ms-bubble{background:#ffffffdd;border-radius:16px;padding:12px 28px;font-size:2.1em;font-weight:900;color:#0b3a47;box-shadow:0 2px 18px #0001}\r\n    <\/style>\r\n\r\n    <div class=\"ms-wrap\" id=\"ms-wrap\">\r\n      <div class=\"ms-grid\" id=\"m-grid\"><\/div>\r\n      <div class=\"ms-count\" id=\"ms-count\" style=\"display:none\"><div class=\"ms-bubble\" id=\"ms-count-num\">5<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  const wrap   = document.getElementById('ms-wrap');\r\n  const gridEl = document.getElementById('m-grid');\r\n  const cntBox = document.getElementById('ms-count');\r\n  const cntNum = document.getElementById('ms-count-num');\r\n\r\n  const colores = [\r\n    {nombre:'rojo',     color:'#e74c3c', freq:523}, \/\/ C5\r\n    {nombre:'verde',    color:'#27ae60', freq:659}, \/\/ E5\r\n    {nombre:'azul',     color:'#3498db', freq:784}, \/\/ G5\r\n    {nombre:'amarillo', color:'#f1c40f', freq:880}, \/\/ A5\r\n  ];\r\n\r\n  \/\/ \u041a\u043d\u043e\u043f\u043a\u0438\r\n  const buttons = colores.map((c, i) => {\r\n    const b = document.createElement('button');\r\n    b.className = 'ms-btn';\r\n    b.style.background = c.color;\r\n    b.dataset.idx = i;\r\n    b.disabled = true;\r\n    b.addEventListener('click', () => { if (enJuego) press(i); });\r\n    gridEl.appendChild(b);\r\n    return b;\r\n  });\r\n\r\n  \/\/ \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\/\u0443\u0442\u0438\u043b\u0438\u0442\u044b\r\n  let seq = [], step = 0, enJuego = false;\r\n  const wait = (ms) => new Promise(r => setTimeout(r, ms));\r\n  function setCoreVisible(v){ wrap.style.display = v ? 'block' : 'none'; }\r\n\r\n  function flash(i){\r\n    const b = buttons[i];\r\n    b.classList.add('flash');\r\n    if (window.SFX) SFX.tone(colores[i].freq, 180, 'sine', 0.22);\r\n    setTimeout(()=> b.classList.remove('flash'), 220);\r\n  }\r\n\r\n  async function countdown(seconds=5){\r\n    cntBox.style.display = '';\r\n    for (let x=seconds; x>=1; x--){\r\n      cntNum.textContent = String(x);\r\n      if (window.SFX) SFX.ui();\r\n      await wait(1000);\r\n    }\r\n    cntNum.textContent = '\u00a1Mira!';\r\n    await wait(400);\r\n    cntBox.style.display = 'none';\r\n  }\r\n\r\n  async function mostrarSecuencia(){\r\n    enJuego = false;\r\n    buttons.forEach(b=> b.disabled = true);\r\n    game.setMessage('Observa la secuencia\u2026');\r\n    await countdown(5);\r\n    for (const idx of seq){\r\n      flash(idx);\r\n      await wait(520);\r\n      await wait(260);\r\n    }\r\n    buttons.forEach(b=> b.disabled = false);\r\n    enJuego = true;\r\n    step = 0;\r\n    game.setMessage('\u00a1Ahora repite la secuencia!');\r\n  }\r\n\r\n  function siguienteRonda(){\r\n    seq.push( Math.floor(Math.random()*buttons.length) );\r\n    mostrarSecuencia();\r\n  }\r\n\r\n  async function press(i){\r\n    flash(i);\r\n    if (i === seq[step]){\r\n      step++;\r\n      if (step === seq.length){\r\n        \/\/ \u0420\u0430\u0443\u043d\u0434 \u043f\u0440\u043e\u0439\u0434\u0435\u043d \u2014 \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0431\u0435\u0437 \u043a\u043d\u043e\u043f\u043a\u0438 \u00abContinuar\u00bb\r\n        enJuego = false;\r\n        buttons.forEach(b=> b.disabled = true);\r\n        game.addScore(5);\r\n        if (window.SFX) SFX.success();\r\n        game.setMessage('<span style=\"color:#298d34;font-weight:600;\">\u00a1Correcto!<\/span>');\r\n        await wait(800);          \/\/ \u043a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u043f\u0430\u0443\u0437\u0430 \u0434\u043b\u044f \u0440\u0435\u0430\u043a\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\r\n        siguienteRonda();         \/\/ \u0441\u0440\u0430\u0437\u0443 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043e\u0442\u0441\u0447\u0451\u0442 \u0438 \u043d\u043e\u0432\u0443\u044e \u0441\u0435\u043a\u0432\u0435\u043d\u0446\u0438\u044e\r\n      }\r\n    } else {\r\n      \/\/ \u041f\u0440\u043e\u0438\u0433\u0440\u044b\u0448 \u2014 \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0441\u0435\u0442\u043a\u0443, \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0443\u044e \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u00abJugar otra vez\u00bb\r\n      enJuego = false;\r\n      buttons.forEach(b=> b.disabled = true);\r\n      if (window.SFX) SFX.fail();\r\n      const names = seq.map(ix => colores[ix].nombre).join(', ');\r\n      setCoreVisible(false); \/\/ \u00ab\u0443\u0431\u0438\u0440\u0430\u0435\u043c \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u044b\u00bb\r\n      game.setMessage(`<span style=\"color:#c33;font-weight:600;\">\u00a1Fin del juego!<\/span> Secuencia correcta: <b>${names}<\/b>`);\r\n      game.endGame({ save:true });\r\n    }\r\n  }\r\n\r\n  \/\/ \u0421\u0442\u0430\u0440\u0442 (\u043a\u043d\u043e\u043f\u043a\u0430 \u0432 \u0446\u0435\u043d\u0442\u0440\u0435). \u041a\u043d\u043e\u043f\u043a\u0443 \u00abContinuar\u00bb \u041d\u0415 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c.\r\n  game.onStart(() => {\r\n    if (window.SFX) SFX.unlock();\r\n    game.showStart(false);\r\n    game.showContinue(false);\r\n    game.setMessage('');\r\n    setCoreVisible(true);\r\n    game.startTimer();\r\n    seq = [];\r\n    game.setScore(0);\r\n    siguienteRonda();\r\n  });\r\n\r\n  \/\/ \u041d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u043e\u0442\u043a\u043b\u044e\u0447\u0438\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u00abContinuar\u00bb, \u0435\u0441\u043b\u0438 \u043a\u0430\u0440\u043a\u0430\u0441 \u0435\u0433\u043e \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442.\r\n  game.onContinue(() => {});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-546e2aa elementor-widget elementor-widget-html\" data-id=\"546e2aa\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Pares(){\r\n  const game = crearMarcoJuego({ gameId:2, titulo:'Tarjetas de pares', bg:'#e4f7fa' });\r\n\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Toca dos tarjetas para encontrar <b>parejas iguales<\/b>.<br>\r\n    Acierto: <b>+10<\/b>, error: <b>\u22121<\/b>. Cada nivel a\u00f1ade <b>+2 parejas<\/b>.\r\n  `);\r\n\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n.p-hide-msg .zg-core .zg-msg{ display:none !important; }\r\n.zg-main{ align-items:flex-start; }\r\n.zg-core{ flex:1 1 0% !important; min-width:0 !important; max-width:none !important; }\r\n.zg-core .zg-core-ui{ width:100% !important; }\r\n.zg-core [data-zg-core-host]{ width:100% !important; }\r\n\r\n.pares-wrap{\r\n  position:relative; background:#fff; border-radius:22px; box-shadow:0 0 24px 2px #ddd;\r\n  padding:16px; margin:0; display:none; width:100% !important; max-width:100% !important;\r\n  box-sizing:border-box; overflow:visible;\r\n}\r\n.pares-grid{\r\n  display:grid; gap:12px; justify-content:center; align-content:start; width:100%;\r\n}\r\n.pares-card{\r\n  width:var(--p-tile, 120px); height:var(--p-tile, 120px);\r\n  display:flex; align-items:center; justify-content:center;\r\n  background:#b9eafc; color:#1f2937; border-radius:18px; border:3px solid #94a3b8;\r\n  box-shadow:0 0 8px #cbd5e1; cursor:pointer; transition:.12s;\r\n  -webkit-tap-highlight-color:transparent; touch-action:manipulation; user-select:none; font-size:32px;\r\n}\r\n.pares-card[disabled]{ cursor:default; filter:saturate(.75); opacity:.85; }\r\n.pares-card.is-open{ background:#ffeaa7; }\r\n\r\n.p-count{ position:absolute; inset:0; display:none; align-items:center; justify-content:center; pointer-events:none; }\r\n.p-bubble{ background:#ffffffdd; border-radius:16px; padding:12px 28px; font-size:2.1em; font-weight:900; color:#0b3a47; box-shadow:0 2px 18px #0001; }\r\n\r\n.zg-score{ flex-wrap:nowrap !important; gap:8px !important; }\r\n.zg-score > *{ white-space:nowrap; }\r\n.zg-score .nivel-badge{\r\n  display:inline-flex; align-items:center; gap:6px;\r\n  background:#cffafe; border:1px solid #a5f3fc; color:#083344;\r\n  padding:4px 8px; border-radius:10px; font-weight:800; font-size:.95em;\r\n}\r\n    <\/style>\r\n<style>\r\n\/* ===== \u0425\u043e\u0441\u0442 \u044f\u0434\u0440\u0430 \u2014 \u0440\u0430\u0441\u0442\u044f\u043d\u0443\u0442\u044c \u043d\u0430 \u0432\u0441\u044e \u0448\u0438\u0440\u0438\u043d\u0443 \u0438 \u0443\u0431\u0440\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0440\u0430\u043c\u043a\u0438\/\u043f\u0430\u0434\u0434\u0438\u043d\u0433\u0438 *\/\r\n.zg-core{\r\n  flex:1 1 auto !important;\r\n  min-width:0 !important;\r\n  max-width:none !important;\r\n  width:100% !important;\r\n  border:0 !important;\r\n  padding:0 !important;\r\n  background:transparent !important;\r\n  overflow:visible !important;\r\n}\r\n.zg-core .zg-core-ui{ padding:0 !important; }\r\n.zg-core [data-zg-core-host]{ width:100% !important; }\r\n\r\n\/* ===== \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0440\u0430\u0441\u043a\u043b\u0430\u0434\u043a\u0430: \u043d\u0430 \u043c\u043e\u0431\u0438\u043b\u0435 \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0435\u043c \u0432 \u043a\u043e\u043b\u043e\u043d\u043a\u0443,\r\n   \u0447\u0442\u043e\u0431\u044b \u044f\u0434\u0440\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u043b\u043e 100% \u0448\u0438\u0440\u0438\u043d\u044b \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 *\/\r\n@media (max-width: 980px){\r\n  .zg-main{\r\n    display:flex !important;\r\n    flex-direction:column !important;\r\n    align-items:stretch !important;\r\n    gap:12px !important;\r\n  }\r\n  .zg-sidebar{ width:100% !important; flex:0 0 auto !important; }\r\n  .zg-core{ width:100% !important; max-width:100% !important; }\r\n}\r\n\r\n\/* ===== \u0421\u0430\u043c\u043e \u043f\u043e\u043b\u0435 \u043f\u0430\u0440 \u2014 \u0431\u0435\u0437 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0439 *\/\r\n.pares-wrap{\r\n  position:relative;\r\n  background:#fff;\r\n  border-radius:22px;\r\n  box-shadow:0 0 24px 2px #ddd;\r\n  padding:16px;\r\n  margin:0 !important;\r\n  display:none;\r\n  width:100% !important;\r\n  max-width:100% !important;\r\n  box-sizing:border-box;\r\n  overflow:visible;\r\n}\r\n\r\n\/* \u0441\u0435\u0442\u043a\u0430 \u2014 \u0442\u044f\u043d\u0435\u043c \u043d\u0430 100% *\/\r\n.pares-grid{\r\n  display:grid;\r\n  gap:12px;\r\n  width:100%;\r\n  justify-items:stretch;\r\n  overflow:visible;\r\n}\r\n<\/style>\r\n\r\n    <div class=\"pares-wrap\" id=\"p-wrap\">\r\n      <div id=\"p-grid\" class=\"pares-grid\"><\/div>\r\n      <div class=\"p-count\" id=\"p-count\"><div class=\"p-bubble\" id=\"p-count-num\">3<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  const wrap   = document.getElementById('p-wrap');\r\n  const grid   = document.getElementById('p-grid');\r\n  const cntBox = document.getElementById('p-count');\r\n  const cntNum = document.getElementById('p-count-num');\r\n\r\n  \/\/ HUD: Nivel \u2192 \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('.nivel-badge');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.className = 'nivel-badge';\r\n    lvlBadge.innerHTML = 'N: <b>1\/5<\/b>';\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setLvlBadge = (n, max)=>{ if(lvlBadge) lvlBadge.innerHTML = `N: <b>${n}\/${max}<\/b>`; };\r\n\r\n  \/\/ \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\r\n  const MAX_NIVEL = 5;              \/\/ \u2190 \u0431\u044b\u043b\u043e 8\r\n  const BASE_PARES = 6;\r\n  const VALORES = ['\ud83c\udf4e','\ud83c\udf4c','\ud83c\udf52','\ud83c\udf49','\ud83c\udf47','\ud83c\udf4b','\ud83e\udd5d','\ud83c\udf51','\ud83e\udd65','\ud83c\udf4f','\ud83c\udf4a','\ud83c\udf4d','\ud83c\udf53','\ud83e\udd6d','\ud83c\udf48','\ud83e\udd55','\ud83e\udd66','\ud83c\udf44','\ud83c\udf46','\ud83c\udf50','\ud83c\udf1e','\u2b50','\u26bd','\ud83d\ude97','\ud83d\udc36','\ud83d\udc31','\ud83d\udc3c','\ud83e\udd8b','\ud83c\udf88','\ud83c\udf6a'];\r\n\r\n  \/\/ SFX\r\n  const BASE_FREQS = [523,587,659,698,784,880];\r\n  const toneFor = (val)=> { let h=0; for (const ch of val) h=(h+ch.codePointAt(0))%BASE_FREQS.length; return BASE_FREQS[h]; };\r\n  const sfxFlip = (val)=> window.SFX && (SFX.click?.() ?? SFX.tone?.(toneFor(val),140,'sine',0.22));\r\n  const sfxOk   = ()=> window.SFX?.success?.();\r\n  const sfxFail = ()=> window.SFX?.fail?.();\r\n\r\n  \/\/ \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\r\n  let nivel = 1, primera=null, segunda=null, bloqueado=false, paresRestantes=0;\r\n  const wait = (ms)=> new Promise(r=>setTimeout(r,ms));\r\n  const setCoreVisible = (v)=> wrap.style.display = v ? 'block' : 'none';\r\n\r\n  \/\/ ===== \u041f\u043e\u0434\u0431\u043e\u0440 \u043a\u043e\u043b\u043e\u043d\u043e\u043a \u0438 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u0442\u0430\u0439\u043b\u0430 \u0441\u0442\u0440\u043e\u0433\u043e \u043f\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u043c \u0440\u0430\u0437\u043c\u0435\u0440\u0430\u043c =====\r\n  function layoutGrid(nCards){\r\n    const wcs = getComputedStyle(wrap);\r\n    const padX = parseFloat(wcs.paddingLeft) + parseFloat(wcs.paddingRight);\r\n    const padY = parseFloat(wcs.paddingTop)  + parseFloat(wcs.paddingBottom);\r\n\r\n    const wAvail = Math.max(220, wrap.clientWidth  - padX);\r\n\r\n    const top = wrap.getBoundingClientRect().top;\r\n    const vpH = (window.visualViewport?.height) || window.innerHeight;\r\n    const hAvail = Math.max(240, vpH - top - 24) - padY;\r\n\r\n    const gcs = getComputedStyle(grid);\r\n    const colGap = parseFloat(gcs.columnGap || gcs.gap) || 12;\r\n    const rowGap = parseFloat(gcs.rowGap || gcs.gap) || 12;\r\n\r\n    let best = { cols:2, rows:Math.ceil(nCards\/2), tile:10 };\r\n    const maxCols = Math.min(10, nCards);\r\n\r\n    for (let cols=2; cols<=maxCols; cols++){\r\n      const rows  = Math.ceil(nCards\/cols);\r\n      const tileW = Math.floor((wAvail - colGap*(cols-1)) \/ cols);\r\n      const tileH = Math.floor((hAvail - rowGap*(rows-1)) \/ rows);\r\n      const tile  = Math.max(0, Math.min(tileW, tileH));\r\n      if (tile > best.tile) best = { cols, rows, tile };\r\n    }\r\n\r\n    grid.style.gridTemplateColumns = `repeat(${best.cols}, ${best.tile}px)`;\r\n    wrap.style.setProperty('--p-tile', best.tile+'px');\r\n\r\n    \/\/ \u043f\u043e\u0434\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0448\u0440\u0438\u0444\u0442 \u044d\u043c\u043e\u0434\u0437\u0438\r\n    grid.querySelectorAll('.pares-card').forEach(btn=>{\r\n      btn.style.width  = best.tile+'px';\r\n      btn.style.height = best.tile+'px';\r\n      btn.style.fontSize = Math.floor(best.tile*0.7)+'px';\r\n    });\r\n  }\r\n\r\n  const ro = new ResizeObserver(()=> layoutGrid(grid.children.length));\r\n  ro.observe(wrap);\r\n  const core = game.coreEl.closest('.zg-core');\r\n  if (core) ro.observe(core);\r\n  window.addEventListener('resize', ()=>layoutGrid(grid.children.length), {passive:true});\r\n  if (window.visualViewport){\r\n    visualViewport.addEventListener('resize', ()=>layoutGrid(grid.children.length), {passive:true});\r\n    visualViewport.addEventListener('scroll', ()=>layoutGrid(grid.children.length), {passive:true});\r\n  }\r\n\r\n  \/\/ \u041e\u0442\u0441\u0447\u0451\u0442 \u043c\u0435\u0436\u0434\u0443 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 (3\u21922\u21921)\r\n  async function countdown(seconds=3){\r\n    cntBox.style.display = 'flex';\r\n    for (let x=seconds; x>=1; x--){\r\n      cntNum.textContent = String(x);\r\n      window.SFX?.ui?.();\r\n      await wait(1000);\r\n    }\r\n    cntBox.style.display = 'none';\r\n  }\r\n\r\n  function mostrarCarta(btn){\r\n    btn.textContent = btn.dataset.valor;\r\n    btn.classList.add('is-open');\r\n    btn.dataset.open = '1';\r\n    sfxFlip(btn.dataset.valor);\r\n  }\r\n  function ocultarCarta(btn){\r\n    btn.textContent = '';\r\n    btn.classList.remove('is-open');\r\n    btn.dataset.open = '0';\r\n  }\r\n\r\n  function clickCarta(btn){\r\n    if (bloqueado || btn.dataset.open==='1') return;\r\n    mostrarCarta(btn);\r\n    if (!primera){ primera = btn; return; }\r\n    segunda = btn; bloqueado = true;\r\n    setTimeout(comprobarPares, 420);\r\n  }\r\n\r\n  async function crearTablero(){\r\n    const nPares  = BASE_PARES + (nivel-1)*2;\r\n    const valores = VALORES.slice(0, nPares);\r\n    let pool = valores.concat(valores).sort(()=> Math.random()-0.5);\r\n\r\n    grid.innerHTML = '';\r\n    primera = segunda = null; bloqueado = false;\r\n    paresRestantes = valores.length;\r\n\r\n    pool.forEach(v=>{\r\n      const btn = document.createElement('button');\r\n      btn.className = 'pares-card';\r\n      btn.dataset.valor = v;\r\n      btn.dataset.open  = '0';\r\n      btn.addEventListener('click', ()=> clickCarta(btn));\r\n      grid.appendChild(btn);\r\n    });\r\n\r\n    setLvlBadge(nivel, MAX_NIVEL);\r\n    layoutGrid(pool.length);\r\n    requestAnimationFrame(()=> layoutGrid(pool.length)); \/\/ \u0435\u0449\u0451 \u0440\u0430\u0437 \u043f\u043e\u0441\u043b\u0435 \u0440\u0435\u043d\u0434\u0435\u0440\u0430\r\n  }\r\n\r\n  async function finDeNivel(){\r\n    sfxOk();\r\n    if (nivel >= MAX_NIVEL){\r\n      await game.endGame({save:true});\r\n      return;\r\n    }\r\n    await countdown(3);\r\n    nivel++;\r\n    await crearTablero();\r\n  }\r\n\r\n  function comprobarPares(){\r\n    if (primera.dataset.valor === segunda.dataset.valor){\r\n      game.addScore(10);\r\n      primera.disabled = true; segunda.disabled = true;\r\n      paresRestantes--;\r\n      sfxOk();\r\n      if (paresRestantes===0){ finDeNivel(); }\r\n    } else {\r\n      game.setScore(Math.max(0, game.score - 1));\r\n      ocultarCarta(primera); ocultarCarta(segunda);\r\n      sfxFail();\r\n    }\r\n    primera=null; segunda=null; bloqueado=false;\r\n  }\r\n\r\n  \/\/ \u0421\u0442\u0430\u0440\u0442\r\n  game.onStart(()=>{\r\n    window.SFX?.unlock?.();\r\n    nivel = 1;\r\n    game.setScore(0);\r\n    game.showStart(false);\r\n    game.showContinue(false);\r\n    game.setMessage('');\r\n    game.coreEl.closest('.zg-root')?.classList.add('p-hide-msg');\r\n    setCoreVisible(true);\r\n    game.startTimer();\r\n    crearTablero();\r\n\r\n    \/\/ \u043e\u0442\u043f\u0438\u0441\u043a\u0430 \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u0435\u043b\u044f \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435\r\n    game.coreEl.closest('.zg-root')?.querySelector('.zg-exit')?.addEventListener('click', ()=> ro.disconnect(), { once:true });\r\n  });\r\n\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-83a098f elementor-widget elementor-widget-html\" data-id=\"83a098f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_QueCambio(){\r\n  const game = crearMarcoJuego({ gameId:3, titulo:'\u00bfQu\u00e9 ha cambiado?', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Memoriza el conjunto de tarjetas (emojis). Luego una de ellas cambiar\u00e1.<br>\r\n    <b>Se\u00f1ala cu\u00e1l ha cambiado<\/b>. Acierto: <b>+10 pts<\/b>, error: <b>\u22121 pt<\/b>.\r\n  `);\r\n\r\n  \/\/ ---------- \u044f\u0434\u0440\u043e UI ----------\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      \/* \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043d\u0435 \u0441\u0443\u0436\u0430\u0435\u0442 \u044f\u0434\u0440\u043e *\/\r\n      .zg-core .zg-msg{\r\n        width:100% !important; max-width:none !important;\r\n        margin:0 0 12px !important; min-height:54px; box-sizing:border-box;\r\n      }\r\n      \/* \u041f\u043e\u043b\u0435 \u2014 \u043d\u0430 \u0432\u0441\u044e \u0448\u0438\u0440\u0438\u043d\u0443 *\/\r\n      .qc-wrap{\r\n        position:relative; background:#fff; border-radius:22px; box-shadow:0 0 24px 2px #ddd;\r\n        padding:16px; margin:0 !important; display:none;\r\n        width:100% !important; max-width:100% !important; box-sizing:border-box; overflow:visible;\r\n      }\r\n      .qc-grid{ display:grid; gap:12px; width:100%; justify-items:stretch; overflow:visible; }\r\n\r\n      .qc-card{\r\n        width:100%; aspect-ratio:1\/1; display:flex; align-items:center; justify-content:center;\r\n        line-height:1; padding:0; font-weight:800; font-size:32px;\r\n        background:#fffbe4; color:#1f2937;\r\n        border-radius:18px; border:4px solid #94a3b8;\r\n        box-shadow:0 0 8px #cbd5e1; transition:background .12s, border-color .12s, filter .12s, opacity .12s;\r\n        -webkit-tap-highlight-color:transparent; touch-action:manipulation; user-select:none;\r\n      }\r\n      .qc-card[disabled]{cursor:default; filter:saturate(.75); opacity:.9}\r\n\r\n      \/* \u041e\u0432\u0435\u0440\u043b\u0435\u0439 \u043e\u0442\u0441\u0447\u0451\u0442\u0430 \u043c\u0435\u0436\u0434\u0443 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 *\/\r\n      .qc-count{position:absolute; inset:0; display:none; align-items:center; justify-content:center; pointer-events:none;}\r\n      .qc-bubble{background:#ffffffdd; border-radius:16px; padding:12px 28px; font-size:2.1em; font-weight:900; color:#0b3a47; box-shadow:0 2px 18px #0001;}\r\n\r\n      \/* HUD: \u0432\u0441\u0451 \u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443 + \u0431\u0435\u0439\u0434\u0436 \u0443\u0440\u043e\u0432\u043d\u044f *\/\r\n      .zg-score{ flex-wrap:nowrap !important; gap:8px !important; }\r\n      .zg-score > *{ white-space:nowrap; }\r\n      .zg-score .nivel-badge{\r\n        display:inline-flex; align-items:center; gap:6px;\r\n        background:#cffafe; border:1px solid #a5f3fc; color:#083344;\r\n        padding:4px 8px; border-radius:10px; font-weight:800; font-size:.95em;\r\n      }\r\n\r\n      \/* \u041c\u043e\u0431\u0438\u043b\u044c\u043d\u0430\u044f \u043f\u0440\u0430\u0432\u043a\u0430: \u0442\u044f\u043d\u0435\u043c \u044f\u0434\u0440\u043e \u043d\u0430 100% *\/\r\n      @media (max-width:900px){\r\n        .zg-main{ flex-direction:column; align-items:stretch !important; }\r\n        .zg-core{ width:100% !important; }\r\n        .zg-core .zg-core-ui,\r\n        .zg-core [data-zg-core-host],\r\n        .qc-wrap{ width:100% !important; max-width:100% !important; }\r\n      }\r\n    <\/style>\r\n\r\n    <div class=\"qc-wrap\" id=\"qc-wrap\">\r\n      <div id=\"qc-grid\" class=\"qc-grid\"><\/div>\r\n      <div class=\"qc-count\" id=\"qc-count\"><div class=\"qc-bubble\" id=\"qc-count-num\">5<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  const wrap   = document.getElementById('qc-wrap');\r\n  const grid   = document.getElementById('qc-grid');\r\n  const cntBox = document.getElementById('qc-count');\r\n  const cntNum = document.getElementById('qc-count-num');\r\n\r\n  \/\/ HUD: \u0431\u0435\u0439\u0434\u0436 \u00abNivel\u00bb \u0432 \u043e\u0434\u043d\u0443 \u043b\u0438\u043d\u0438\u044e\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('.nivel-badge');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.className = 'nivel-badge';\r\n    lvlBadge.innerHTML = 'Nivel: <b>1<\/b>';\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setNivelBadge = (n)=>{ if(lvlBadge) lvlBadge.innerHTML = `Nivel: <b>${n}<\/b>`; };\r\n\r\n  \/\/ ---------- \u0437\u0432\u0443\u043a\u0438 (\u0444\u043e\u043b\u0431\u044d\u043a\u0438) ----------\r\n  const tone = (f=660, ms=100, type='sine', vol=0.18)=>{ try{ SFX && SFX.tone && SFX.tone(f,ms,type,vol); }catch(e){} };\r\n  const sfxClick  = ()=>{ if (window.SFX?.click) SFX.click(); else tone(700,80,'square',0.15); };\r\n  const sfxOk     = ()=>{ if (window.SFX?.success) SFX.success(); else tone(980,140,'sine',0.22); };\r\n  const sfxFail   = ()=>{ if (window.SFX?.fail) SFX.fail(); else tone(220,180,'sawtooth',0.2); };\r\n  const sfxTick   = ()=>{ if (window.SFX?.ui) SFX.ui(); else tone(880,60,'sine',0.12); };\r\n  const sfxDing   = ()=>{ if (window.SFX?.tone) SFX.tone(1200,180,'triangle',0.22); else tone(1200,180,'triangle',0.22); };\r\n  const sfxWhoosh = ()=>{ if (window.SFX?.gliss) SFX.gliss(700,300,220); else tone(480,90,'sawtooth',0.12); };\r\n\r\n  \/\/ ---------- \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 ----------\r\n  let nivel = 1;\r\n  const ICONOS = ['\ud83c\udf4f','\ud83c\udf4e','\ud83c\udf4b','\ud83c\udf4c','\ud83c\udf49','\ud83c\udf47','\ud83c\udf53','\ud83e\udd5d','\ud83c\udf52','\ud83c\udf51','\ud83e\udd65','\ud83c\udf45','\ud83c\udf46','\ud83e\udd55','\ud83e\udd66','\ud83e\udd51','\ud83c\udf4d','\ud83c\udf4a','\ud83e\udd6c','\ud83c\udf50','\u2b50','\u26bd','\ud83d\ude97','\ud83d\udc36','\ud83d\udc31','\ud83d\udc3c','\ud83e\udd8b','\ud83c\udf88','\ud83c\udf6a'];\r\n  let countdownId = null;\r\n\r\n  let currentButtons = [];\r\n  let answered = false;\r\n  let lastCorrectIndex = -1;\r\n\r\n  function lockButtons(){ currentButtons.forEach(b=>{ b.disabled=true; b.style.pointerEvents='none'; }); }\r\n  function flashCorrect(times=3, done){\r\n    const btn = currentButtons[lastCorrectIndex];\r\n    if (!btn){ done && done(); return; }\r\n    let k=0;\r\n    const on = ()=>{ btn.style.outline='6px solid rgba(34,197,94,.45)'; btn.style.borderColor='#22c55e'; setTimeout(off,200); };\r\n    const off= ()=>{ btn.style.outline='none'; btn.style.borderColor='#94a3b8'; if(++k<times) setTimeout(on,140); else done&&done(); };\r\n    on();\r\n  }\r\n\r\n  \/\/ \u0430\u0432\u0442\u043e-\u043f\u043e\u0434\u0433\u043e\u043d \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u044d\u043c\u043e\u0434\u0437\u0438\r\n  function fitEmoji(el){ const side = el.clientWidth||100; el.style.fontSize = Math.floor(side*0.75)+'px'; }\r\n  function fitAll(){ grid.querySelectorAll('.qc-card').forEach(fitEmoji); }\r\n\r\n  \/\/ \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u0430\u044f \u0441\u0435\u0442\u043a\u0430 \u2014 \u0441\u0447\u0438\u0442\u0430\u0435\u043c \u043a\u043e\u043b\u043e\u043d\u043a\u0438 \u043e\u0442 \u0448\u0438\u0440\u0438\u043d\u044b \u044f\u0434\u0440\u0430\r\n  function layoutGrid(nCards){\r\n    const core = game.coreEl.closest('.zg-core');\r\n    const w = (core && core.clientWidth) || (wrap && wrap.clientWidth) || 600;\r\n    let cols = Math.ceil(Math.sqrt(nCards));\r\n    if (w < 420) cols = Math.max(2, Math.min(cols, 3));\r\n    else if (w < 700) cols = Math.max(3, Math.min(cols, 4));\r\n    else cols = Math.max(4, Math.min(cols, 6));\r\n    grid.style.gridTemplateColumns = `repeat(${cols}, minmax(0,1fr))`;\r\n  }\r\n\r\n  const ro = new ResizeObserver(()=>{ layoutGrid(grid.children.length); fitAll(); });\r\n\r\n  const wait = (ms)=> new Promise(r=>setTimeout(r,ms));\r\n  const setMsg = (html)=> game.setMessage(html||'');\r\n  const setNivel = (n)=>{ nivel=n; setNivelBadge(n); };\r\n\r\n  \/\/ \u041e\u0442\u0441\u0447\u0451\u0442 \u043c\u0435\u0436\u0434\u0443 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438\r\n  async function countdown(seconds=3){\r\n    cntBox.style.display = 'flex';\r\n    for (let x=seconds; x>=1; x--){\r\n      cntNum.textContent = String(x);\r\n      sfxTick();\r\n      await wait(1000);\r\n    }\r\n    cntBox.style.display = 'none';\r\n    sfxDing();\r\n  }\r\n\r\n  \/\/ \u044d\u0442\u0430\u043f \u0437\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u044f\r\n  function renderMemoriza(conjunto, secs){\r\n    grid.innerHTML=''; currentButtons=[]; answered=false; lastCorrectIndex=-1;\r\n    layoutGrid(conjunto.length);\r\n    conjunto.forEach(val=>{\r\n      const d=document.createElement('div');\r\n      d.className='qc-card'; d.textContent=val; grid.appendChild(d);\r\n    });\r\n    requestAnimationFrame(fitAll);\r\n\r\n    let left=secs; setMsg(`Memoriza el conjunto\u2026 <b>${left}s<\/b>`);\r\n    clearInterval(countdownId);\r\n    countdownId = setInterval(()=>{\r\n      left--; sfxTick();\r\n      setMsg(`Memoriza el conjunto\u2026 <b>${left}s<\/b>`);\r\n      if(left<=0){ clearInterval(countdownId); sfxDing(); }\r\n    },1000);\r\n  }\r\n\r\n  \/\/ \u044d\u0442\u0430\u043f \u0432\u043e\u043f\u0440\u043e\u0441\u0430\r\n  function renderPregunta(original, cambiado, changedIndex){\r\n    sfxWhoosh();\r\n    grid.innerHTML=''; currentButtons=[]; answered=false; lastCorrectIndex=changedIndex;\r\n    layoutGrid(cambiado.length);\r\n    cambiado.forEach((val,i)=>{\r\n      const b=document.createElement('button');\r\n      b.className='qc-card'; b.textContent=val; b.style.cursor='pointer';\r\n      b.onclick=()=> handleAnswer(i===changedIndex, original, cambiado, changedIndex);\r\n      grid.appendChild(b);\r\n      currentButtons.push(b);\r\n    });\r\n    requestAnimationFrame(fitAll);\r\n    setMsg('\u00bfQu\u00e9 ha cambiado?');\r\n  }\r\n\r\n  async function handleAnswer(correcto, original, cambiado, changedIndex){\r\n    if (answered) return;\r\n    answered=true; lockButtons(); sfxClick();\r\n\r\n    if (correcto){\r\n      game.addScore(10); sfxOk();\r\n      setMsg('<span style=\"color:#298d34;font-weight:600;\">\u00a1Correcto! (+10)<\/span> El siguiente\u2026');\r\n      flashCorrect(3, async ()=>{\r\n        await wait(700);\r\n        await countdown(3);          \/\/ \u23f1\ufe0f \u0430\u0432\u0442\u043e-\u043e\u0442\u0441\u0447\u0451\u0442\r\n        setNivel(nivel+1);           \/\/ \u2b06\ufe0f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c\r\n        ronda(nivel);                 \/\/ \ud83d\udc49 \u0441\u0442\u0430\u0440\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0440\u0430\u0443\u043d\u0434\u0430\r\n      });\r\n    }else{\r\n      sfxFail();\r\n      game.setScore(Math.max(0, game.score-1));\r\n      setMsg('<span style=\"color:#c33;font-weight:600;\">\u00a1No son iguales! (-1)<\/span>');\r\n      flashCorrect(3, ()=> setTimeout(()=>{ setMsg(''); ronda(nivel); }, 300)); \/\/ \u0442\u043e\u0442 \u0436\u0435 \u0443\u0440\u043e\u0432\u0435\u043d\u044c\r\n    }\r\n  }\r\n\r\n  \/\/ \u043e\u0434\u043d\u0430 \u00ab\u0440\u043e\u043d\u0434\u0430\u00bb: \u0437\u0430\u043f\u043e\u043c\u043d\u0438 \u2192 \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u2192 \u0441\u043f\u0440\u043e\u0441\u0438\u043c\r\n  function ronda(n){\r\n    const cantidad = n + 2; \/\/ 1-\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c = 3 \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0438\r\n    const base = ICONOS.slice(0, cantidad).sort(()=>Math.random()-0.5);\r\n    const secs = Math.min(10, 6 + Math.floor(cantidad\/2));\r\n\r\n    renderMemoriza(base, secs);\r\n\r\n    setTimeout(()=>{\r\n      const idx = Math.floor(Math.random()*base.length);\r\n      let nuevo; do { nuevo = ICONOS[Math.floor(Math.random()*ICONOS.length)]; } while (nuevo===base[idx]);\r\n      const cambiado = base.slice(); cambiado[idx]=nuevo;\r\n      renderPregunta(base, cambiado, idx);\r\n    }, secs*1000 + 250);\r\n  }\r\n\r\n  \/\/ \u0417\u0430\u043f\u0443\u0441\u043a\r\n  game.onStart(()=>{\r\n    window.SFX?.unlock?.();\r\n    game.showStart(false);\r\n    game.showContinue(false); \/\/ \u043a\u043d\u043e\u043f\u043a\u0430 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\r\n    game.setMessage('');\r\n    wrap.style.display='block';\r\n    setNivel(1); game.setScore(0);\r\n    game.startTimer();\r\n    ronda(nivel);\r\n\r\n    \/\/ \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0435\u043c \u0448\u0438\u0440\u0438\u043d\u0443 \u044f\u0434\u0440\u0430\r\n    ro.observe(game.coreEl.closest('.zg-core') || wrap);\r\n\r\n    \/\/ \u043e\u0447\u0438\u0441\u0442\u043a\u0430\r\n    game.coreEl.closest('.zg-root')?.querySelector('.zg-exit')?.addEventListener('click', ()=>{\r\n      ro.disconnect();\r\n      clearInterval(countdownId);\r\n    }, { once:true });\r\n  });\r\n\r\n  \/\/ \u00abContinuar\u00bb \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-8b6332e elementor-widget elementor-widget-html\" data-id=\"8b6332e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Asociaciones(){\r\n  const game = crearMarcoJuego({ gameId:4, titulo:'Asociaciones', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0431\u0435\u0437 \"Continuar\"\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Empareja cada <b>imagen<\/b> con su <b>palabra<\/b> (dos columnas).<br>\r\n    Acierto: <b>+10 pts<\/b>. Error: <b>\u22125 pts<\/b>. Los niveles avanzan autom\u00e1ticamente.\r\n  `);\r\n\r\n  const FALLBACK_LEVELS = [\r\n    [{id:\"manzana\",emoji:\"\ud83c\udf4e\",label:\"Rojo\"}, {id:\"zanahoria\",emoji:\"\ud83e\udd55\",label:\"Naranja\"}, {id:\"pez\",emoji:\"\ud83d\udc20\",label:\"Agua\"}, {id:\"pajaro\",emoji:\"\ud83d\udc26\",label:\"Aire\"}],\r\n    [{id:\"platano\",emoji:\"\ud83c\udf4c\",label:\"Amarillo\"}, {id:\"arbol\",emoji:\"\ud83c\udf33\",label:\"Verde\"}, {id:\"oso\",emoji:\"\ud83d\udc3b\",label:\"Bosque\"}, {id:\"delfin\",emoji:\"\ud83d\udc2c\",label:\"Mar\"}],\r\n    [{id:\"fresa\",emoji:\"\ud83c\udf53\",label:\"Rojo\"}, {id:\"limon\",emoji:\"\ud83c\udf4b\",label:\"Amarillo\"}, {id:\"camello\",emoji:\"\ud83d\udc2b\",label:\"Desierto\"}, {id:\"rana\",emoji:\"\ud83d\udc38\",label:\"Agua\"}],\r\n    [{id:\"pato\",emoji:\"\ud83e\udd86\",label:\"Agua\"}, {id:\"lobo\",emoji:\"\ud83d\udc3a\",label:\"Bosque\"}, {id:\"gato\",emoji:\"\ud83d\udc31\",label:\"Casa\"}, {id:\"mariposa\",emoji:\"\ud83e\udd8b\",label:\"Aire\"}],\r\n    [{id:\"naranja\",emoji:\"\ud83c\udf4a\",label:\"Naranja\"}, {id:\"elefante\",emoji:\"\ud83d\udc18\",label:\"Sabana\"}, {id:\"tortuga\",emoji:\"\ud83d\udc22\",label:\"Agua\"}, {id:\"mono\",emoji:\"\ud83d\udc12\",label:\"Selva\"}]\r\n  ];\r\n  const LEVELS = (Array.isArray(window.asociacionesLevels) && window.asociacionesLevels.length)\r\n    ? window.asociacionesLevels : FALLBACK_LEVELS;\r\n  const maxN = LEVELS.length;\r\n\r\n  \/\/ ---------- \u044f\u0434\u0440\u043e UI ----------\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      #asoc-wrap{\r\n        background:#fff;border-radius:22px;box-shadow:0 0 24px 2px #ddd;\r\n        padding:12px;margin:0;display:none;width:100%;max-width:100%;box-sizing:border-box;position:relative;\r\n      }\r\n      #asoc-layout{ display:grid; gap:12px; align-items:start; grid-template-columns:1fr 1fr; }\r\n      .asoc-col{ display:flex; flex-direction:column; gap:12px; align-items:center; }\r\n      .asoc-emoji,.asoc-label{\r\n        width:var(--cell); height:var(--cell);\r\n        display:flex;align-items:center;justify-content:center;text-align:center;line-height:1.1;\r\n        padding:8px 10px;font-weight:800;border-radius:18px;border:4px solid #94a3b8;\r\n        box-shadow:0 0 8px #cbd5e1;cursor:pointer;transition:.15s;\r\n        -webkit-tap-highlight-color:transparent; touch-action:manipulation; user-select:none;\r\n      }\r\n      .asoc-emoji{ background:#ffeaa7; }\r\n      .asoc-label{ background:#fffbe4; color:#1f2937; word-break:break-word; }\r\n      .asoc-emoji[disabled], .asoc-label[disabled]{ cursor:default; filter:saturate(.8); opacity:.95 }\r\n\r\n      \/* \u041e\u0432\u0435\u0440\u043b\u0435\u0439 \u043e\u0442\u0441\u0447\u0451\u0442\u0430 \u043c\u0435\u0436\u0434\u0443 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 *\/\r\n      .asoc-count{position:absolute; inset:0; display:none; align-items:center; justify-content:center; pointer-events:none;}\r\n      .asoc-bubble{background:#ffffffdd; border-radius:16px; padding:12px 28px; font-size:2.1em; font-weight:900; color:#0b3a47; box-shadow:0 2px 18px #0001;}\r\n      @media (max-width:680px){ #asoc-layout{ gap:10px; } }\r\n    <\/style>\r\n\r\n    <div id=\"asoc-wrap\">\r\n      <div id=\"asoc-layout\">\r\n        <div id=\"asoc-col-a\" class=\"asoc-col\"><\/div>\r\n        <div id=\"asoc-col-b\" class=\"asoc-col\"><\/div>\r\n      <\/div>\r\n      <div class=\"asoc-count\" id=\"asoc-count\"><div class=\"asoc-bubble\" id=\"asoc-count-num\">5<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  const wrap  = document.getElementById('asoc-wrap');\r\n  const layout= document.getElementById('asoc-layout');\r\n  const colA  = document.getElementById('asoc-col-a');\r\n  const colB  = document.getElementById('asoc-col-b');\r\n  const cntBox= document.getElementById('asoc-count');\r\n  const cntNum= document.getElementById('asoc-count-num');\r\n\r\n\r\n\/\/ HUD: \u0437\u0432\u0443\u043a + \u0431\u0435\u0439\u0434\u0436 \u0443\u0440\u043e\u0432\u043d\u044f (\u0432 \u043e\u0434\u043d\u0443 \u0441\u0442\u0440\u043e\u043a\u0443 \u0440\u044f\u0434\u043e\u043c \u0441 Puntos)\r\nconst scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\nif (window.SFX && scoreBox && typeof SFX.injectToggle === 'function') SFX.injectToggle(scoreBox);\r\n\r\nlet lvlBadge = scoreBox?.querySelector('[data-asoc-nivel]');\r\nif (!lvlBadge && scoreBox){\r\n  lvlBadge = document.createElement('span');                \/\/ <-- span, \u043d\u0435 div\r\n  lvlBadge.setAttribute('data-asoc-nivel','');\r\n  lvlBadge.style.cssText = `\r\n    margin-left:8px;\r\n    display:inline-flex; align-items:center;\r\n    background:#cffafe; border:1px solid #a5f3fc; color:#083344;\r\n    padding:4px 8px; border-radius:10px; font-weight:800;\r\n    white-space:nowrap;                                     \/* \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0438\u043b\u043e\u0441\u044c *\/\r\n  `;\r\n  scoreBox.appendChild(lvlBadge);\r\n}\r\nconst setNivelBadge = n => { if (lvlBadge) lvlBadge.textContent = `N: ${n}\/${maxN}`; };\r\n\r\n\r\n  \/\/ ===== \u0437\u0432\u0443\u043a\u0438 =====\r\n  const tone = (f=660, ms=100, type='sine', vol=0.18)=>{ try{ SFX && SFX.tone && SFX.tone(f,ms,type,vol); }catch(e){} };\r\n  const sfxClick  = ()=>{ if (window.SFX?.click) SFX.click(); else tone(700,80,'square',0.15); };\r\n  const sfxOk     = ()=>{ if (window.SFX?.success) SFX.success(); else tone(980,140,'sine',0.22); };\r\n  const sfxFail   = ()=>{ if (window.SFX?.fail) SFX.fail(); else tone(220,180,'sawtooth',0.2); };\r\n  const sfxWhoosh = ()=>{ if (window.SFX?.whoosh) SFX.whoosh(); else tone(480,90,'sawtooth',0.12); };\r\n\r\n  const wait = (ms)=> new Promise(r=>setTimeout(r,ms));\r\n\r\n  \/\/ \u0441\u0442\u043e\u043f \u0437\u0432\u0443\u043a\u0430 \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435\r\n  const stopAllAudio = ()=>{ try{ if (window.SFX){ SFX.stopAll?.(); SFX.musicStop?.(); SFX.loopStop?.('*'); } }catch(e){} };\r\n  function wireExitStop(){\r\n    const root = game.coreEl.closest('.zg-root');\r\n    const bind = ()=> root?.querySelectorAll('.zg-exit, .zg-close, [data-zg-exit]').forEach(b=> b.addEventListener('click', stopAllAudio, { once:true }));\r\n    bind(); new MutationObserver(bind).observe(root, { childList:true, subtree:true });\r\n    window.addEventListener('pagehide', stopAllAudio, { once:true });\r\n    document.addEventListener('visibilitychange', ()=>{ if (document.hidden) stopAllAudio(); }, { passive:true });\r\n  }\r\n\r\n  \/\/ ====== \u043e\u0431\u0449\u0438\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u043a\u043b\u0435\u0442\u043a\u0438\r\n  function recalcCell(){\r\n    const colWidth = (layout.clientWidth - 12) \/ 2;\r\n    const cell = Math.max(96, Math.min(180, Math.floor(colWidth*0.9)));\r\n    colA.style.setProperty('--cell', cell+'px');\r\n    colB.style.setProperty('--cell', cell+'px');\r\n    colA.querySelectorAll('.asoc-emoji').forEach(btn=> btn.style.fontSize = Math.floor(cell*0.72)+'px');\r\n    colB.querySelectorAll('.asoc-label').forEach(btn=> btn.style.fontSize = Math.max(16, Math.floor(cell*0.22))+'px');\r\n  }\r\n  new ResizeObserver(recalcCell).observe(layout);\r\n  window.addEventListener('orientationchange', recalcCell);\r\n\r\n  \/\/ ====== \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \/ \u0443\u0442\u0438\u043b\u0438\u0442\u044b\r\n  let nivel = 1;\r\n  let seleccionA = null, seleccionB = null, bloqueado=false;\r\n  let encontrados = new Set();\r\n\r\n  const shuffle = arr => arr.map(v=>[Math.random(),v]).sort((a,b)=>a[0]-b[0]).map(x=>x[1]);\r\n  const setNivel = n => { nivel=Math.min(Math.max(1,n),maxN); setNivelBadge(nivel); };\r\n\r\n  function clearOutline(el){ if (el) el.style.outline='none'; }\r\n  function outline(el, color){ el.style.outline=`4px solid ${color}`; }\r\n  function blink(el, ok=true){\r\n    const color = ok ? '#22c55e' : '#ef4444';\r\n    const old   = el.style.borderColor;\r\n    let k=0; const on=()=>{ el.style.borderColor=color; el.style.outline=`6px solid ${ok?'rgba(34,197,94,.35)':'rgba(239,68,68,.35)'}`; setTimeout(off,170); };\r\n    const off=()=>{ el.style.borderColor=old; el.style.outline='none'; if(++k<2) setTimeout(on,130); };\r\n    on();\r\n  }\r\n\r\n  \/\/ ====== \u0440\u0435\u043d\u0434\u0435\u0440 \u0443\u0440\u043e\u0432\u043d\u044f\r\n  function renderLevel(){\r\n    sfxWhoosh();\r\n    seleccionA = seleccionB = null;\r\n    bloqueado = false;\r\n    encontrados.clear();\r\n\r\n    const data = LEVELS[nivel-1];\r\n\r\n    \/\/ \u043b\u0435\u0432\u0430\u044f \u043a\u043e\u043b\u043e\u043d\u043a\u0430 \u2014 \u044d\u043c\u043e\u0434\u0437\u0438\r\n    colA.innerHTML = '';\r\n    shuffle(data.map(x=>({id:x.id, emoji:x.emoji}))).forEach(item=>{\r\n      const b = document.createElement('button');\r\n      b.className='asoc-emoji'; b.dataset.id=item.id; b.textContent=item.emoji;\r\n      b.onclick=()=> selectA(b);\r\n      colA.appendChild(b);\r\n    });\r\n\r\n    \/\/ \u043f\u0440\u0430\u0432\u0430\u044f \u043a\u043e\u043b\u043e\u043d\u043a\u0430 \u2014 \u0441\u043b\u043e\u0432\u0430\r\n    colB.innerHTML = '';\r\n    shuffle(data.map(x=>({id:x.id, label:x.label}))).forEach(item=>{\r\n      const b = document.createElement('button');\r\n      b.className='asoc-label'; b.dataset.id=item.id; b.textContent=item.label;\r\n      b.onclick=()=> selectB(b);\r\n      colB.appendChild(b);\r\n    });\r\n\r\n    recalcCell();\r\n  }\r\n\r\n  function selectA(btn){\r\n    if (bloqueado || btn.disabled) return;\r\n    sfxClick();\r\n    if (seleccionA === btn){ clearOutline(seleccionA); seleccionA=null; return; }\r\n    clearOutline(seleccionA); seleccionA=btn; outline(btn,'#0ea5e9'); intentarPareja();\r\n  }\r\n  function selectB(btn){\r\n    if (bloqueado || btn.disabled) return;\r\n    sfxClick();\r\n    if (seleccionB === btn){ clearOutline(seleccionB); seleccionB=null; return; }\r\n    clearOutline(seleccionB); seleccionB=btn; outline(btn,'#0ea5e9'); intentarPareja();\r\n  }\r\n\r\n  async function saveProgress(){\r\n    try{\r\n      const recEl = document.querySelector('[data-zg-record]');\r\n      const currRecord = Number(recEl?.textContent || 0);\r\n      if (game.score > currRecord){\r\n        await zSaveProgress(usuarioId, 4, game.score, game.elapsed);\r\n        if (recEl) recEl.textContent = game.score;\r\n      } else {\r\n        await zSaveProgress(usuarioId, 4, currRecord, game.elapsed);\r\n      }\r\n      const total = await zLoadTotalTime(usuarioId, 4);\r\n      const totalEl = document.querySelector('[data-zg-total]');\r\n      if (totalEl) totalEl.textContent = zFmtMMSS(total);\r\n    }catch(e){}\r\n  }\r\n\r\n  async function nextLevelWithCountdown(){\r\n    await saveProgress();\r\n    if (nivel >= maxN){\r\n      await game.endGame({save:true});\r\n      stopAllAudio();\r\n      return;\r\n    }\r\n    \/\/ \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434: \u043a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u043f\u0430\u0443\u0437\u0430 + \u043e\u0442\u0441\u0447\u0451\u0442\r\n    game.setMessage(`<span style=\"color:#298d34;font-weight:600;\">\u00a1Nivel ${nivel} completado!<\/span> Prep\u00e1rate\u2026`);\r\n    await wait(700);\r\n    cntBox.style.display = 'flex';\r\n    for (let x=5; x>=1; x--){\r\n      cntNum.textContent = String(x);\r\n      window.SFX?.ui?.();\r\n      await wait(1000);\r\n    }\r\n    cntBox.style.display = 'none';\r\n    setNivel(nivel+1);\r\n    renderLevel();\r\n    game.setMessage('Empareja las columnas.');\r\n  }\r\n\r\n  function intentarPareja(){\r\n    if (!seleccionA || !seleccionB || bloqueado) return;\r\n    bloqueado = true;\r\n\r\n    const ok = (seleccionA.dataset.id === seleccionB.dataset.id);\r\n    if (ok){\r\n      game.addScore(10);\r\n      sfxOk();\r\n      seleccionA.disabled = true; seleccionB.disabled = true;\r\n      seleccionA.style.background = '#caffae'; seleccionB.style.background = '#caffae';\r\n      blink(seleccionA,true); blink(seleccionB,true);\r\n      encontrados.add(seleccionA.dataset.id);\r\n      setTimeout(()=>{\r\n        clearOutline(seleccionA); clearOutline(seleccionB);\r\n        seleccionA = null; seleccionB = null; bloqueado=false;\r\n        if (encontrados.size === LEVELS[nivel-1].length) nextLevelWithCountdown();\r\n      }, 280);\r\n    }else{\r\n      game.setScore(Math.max(0, game.score-5));\r\n      sfxFail();\r\n      blink(seleccionA,false); blink(seleccionB,false);\r\n      setTimeout(()=>{\r\n        clearOutline(seleccionA); clearOutline(seleccionB);\r\n        seleccionA=null; seleccionB=null; bloqueado=false;\r\n      }, 340);\r\n    }\r\n  }\r\n\r\n  \/\/ ====== HUD\r\n  game.onStart(()=>{\r\n    if (window.SFX?.unlock) SFX.unlock();\r\n    wireExitStop();\r\n    game.showStart(false);\r\n    game.showContinue(false);         \/\/ \u043a\u043d\u043e\u043f\u043a\u0430 \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\r\n    game.setMessage('');\r\n    wrap.style.display='block';\r\n\r\n    setNivel(1);\r\n    game.setScore(0);\r\n    game.startTimer();\r\n    renderLevel();\r\n  });\r\n\r\n  \/\/ \u00abContinuar\u00bb \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d5eeb75 elementor-widget elementor-widget-html\" data-id=\"d5eeb75\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Diferencias(){\r\n  const game = crearMarcoJuego({ gameId:5, titulo:'Encuentra diferencias', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430 \u2014 \u0431\u0435\u0437 \"Continuar\"\r\n  game.setMessage(`\r\n    Observa dos im\u00e1genes (A y B) y toca en <b>B<\/b> los elementos que son distintos.\r\n    Acierto: <b>+10 pts<\/b>. Error: <b>\u22125 pts<\/b> (se reinicia el nivel).\r\n    Algunos niveles tienen cuenta atr\u00e1s \u23f0. Los niveles avanzan autom\u00e1ticamente.\r\n  `);\r\n\r\n  \/\/ ====== \u0417\u0412\u0423\u041a\u0418 \/ \u0418\u041d\u0422\u0415\u0413\u0420\u0410\u0426\u0418\u042f SFX ======\r\n  const tone = (f=660, ms=100, type='sine', vol=0.18)=>{ try{ if (window.SFX?.tone) SFX.tone(f,ms,type,vol); }catch(e){} };\r\n  const sfxClick   = ()=>{ if (window.SFX?.click)   SFX.click();   else tone(700,70,'square',0.14); };\r\n  const sfxOk      = ()=>{ if (window.SFX?.success) SFX.success(); else tone(980,140,'sine',0.22); };\r\n  const sfxFail    = ()=>{ if (window.SFX?.fail)    SFX.fail();    else tone(240,180,'sawtooth',0.2); };\r\n  const sfxWhoosh  = ()=>{ if (window.SFX?.whoosh)  SFX.whoosh();  else tone(520,90,'sawtooth',0.12); };\r\n  const sfxTick    = ()=>{ if (window.SFX?.ui)      SFX.ui();      else tone(880,55,'square',0.12); };\r\n  const sfxTimesUp = ()=>{ if (window.SFX?.timesup) SFX.timesup(); else { tone(200,180,'triangle',0.22); setTimeout(()=>tone(160,200,'triangle',0.22),100); } };\r\n  const sfxWin     = ()=>{ if (window.SFX?.win)     SFX.win();     else { tone(660,120,'sine',0.18); setTimeout(()=>tone(880,120,'sine',0.18),110); setTimeout(()=>tone(1046,200,'sine',0.22),240); } };\r\n\r\n  function startBg(kind){ try{ if (!window.SFX) return; SFX.musicLoop?.(kind||'focus') ?? SFX.loopStart?.(kind||'focus'); }catch(e){} }\r\n  function stopBg(){ try{ if (!window.SFX) return; SFX.musicStop?.(); SFX.loopStop?.('*'); }catch(e){} }\r\n  function stopAllAudio(){ try{ if (!window.SFX) return; SFX.stopAll?.(); SFX.musicStop?.(); SFX.loopStop?.('*'); }catch(e){} }\r\n  function wireExitStop(){\r\n    const root = game.coreEl.closest('.zg-root');\r\n    const bind = ()=> root?.querySelectorAll('.zg-exit, .zg-close, [data-zg-exit]').forEach(b=> b.addEventListener('click', stopAllAudio, { once:true }));\r\n    bind(); new MutationObserver(bind).observe(root, { childList:true, subtree:true });\r\n    window.addEventListener('pagehide', stopAllAudio, { once:true });\r\n    document.addEventListener('visibilitychange', ()=>{ if (document.hidden) stopAllAudio(); }, { passive:true });\r\n  }\r\n\r\n  \/\/ \u041d\u0430\u0431\u043e\u0440 \u0443\u0440\u043e\u0432\u043d\u0435\u0439\r\n  const LOCAL_LEVELS = [\r\n    { izquierda:\"\ud83c\udf4e\ud83c\udf4c\ud83c\udf52\ud83c\udf49\ud83c\udf47\", derecha:\"\ud83c\udf4e\ud83c\udf4b\ud83c\udf52\ud83c\udf4d\ud83c\udf48\", diferencias:[1,3,4], tiempo:0 },\r\n    { izquierda:\"\ud83d\udc36\ud83d\udc31\ud83d\udc30\ud83e\udd81\ud83d\udc38\", derecha:\"\ud83d\udc36\ud83e\udd8a\ud83d\udc30\ud83e\udd81\ud83d\udc35\", diferencias:[1,4],   tiempo:25 },\r\n    { izquierda:\"\ud83c\udf44\ud83c\udf32\ud83c\udf33\ud83c\udf34\ud83c\udf35\ud83c\udf3b\", derecha:\"\ud83c\udf44\ud83c\udf3c\ud83c\udf33\ud83c\udf34\ud83c\udf37\ud83c\udf3b\", diferencias:[1,4],  tiempo:35 },\r\n    { izquierda:\"\u26bd\ud83c\udfc0\ud83c\udfc8\u26be\ud83e\udd4e\ud83c\udfd0\ud83c\udfc9\", derecha:\"\u26bd\ud83c\udfc0\ud83e\udd4f\u26be\ud83e\udd4e\ud83c\udfd3\ud83c\udfc9\", diferencias:[2,5],  tiempo:30 },\r\n    { izquierda:\"\ud83d\ude97\ud83d\ude99\ud83d\ude95\ud83d\ude93\ud83d\ude91\ud83d\ude92\ud83d\ude90\ud83d\ude9a\", derecha:\"\ud83d\ude97\ud83d\ude99\ud83d\ude95\ud83d\ude92\ud83d\ude91\ud83d\ude92\ud83d\udeb2\ud83d\ude9a\", diferencias:[3,6], tiempo:40 }\r\n  ];\r\n  const LEVELS = (Array.isArray(window.nivelesDiferencias) && window.nivelesDiferencias.length)\r\n    ? window.nivelesDiferencias : LOCAL_LEVELS;\r\n\r\n  \/\/ \u2014\u2014\u2014 \u044f\u0434\u0440\u043e (\u0441\u043a\u0440\u044b\u0442\u043e \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430) \u2014\u2014\u2014\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      #dif-wrap{\r\n        background:#fff;border-radius:22px;box-shadow:0 0 24px 2px #ddd;\r\n        padding:16px;display:none;margin:0;width:100%;max-width:100%;box-sizing:border-box;position:relative;\r\n      }\r\n      #dif-boards{display:grid;gap:16px;grid-template-columns:1fr 1fr;align-items:start}\r\n      @media (max-width:560px){ #dif-boards{grid-template-columns:1fr} }\r\n      .dif-cell{\r\n        width:var(--cell,72px); height:var(--cell,72px);\r\n        display:flex;align-items:center;justify-content:center;\r\n        line-height:1; padding:0; border-radius:12px; transition:.15s; font-weight:800;\r\n        background:#fff; -webkit-tap-highlight-color:transparent; touch-action:manipulation; user-select:none;\r\n      }\r\n      .dif-b{ cursor:pointer; }\r\n\r\n      \/* \u041e\u0432\u0435\u0440\u043b\u0435\u0439 \u043e\u0442\u0441\u0447\u0451\u0442\u0430 \u043c\u0435\u0436\u0434\u0443 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 *\/\r\n      .dif-count{position:absolute; inset:0; display:none; align-items:center; justify-content:center; pointer-events:none;}\r\n      .dif-bubble{background:#ffffffdd; border-radius:16px; padding:12px 28px; font-size:2.1em; font-weight:900; color:#0b3a47; box-shadow:0 2px 18px #0001;}\r\n    <\/style>\r\n\r\n    <div id=\"dif-wrap\">\r\n      <div id=\"dif-nivel\" style=\"text-align:center;font-weight:800;margin-bottom:8px;\">\r\n        Nivel: <b>1<\/b> \/ <b>${LEVELS.length}<\/b> <span id=\"dif-chrono\" style=\"margin-left:10px;\"><\/span>\r\n      <\/div>\r\n      <div id=\"dif-boards\">\r\n        <div>\r\n          <div class=\"dif-sub\" style=\"font-weight:700;margin:4px 0 8px 0;\">Imagen A<\/div>\r\n          <div id=\"dif-a\" style=\"display:flex;flex-wrap:wrap;gap:8px;justify-content:center;\"><\/div>\r\n        <\/div>\r\n        <div>\r\n          <div class=\"dif-sub\" style=\"font-weight:700;margin:4px 0 8px 0;\">Imagen B<\/div>\r\n          <div id=\"dif-b\" style=\"display:flex;flex-wrap:wrap;gap:8px;justify-content:center;\"><\/div>\r\n        <\/div>\r\n      <\/div>\r\n      <div class=\"dif-count\" id=\"dif-count\"><div class=\"dif-bubble\" id=\"dif-count-num\">5<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  const wrap    = document.getElementById('dif-wrap');\r\n  const levelEl = document.getElementById('dif-nivel');\r\n  const boardsEl= document.getElementById('dif-boards');\r\n  const aEl     = document.getElementById('dif-a');\r\n  const bEl     = document.getElementById('dif-b');\r\n  const cntBox  = document.getElementById('dif-count');\r\n  const cntNum  = document.getElementById('dif-count-num');\r\n\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  if (window.SFX && scoreBox && typeof SFX.injectToggle==='function') SFX.injectToggle(scoreBox);\r\n\r\n  const getChrono = () => document.getElementById('dif-chrono');\r\n\r\n  \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\r\n  let nivel = 1;\r\n  let countdownId = null;\r\n  let found = new Set();\r\n  let locked = false;\r\n\r\n  const wait = (ms)=> new Promise(r=>setTimeout(r,ms));\r\n  const setMsg = html => game.setMessage(html||'');\r\n  const splitEmoji = str => str.match(\/\\p{Emoji}(?:\\u200d\\p{Emoji})*\/gu) || [];\r\n  const shuffleIdx = n => Array.from({length:n}, (_,i)=>i).sort(()=>Math.random()-0.5);\r\n\r\n  const setNivel = n => {\r\n    nivel = Math.min(Math.max(1, n), LEVELS.length);\r\n    levelEl.innerHTML = `Nivel: <b>${nivel}<\/b> \/ <b>${LEVELS.length}<\/b> <span id=\"dif-chrono\" style=\"margin-left:10px;\"><\/span>`;\r\n  };\r\n\r\n  function recalcCell(count){\r\n    const colWidth = (boardsEl.clientWidth - 16) \/ 2;\r\n    const perRow = Math.min(count, 8);\r\n    const size = Math.max(56, Math.min(92, Math.floor(colWidth \/ perRow) - 8));\r\n    aEl.style.setProperty('--cell', size+'px');\r\n    bEl.style.setProperty('--cell', size+'px');\r\n    aEl.querySelectorAll('.dif-cell').forEach(c=> c.style.fontSize = Math.floor(size*0.72)+'px');\r\n    bEl.querySelectorAll('.dif-cell').forEach(c=> c.style.fontSize = Math.floor(size*0.72)+'px');\r\n  }\r\n  new ResizeObserver(()=>recalcCell(aEl.childElementCount || 5)).observe(boardsEl);\r\n  window.addEventListener('orientationchange', ()=>recalcCell(aEl.childElementCount || 5));\r\n\r\n  function blink(el, ok=true){\r\n    const color = ok ? 'rgba(34,197,94,.45)' : 'rgba(239,68,68,.45)';\r\n    const oldBg = el.style.backgroundColor;\r\n    let k=0; const on=()=>{ el.style.boxShadow=`0 0 0 6px ${color}`; el.style.background = ok?'#d1fae5':'#fee2e2'; setTimeout(off,160); };\r\n    const off=()=>{ el.style.boxShadow='none'; el.style.background = oldBg || '#fff'; if(++k<2) setTimeout(on,120); };\r\n    on();\r\n  }\r\n  function stopCountdown(){\r\n    if (countdownId){ clearInterval(countdownId); countdownId=null; }\r\n    const ch = getChrono(); if (ch) ch.textContent='';\r\n  }\r\n\r\n  \/\/ \u0440\u0435\u043d\u0434\u0435\u0440 \u0443\u0440\u043e\u0432\u043d\u044f\r\n  function renderLevel(restart=false){\r\n    stopCountdown();\r\n    stopBg();\r\n    sfxWhoosh();\r\n    found.clear(); locked=false;\r\n\r\n    const lvl = LEVELS[nivel-1];\r\n    const Aorig = splitEmoji(lvl.izquierda);\r\n    const Borig = splitEmoji(lvl.derecha);\r\n\r\n    const orderA = shuffleIdx(Aorig.length);\r\n    const orderB = shuffleIdx(Borig.length);\r\n    const A = orderA.map(i=>Aorig[i]);\r\n    const B = orderB.map(i=>Borig[i]);\r\n\r\n    aEl.innerHTML = A.map(ch=>`<div class=\"dif-cell\">${ch}<\/div>`).join('');\r\n    bEl.innerHTML = B.map((ch,i)=>`<button class=\"dif-cell dif-b\" data-orden=\"${orderB[i]}\">${ch}<\/button>`).join('');\r\n    recalcCell(A.length);\r\n\r\n    \/\/ \u0442\u0430\u0439\u043c\u0435\u0440\r\n    if (lvl.tiempo && lvl.tiempo>0){\r\n      let t = lvl.tiempo;\r\n      const ch = getChrono(); if (ch) ch.innerHTML = `\u23f0 <b id=\"dif-t\">${t}<\/b>s`;\r\n      startBg('timer');\r\n      countdownId = setInterval(()=>{\r\n        t--;\r\n        const tEl = document.getElementById('dif-t'); if (tEl) tEl.textContent=t;\r\n        if (t<=5) sfxTick();\r\n        if (t<=0){\r\n          stopCountdown(); stopBg(); sfxTimesUp();\r\n          setMsg('<span style=\"color:#c33;font-weight:600;\">\u00a1Se acab\u00f3 el tiempo!<\/span>');\r\n          stopAllAudio();\r\n          game.endGame({save:true});\r\n        }\r\n      },1000);\r\n    } else {\r\n      startBg('focus');\r\n    }\r\n\r\n    setMsg(restart ? 'Intentemos de nuevo este nivel.' : 'Haz clic en las diferencias de la imagen B.');\r\n\r\n    bEl.querySelectorAll('.dif-b').forEach(btn=>{\r\n      btn.onclick = ()=>{\r\n        if (locked) return;\r\n        sfxClick();\r\n        const idxReal = +btn.dataset.orden;\r\n\r\n        if (LEVELS[nivel-1].diferencias.includes(idxReal)){\r\n          found.add(idxReal);\r\n          blink(btn,true);\r\n          game.addScore(10);\r\n          setMsg(`\u00a1Correcto! Diferencias: <b>${found.size}<\/b> \/ <b>${LEVELS[nivel-1].diferencias.length}<\/b>`);\r\n          sfxOk();\r\n          if (found.size === LEVELS[nivel-1].diferencias.length){\r\n            locked = true; stopCountdown(); stopBg();\r\n            sfxWin();\r\n            afterLevel();  \/\/ \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434\r\n          }\r\n        } else {\r\n          locked = true;\r\n          blink(btn,false);\r\n          game.setScore(Math.max(0, game.score-5));\r\n          sfxFail();\r\n          setMsg('<span style=\"color:#c33;font-weight:600;\">\u00a1Error!<\/span> Reiniciamos el nivel\u2026');\r\n          setTimeout(()=> renderLevel(true), 550);\r\n        }\r\n      };\r\n    });\r\n  }\r\n\r\n  \/\/ \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\u0430\r\n  async function saveProgress(){\r\n    try{\r\n      const recEl = document.querySelector('[data-zg-record]');\r\n      const currRecord = Number(recEl?.textContent || 0);\r\n      if (game.score > currRecord){ await zSaveProgress(usuarioId,5,game.score,game.elapsed); if (recEl) recEl.textContent = game.score; }\r\n      else                         { await zSaveProgress(usuarioId,5,currRecord,game.elapsed); }\r\n      const total = await zLoadTotalTime(usuarioId,5);\r\n      const totalEl = document.querySelector('[data-zg-total]'); if (totalEl) totalEl.textContent = zFmtMMSS(total);\r\n    }catch(e){}\r\n  }\r\n\r\n  \/\/ \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0435 \u0443\u0440\u043e\u0432\u043d\u044f \u2192 \u0430\u0432\u0442\u043e-\u043e\u0442\u0441\u0447\u0451\u0442 \u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439\r\n  async function afterLevel(){\r\n    await saveProgress();\r\n\r\n    if (nivel >= LEVELS.length){\r\n      setMsg('<span style=\"color:#298d34;font-weight:600;\">\u00a1Todos los niveles completados!<\/span>');\r\n      stopAllAudio();\r\n      await game.endGame({save:true});\r\n      return;\r\n    }\r\n\r\n    setMsg(`<span style=\"color:#298d34;font-weight:600;\">\u00a1Nivel ${nivel} superado!<\/span> Prep\u00e1rate\u2026`);\r\n    await wait(700);\r\n    cntBox.style.display = 'flex';\r\n    for (let x=5; x>=1; x--){\r\n      cntNum.textContent = String(x);\r\n      sfxTick();\r\n      await wait(1000);\r\n    }\r\n    cntBox.style.display = 'none';\r\n\r\n    setNivel(nivel+1);\r\n    setMsg('');\r\n    renderLevel();\r\n  }\r\n\r\n  \/\/ HUD\r\n  game.onStart(()=>{\r\n    if (window.SFX?.unlock) SFX.unlock();\r\n    wireExitStop();\r\n\r\n    game.showStart(false);\r\n    game.showContinue(false);   \/\/ \u043a\u043d\u043e\u043f\u043a\u0430 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\r\n    game.setMessage('');\r\n    wrap.style.display='block';\r\n\r\n    setNivel(1);\r\n    game.setScore(0);\r\n    game.startTimer();\r\n    renderLevel();\r\n  });\r\n\r\n  \/\/ \u00abContinuar\u00bb \u043d\u0435 \u043d\u0443\u0436\u0435\u043d\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ead3414 elementor-widget elementor-widget-html\" data-id=\"ead3414\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_AtrapaObjeto(){\r\n  const game = crearMarcoJuego({ gameId: 6, titulo: 'Atrapa objetos', bg:'#e4f7fa' });\r\n\r\n  game.setMessage(`\r\n    Toca las frutas y monedas para sumar puntos (+2\u2026+8) y evita las <b>bombas<\/b> (\u22128).<br>\r\n    Cada nivel dura unos segundos; la velocidad y la frecuencia aumentan.\r\n  `);\r\n\r\n  \/\/ --- SFX helpers (\u043c\u044f\u0433\u043a\u0438\u0435 \u0444\u043e\u043b\u0431\u044d\u043a\u0438) ---\r\n  const tone=(f=660,ms=120,type='sine',vol=.2)=>{try{if(window.SFX?.tone){SFX.tone(f,ms,type,vol);return;}const AC=window.AudioContext||window.webkitAudioContext;if(!AC)return;tone.ctx=tone.ctx||new AC();const c=tone.ctx,o=c.createOscillator(),g=c.createGain();o.type=type;o.frequency.value=f;g.gain.value=vol;o.connect(g);g.connect(c.destination);o.start();setTimeout(()=>{try{o.stop();o.disconnect();g.disconnect();}catch(e){}},ms);}catch(e){}};\r\n  const rand=(a,b)=>a+Math.random()*(b-a);\r\n  const sfxClick=()=>{if(SFX?.click)SFX.click();else tone(700,70,'square',.14);};\r\n  const sfxWhoosh=()=>{if(SFX?.whoosh)SFX.whoosh();else tone(520,90,'sawtooth',.12);};\r\n  const sfxTick=()=>{if(SFX?.tick)SFX.tick();else tone(880,55,'square',.12);};\r\n  const sfxWin=()=>{if(SFX?.win)SFX.win();else{tone(660,120,'sine',.18);setTimeout(()=>tone(880,120,'sine',.18),110);setTimeout(()=>tone(1046,200,'sine',.22),240);}};\r\n  const sfxTimesUp=()=>{if(SFX?.timesup)SFX.timesup();else{tone(200,180,'triangle',.22);setTimeout(()=>tone(160,200,'triangle',.22),110);}};\r\n\r\n  const sfxCoin=()=>{if(SFX?.coin)SFX.coin();else{tone(1200,80,'triangle',.18);setTimeout(()=>tone(1600,90,'triangle',.18),70);}};\r\n  const sfxFruit=()=>{if(SFX?.pop)SFX.pop();else tone(rand(500,650),90,'square',.16);};\r\n  const sfxBomb=()=>{if(SFX?.boom)SFX.boom();else{tone(160,200,'sawtooth',.22);setTimeout(()=>tone(120,220,'sawtooth',.22),120);}};\r\n  const sfxSpawn=()=>{if(SFX?.plop)SFX.plop();else tone(rand(300,420),60,'sine',.08);};\r\n\r\n  function startBg(kind){try{if(!window.SFX)return;SFX.musicLoop?.(kind||'arcade')??SFX.loopStart?.(kind||'arcade');}catch(e){}}\r\n  function stopBg(){try{if(!window.SFX)return;SFX.musicStop?.();SFX.loopStop?.('*');}catch(e){}}\r\n  function stopAllAudio(){try{SFX?.stopAll?.();SFX?.musicStop?.();SFX?.loopStop?.('*');}catch(e){}}\r\n  function wireExitStop(){\r\n    const root=game.coreEl.closest('.zg-root');\r\n    const bind=()=>root?.querySelectorAll('.zg-exit, .zg-close, [data-zg-exit]')?.forEach(b=>b.addEventListener('click',stopAllAudio,{once:true}));\r\n    bind(); new MutationObserver(bind).observe(root,{childList:true,subtree:true});\r\n    window.addEventListener('pagehide',stopAllAudio,{once:true});\r\n    document.addEventListener('visibilitychange',()=>{if(document.hidden)stopAllAudio();},{passive:true});\r\n  }\r\n\r\n  \/\/ \u0443\u0440\u043e\u0432\u043d\u0438\r\n  const LEVELS=[\r\n    { tiempo:28, minVy:120, maxVy:180, spawnMs:1100, bombChance:.08 },\r\n    { tiempo:30, minVy:150, maxVy:220, spawnMs:950,  bombChance:.12 },\r\n    { tiempo:32, minVy:185, maxVy:260, spawnMs:820,  bombChance:.16 },\r\n    { tiempo:34, minVy:220, maxVy:300, spawnMs:700,  bombChance:.20 },\r\n    { tiempo:36, minVy:260, maxVy:340, spawnMs:600,  bombChance:.24 }\r\n  ];\r\n  const FRUTAS=[\"\ud83c\udf4e\",\"\ud83c\udf4c\",\"\ud83c\udf52\",\"\ud83c\udf47\",\"\ud83c\udf49\",\"\ud83c\udf53\",\"\ud83e\udd5d\",\"\ud83e\ude99\"];\r\n  const PUNTOS={\"\ud83c\udf4e\":2,\"\ud83c\udf4c\":2,\"\ud83c\udf52\":3,\"\ud83c\udf47\":3,\"\ud83c\udf49\":3,\"\ud83c\udf53\":4,\"\ud83e\udd5d\":5,\"\ud83e\ude99\":8,\"\ud83d\udca3\":-8};\r\n\r\n  \/\/ \u2500\u2500\u2500 \u044f\u0434\u0440\u043e \u2500\u2500\u2500\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      \/* \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0432\u044b\u0441\u043e\u0442\u044b \u2014 \u0431\u0435\u0437 \u00ab\u043f\u0440\u044b\u0436\u043a\u043e\u0432\u00bb layout *\/\r\n      .zg-core .zg-msg{\r\n        width:100% !important; max-width:none !important;\r\n        margin:0 0 12px 0 !important; box-sizing:border-box;\r\n        overflow-wrap:anywhere; min-height:54px;\r\n      }\r\n      .zg-core-host,[data-zg-core-host]{ overflow-x:hidden; }\r\n\r\n      #ao-wrap{\r\n        background:#fff;border-radius:24px;box-shadow:0 0 24px 2px #ddd;\r\n        padding:16px;display:none;width:100%;max-width:100%;margin:0;box-sizing:border-box;position:relative;overflow-x:hidden;\r\n      }\r\n      .ao-area{\r\n        position:relative;width:100%;max-width:520px;height:clamp(360px,70vh,640px);\r\n        background:#fff;border-radius:28px;box-shadow:0 3px 18px rgba(0,0,0,.08);\r\n        overflow:hidden;touch-action:none;-webkit-tap-highlight-color:transparent;\r\n      }\r\n\r\n      \/* \u041e\u0432\u0435\u0440\u043b\u0435\u0439 \u043e\u0442\u0441\u0447\u0451\u0442\u0430 \u043c\u0435\u0436\u0434\u0443 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 *\/\r\n      .ao-count{position:absolute;inset:0;display:none;align-items:center;justify-content:center;pointer-events:none;}\r\n      .ao-bubble{background:#ffffffdd;border-radius:16px;padding:12px 28px;font-size:2.1em;font-weight:900;color:#0b3a47;box-shadow:0 2px 18px #0001;}\r\n      \/* \u044f\u0434\u0440\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u043d\u0430 \u0432\u0441\u044e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0443\u044e \u0448\u0438\u0440\u0438\u043d\u0443 *\/\r\n.zg-main{ display:flex; gap:14px; align-items:flex-start; }\r\n.zg-core{ flex:1 1 auto; min-width:0; max-width:none; width:100%; }\r\n.zg-core .zg-core-ui{ width:100%; }\r\n.zg-core [data-zg-core-host]{ width:100%; }\r\n\r\n\/* \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435: \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0430\u044f \u00ab\u043f\u043e\u043b\u043e\u0441\u0430\u00bb, \u041d\u0415 \u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u0435\u0442\u0441\u044f, \u041d\u0415 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u043e \u043f\u043e max-width *\/\r\n.zg-core .zg-msg{\r\n  display:block;\r\n  width:100% !important;\r\n  max-width:none !important;\r\n  margin:0 0 12px 0 !important;     \/* \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e 0 auto *\/\r\n  padding:16px 18px;\r\n  box-sizing:border-box;\r\n  min-height:56px;                   \/* \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0441\u043e\u0442\u0430 \u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0435 \u00ab\u043f\u043e\u0434\u043f\u0440\u044b\u0433\u0438\u0432\u0430\u043b\u0430\u00bb *\/\r\n  white-space:normal;\r\n  word-break:break-word;\r\n  overflow-wrap:anywhere;\r\n}\r\n\r\n\/* \u0445\u043e\u0441\u0442 \u0438\u0433\u0440\u044b \u0438 \u0432\u0441\u044f \u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0430 \u2014 \u0431\u0435\u0437 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043a\u0440\u043e\u043b\u043b\u0430 *\/\r\n.zg-root{ overflow-x:hidden; }\r\n.zg-core-host, [data-zg-core-host]{ overflow-x:hidden; }\r\n\/* HUD \u0432 \u043e\u0434\u043d\u0443 \u043b\u0438\u043d\u0438\u044e + \u0431\u0435\u0439\u0434\u0436 \u0443\u0440\u043e\u0432\u043d\u044f *\/\r\n.zg-score{ flex-wrap:nowrap; gap:10px; }\r\n.zg-lvl-badge{\r\n  display:inline-flex; align-items:center; gap:6px;\r\n  background:#cffafe; border:1px solid #a5f3fc; color:#083344;\r\n  padding:4px 8px; border-radius:10px; font-weight:800; font-size:.95em;\r\n}\r\n\r\n\r\n    <\/style>\r\n\r\n  <div id=\"ao-wrap\">\r\n    <div id=\"ao-meta\" style=\"text-align:center;font-weight:800;margin:4px 0 8px\">\r\n      <span id=\"ao-chrono\"><\/span>\r\n    <\/div>\r\n    <div style=\"display:flex;justify-content:center;\">\r\n      <div id=\"ao-area\" class=\"ao-area\"><\/div>\r\n    <\/div>\r\n    <div class=\"ao-count\" id=\"ao-count\"><div class=\"ao-bubble\" id=\"ao-count-num\">5<\/div><\/div>\r\n  <\/div>\r\n`;\r\n\r\n  const wrap=document.getElementById('ao-wrap');\r\n  const metaEl=document.getElementById('ao-meta');\r\n  const area=document.getElementById('ao-area');\r\n  const cntBox=document.getElementById('ao-count');\r\n  const cntNum=document.getElementById('ao-count-num');\r\n  const getChrono=()=>document.getElementById('ao-chrono');\r\n\r\n  \/\/ mute in HUD\r\n  const scoreBox=game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  if (window.SFX && scoreBox && typeof SFX.injectToggle==='function') SFX.injectToggle(scoreBox);\r\n  \/\/ \u0411\u0435\u0439\u0434\u0436 \u0443\u0440\u043e\u0432\u043d\u044f \u0432 HUD\r\nlet lvlBadge = scoreBox?.querySelector('.zg-lvl-badge');\r\nif (!lvlBadge && scoreBox){\r\n  lvlBadge = document.createElement('span');\r\n  lvlBadge.className = 'zg-lvl-badge';\r\n  lvlBadge.textContent = `N: 1 \/ ${LEVELS.length}`;\r\n  scoreBox.appendChild(lvlBadge);\r\n}\r\nconst setNivelBadge = (n)=>{ if (lvlBadge) lvlBadge.textContent = `N: ${n} \/ ${LEVELS.length}`; };\r\n\r\n\r\n  \/\/ \u0430\u043d\u0442\u0438 double-tap\r\n  let lastTouch=0;\r\n  area.addEventListener('touchend',e=>{const n=Date.now();if(n-lastTouch<350)e.preventDefault();lastTouch=n;},{passive:false});\r\n\r\n  \/\/ lock scroll\r\n  const ScrollLock=(()=>{let L=false,ph='',pb='',whe,tou;function lock(){if(L)return;L=true;const h=document.documentElement,b=document.body;ph=h.style.overflow;pb=b.style.overflow;h.style.overflow='hidden';b.style.overflow='hidden';whe=e=>e.preventDefault();tou=e=>e.preventDefault();window.addEventListener('wheel',whe,{passive:false});window.addEventListener('touchmove',tou,{passive:false});}function unlock(){if(!L)return;L=false;const h=document.documentElement,b=document.body;h.style.overflow=ph;b.style.overflow=pb;window.removeEventListener('wheel',whe);window.removeEventListener('touchmove',tou);}return{lock,unlock};})();\r\n\r\nlet nivel = 1;\r\nconst setNivel = (n)=>{\r\n  nivel = Math.min(Math.max(1,n), LEVELS.length);\r\n  setNivelBadge(nivel);                    \/\/ \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u0431\u0435\u0439\u0434\u0436 \u0432 HUD\r\n  const ch = document.getElementById('ao-chrono');\r\n  if (ch) ch.innerHTML = '';               \/\/ \u043c\u0435\u0442\u0430 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u23f0, \u0442\u0435\u043a\u0441\u0442 \u043d\u0435 \u043d\u0443\u0436\u0435\u043d\r\n};\r\n\r\n\r\n  const wait=ms=>new Promise(r=>setTimeout(r,ms));\r\n\r\n  async function saveProgress(){\r\n    try{\r\n      const recEl=document.querySelector('[data-zg-record]');\r\n      const prevRec=Number(recEl?.textContent||0);\r\n      if (game.score>prevRec){ await zSaveProgress(usuarioId,6,game.score,game.elapsed); if(recEl) recEl.textContent=game.score; }\r\n      else { await zSaveProgress(usuarioId,6,prevRec,game.elapsed); }\r\n      const total=await zLoadTotalTime(usuarioId,6);\r\n      const totalEl=document.querySelector('[data-zg-total]'); if(totalEl) totalEl.textContent=zFmtMMSS(total);\r\n    }catch(e){}\r\n  }\r\n\r\n\r\n    \r\n    async function showCountdown(seconds = 5, text = 'Prep\u00e1rate\u2026'){\r\n      \/\/ \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u043a\u0430\u0436\u0435\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\r\n      if (text) game.setMessage(`<span style=\"color:#298d34;font-weight:600;\">${text}<\/span>`);\r\n      await wait(400);\r\n      cntBox.style.display = 'flex';\r\n      for (let x = seconds; x >= 1; x--){\r\n        cntNum.textContent = String(x);\r\n        sfxTick();\r\n        await wait(1000);\r\n      }\r\n      cntBox.style.display = 'none';\r\n    }\r\n\r\n\r\n    async function nextLevelCountdown(){\r\n      if (nivel >= LEVELS.length){\r\n        game.setMessage('<span style=\"color:#298d34;font-weight:600;\">\u00a1Todos los niveles completados!<\/span>');\r\n        stopAllAudio();\r\n        await game.endGame({save:true});\r\n        return;\r\n      }\r\n      game.setMessage(`<span style=\"color:#298d34;font-weight:600;\">\u00a1Nivel ${nivel} superado!<\/span>`);\r\n      await showCountdown(5, 'Prep\u00e1rate\u2026');   \/\/ \u2b05\ufe0f \u0432\u043e\u0442 \u0437\u0434\u0435\u0441\u044c\r\n      setNivel(nivel + 1);\r\n      startRound();\r\n    }\r\n\r\n  function startRound(){\r\n    ScrollLock.lock();\r\n    stopBg(); sfxWhoosh();\r\n    setTimeout(()=>startBg('arcade'),120);\r\n\r\n    const L=LEVELS[nivel-1];\r\n    let running=true, lastTs=performance.now();\r\n    let rafId=0, spawnId=0, countdownId=0;\r\n    let timeLeft=L.tiempo, tStart=performance.now();\r\n    let objects=[]; \/\/ {node,x,y,vy,hit,emoji}\r\n\r\n    area.innerHTML='';\r\n    game.setMessage('Haz clic en los objetos que caen.');\r\n    game.startTimer();\r\n\r\n    function chronoStart(){\r\n      const ch=getChrono(); if (ch) ch.innerHTML=`\u23f0 <b id=\"ao-t\">${timeLeft}<\/b>s`;\r\n      countdownId=setInterval(()=>{\r\n        timeLeft--;\r\n        const tEl=document.getElementById('ao-t'); if (tEl) tEl.textContent=timeLeft;\r\n        if (timeLeft<=5) sfxTick();\r\n        if (timeLeft<=0) finishRound();\r\n      },1000);\r\n    }\r\n\r\n    function spawnOne(){\r\n      const isBomb=Math.random()<L.bombChance;\r\n      const emoji=isBomb?\"\ud83d\udca3\":FRUTAS[Math.floor(Math.random()*FRUTAS.length)];\r\n      const areaW=area.clientWidth, areaH=area.clientHeight;\r\n\r\n      const hitSize=Math.max(56,Math.min(72,Math.floor(areaW*0.12)));\r\n      const fontPx=Math.floor(hitSize*0.82);\r\n      const x=Math.random()*(areaW-hitSize);         \/\/ \u0421\u041b\u0423\u0427\u0410\u0419\u041d\u041e \u041f\u041e \u0412\u0421\u0415\u0419 \u0428\u0418\u0420\u0418\u041d\u0415\r\n      const vy=Math.random()*(L.maxVy-L.minVy)+L.minVy;\r\n\r\n      const node=document.createElement('div');\r\n      node.style.cssText=`\r\n        position:absolute; top:-${hitSize}px; left:${x}px;\r\n        width:${hitSize}px; height:${hitSize}px;\r\n        display:flex; align-items:center; justify-content:center;\r\n        border-radius:14px; user-select:none; will-change:transform; cursor:pointer;\r\n      `;\r\n      const span=document.createElement('span');\r\n      span.textContent=emoji;\r\n      span.style.cssText=`font-size:${fontPx}px; line-height:1; filter:drop-shadow(0 2px 5px rgba(0,0,0,.15));`;\r\n      node.appendChild(span);\r\n      area.appendChild(node);\r\n\r\n      const obj={node,x,y:-hitSize,vy,hit:false,emoji};\r\n      objects.push(obj);\r\n\r\n      if (Math.random()<0.3) sfxSpawn();\r\n\r\n      node.addEventListener('pointerdown',()=>{\r\n        if(!running || obj.hit) return;\r\n        obj.hit=true;\r\n\r\n        if(emoji==='\ud83d\udca3') sfxBomb();\r\n        else if(emoji==='\ud83e\ude99') sfxCoin();\r\n        else sfxFruit();\r\n\r\n        const add=PUNTOS[emoji]??1;\r\n        game.setScore(Math.max(0,game.score+add));\r\n        if(add<0) game.setMessage('\u00a1Cuidado con las bombas! (\u22128)');\r\n\r\n        blink(node,add>=0);\r\n        setTimeout(()=>node.remove(),110);\r\n      },{passive:true});\r\n    }\r\n\r\n    function speedRamp(){\r\n      const elapsed=(performance.now()-tStart)\/1000;\r\n      if(elapsed<=0) return .6;\r\n      if(elapsed>=1.2) return 1.0;\r\n      return .6+(elapsed\/1.2)*.4;\r\n    }\r\n\r\n    function loop(ts){\r\n      if(!running) return;\r\n      rafId=requestAnimationFrame(loop);\r\n      const dt=Math.min(.05,(ts-lastTs)\/1000); lastTs=ts;\r\n      const ramp=speedRamp();\r\n      const areaH=area.clientHeight;\r\n\r\n      for(let i=objects.length-1;i>=0;i--){\r\n        const o=objects[i];\r\n        if(!o.node || o.hit) continue;\r\n        o.y+=(o.vy*ramp)*dt;\r\n        o.node.style.transform=`translate3d(0, ${o.y}px, 0)`;\r\n        if(o.y>areaH+24){ o.node.remove(); objects.splice(i,1); }\r\n      }\r\n\r\n      if(!document.body.contains(area)) stopAll();\r\n    }\r\n\r\n    function blink(node,ok){\r\n      if(!node) return;\r\n      node.style.boxShadow=`0 0 0 10px ${ok?'rgba(34,197,94,.45)':'rgba(239,68,68,.45)'}`;\r\n      setTimeout(()=>{ if(node) node.style.boxShadow='none'; },140);\r\n    }\r\n\r\n    function stopAll(){\r\n      running=false;\r\n      cancelAnimationFrame(rafId);\r\n      clearInterval(spawnId);\r\n      clearInterval(countdownId);\r\n      objects.forEach(o=>o.node&&o.node.remove()); objects=[];\r\n      ScrollLock.unlock(); stopBg();\r\n    }\r\n\r\n    async function finishRound(){\r\n      if(!running) return;\r\n      stopAll();\r\n      await saveProgress();\r\n      sfxWin();\r\n      await nextLevelCountdown();   \/\/ \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434\r\n    }\r\n\r\n    \/\/ \u0437\u0430\u043f\u0443\u0441\u043a\r\n    chronoStart();\r\n    spawnOne();\r\n    spawnId=setInterval(()=>running&&spawnOne(),L.spawnMs);\r\n    lastTs=performance.now();\r\n    requestAnimationFrame(loop);\r\n\r\n    \/\/ cleanup \u043f\u0440\u0438 \u00abSalir\u00bb\r\n    const mo=new MutationObserver(()=>{ if(!document.body.contains(area)) stopAll(); });\r\n    mo.observe(document.body,{childList:true,subtree:true});\r\n  }\r\n\r\n  \/\/ HUD\r\n    game.onStart(async ()=>{\r\n      window.SFX?.unlock?.();\r\n      wireExitStop();\r\n      game.showStart(false); game.showContinue(false); game.setMessage('');\r\n      wrap.style.display = 'block';\r\n      setNivel(1);\r\n      game.setScore(0);\r\n      await showCountdown(5, 'Comienza en\u2026'); \/\/ \u2b05\ufe0f \u043e\u0442\u0441\u0447\u0451\u0442 \u043f\u0435\u0440\u0435\u0434 1-\u043c \u0440\u0430\u0443\u043d\u0434\u043e\u043c\r\n      startRound();\r\n    });\r\n\r\n\r\n  \/\/ \u00abContinuar\u00bb \u043d\u0435 \u043d\u0443\u0436\u0435\u043d\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2826885 elementor-widget elementor-widget-html\" data-id=\"2826885\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_EscuchaRepite(){\r\n  const game = crearMarcoJuego({ gameId: 7, titulo: 'Escucha y repite', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430\r\n    game.setMessage(`\r\n      Pulsa <b>Empezar<\/b> y sigue estos pasos:<br>\r\n      1) Toca <b>\ud83d\udd0a Escuchar<\/b> para o\u00edr la frase (sube el volumen si hace falta).<br>\r\n      2) Toca <b>\ud83c\udfa4 Grabar<\/b> y <b>permite<\/b> el uso del micr\u00f3fono cuando el navegador lo pida.<br>\r\n      3) Repite exactamente la frase. Se ignoran tildes y signos (\u00bf?\u00a1!).<br>\r\n      Si el micr\u00f3fono no funciona, podr\u00e1s <b>escribir<\/b> lo que dijiste.\r\n    `);\r\n\r\n\r\n  \/\/ \u0424\u0440\u0430\u0437\u044b (\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u044f\u0442\u044c)\r\n  const PHRASES = [\r\n    \"Hola\",\r\n    \"\u00bfC\u00f3mo est\u00e1s?\",\r\n    \"Me gusta el helado\",\r\n    \"La casa es grande\",\r\n    \"Hoy hace sol\"\r\n  ];\r\n\r\n  \/\/ \u042f\u0434\u0440\u043e (\u0441\u043a\u0440\u044b\u0442\u043e \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430)\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n\/* \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435: \u043f\u043e\u043b\u043e\u0441\u0430 \u043d\u0430 \u0432\u0441\u044e \u0448\u0438\u0440\u0438\u043d\u0443, \u043f\u0435\u0440\u0435\u043d\u043e\u0441 \u0441\u043b\u043e\u0432, \u0431\u0435\u0437 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043a\u0440\u043e\u043b\u043b\u0430 *\/\r\n.zg-core .zg-msg{\r\n  display:block;\r\n  width:100% !important;\r\n  max-width:none !important;\r\n  margin:0 0 12px 0 !important;   \/* \u0431\u044b\u043b\u043e 0 auto *\/\r\n  padding:16px 18px;\r\n  box-sizing:border-box;\r\n  white-space:normal;\r\n  word-break:break-word;\r\n  overflow-wrap:anywhere;\r\n  hyphens:auto;\r\n}\r\n\r\n\/* \u041a\u043e\u0440 \u043e\u0431\u0451\u0440\u0442\u043a\u0438 \u2014 \u043d\u0435 \u0434\u0430\u0451\u043c \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043a\u0440\u043e\u043b\u043b \u0438 \u043d\u0435 \u0446\u0435\u043d\u0442\u0440\u0438\u0440\u0443\u0435\u043c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u043f\u043e \u0448\u0438\u0440\u0438\u043d\u0435 *\/\r\n.zg-root, .zg-core, .zg-core-host, [data-zg-core-host]{ overflow-x:hidden; }\r\n.zg-core{ justify-content:flex-start; }    \/* \u0432\u043c\u0435\u0441\u0442\u043e center *\/\r\n.zg-core .zg-core-ui{ width:100%; }\r\n\r\n\/* \u0421\u0430\u043c\u043e \u044f\u0434\u0440\u043e \u0438\u0433\u0440\u044b \u2014 \u0442\u043e\u0436\u0435 \u0432\u043e \u0432\u0441\u044e \u0448\u0438\u0440\u0438\u043d\u0443, \u0431\u0435\u0437 max-width \u0438 \u0446\u0435\u043d\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f *\/\r\n#er-wrap{\r\n  background:#fff; border-radius:24px; box-shadow:0 0 24px 2px #ddd;\r\n  padding:18px 16px; width:100%; max-width:100%; margin:0; display:none;\r\n  box-sizing:border-box;\r\n}\r\n\r\n\/* \u041f\u0435\u0440\u0435\u043d\u043e\u0441\u044b \u0432 \u0444\u0440\u0430\u0437\u0435\/\u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 *\/\r\n#er-phrase, #er-result b{\r\n  word-break:break-word; overflow-wrap:anywhere; hyphens:auto;\r\n}\r\n\r\n    <\/style>\r\n\r\n    <div id=\"er-wrap\">\r\n      <div id=\"er-phrase\" aria-live=\"polite\">\u2014<\/div>\r\n\r\n      <div class=\"er-toolbar\" style=\"display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin:12px 0 10px\">\r\n        <button id=\"er-listen\"  class=\"z-btn z-btn-primary\">\ud83d\udd0a Escuchar<\/button>\r\n        <button id=\"er-record\"  class=\"z-btn\" style=\"background:#f8c444;color:#222\">\ud83c\udfa4 Grabar<\/button>\r\n      <\/div>\r\n\r\n      <div id=\"er-result\" style=\"min-height:46px;margin:6px 0 8px\"><\/div>\r\n\r\n      <!-- \u0424\u043e\u043b\u0431\u044d\u043a, \u0435\u0441\u043b\u0438 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435 \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e -->\r\n      <div id=\"er-fallback\" style=\"display:none;margin-top:8px\">\r\n        <div style=\"font-size:.98em;color:#6b7280;margin-bottom:6px\">Escribe lo que dijiste:<\/div>\r\n        <div style=\"display:flex;gap:8px;justify-content:center;flex-wrap:wrap\">\r\n          <input id=\"er-input\" type=\"text\" inputmode=\"latin-name\" autocomplete=\"off\"\r\n                 style=\"padding:10px 12px;border:2px solid #cbd5e1;border-radius:12px;font-size:1.02em\">\r\n          <button id=\"er-check\" class=\"z-btn z-btn-primary\">Comprobar<\/button>\r\n        <\/div>\r\n      <\/div>\r\n    <\/div>\r\n  `;\r\n\r\n    \/\/ 1) \u0414\u041e\u0411\u0410\u0412\u042c \u0440\u044f\u0434\u043e\u043c \u0441 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u043c\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f:\r\n\r\n  const wrap     = document.getElementById('er-wrap');\r\n  const elPhrase = document.getElementById('er-phrase');\r\n  const elResult = document.getElementById('er-result');\r\n  const elListen = document.getElementById('er-listen');\r\n  const elRecord = document.getElementById('er-record');\r\n  const elFallback = document.getElementById('er-fallback');\r\n\r\n  \/\/ \u0411\u0435\u0439\u0434\u0436 \"Frase x\/y\" \u0432 \u043f\u0430\u043d\u0435\u043b\u0438 \u0441\u043e \u0441\u0447\u0451\u0442\u043e\u043c\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let rondaBadge = scoreBox?.querySelector('[data-er-ronda]');\r\n  if (!rondaBadge && scoreBox){\r\n    rondaBadge = document.createElement('div');\r\n    rondaBadge.setAttribute('data-er-ronda','');\r\n    rondaBadge.style.cssText='margin-top:6px;font-size:.95em;color:#334';\r\n    scoreBox.appendChild(rondaBadge);\r\n  }\r\n  const setRondaBadge = (i, tot) => { if (rondaBadge) rondaBadge.innerHTML = `Frase: <b>${i+1}\/${tot}<\/b>`; };\r\n\r\n  \/\/ \u0410\u043d\u0442\u0438 \u0434\u0432\u043e\u0439\u043d\u043e\u0433\u043e \u0442\u0430\u043f\u0430 (iOS)\r\n  let lastTouch = 0;\r\n  game.coreEl.addEventListener('touchend', (e)=>{\r\n    const now = Date.now();\r\n    if (now - lastTouch <= 350) e.preventDefault();\r\n    lastTouch = now;\r\n  }, {passive:false});\r\n\r\n  \/\/ Speech\r\n  const SR = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n  let idx = 0, rec = null, listening = false, cleanupMo = null;\r\n  const rootEl = game.coreEl.closest('.zg-root');\r\n\r\n  function norm(s){\r\n    return (s||'')\r\n      .toLowerCase()\r\n      .normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'')\r\n      .replace(\/[\u00bf?\u00a1!.,:;\"']\/g,'')\r\n      .replace(\/\\s+\/g,' ')\r\n      .trim();\r\n  }\r\n\r\n    \/\/ 2) \u041e\u0411\u041d\u041e\u0412\u0418 setPhrase(), \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0443\u043d\u0434 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u043b\u0441\u044f:\r\n    function setPhrase(){\r\n      elPhrase.textContent = `\u00ab${PHRASES[idx]}\u00bb`;\r\n      elResult.innerHTML = '';\r\n      hideFallback();\r\n    \r\n      elListen.disabled = false;\r\n      elRecord.disabled = false;\r\n    \r\n      game.showContinue(false);\r\n      game.setMessage('Pulsa <b>\ud83d\udd0a Escuchar<\/b> y luego <b>\ud83c\udfa4 Grabar<\/b>. Si no te oye, revisa el permiso del micr\u00f3fono.');\r\n    }\r\n    \r\nconst wait = (ms)=> new Promise(r=>setTimeout(r, ms));\r\n\r\nasync function goNext(delay=1200){\r\n  await wait(delay);\r\n  if (idx + 1 < PHRASES.length){\r\n    idx++;\r\n    setRondaBadge?.(idx, PHRASES.length);  \/\/ \u0435\u0441\u043b\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0448\u044c \u0431\u0435\u0439\u0434\u0436 \u0444\u0440\u0430\u0437\r\n    setPhrase();\r\n  }else{\r\n    await finishGame();\r\n  }\r\n}\r\n\r\nasync function finishGame(){\r\n  try{\r\n    const recEl = document.querySelector('[data-zg-record]');\r\n    const prev  = Number(recEl?.textContent || 0);\r\n    if (game.score > prev){\r\n      await zSaveProgress(usuarioId, 7, game.score, game.elapsed);\r\n      if (recEl) recEl.textContent = game.score;\r\n      game.setMessage('<span style=\"color:#2b7e37;font-weight:700\">\u00a1Nuevo r\u00e9cord!<\/span>');\r\n    }else{\r\n      await zSaveProgress(usuarioId, 7, prev, game.elapsed);\r\n    }\r\n    const total = await zLoadTotalTime(usuarioId, 7);\r\n    const totalEl = document.querySelector('[data-zg-total]');\r\n    if (totalEl) totalEl.textContent = zFmtMMSS(total);\r\n  }catch(_){}\r\n  cleanup();\r\n  await game.endGame({save:true});\r\n}\r\n\r\n\r\n\r\n  function ttsSpeak(){\r\n    if (!('speechSynthesis' in window)) return;\r\n    try{\r\n      window.speechSynthesis.cancel();\r\n      const u = new SpeechSynthesisUtterance(PHRASES[idx]);\r\n      u.lang = 'es-ES'; u.rate = 0.95;\r\n      window.speechSynthesis.speak(u);\r\n    }catch(_){}\r\n  }\r\n\r\n  function startRecognize(){\r\n    if (!SR){ showFallback('Tu navegador no soporta reconocimiento de voz.'); return; }\r\n    stopRecognize();\r\n    rec = new SR();\r\n    rec.lang = 'es-ES';\r\n    rec.interimResults = false;\r\n    rec.maxAlternatives = 1;\r\n    listening = true;\r\n    game.setMessage('\ud83c\udfa4 Escuchando\u2026 habla ahora.');\r\n    elResult.innerHTML = '<span style=\"color:#0ea5e9\">Escuchando\u2026<\/span>';\r\n\r\n    rec.onresult = (e)=>{\r\n      if (!listening) return;\r\n      const said = e.results[0][0].transcript || '';\r\n      verifyAnswer(said);\r\n    };\r\n    rec.onerror = ()=>{ showFallback('No se pudo reconocer. Puedes escribir la frase:'); };\r\n    rec.onend = ()=>{ listening = false; };\r\n    try{ rec.start(); }catch(_){ showFallback('No se pudo iniciar el micr\u00f3fono.'); }\r\n  }\r\n\r\n  function stopRecognize(){ listening=false; try{ rec && rec.abort(); }catch(_){ } rec=null; }\r\n\r\n\r\nfunction showFallback(msg){\r\n  elFallback.style.display = '';\r\n  elResult.innerHTML = `<span style=\"color:#c33\">${msg||''}<\/span>`;\r\n\r\n  const btn = game.coreEl.querySelector('#er-check');\r\n  const inp = game.coreEl.querySelector('#er-input');\r\n\r\n  if (btn && inp){\r\n    btn.disabled = false;\r\n    inp.disabled = false;\r\n\r\n    btn.onclick = () => verifyAnswer(inp.value || '', { mode:'fallback' });\r\n    inp.onkeydown = (e)=>{ if(e.key==='Enter') btn.click(); };\r\n    setTimeout(()=> inp.focus(), 0);\r\n  }\r\n}\r\n\r\n  function hideFallback(){ elFallback.style.display = 'none'; }\r\n\r\nfunction verifyAnswer(saidRaw, opts = {}){\r\n  const esperado = norm(PHRASES[idx]);\r\n  const recibido = norm(saidRaw);\r\n  const ok = (esperado === recibido);\r\n\r\n  if (ok){\r\n    game.addScore(10);\r\n    elResult.innerHTML = `\r\n      T\u00fa has dicho: <b style=\"color:#14532d\">${saidRaw}<\/b><br>\r\n      <span style=\"color:#298d34;font-weight:700\">\u00a1Correcto! (+10)<\/span>\r\n    `;\r\n    game.setMessage('\u00a1Bien hecho! Siguiente frase\u2026');\r\n    \/\/ \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u044b\u0435 \u043a\u043d\u043e\u043f\u043a\u0438 \u0438 \u0432\u0432\u043e\u0434, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u00ab\u0434\u0432\u043e\u0438\u043b\u0438\u00bb \u043f\u0435\u0440\u0435\u0445\u043e\u0434\r\n    elListen.disabled = true;\r\n    elRecord.disabled = true;\r\n    const inp = game.coreEl.querySelector('#er-input'); if (inp) inp.disabled = true;\r\n    const btn = game.coreEl.querySelector('#er-check'); if (btn) btn.disabled = true;\r\n    goNext(1100);                    \/\/ \u2b05\ufe0f \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0447\u0435\u0440\u0435\u0437 ~1.1s\r\n  } else {\r\n    elResult.innerHTML = `\r\n      T\u00fa has dicho: <b style=\"color:#7f1d1d\">${saidRaw}<\/b><br>\r\n      <span style=\"color:#c33;font-weight:700\">\u00a1Int\u00e9ntalo otra vez!<\/span>\r\n      <div style=\"font-size:.95em;color:#334155;margin-top:6px\">Frase esperada: \u00ab${PHRASES[idx]}\u00bb<\/div>\r\n    `;\r\n    \/\/ \u0433\u043e\u043b\u043e\u0441\u043e\u043c \u2014 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c; \u0432 fallback \u2014 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u0432\u0432\u0435\u0441\u0442\u0438 \u0441\u043d\u043e\u0432\u0430 \u0438 volver a comprobar\r\n    game.setMessage('Int\u00e9ntalo otra vez o pulsa Escuchar.');\r\n  }\r\n}\r\n\r\n\r\n  function cleanup(){\r\n    stopRecognize();\r\n    try{ window.speechSynthesis && window.speechSynthesis.cancel(); }catch(_){}\r\n    if (cleanupMo){ cleanupMo.disconnect(); cleanupMo=null; }\r\n  }\r\n\r\n  \/\/ \u041a\u043d\u043e\u043f\u043a\u0438\r\n  elListen.onclick = ()=> ttsSpeak();\r\n  elRecord.onclick = ()=> startRecognize();\r\n\r\n  \/\/ \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0441\u0438\u043d\u0442\u0435\u0437\u0430\/\u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435\r\n  cleanupMo = new MutationObserver(()=>{ if (!document.body.contains(game.coreEl)) cleanup(); });\r\n  cleanupMo.observe(document.body, { childList:true, subtree:true });\r\n\r\n  \/\/ HUD\r\n  game.onStart(async ()=>{\r\n    \/\/ \u0441\u043f\u0440\u044f\u0442\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e \u0432 \u0441\u0442\u0430\u0442\u0438\u043a\u0435 + \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u044f\u0434\u0440\u043e\r\n    if (rootEl) rootEl.classList.add('er-hide-instr');\r\n    wrap.style.display = 'block';\r\n\r\n    idx = 0;\r\n    game.setScore(0);\r\n    game.showStart(false);\r\n    game.showContinue(false);\r\n    game.startTimer();\r\n    setPhrase();\r\n  });\r\n\r\n  game.onContinue(async ()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-2d790da elementor-widget elementor-widget-html\" data-id=\"2d790da\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_SigueLinea(){\r\n  const game = crearMarcoJuego({ gameId: 8, titulo: 'Sigue la l\u00ednea', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u2014\u2014 SFX helpers \u2014\u2014\r\n  const rand=(a,b)=>a+Math.random()*(b-a);\r\n  const tone=(f=660,ms=120,type='sine',vol=.2)=>{try{if(window.SFX?.tone){SFX.tone(f,ms,type,vol);return;}const AC=window.AudioContext||window.webkitAudioContext;if(!AC)return;tone.ctx=tone.ctx||new AC();const c=tone.ctx,o=c.createOscillator(),g=c.createGain();o.type=type;o.frequency.value=f;g.gain.value=vol;o.connect(g);g.connect(c.destination);o.start();setTimeout(()=>{try{o.stop();o.disconnect();g.disconnect();}catch(_){}} ,ms);}catch(_){}}; \r\n  const sfxClick = ()=>{ if (SFX?.click) SFX.click(); else tone(780,70,'square',.14); };\r\n  const sfxStart = ()=>{ if (SFX?.whoosh) SFX.whoosh(); else tone(520,110,'sawtooth',.16); };\r\n  const sfxGlide = ()=> tone(rand(900,1150),40,'triangle',.06);\r\n  const sfxBuzz  = ()=>{ if (SFX?.buzz) SFX.buzz(); else { tone(220,180,'sawtooth',.22); setTimeout(()=>tone(160,200,'sawtooth',.22),110);} };\r\n  const sfxWin   = ()=>{ if (SFX?.win)  SFX.win(); else { tone(660,120,'sine',.18); setTimeout(()=>tone(880,120,'sine',.18),110); setTimeout(()=>tone(1046,200,'sine',.22),240);} };\r\n  const sfxTimesUp=()=>{ if (SFX?.timesup) SFX.timesup(); else { tone(200,180,'triangle',.22); setTimeout(()=>tone(160,200,'triangle',.22),110);} };\r\n  const sfxTick  = ()=>{ if (SFX?.tick) SFX.tick(); else tone(880,55,'square',.12); };\r\n  const stopAllAudio=()=>{ try{ SFX?.stopAll?.(); SFX?.musicStop?.(); SFX?.loopStop?.('*'); }catch(_){} };\r\n  const wireExitStop=()=>{ const root=game.coreEl.closest('.zg-root'); const bind=()=>root?.querySelectorAll('.zg-exit,.zg-close,[data-zg-exit]')?.forEach(b=>b.addEventListener('click',stopAllAudio,{once:true})); bind(); const mo=new MutationObserver(()=>bind()); if(root) mo.observe(root,{childList:true,subtree:true}); window.addEventListener('pagehide',stopAllAudio,{once:true}); document.addEventListener('visibilitychange',()=>{if(document.hidden)stopAllAudio();},{passive:true}); };\r\n\r\n  \/\/ \u2014\u2014 \u0443\u0440\u043e\u0432\u043d\u0438 \u2014\u2014\r\n  const LEVELS = [\r\n    { svg:`<polyline points=\"60,350 460,350\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\" \/>`, start:[60,350], finish:[460,350], tiempo:10, tolerance:24 },\r\n    { svg:`<polyline points=\"70,370 260,150 450,370\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\" \/>`, start:[70,370], finish:[450,370], tiempo:12, tolerance:22 },\r\n    { svg:`<polyline points=\"70,390 150,220 230,390 310,220 390,390 470,220\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\" \/>`, start:[70,390], finish:[470,220], tiempo:14, tolerance:18 },\r\n    { svg:`<path d=\"M70,320 Q170,220 270,320 T470,320\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\"\/>`, start:[70,320], finish:[470,320], tiempo:16, tolerance:16 },\r\n    { svg:`<path d=\"M410,200 A130,130 0 1,0 410,350 L330,350\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\"\/>`, start:[410,200], finish:[330,350], tiempo:19, tolerance:16 },\r\n    { svg:`<path d=\"M120,340 Q260,210 400,340 L360,350 Q260,260 160,350 L200,360 Q260,310 320,360\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\"\/>`, start:[120,340], finish:[320,360], tiempo:22, tolerance:14 },\r\n    { svg:`<path d=\"M400,200 Q260,70 160,220 Q70,350 260,390 Q420,380 340,290 Q270,210 200,280 Q170,330 260,350\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\"\/>`, start:[400,200], finish:[260,350], tiempo:23, tolerance:15 },\r\n    { svg:`<path d=\"M120,350 Q70,270 120,210 Q180,140 240,220 Q300,310 170,350 L300,350 Q350,340 370,300 Q390,260 360,240 Q340,220 320,260 Q300,300 350,320\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\"\/>`, start:[120,350], finish:[350,320], tiempo:25, tolerance:14 },\r\n    { svg:`<polyline points=\"100,370 100,170 400,170 400,320 200,320 200,220 300,220 300,370\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\" \/>`, start:[100,370], finish:[300,370], tiempo:28, tolerance:13 },\r\n    { svg:`<polyline points=\"80,380 80,120 440,120 440,380 160,380 160,160 360,160 360,340 240,340 240,200 320,200 320,300\" stroke=\"#6ad\" stroke-width=\"10\" fill=\"none\" \/>`, start:[80,380], finish:[320,300], tiempo:36, tolerance:12 }\r\n  ];\r\n\r\n  \/\/ \u2014\u2014 \u044f\u0434\u0440\u043e + \u0441\u0442\u0438\u043b\u0438 (\u0440\u0435\u0437\u0438\u043d\u043e\u0432\u0430\u044f \u0448\u0438\u0440\u0438\u043d\u0430, \u0431\u0435\u0437 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043a\u0440\u043e\u043b\u043b\u0430) \u2014\u2014\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .zg-core .zg-msg{\r\n        width:100% !important; max-width:none !important;\r\n        margin:0 0 12px 0 !important; box-sizing:border-box;\r\n        overflow-wrap:anywhere; min-height:54px;\r\n      }\r\n      .zg-root,.zg-core-host,[data-zg-core-host]{ overflow-x:hidden; }\r\n      \/* \u0442\u0430\u0439\u043c\u0435\u0440 \u043d\u0430\u0434 \u043f\u043e\u043b\u0435\u043c *\/\r\n      #sl-meta{ text-align:center;font-weight:800;margin:4px 0 8px; }\r\n      \/* \u0438\u0433\u0440\u043e\u0432\u0430\u044f \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u2014 \u0440\u0435\u0437\u0438\u043d\u043e\u0432\u0430\u044f *\/\r\n      #sl-wrap{ width:100%; max-width:640px; margin:0 auto; display:none; }\r\n      #sl-area{\r\n        position:relative; width:100%; max-width:640px; aspect-ratio:520\/420;\r\n        background:#fff; border-radius:24px; box-shadow:0 3px 18px rgba(0,0,0,.08);\r\n        overflow:hidden; display:flex; align-items:center; justify-content:center;\r\n        overscroll-behavior:contain; touch-action:none; user-select:none; -webkit-user-select:none;\r\n      }\r\n      #sl-overlay{ position:absolute; inset:0; display:none; align-items:center; justify-content:center; background:rgba(255,255,255,.75); }\r\n      .sl-bubble{ background:#ffffffdd; border-radius:16px; padding:12px 28px; font-size:2.1em; font-weight:900; color:#0b3a47; box-shadow:0 2px 18px #0001; }\r\n      \/* \u0431\u0435\u0439\u0434\u0436 \u0443\u0440\u043e\u0432\u043d\u044f \u0432 HUD *\/\r\n      .zg-score .nivel-badge{\r\n        display:inline-flex; align-items:center; gap:6px;\r\n        background:#cffafe; border:1px solid #a5f3fc; color:#083344;\r\n        padding:4px 8px; border-radius:10px; font-weight:800; font-size:.95em;\r\n      }\r\n    <\/style>\r\n\r\n    <div id=\"sl-wrap\">\r\n      <div id=\"sl-meta\"><span id=\"sl-chrono\"><\/span><\/div>\r\n      <div id=\"sl-area\">\r\n        <svg id=\"sl-svg\" viewBox=\"0 0 520 420\" style=\"width:100%;height:100%;touch-action:none;user-select:none;-webkit-user-select:none\"><\/svg>\r\n        <div id=\"sl-overlay\"><\/div>\r\n      <\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  \/\/ \u043a\u0440\u0430\u0442\u043a\u0430\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\r\n  game.setMessage(`Pulsa en <b>START<\/b> y sigue la l\u00ednea hasta <b>FINISH<\/b> sin salirte.<br>Si te sales o sueltas, repites el nivel.`);\r\n\r\n  const wrap = document.getElementById('sl-wrap');\r\n  const chronoEl = document.getElementById('sl-chrono');\r\n  const svg  = document.getElementById('sl-svg');\r\n  const overlay = document.getElementById('sl-overlay');\r\n\r\n  \/\/ HUD: mute + \u0431\u0435\u0439\u0434\u0436 \"Nivel\"\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  if (window.SFX && scoreBox && typeof SFX.injectToggle==='function') SFX.injectToggle(scoreBox);\r\n  let lvlBadge = scoreBox?.querySelector('.nivel-badge');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.className = 'nivel-badge';\r\n    lvlBadge.innerHTML = 'N: <b>1\/'+LEVELS.length+'<\/b>';\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setLvlBadge = (n)=>{ if (lvlBadge) lvlBadge.innerHTML = `N: <b>${n}\/${LEVELS.length}<\/b>`; };\r\n\r\n  \/\/ anti\u2013double-tap zoom (iOS)\r\n  let _lastTouch = 0;\r\n  svg.addEventListener('touchend', (e)=>{ const now=Date.now(); if(now-_lastTouch<350) e.preventDefault(); _lastTouch=now; }, {passive:false});\r\n\r\n  \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\r\n  let nivel = 1, dragging=false, won=false, countdownId=null, lastStepSound=0;\r\n  let userPts=[];\r\n\r\n  const START_R=18, FIN_R=22;\r\n  const d2=(a,b)=>{const dx=a.x-b.x,dy=a.y-b.y;return dx*dx+dy*dy;};\r\n  const clamp01=t=>Math.max(0,Math.min(1,t));\r\n\r\n  function svgPointFromEvent(e){\r\n    const pt = svg.createSVGPoint();\r\n    if (e.touches && e.touches[0]){ pt.x=e.touches[0].clientX; pt.y=e.touches[0].clientY; }\r\n    else { pt.x=e.clientX; pt.y=e.clientY; }\r\n    const m = svg.getScreenCTM(); return m ? pt.matrixTransform(m.inverse()) : {x:0,y:0};\r\n  }\r\n  function segDist(p,a,b){\r\n    const vx=b.x-a.x, vy=b.y-a.y, wx=p.x-a.x, wy=p.y-a.y;\r\n    const t=clamp01((wx*vx+wy*vy)\/((vx*vx+vy*vy)||1));\r\n    const proj={x:a.x+t*vx,y:a.y+t*vy};\r\n    return Math.hypot(p.x-proj.x,p.y-proj.y);\r\n  }\r\n  function nearAnyPath(p,tol){\r\n    const pls=svg.querySelectorAll('polyline:not(#sl-user)'); \r\n    for (const pl of pls){\r\n      const list=pl.points; if(!list||list.length<2) continue;\r\n      for(let i=0;i<list.length-1;i++){\r\n        const a=list.getItem(i), b=list.getItem(i+1);\r\n        if (segDist(p,{x:a.x,y:a.y},{x:b.x,y:b.y})<=tol) return true;\r\n      }\r\n    }\r\n    const paths=svg.querySelectorAll('path');\r\n    for (const path of paths){\r\n      try{\r\n        const len=path.getTotalLength(), steps=Math.max(80,Math.min(260,Math.floor(len\/2.8)));\r\n        let prev=path.getPointAtLength(0);\r\n        for(let i=1;i<=steps;i++){\r\n          const pt=path.getPointAtLength((i\/steps)*len);\r\n          if (segDist(p,{x:prev.x,y:prev.y},{x:pt.x,y:pt.y})<=tol) return true;\r\n          prev=pt;\r\n        }\r\n      }catch(_){}\r\n    }\r\n    return false;\r\n  }\r\n\r\n  function showFail(msg, retry){\r\n    overlay.style.display='flex';\r\n    overlay.style.pointerEvents='auto';\r\n    overlay.innerHTML = `\r\n      <div style=\"background:#fff;border-radius:16px;box-shadow:0 6px 28px rgba(0,0,0,.18);padding:18px 22px;text-align:center;\">\r\n        <div style=\"margin-bottom:12px;font-weight:700;\">${msg}<\/div>\r\n        <button class=\"z-btn z-btn-primary\" id=\"sl-ov-btn\">Reintentar nivel<\/button>\r\n      <\/div>`;\r\n    overlay.querySelector('#sl-ov-btn').onclick = ()=>{ sfxClick(); overlay.style.display='none'; overlay.innerHTML=''; retry(); };\r\n  }\r\n\r\n  async function countdown(sec=3){\r\n    overlay.style.pointerEvents='none';\r\n    overlay.style.display='flex';\r\n    overlay.innerHTML = `<div class=\"sl-bubble\">${sec}<\/div>`;\r\n    for(let t=sec; t>=1; t--){\r\n      overlay.firstChild.textContent = t;\r\n      sfxTick();\r\n      \/\/ \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0434\u043b\u044f \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043a\u0430\u0434\u0440\u0430 \u043d\u0435 \u043d\u0443\u0436\u043d\u0430\r\n      await new Promise(r=>setTimeout(r,1000));\r\n    }\r\n    overlay.style.display='none';\r\n    overlay.innerHTML='';\r\n    overlay.style.pointerEvents='auto';\r\n  }\r\n\r\n  function drawSvgForLevel(L){\r\n    svg.innerHTML = `\r\n      ${L.svg}\r\n      <circle id=\"sl-start\"  cx=\"${L.start[0]}\"  cy=\"${L.start[1]}\"  r=\"${START_R}\" fill=\"#48e37a\" stroke=\"#197a3b\" stroke-width=\"3\"\/>\r\n      <text   x=\"${L.start[0]}\" y=\"${L.start[1]+5}\" text-anchor=\"middle\" font-size=\"13\" fill=\"#194\" font-weight=\"bold\">START<\/text>\r\n      <circle id=\"sl-finish\" cx=\"${L.finish[0]}\" cy=\"${L.finish[1]}\" r=\"${FIN_R}\"  fill=\"#f86a6a\" stroke=\"#b22121\" stroke-width=\"3\"\/>\r\n      <text   x=\"${L.finish[0]}\" y=\"${L.finish[1]+5}\" text-anchor=\"middle\" font-size=\"13\" fill=\"#b22\" font-weight=\"bold\">FINISH<\/text>\r\n      <polyline id=\"sl-user\" points=\"\" stroke=\"#f6a\" stroke-width=\"5\" fill=\"none\" stroke-linecap=\"round\"\/>\r\n    `;\r\n  }\r\n\r\n  async function drawLevel(){\r\n    clearInterval(countdownId);\r\n    dragging=false; won=false; userPts=[]; lastStepSound=0;\r\n\r\n    const L = LEVELS[nivel-1];\r\n    setLvlBadge(nivel);\r\n    drawSvgForLevel(L);\r\n    game.setMessage('Pulsa en START y sigue la l\u00ednea hasta FINISH.');\r\n\r\n    \/\/ 3-\u0441\u0435\u043a. \u043e\u0442\u0441\u0447\u0451\u0442 \u043f\u0435\u0440\u0435\u0434 \u043d\u0430\u0447\u0430\u043b\u043e\u043c\r\n    await countdown(3);\r\n\r\n    const anyStroke = svg.querySelector('polyline, path');\r\n    const sw = parseFloat(anyStroke?.getAttribute('stroke-width') || '10');\r\n    const tol = Math.max((sw*0.45), (L.tolerance ?? 8));\r\n    const user = svg.querySelector('#sl-user');\r\n    const updateUser = ()=> user.setAttribute('points', userPts.map(p=>`${p.x},${p.y}`).join(' '));\r\n\r\n    let offRun=0; const STEP=6;\r\n    function checkSegment(prev,curr){\r\n      const dx=curr.x-prev.x, dy=curr.y-prev.y, dist=Math.hypot(dx,dy);\r\n      const steps=Math.max(1,Math.ceil(dist\/STEP));\r\n      for(let i=1;i<=steps;i++){\r\n        const t=i\/steps, q={x:prev.x+dx*t,y:prev.y+dy*t};\r\n        if (!nearAnyPath(q,tol)){ if(++offRun>=1) return false; } else offRun=0;\r\n      }\r\n      return true;\r\n    }\r\n\r\n    function fail(msg){\r\n      dragging=false; sfxBuzz();\r\n      game.setMessage(`<span style=\"color:#c33;font-weight:700\">${msg}<\/span>`);\r\n      showFail(msg, ()=>drawLevel());\r\n    }\r\n\r\n    function onPointerDown(e){\r\n      const p = svgPointFromEvent(e);\r\n      if (d2(p,{x:L.start[0],y:L.start[1]}) <= (START_R*START_R)){\r\n        dragging=true; won=false; offRun=0; userPts=[p]; updateUser();\r\n        sfxStart(); game.setMessage('\u00a1Sigue la l\u00ednea!');\r\n        if (e.pointerId && svg.setPointerCapture) svg.setPointerCapture(e.pointerId);\r\n      }\r\n      e.preventDefault?.();\r\n    }\r\n    function onPointerMove(e){\r\n      if (!dragging) return;\r\n      const p = svgPointFromEvent(e);\r\n      const prev = userPts[userPts.length-1] || p;\r\n      if (!checkSegment(prev,p)) return fail('\u00a1Te saliste de la l\u00ednea!');\r\n      userPts.push(p); updateUser();\r\n      const now=performance.now(); if(now-lastStepSound>110){ lastStepSound=now; sfxGlide(); }\r\n      if (d2(p,{x:L.finish[0],y:L.finish[1]}) <= (FIN_R*FIN_R)){ dragging=false; won=true; afterLevel(); }\r\n      e.preventDefault?.();\r\n    }\r\n    function onPointerUp(){ if (dragging && !won) return fail('\u00a1Has soltado fuera de la l\u00ednea!'); }\r\n\r\n    svg.onpointerdown = onPointerDown;\r\n    svg.onpointermove = onPointerMove;\r\n    svg.onpointerup   = onPointerUp;\r\n    svg.onpointercancel = onPointerUp;\r\n\r\n    \/\/ \u0442\u0430\u0439\u043c\u0435\u0440 \u0443\u0440\u043e\u0432\u043d\u044f\r\n    let t=L.tiempo;\r\n    chronoEl.innerHTML = `\u23f0 <b id=\"sl-t\">${t}<\/b>s`;\r\n    countdownId = setInterval(()=>{\r\n      t--;\r\n      const el=document.getElementById('sl-t'); if(el) el.textContent=t;\r\n      if (t<=5) sfxTick();\r\n      if (t<=0){ clearInterval(countdownId); sfxTimesUp(); fail('\u00a1Se acab\u00f3 el tiempo!'); }\r\n    },1000);\r\n  }\r\n\r\n  async function afterLevel(){\r\n    clearInterval(countdownId);\r\n    sfxWin();\r\n    game.addScore(10);\r\n    if (nivel >= LEVELS.length){\r\n      \/\/ \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c\r\n      try{\r\n        const recEl=document.querySelector('[data-zg-record]');\r\n        const prev=Number(recEl?.textContent||0);\r\n        if (game.score>prev){ await zSaveProgress(usuarioId,8,game.score,game.elapsed); if(recEl) recEl.textContent=game.score; }\r\n        else { await zSaveProgress(usuarioId,8,prev,game.elapsed); }\r\n      }catch(_){}\r\n      stopAllAudio();\r\n      await game.endGame({save:true});\r\n      return;\r\n    }\r\n    \/\/ \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0441 3-\u0441\u0435\u043a. \u043e\u0442\u0441\u0447\u0451\u0442\u043e\u043c\r\n    game.setMessage('<span style=\"color:#298d34;font-weight:700\">\u00a1Nivel superado! (+10)<\/span> Prep\u00e1rate\u2026');\r\n    await new Promise(r=>setTimeout(r,600));\r\n    nivel++; await drawLevel();\r\n  }\r\n\r\n  \/\/ \u2014\u2014 HUD \u2014\u2014\r\n  game.onStart(async ()=>{\r\n    if (window.SFX?.unlock) SFX.unlock();\r\n    wireExitStop();\r\n\r\n    wrap.style.display='block';\r\n    nivel=1; game.setScore(0);\r\n    game.showStart(false); game.showContinue(false);\r\n    game.startTimer();\r\n    await drawLevel();\r\n  });\r\n\r\n  \/\/ \u00abContinuar\u00bb \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5c04a46 elementor-widget elementor-widget-html\" data-id=\"5c04a46\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_DedosRapidos(){\r\n  const game = crearMarcoJuego({ gameId: 9, titulo: 'Dedos r\u00e1pidos', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u2500\u2500 SFX \u2500\u2500\r\n  const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));\r\n  const map=(v,a1,a2,b1,b2)=> b1 + ((v-a1)*(b2-b1)\/((a2-a1)||1));\r\n  const tone=(f=660,ms=120,type='sine',vol=.2)=>{try{if(window.SFX?.tone){SFX.tone(f,ms,type,vol);return;}const AC=window.AudioContext||window.webkitAudioContext;if(!AC)return;tone.ctx=tone.ctx||new AC();const c=tone.ctx,o=c.createOscillator(),g=c.createGain();o.type=type;o.frequency.value=f;g.gain.value=vol;o.connect(g);g.connect(c.destination);o.start();setTimeout(()=>{try{o.stop();o.disconnect();g.disconnect();}catch(_){}} ,ms);}catch(_){}}; \r\n  const sfxClick = ()=>{ if (SFX?.click) SFX.click(); else tone(820,70,'square',.14); };\r\n  const sfxBeep  = (n)=>{ if (SFX?.beep)  SFX.beep();  else tone(500+n*130,110,'sine',.18); };\r\n  const sfxReady = ()=>{ if (SFX?.ready) SFX.ready(); else tone(980,120,'triangle',.18); };\r\n  const sfxSpawn = ()=>{ if (SFX?.pop)   SFX.pop();   else tone(740,80,'square',.16); };\r\n  const sfxTapVar=(ms)=>{ const f=clamp(map(ms,80,800,1300,380),320,1400); if(SFX?.tap) SFX.tap(ms); else tone(f,90,'triangle',.2); };\r\n  const sfxPerfect=()=>{ if (SFX?.sparkle) SFX.sparkle(); else { tone(880,90,'sine',.18); setTimeout(()=>tone(1175,100,'sine',.18),90); setTimeout(()=>tone(1400,140,'sine',.2),190);} };\r\n  const sfxLevelUp=()=>{ if (SFX?.win) SFX.win(); else { tone(660,120,'sine',.18); setTimeout(()=>tone(880,120,'sine',.18),120); setTimeout(()=>tone(1046,200,'sine',.22),240);} };\r\n  const sfxFinish = ()=>{ if (SFX?.finish) SFX.finish(); else { tone(523,140,'sine',.2); setTimeout(()=>tone(659,160,'sine',.2),140); setTimeout(()=>tone(784,220,'sine',.24),300);} };\r\n  const stopAllAudio=()=>{ try{ SFX?.stopAll?.(); SFX?.musicStop?.(); SFX?.loopStop?.('*'); }catch(_){ } };\r\n\r\n  const wireExitStop=()=>{ const root=game.coreEl.closest('.zg-root'); const bind=()=>root?.querySelectorAll('.zg-exit,.zg-close,[data-zg-exit]')?.forEach(b=>b.addEventListener('click',stopAllAudio,{once:true})); bind(); const mo=new MutationObserver(()=>bind()); if(root) mo.observe(root,{childList:true,subtree:true}); window.addEventListener('pagehide',stopAllAudio,{once:true}); document.addEventListener('visibilitychange',()=>{ if(document.hidden) stopAllAudio(); },{passive:true}); };\r\n\r\n  \/\/ \u2500\u2500 \u0443\u0440\u043e\u0432\u043d\u0438 \u2500\u2500\r\n  const LEVELS = [\r\n    { rounds:5, delay:[850,1600], btn:{w:168,h:62} },\r\n    { rounds:5, delay:[780,1500], btn:{w:162,h:60} },\r\n    { rounds:5, delay:[700,1400], btn:{w:156,h:58} },\r\n    { rounds:5, delay:[630,1200], btn:{w:150,h:56} },\r\n    { rounds:5, delay:[560,1100], btn:{w:144,h:54} },\r\n  ];\r\n\r\n  \/\/ \u2500\u2500 \u044f\u0434\u0440\u043e + \u0441\u0442\u0438\u043b\u0438: \u0440\u0435\u0437\u0438\u043d\u043e\u0432\u0430\u044f \u0448\u0438\u0440\u0438\u043d\u0430, \u0431\u0435\u0437 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043a\u0440\u043e\u043b\u043b\u0430 \u2500\u2500\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      \/* \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u2014 \u0432\u043e \u0432\u0441\u044e \u0448\u0438\u0440\u0438\u043d\u0443, \u0431\u0435\u0437 \"\u0446\u0435\u043d\u0442\u0440\u043e\u0432\u043a\u0438\" \u0438 \u0441 \u043f\u0435\u0440\u0435\u043d\u043e\u0441\u0430\u043c\u0438 *\/\r\n      .zg-core .zg-msg{\r\n        width:100% !important; max-width:none !important;\r\n        margin:0 0 12px 0 !important; box-sizing:border-box;\r\n        overflow-wrap:anywhere; min-height:54px;\r\n      }\r\n      .zg-root,.zg-core-host,[data-zg-core-host]{ overflow-x:hidden; }\r\n\r\n      \/* \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440 \u0438\u0433\u0440\u044b *\/\r\n      #dr-wrap{ width:100%; max-width:520px; margin:0 auto; }\r\n      #dr-area{\r\n        position:relative; width:100%; max-width:520px;\r\n        background:#fff; border-radius:24px; box-shadow:0 3px 18px rgba(0,0,0,.08);\r\n        overflow:hidden; margin:0 auto;\r\n        overscroll-behavior:contain; touch-action:none;\r\n        user-select:none; -webkit-user-select:none; display:none;\r\n      }\r\n      \/* \u0431\u0435\u0439\u0434\u0436 \u0443\u0440\u043e\u0432\u043d\u044f \u0432 HUD *\/\r\n      .zg-score .nivel-badge{\r\n        display:inline-flex; align-items:center; gap:6px;\r\n        background:#cffafe; border:1px solid #a5f3fc; color:#083344;\r\n        padding:4px 8px; border-radius:10px; font-weight:800; font-size:.95em;\r\n      }\r\n    <\/style>\r\n\r\n    <div id=\"dr-wrap\">\r\n      <div id=\"dr-area\">\r\n        <div id=\"dr-msg\" style=\"position:absolute;inset:0;display:flex;align-items:center;justify-content:center;\r\n             font-size:1.08em;color:#334155;padding:0 16px;text-align:center;\">\r\n          Toca el bot\u00f3n lo m\u00e1s r\u00e1pido posible cuando aparezca.\r\n        <\/div>\r\n        <div id=\"dr-count\" style=\"position:absolute;inset:0;display:none;align-items:center;justify-content:center;pointer-events:none;\">\r\n          <div id=\"dr-count-num\" style=\"background:#ffffffdd;border-radius:16px;padding:12px 28px;font-size:2.2em;font-weight:900;color:#0b3a47;box-shadow:0 2px 18px #0001\">3<\/div>\r\n        <\/div>\r\n      <\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\r\n  game.setMessage('Pulsa <b>Empezar<\/b>. Tras una cuenta atr\u00e1s (3\u20132\u20131) aparecer\u00e1 un bot\u00f3n: t\u00f3cala <b>una sola vez<\/b> lo m\u00e1s r\u00e1pido posible.');\r\n\r\n  const wrap  = game.coreEl.querySelector('#dr-wrap');\r\n  const area  = game.coreEl.querySelector('#dr-area');\r\n  const msgEl = game.coreEl.querySelector('#dr-msg');\r\n  const cntWrap = game.coreEl.querySelector('#dr-count');\r\n  const cntNum  = game.coreEl.querySelector('#dr-count-num');\r\n\r\n  \/\/ HUD: mute + \u0431\u0435\u0439\u0434\u0436 \u00abNivel\u00bb\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  if (window.SFX && scoreBox && typeof SFX.injectToggle==='function') SFX.injectToggle(scoreBox);\r\n  let lvlBadge = scoreBox?.querySelector('.nivel-badge');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.className = 'nivel-badge';\r\n    lvlBadge.innerHTML = 'N: <b>1\/'+LEVELS.length+'<\/b>';\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setLvlBadge = (n)=>{ if (lvlBadge) lvlBadge.innerHTML = `N: <b>${n}\/${LEVELS.length}<\/b>`; };\r\n\r\n  \/\/ \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0432\u044b\u0441\u043e\u0442\u0430 \u0431\u0435\u0437 \u0441\u043a\u0440\u043e\u043b\u043b\u0430\r\n  function fitArea(){\r\n    const head = 220; \/\/ \u0437\u0430\u043f\u0430\u0441 \u043f\u043e\u0434 \u0448\u0430\u043f\u043a\u0443\/HUD\r\n    const h = Math.max(260, Math.min(Math.round(wrap.clientWidth*1.05), window.innerHeight - head));\r\n    area.style.height = h + 'px';\r\n  }\r\n  fitArea();\r\n  window.addEventListener('resize', fitArea);\r\n  window.addEventListener('orientationchange', fitArea);\r\n\r\n  const blockScroll = e => e.preventDefault();\r\n\r\n  \/\/ \u2500\u2500 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \u2500\u2500\r\n  let nivel=1, round=0, times=[];\r\n  let buttonEl=null, showTO=null, startStamp=0;\r\n\r\n  const randInt=(a,b)=> Math.floor(Math.random()*(b-a+1))+a;\r\n  const pointsFromMs=(ms)=> 22 + Math.max(0, Math.round(170\/(1+ms\/360)));\r\n\r\n  function clearTimers(){ if (showTO){ clearTimeout(showTO); showTO=null; } }\r\n  function clearButton(){ if (buttonEl && buttonEl.parentNode) buttonEl.remove(); buttonEl=null; }\r\n\r\n  function toastPlus(x,y,pts){\r\n    const t=document.createElement('div');\r\n    t.textContent=`+${pts}`;\r\n    t.style.cssText=`position:absolute;left:${x}px;top:${y}px;transform:translate(-50%,-50%);font-weight:900;font-size:24px;color:#0f9d58;text-shadow:0 1px 2px #0001;pointer-events:none;opacity:1;z-index:5;transition:transform 1s ease, opacity 1s ease;`;\r\n    area.appendChild(t);\r\n    requestAnimationFrame(()=>{ t.style.transform='translate(-50%,-140%)'; t.style.opacity='0'; });\r\n    setTimeout(()=>t.remove(),1000);\r\n  }\r\n\r\n  const wait = ms => new Promise(r=>setTimeout(r,ms));\r\n\r\n  async function countdownStart(){\r\n    area.style.display='block';\r\n    msgEl.style.display='none';\r\n    cntWrap.style.display='flex';\r\n    for (let x=3;x>=1;x--){ cntNum.textContent=x; sfxBeep(4-x); await wait(800); }\r\n    cntNum.textContent='\u00a1Listo!'; sfxReady(); await wait(500);\r\n    cntWrap.style.display='none';\r\n    msgEl.style.display='flex';\r\n    msgEl.textContent='Prep\u00e1rate\u2026';\r\n  }\r\n\r\n  function nextRound(){\r\n    clearButton();\r\n    round++;\r\n    if (round > LEVELS[nivel-1].rounds){ afterLevel(); return; }\r\n    const [minD,maxD] = LEVELS[nivel-1].delay;\r\n    const delay = randInt(minD,maxD);\r\n    msgEl.style.display='flex'; msgEl.textContent='Prep\u00e1rate\u2026';\r\n    showTO = setTimeout(spawnButton, delay);\r\n  }\r\n\r\n  function spawnButton(){\r\n    msgEl.style.display='none';\r\n    sfxSpawn();\r\n\r\n    const {w,h} = LEVELS[nivel-1].btn;\r\n    const aw = area.clientWidth, ah = area.clientHeight;\r\n    const x = randInt(10, Math.max(10, aw - w - 10));\r\n    const y = randInt(10, Math.max(10, ah - h - 10));\r\n\r\n    const b=document.createElement('button');\r\n    b.type='button'; b.textContent='\u00a1Clic!';\r\n    b.style.cssText=`\r\n      position:absolute;left:${x}px;top:${y}px;width:${w}px;height:${h}px;\r\n      border:none;border-radius:16px;background:#f8c444;color:#111827;\r\n      font-weight:900;box-shadow:0 2px 10px rgba(0,0,0,.15);\r\n      cursor:pointer; touch-action:none; transition:transform .08s, filter .08s; z-index:4;\r\n    `;\r\n    area.appendChild(b); buttonEl=b;\r\n\r\n    document.body.addEventListener('touchmove', blockScroll, {passive:false});\r\n\r\n    let clicked=false;\r\n    b.style.pointerEvents='none'; requestAnimationFrame(()=>{ b.style.pointerEvents='auto'; });\r\n\r\n    startStamp=performance.now();\r\n\r\n    const handleTap=(clientX,clientY)=>{\r\n      if(clicked) return; clicked=true;\r\n      b.disabled=true; b.style.pointerEvents='none';\r\n\r\n      const ms=performance.now()-startStamp;\r\n      const pts=pointsFromMs(ms); times.push(ms); game.addScore(pts);\r\n\r\n      sfxTapVar(ms); if(ms<180) sfxPerfect();\r\n\r\n      b.style.filter='brightness(1.08)'; b.style.transform='scale(0.96)';\r\n      const rect=area.getBoundingClientRect();\r\n      toastPlus(clientX-rect.left, clientY-rect.top, pts);\r\n\r\n      game.setMessage(`Tiempo: <b>${Math.round(ms)} ms<\/b> \u00b7 +${pts} puntos`);\r\n      setTimeout(()=> nextRound(), 240);\r\n    };\r\n\r\n    b.addEventListener('pointerdown', ev=>{ ev.preventDefault(); handleTap(ev.clientX, ev.clientY); }, {passive:false, once:true});\r\n  }\r\n\r\n  async function saveProgress(){\r\n    try{\r\n      const recEl=document.querySelector('[data-zg-record]'); const prev=Number(recEl?.textContent||0);\r\n      if (game.score>prev){ await zSaveProgress(usuarioId,9,game.score,game.elapsed); if(recEl) recEl.textContent=game.score; }\r\n      else { await zSaveProgress(usuarioId,9,prev,game.elapsed); }\r\n      const total=await zLoadTotalTime(usuarioId,9);\r\n      const totalEl=document.querySelector('[data-zg-total]'); if(totalEl) totalEl.textContent=zFmtMMSS(total);\r\n    }catch(_){}\r\n  }\r\n\r\n  async function afterLevel(){\r\n    clearButton(); clearTimers();\r\n    document.body.removeEventListener('touchmove', blockScroll);\r\n    await saveProgress();\r\n\r\n    if (nivel >= LEVELS.length){\r\n      sfxFinish();\r\n      game.setMessage('<span style=\"color:#16a34a;font-weight:700;\">\u00a1Todos los niveles completados!<\/span>');\r\n      stopAllAudio();\r\n      await game.endGame({save:true});\r\n      return;\r\n    }\r\n    \/\/ \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0431\u0435\u0437 Continue\r\n    sfxLevelUp();\r\n    game.setMessage('<span style=\"color:#16a34a;font-weight:700;\">\u00a1Nivel superado!<\/span> Prep\u00e1rate\u2026');\r\n    await wait(600);\r\n    nivel++; setLvlBadge(nivel);\r\n    round=0;\r\n    await countdownStart();\r\n    nextRound();\r\n  }\r\n\r\n  \/\/ \u2500\u2500 HUD \u2500\u2500\r\n  game.onStart(async ()=>{\r\n    window.SFX?.unlock?.();\r\n    wireExitStop();\r\n\r\n    game.showStart(false);\r\n    game.showContinue(false);\r\n    game.setScore(0);\r\n    game.startTimer();\r\n\r\n    nivel=1; setLvlBadge(nivel);\r\n    round=0; times=[];\r\n    fitArea();\r\n    await countdownStart();\r\n    nextRound();\r\n  });\r\n\r\n  \/\/ Continue \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\r\n  game.onContinue(()=>{});\r\n  game.onExit?.(()=> { stopAllAudio(); document.body.removeEventListener('touchmove', blockScroll); });\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7942de4 elementor-widget elementor-widget-html\" data-id=\"7942de4\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Puzles(){\r\n  const game = crearMarcoJuego({ gameId: 10, titulo: 'Puzles', bg:'#e4f7fa' });\r\n\r\n  \/\/ ===== \u041f\u0443\u043b \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a =====\r\n  const LOCAL_IDS = [1015,1016,1025,1027,1041,1050,1069,1074,1084,1080,1081,1020,1003,1004,1024,1018,1011,1012,1101,1102,1103,1104,1105,1201,1202,1203,1204];\r\n  const RANDOM_SEEDS = Array.from({length:50}, (_,i)=>`seed-${1000+i}`);\r\n  const IMG_POOL = [\r\n    ...LOCAL_IDS.map(id=>({type:\"local\", id})),\r\n    ...RANDOM_SEEDS.map(seed=>({type:\"remote\", seed}))\r\n  ];\r\n  const HINT_COST = 10;\r\n  const LEVELS = [3,4,5,6,7,8];\r\n  const side = Math.min(520, Math.floor(window.innerWidth*0.96));\r\n  \/\/ \u2500\u2500 SFX: \u0431\u0438\u043f\u044b \u0441 \u0444\u043e\u043b\u043b\u0431\u044d\u043a\u043e\u043c \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\nconst tone = (f=660, ms=120, type='sine', vol=0.2)=>{\r\n  try{\r\n    if (window.SFX?.tone){ SFX.tone(f,ms,type,vol); return; }\r\n    const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return;\r\n    tone.ctx = tone.ctx || new AC();\r\n    const ctx=tone.ctx, o=ctx.createOscillator(), g=ctx.createGain();\r\n    o.type=type; o.frequency.value=f; g.gain.value=vol; o.connect(g); g.connect(ctx.destination);\r\n    o.start(); setTimeout(()=>{ try{o.stop();o.disconnect();g.disconnect();}catch(e){}; }, ms);\r\n  }catch(e){}\r\n};\r\nconst sfxBeep  = (n)=>{ if (SFX?.beep) SFX.beep(); else tone(520 + n*140, 110, 'sine', .22); }; \/\/ 3-2-1\r\nconst sfxReady = ()=>{ if (SFX?.ready) SFX.ready(); else tone(980, 130, 'triangle', .22); };     \/\/ \u00ab\u00a1Vamos!\u00bb\r\n\r\n\r\n  \/\/ ===== \u0420\u0430\u0437\u043c\u0435\u0442\u043a\u0430\r\n  game.coreEl.innerHTML = `\r\n  <style>\r\n    \/* \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u043e\u043b\u043e\u0441\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439, \u0443\u0431\u0438\u0440\u0430\u0435\u043c \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442. \u0441\u043a\u0440\u043e\u043b\u043b *\/\r\n    .pz-no-msg .zg-core .zg-msg{ display:none !important; }\r\n    .zg-root,.zg-core-host,[data-zg-core-host], .pz-root,.pz-container,.pz-wrap,.pz-actions{ overflow-x:hidden; }\r\n\r\n    .pz-root{ --pz-size:360px; }\r\n    .pz-container{ width:100%; max-width:520px; margin:0 auto; display:flex; flex-direction:column; align-items:center; }\r\n\r\n    \/* \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u0430\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f + \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f \u043a\u043d\u043e\u043f\u043a\u0430 Start *\/\r\n    .pz-instr{ width:100%; max-width:520px; color:#334155; font-size:.95em; line-height:1.35; margin:6px 0 10px; }\r\n    .pz-instr b{ font-weight:700; }\r\n    .pz-start{ display:inline-block; margin-top:8px; padding:10px 16px; border-radius:12px;\r\n               border:none; background:#f8c444; color:#111827; font-weight:900; box-shadow:0 2px 10px rgba(0,0,0,.15); }\r\n\r\n    .pz-wrap{ position:relative; width:var(--pz-size); height:var(--pz-size);\r\n              background:#fff; border-radius:16px; box-shadow:0 3px 18px rgba(0,0,0,.08);\r\n              overflow:hidden; overscroll-behavior:contain; user-select:none; display:none; } \/* <\u2014 \u0441\u043a\u0440\u044b\u0442\u043e \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430 *\/\r\n\r\n    #pz-count{ position:absolute; inset:0; display:none; align-items:center; justify-content:center;\r\n               background:rgba(255,255,255,.85); font-size:48px; font-weight:900; color:#0b3a47; border-radius:16px; }\r\n\r\n    .pz-grid{ position:absolute; inset:10px; display:grid; gap:1.5px; border-radius:10px; overflow:hidden; background:#cbd5e1; }\r\n\r\n    .pz-loader{ position:absolute; inset:0; display:none; align-items:center; justify-content:center;\r\n                background:rgba(255,255,255,.9); backdrop-filter:blur(2px); }\r\n    .pz-hint{ position:absolute; inset:10px; border-radius:10px; display:none; align-items:center; justify-content:center; }\r\n\r\n    \/* \u041a\u043d\u043e\u043f\u043a\u0438 \u0441\u043d\u0438\u0437\u0443 \u2014 \u0432\u0441\u0435\u0433\u0434\u0430 \u0432 \u043e\u0434\u0438\u043d \u0440\u044f\u0434 *\/\r\n    .pz-actions{ display:grid; grid-template-columns:repeat(3,1fr); gap:8px; margin-top:8px; width:100%; box-sizing:border-box; padding:0 4px; }\r\n    .pz-btn{ min-width:0; white-space:nowrap; text-overflow:ellipsis; overflow:hidden;\r\n             padding:8px 12px; border-radius:12px; border:2px solid #c8d5ff;\r\n             background:#eef3ff; color:#123; font-weight:800; cursor:pointer;\r\n             box-shadow:0 2px 6px #0001; transition:transform .08s, filter .08s; font-size:clamp(11px,2.7vw,15px); }\r\n             \r\n#pz-count{\r\n  position:absolute; inset:0; display:none; align-items:center; justify-content:center;\r\n  background:rgba(255,255,255,.85); font-size:48px; font-weight:900; color:#0b3a47; border-radius:16px;\r\n  z-index:50;                         \/* \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0435: \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u043f\u043e\u0432\u0435\u0440\u0445 \u0441\u0435\u0442\u043a\u0438\/\u043b\u043e\u0430\u0434\u0435\u0440\u0430\/\u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 *\/\r\n}\r\n.pz-grid   { position:absolute; inset:10px; display:grid; gap:1.5px; border-radius:10px; overflow:hidden; background:#cbd5e1; z-index:1; }\r\n.pz-loader { position:absolute; inset:0;   display:none; align-items:center; justify-content:center; background:rgba(255,255,255,.9); backdrop-filter:blur(2px); z-index:40; }\r\n.pz-hint   { position:absolute; inset:10px; display:none; align-items:center; justify-content:center; border-radius:10px; z-index:45; }\r\n             \r\n  <\/style>\r\n\r\n  <div class=\"pz-root\">\r\n    <div class=\"pz-container\" id=\"pz-container\">\r\n      <!-- \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u0430\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044f\u044f \u043a\u043d\u043e\u043f\u043a\u0430 \u0421\u0442\u0430\u0440\u0442 -->\r\n      <div class=\"pz-instr\" id=\"pz-instr\">\r\n        <b>C\u00f3mo jugar:<\/b> Reconstruye la imagen tocando <b>dos piezas<\/b> para intercambiarlas.\r\n        Tras <b>Empezar<\/b> habr\u00e1 una cuenta atr\u00e1s (3\u20132\u20131). Si una imagen no carga, se cambia autom\u00e1ticamente.\r\n        <b>Pista<\/b> muestra la imagen 2,5s (\u221210).<br>\r\n        <button id=\"pz-start\" class=\"pz-start\">Empezar<\/button>\r\n      <\/div>\r\n\r\n      <div id=\"pz-wrap\" class=\"pz-wrap\">\r\n        <div id=\"pz-count\"><\/div>\r\n        <div id=\"pz-grid\" class=\"pz-grid\"><\/div>\r\n\r\n        <div id=\"pz-loader\" class=\"pz-loader\">\r\n          <div style=\"width:80%;max-width:280px;\">\r\n            <div style=\"font-weight:700;text-align:center;margin-bottom:8px;\">Cargando imagen\u2026<\/div>\r\n            <div style=\"height:10px;border-radius:999px;background:#e5e7eb;overflow:hidden;\">\r\n              <div id=\"pz-loadbar\" style=\"height:100%;width:0%;background:#4fbfa5;transition:width .25s;\"><\/div>\r\n            <\/div>\r\n          <\/div>\r\n        <\/div>\r\n\r\n        <div id=\"pz-hint\" class=\"pz-hint\">\r\n          <div id=\"pz-hint-img\" style=\"position:absolute;inset:0;border-radius:10px;background-size:cover;background-position:center;box-shadow:0 0 0 2px #fff inset;\"><\/div>\r\n          <div style=\"position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);color:#fff;font-weight:800;text-shadow:0 1px 2px #0007;background:#0006;padding:6px 12px;border-radius:8px;\">\r\n            Vista previa (2,5 s)\r\n          <\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"pz-actions\" id=\"pz-actions\">\r\n        <button id=\"pz-restart\" class=\"pz-btn\" style=\"display:none;\">Reiniciar<\/button>\r\n        <button id=\"pz-newimg\"  class=\"pz-btn\" style=\"display:none;\">Otra imagen<\/button>\r\n        <button id=\"pz-hint-btn\" class=\"pz-btn\" style=\"display:none;\">Pista (\u221210)<\/button>\r\n      <\/div>\r\n    <\/div>\r\n  <\/div>\r\n  `;\r\n\r\n  \/\/ ===== \u042d\u043b\u0435\u043c\u0435\u043d\u0442\u044b\r\n  const pzRoot = game.coreEl.querySelector('.pz-root');\r\n  const pzCont = document.getElementById('pz-container');\r\n  const pzWrap = document.getElementById('pz-wrap');\r\n  const countEl= document.getElementById('pz-count');\r\n  const gridEl = document.getElementById('pz-grid');\r\n  const loaderEl=document.getElementById('pz-loader');\r\n  const loadbarEl=document.getElementById('pz-loadbar');\r\n  const hintOver=document.getElementById('pz-hint');\r\n  const hintImgEl=document.getElementById('pz-hint-img');\r\n  const btnRestart=document.getElementById('pz-restart');\r\n  const btnNewImg =document.getElementById('pz-newimg');\r\n  const btnHint   =document.getElementById('pz-hint-btn');\r\n  const instrEl   = document.getElementById('pz-instr');\r\n  const btnStart  = document.getElementById('pz-start');\r\n  \/\/ UI \/ game SFX (\u0441 \u0444\u043e\u043b\u043b\u0431\u044d\u043a\u0430\u043c\u0438)\r\nconst sfxClick   = ()=>{ if (SFX?.click) SFX.click(); else tone(760,80,'square',.16); };\r\nconst sfxPick    = ()=>{ if (SFX?.pop)   SFX.pop();   else tone(820,70,'triangle',.18); };\r\nconst sfxUnpick  = ()=>{ if (SFX?.ui)    SFX.ui();    else tone(420,60,'sine',.14); };\r\nconst sfxSwap    = ()=>{ if (SFX?.whoosh)SFX.whoosh();else tone(600,70,'sawtooth',.18); };\r\nconst sfxReward  = ()=>{ if (SFX?.coin)  SFX.coin();  else { tone(1100,80,'triangle',.2); setTimeout(()=>tone(1400,90,'triangle',.2),80); } };\r\nconst sfxSolved  = ()=>{ if (SFX?.win)   SFX.win();   else { tone(660,120,'sine',.2); setTimeout(()=>tone(880,120,'sine',.2),110); setTimeout(()=>tone(1046,180,'sine',.24),240); } };\r\nconst sfxHint    = ()=>{ if (SFX?.sparkle)SFX.sparkle(); else tone(980,110,'triangle',.2); };\r\n\r\nfunction pulse(el){ el.style.transform='scale(0.97)'; setTimeout(()=>{ el.style.transform=''; }, 110); }\r\n\r\n\/\/ \u041a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u0442\u043e\u0441\u0442 \u0432 \u0446\u0435\u043d\u0442\u0440\u0435 \u043f\u043e\u043b\u044f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 overlay #pz-count\r\nfunction toastCenter(html, ms=700){\r\n  countEl.innerHTML = `<div style=\"\r\n    background:#ffffffee; border-radius:14px; padding:10px 16px;\r\n    font-weight:900; color:#0b3a47; box-shadow:0 4px 20px #0002;\">\r\n    ${html}\r\n  <\/div>`;\r\n  countEl.style.display='flex';\r\n  setTimeout(()=>{ countEl.style.display='none'; countEl.innerHTML=''; }, ms);\r\n}\r\n\r\n\r\n\r\n  \/\/ \u0441\u043f\u0440\u044f\u0447\u0435\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u043e\u043b\u043e\u0441\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0443 \u044d\u0442\u043e\u0439 \u0438\u0433\u0440\u044b\r\n  game.coreEl.closest('.zg-root')?.classList.add('pz-no-msg');\r\n\r\n  \/\/ ===== \u0411\u0435\u0439\u0434\u0436 \u00abNivel\u00bb \u0432 HUD\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('[data-pz-nivel]');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.setAttribute('data-pz-nivel','');\r\n    lvlBadge.style.cssText = 'margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    lvlBadge.textContent = 'N: 1\/'+LEVELS.length;\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setNivelBadge = n => { if (lvlBadge) lvlBadge.textContent = `N: ${n}\/${LEVELS.length}`; };\r\n\r\n  \/\/ ===== \u0410\u0434\u0430\u043f\u0442\u0438\u0432\r\n  function vpHeight(){ return (window.visualViewport?.height) || window.innerHeight; }\r\n  function fitPuzzleOnce(){\r\n    const maxByWidth = Math.min(520, pzCont.clientWidth);\r\n    const top = pzCont.getBoundingClientRect().top;\r\n    const vpH = vpHeight();\r\n    const below = document.getElementById('pz-actions').offsetHeight + 8;\r\n    let avail = Math.floor(vpH - top - below - 8);\r\n    if (!Number.isFinite(avail) || avail < 220) avail = Math.max(220, Math.floor(vpH*0.55));\r\n    const size = Math.floor(Math.min(maxByWidth, avail));\r\n    pzRoot.style.setProperty('--pz-size', size+'px');\r\n  }\r\n  const fitPuzzleSmart=()=>{ fitPuzzleOnce(); requestAnimationFrame(fitPuzzleOnce); setTimeout(fitPuzzleOnce,60); setTimeout(fitPuzzleOnce,250); };\r\n  new ResizeObserver(fitPuzzleSmart).observe(pzCont);\r\n  window.addEventListener('resize', fitPuzzleSmart, {passive:true});\r\n  window.addEventListener('orientationchange', fitPuzzleSmart);\r\n  if (window.visualViewport){\r\n    visualViewport.addEventListener('resize', fitPuzzleSmart, {passive:true});\r\n    visualViewport.addEventListener('scroll', fitPuzzleSmart, {passive:true});\r\n  }\r\n  fitPuzzleSmart();\r\n\r\n  \/\/ ===== \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\r\n  let nivel = 1, N = LEVELS[0];\r\n  let order = [], selected = null;\r\n  const wait = ms => new Promise(r=>setTimeout(r,ms));\r\n  const setNivel = n => { nivel = Math.min(Math.max(1,n), LEVELS.length); N = LEVELS[nivel-1]; setNivelBadge(nivel); };\r\n\r\n  \/\/ ===== \u0423\u0442\u0438\u043b\u0438\u0442\u044b\/\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430\r\n  function shuffle(a){ for(let i=a.length-1;i>0;i--){ const j=(Math.random()*(i+1))|0; [a[i],a[j]]=[a[j],a[i]]; } if (a.every((v,i)=>v===i)) shuffle(a); return a; }\r\n  function isSolved(){ return order.every((v,i)=>v===i); }\r\n  function scoreDeltaBeforeAfter(i,j){\r\n    const before = (Number(order[i])===i) + (Number(order[j])===j);\r\n    const after  = (Number(order[j])===i) + (Number(order[i])===j);\r\n    return Math.max(0, (after - before)*5);\r\n  }\r\n  const entryToURL=(e,px)=> e.type==='local' ? `\/media\/${e.id}.jpg` : `https:\/\/picsum.photos\/seed\/${encodeURIComponent(e.seed)}\/${px}\/${px}`;\r\n  const randomEntry=(ex)=> {\r\n    const pool = ex ? IMG_POOL.filter(e => !(e.type===ex.type && (e.id===ex.id || e.seed===ex.seed))) : IMG_POOL;\r\n    return pool[(Math.random()*pool.length)|0];\r\n  };\r\n  let imgPerLevel = []; let lastEntry=null;\r\n  for (let i=0;i<LEVELS.length;i++){ const e=randomEntry(lastEntry); imgPerLevel.push(e); lastEntry=e; }\r\n\r\n  async function preloadWithTimeout(url, ms=3500){\r\n    const img = new Image();\r\n    return Promise.race([\r\n      new Promise((res,rej)=>{ img.onload=res; img.onerror=rej; img.src=url; }),\r\n      new Promise((_,rej)=> setTimeout(()=>rej(new Error('timeout')), ms))\r\n    ]);\r\n  }\r\n  let currentURL = '';\r\n  async function loadImageOrSwitch(entry, tries=3){\r\n    let cur = entry;\r\n    for (let t=0;t<tries;t++){\r\n      const url = entryToURL(cur, side);\r\n      showLoader();\r\n      const ok = await preloadWithTimeout(url,3500).then(()=>true).catch(()=>false);\r\n      hideLoader();\r\n      if (ok){ currentURL=url; return url; }\r\n      cur = randomEntry(cur);\r\n    }\r\n    const url = `https:\/\/picsum.photos\/seed\/${Date.now()}\/${side}\/${side}`;\r\n    showLoader(); await preloadWithTimeout(url,3500).catch(()=>{}); hideLoader();\r\n    currentURL=url; return url;\r\n  }\r\n  function showLoader(){ loadbarEl.style.width='0%'; loaderEl.style.display='flex';\r\n    loaderEl._fake=setInterval(()=>{ const w=parseFloat(loadbarEl.style.width)||0; loadbarEl.style.width=Math.min(80,w+5)+'%'; },180); }\r\n  function hideLoader(){ if(loaderEl._fake){clearInterval(loaderEl._fake); loaderEl._fake=null;} loadbarEl.style.width='100%'; setTimeout(()=>loaderEl.style.display='none',200); }\r\n\r\n  \/\/ ===== \u041f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u0443\u0440\u043e\u0432\u043d\u044f\r\n  async function buildLevel(){\r\n    selected=null;\r\n    btnRestart.style.display=''; btnNewImg.style.display=''; btnHint.style.display='';\r\n\r\n    gridEl.style.gridTemplate = `repeat(${N},1fr)\/repeat(${N},1fr)`;\r\n    gridEl.innerHTML = '';\r\n    order = Array.from({length:N*N}, (_,i)=>i);\r\n    shuffle(order);\r\n\r\n    const url = await loadImageOrSwitch(imgPerLevel[nivel-1]);\r\n\r\n    const tileW = gridEl.clientWidth \/ N, tileH = gridEl.clientHeight \/ N;\r\n\r\n    order.forEach((origIdx, cellIdx)=>{\r\n      const r0 = Math.floor(origIdx\/N), c0 = origIdx%N;\r\n      const btn = document.createElement('button');\r\n      btn.type='button'; btn.dataset.idx=cellIdx; btn.dataset.orig=origIdx;\r\n      btn.style.cssText='border:none;padding:0;background:transparent;cursor:pointer;position:relative;';\r\n      const inner=document.createElement('div');\r\n      inner.style.cssText = `\r\n        width:100%;height:100%;background:url('${url}');\r\n        background-size:${gridEl.clientWidth}px ${gridEl.clientHeight}px;\r\n        background-position:${-c0*tileW}px ${-r0*tileH}px;\r\n        border:2px solid #fff; box-sizing:border-box; border-radius:6px; transition:box-shadow .1s, transform .1s;\r\n      `;\r\n      btn.appendChild(inner);\r\n\r\n        btn.addEventListener('click', ()=>{\r\n          if (isSolved()) return;\r\n          const idx = Number(btn.dataset.idx);\r\n        \r\n          \/\/ \u0432\u044b\u0431\u043e\u0440\/\u0441\u043d\u044f\u0442\u0438\u0435 \u0432\u044b\u0431\u043e\u0440\u0430\r\n          if (selected === null){\r\n            selected = idx; inner.style.boxShadow='0 0 0 3px #10b981'; sfxPick();\r\n            return;\r\n          }\r\n          if (selected === idx){\r\n            selected = null; inner.style.boxShadow='none'; sfxUnpick();\r\n            return;\r\n          }\r\n        \r\n          \/\/ \u043e\u0431\u043c\u0435\u043d \u0441 \u0440\u0430\u043d\u0435\u0435 \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u043e\u0439\r\n          const i = selected, j = idx;\r\n          const a = gridEl.querySelector(`[data-idx=\"${i}\"]`)?.firstChild;\r\n          const b = gridEl.querySelector(`[data-idx=\"${j}\"]`)?.firstChild;\r\n          if (!a || !b) return;\r\n        \r\n          a.style.boxShadow='none';\r\n          sfxSwap();\r\n          pulse(a); pulse(b);\r\n        \r\n          const delta = scoreDeltaBeforeAfter(i,j);\r\n          [order[i], order[j]] = [order[j], order[i]];\r\n          paintOne(i, order[i], a);\r\n          paintOne(j, order[j], b);\r\n        \r\n          selected = null;\r\n          if (delta > 0){ game.addScore(delta); sfxReward(); }\r\n        \r\n          \/\/ \u0441\u043e\u0431\u0440\u0430\u043b\u0438 \u043f\u0430\u0437\u043b\r\n          if (isSolved()){\r\n            game.addScore(30);\r\n            sfxSolved();\r\n            toastCenter('\u00a1Correcto! +30');\r\n            setTimeout(()=> afterLevel(), 650);   \/\/ \u043a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u043f\u0430\u0443\u0437\u0430 \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u0441\u0447\u0451\u0442\u043e\u043c\r\n          }\r\n        });\r\n\r\n\r\n      gridEl.appendChild(btn);\r\n    });\r\n\r\nfunction paintOne(cellIdx, origIdx, el){\r\n  var r0 = Math.floor(origIdx \/ N);\r\n  var c0 = origIdx % N;\r\n  var w  = gridEl.clientWidth  \/ N;\r\n  var h  = gridEl.clientHeight \/ N;\r\n\r\n  el.style.backgroundSize     = gridEl.clientWidth + \"px \" + gridEl.clientHeight + \"px\";\r\n  el.style.backgroundPosition = (-c0 * w) + \"px \" + (-r0 * h) + \"px\";\r\n}\r\n\r\n\/\/ \u0430\u0434\u0430\u043f\u0442\u0430\u0446\u0438\u044f \u043f\u0440\u0438 \u0440\u0435\u0441\u0430\u0439\u0437\u0435\r\nnew ResizeObserver(function(){\r\n  var w = gridEl.clientWidth  \/ N;\r\n  var h = gridEl.clientHeight \/ N;\r\n\r\n  gridEl.querySelectorAll(\"button\").forEach(function(tile){\r\n    var o  = Number(tile.dataset.orig);\r\n    var r0 = Math.floor(o \/ N);\r\n    var c0 = o % N;\r\n    var inner = tile.firstChild;\r\n    if (!inner) return;\r\n\r\n    inner.style.backgroundSize     = gridEl.clientWidth + \"px \" + gridEl.clientHeight + \"px\";\r\n    inner.style.backgroundPosition = (-c0 * w) + \"px \" + (-r0 * h) + \"px\";\r\n  });\r\n}).observe(gridEl);\r\n\r\nfitPuzzleSmart();\r\n\r\n  }\r\n\r\n  \/\/ ===== \u041a\u043d\u043e\u043f\u043a\u0438\r\n  btnRestart.addEventListener('click', ()=>{ sfxClick(); if (!isSolved()) { buildLevel(); } });\r\n  btnNewImg.addEventListener('click', ()=>{ sfxClick(); imgPerLevel[nivel-1] = randomEntry(imgPerLevel[nivel-1]); buildLevel(); });\r\n  btnHint.addEventListener('click', ()=>{\r\n    if (isSolved()) return;\r\n     sfxHint();\r\n    btnHint.disabled=true;\r\n    game.setScore(Math.max(0, game.score - HINT_COST));\r\n    hintImgEl.style.backgroundImage = `url('${currentURL}')`;\r\n    hintOver.style.display='flex';\r\n    setTimeout(()=>{ hintOver.style.display='none'; btnHint.disabled=false; }, 2500);\r\n  });\r\n\r\n  \/\/ ===== \u041f\u0440\u043e\u0433\u0440\u0435\u0441\u0441\r\n  async function saveProgress(){\r\n    try{\r\n      const recEl = document.querySelector('[data-zg-record]');\r\n      const prev  = Number(recEl?.textContent || 0);\r\n      if (game.score > prev){ await zSaveProgress(usuarioId,10,game.score,game.elapsed); if (recEl) recEl.textContent = game.score; }\r\n      else                  { await zSaveProgress(usuarioId,10,prev,game.elapsed); }\r\n      const total = await zLoadTotalTime(usuarioId,10);\r\n      const totalEl = document.querySelector('[data-zg-total]'); if (totalEl) totalEl.textContent = zFmtMMSS(total);\r\n    }catch(_){}\r\n  }\r\n\r\n    async function afterLevel(){\r\n      await saveProgress();\r\n    \r\n      if (nivel >= LEVELS.length){\r\n        \/\/ \u0444\u0438\u043d\u0430\u043b\r\n        toastCenter('\u00a1Todos los puzles! \ud83c\udf89', 900);\r\n        await new Promise(r=>setTimeout(r, 950));\r\n        await game.endGame({save:true});\r\n        return;\r\n      }\r\n    \r\n      \/\/ 3-2-1 \u0441 \u043e\u0437\u0432\u0443\u0447\u043a\u043e\u0439\r\n      countEl.style.display='flex';\r\n      for (let s=3; s>=1; s--){\r\n        countEl.textContent = s;\r\n        sfxBeep(4-s);\r\n        await new Promise(r=>setTimeout(r, 800));\r\n      }\r\n      countEl.textContent='\u00a1Vamos!'; sfxReady();\r\n      await new Promise(r=>setTimeout(r, 450));\r\n      countEl.style.display='none';\r\n    \r\n      \/\/ \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c\r\n      setNivel(nivel+1);\r\n      pzWrap.scrollIntoView({ behavior:'smooth', block:'center' });\r\n      await buildLevel();\r\n    }\r\n\r\n\r\n  \/\/ ===== \u0421\u0442\u0430\u0440\u0442 \u0438\u0437 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0439 \u043a\u043d\u043e\u043f\u043a\u0438\r\n    async function startRound(){\r\n      instrEl.style.display = 'none';\r\n      pzWrap.style.display  = 'block';                   \/\/ \u043f\u043e\u043b\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0441\u0440\u0430\u0437\u0443\r\n      pzWrap.scrollIntoView({ behavior:'smooth', block:'center' });\r\n    \r\n      \/\/ 3-2-1 \u2014 \u0412\u0418\u0414\u0418\u041c\u042b\u0419 \u0441\u043b\u043e\u0439 + \u0437\u0432\u0443\u043a\r\n      countEl.style.display='flex';\r\n      for (let n=3; n>=1; n--){\r\n        countEl.textContent = n;\r\n        sfxBeep(4-n);                                     \/\/ \u0431\u0438\u043f \u0434\u043b\u044f 3,2,1\r\n        await new Promise(r=>setTimeout(r, 800));\r\n      }\r\n      countEl.textContent = '\u00a1Vamos!'; sfxReady();\r\n      await new Promise(r=>setTimeout(r, 450));\r\n      countEl.style.display='none';\r\n    \r\n      await buildLevel();\r\n    }\r\n\r\nbtnStart.onclick = async ()=>{\r\n  try{ window.SFX?.unlock?.(); }catch(_){}\r\n  sfxClick();\r\n  btnStart.disabled = true;\r\n  game.setScore(0);\r\n  setNivel(1);\r\n  game.startTimer();\r\n  await startRound();\r\n};\r\n\r\n\r\n  \/\/ ===== HUD\r\n  game.showStart(false);       \/\/ \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u044e\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0432\u043c\u0435\u0441\u0442\u043e HUD-\u043a\u043d\u043e\u043f\u043a\u0438\r\n  game.showContinue(false);\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b3f35e1 elementor-widget elementor-widget-html\" data-id=\"b3f35e1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_SudokuMulti(){\r\n  const game = crearMarcoJuego({ gameId: 11, titulo: 'Sudoku', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u043a\u0430\u0440\u043a\u0430\u0441 UI\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .sdk-no-msg .zg-core .zg-msg{ display:none!important; }\r\n      .zg-root,.zg-core-host,[data-zg-core-host]{ overflow-x:hidden; }\r\n      #sdk-wrap{ width:100%; max-width:560px; margin:0 auto; }\r\n      #sdk-instr{ color:#334155; font-size:.95em; line-height:1.35; margin:6px 0 10px; }\r\n      #sdk-instr b{ font-weight:700; }\r\n      #sdk-start{ margin-top:8px; padding:10px 16px; border:none; border-radius:12px;\r\n                  background:#f8c444; color:#111827; font-weight:900; box-shadow:0 2px 10px #0002; }\r\n      #sdk-board{ display:none; }\r\n      #sdk-grid-wrap{ width:min(96vw,520px); margin:0 auto; }\r\n      #sdk-grid{\r\n        width:100%; height:0;            \/* \u0432\u044b\u0441\u043e\u0442\u0443 \u0437\u0430\u0434\u0430\u0451\u043c \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u043c *\/\r\n        display:grid; gap:2px;\r\n        background:#bfcad9; border-radius:12px; overflow:hidden;\r\n      }\r\n      #sdk-pad{\r\n        display:grid; gap:8px; justify-content:center;\r\n        grid-template-columns:repeat(9, minmax(0,1fr));\r\n        max-width:min(96vw,520px); margin:12px auto 6px;\r\n      }\r\n      .sdk-n4 #sdk-pad{ grid-template-columns:repeat(4,1fr); }\r\n      .sdk-n6 #sdk-pad{ grid-template-columns:repeat(6,1fr); }\r\n      #sdk-pad .sdk-key{\r\n        min-width:0; height:48px; border-radius:12px; border:2px solid #69ddbb;\r\n        background:#f3fcfa; color:#2563eb; font-weight:900; font-size:1.05em; cursor:pointer;\r\n      }\r\n      #sdk-bottom{ display:flex; gap:10px; justify-content:center; margin:8px 0 4px; padding:0 6px; }\r\n      #sdk-count{\r\n        position:fixed; inset:0; display:none; align-items:center; justify-content:center;\r\n        background:rgba(255,255,255,.78); z-index:9999;\r\n      }\r\n      #sdk-count .bubble{\r\n        background:#ffffffee; border-radius:16px; padding:12px 22px;\r\n        font-weight:900; color:#0b3a47; box-shadow:0 6px 28px rgba(0,0,0,.18);\r\n        font-size:2.2em; text-align:center;\r\n      }\r\n    <\/style>\r\n\r\n    <div id=\"sdk-wrap\">\r\n      <div id=\"sdk-instr\">\r\n        <b>C\u00f3mo jugar:<\/b> Rellena las celdas sin repetir n\u00famero en <b>fila<\/b>, <b>columna<\/b> ni <b>bloque<\/b>.<br>\r\n        Toca una celda y luego un n\u00famero del panel. <b>Borrar<\/b> vac\u00eda la celda.<br>\r\n        Hay 3 niveles: 4\u00d74, 6\u00d76 y 9\u00d79.\r\n        <br><button id=\"sdk-start\">Empezar<\/button>\r\n      <\/div>\r\n\r\n      <div id=\"sdk-board\">\r\n        <div id=\"sdk-grid-wrap\"><div id=\"sdk-grid\"><\/div><\/div>\r\n        <div id=\"sdk-pad\"><\/div>\r\n        <div id=\"sdk-bottom\">\r\n          <button id=\"sdk-erase\" class=\"z-btn z-btn-secondary\" title=\"Borrar\">\ud83d\uddd1\ufe0f Borrar<\/button>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div id=\"sdk-count\"><div class=\"bubble\" id=\"sdk-count-num\">3<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  \/\/ \u043f\u0440\u044f\u0447\u0435\u043c \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435\r\n  game.coreEl.closest('.zg-root')?.classList.add('sdk-no-msg');\r\n\r\n  \/* \u0417\u0432\u0443\u043a\u0438 *\/\r\n  const tone=(f=660,ms=120,type='sine',vol=.2)=>{\r\n    try{\r\n      if (window.SFX?.tone){ SFX.tone(f,ms,type,vol); return; }\r\n      const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;\r\n      tone.ctx=tone.ctx||new AC(); const c=tone.ctx,o=c.createOscillator(),g=c.createGain();\r\n      o.type=type; o.frequency.value=f; g.gain.value=vol; o.connect(g); g.connect(c.destination);\r\n      o.start(); setTimeout(()=>{ try{o.stop();o.disconnect();g.disconnect();}catch(e){}; }, ms);\r\n    }catch(e){}\r\n  };\r\n  const sfxClick  = ()=>{ if (SFX?.click)  SFX.click();  else tone(740,70,'square',.16); };\r\n  const sfxPick   = ()=>{ if (SFX?.ui)     SFX.ui();     else tone(520,60,'sine',.16);   };\r\n  const sfxErase  = ()=>{ if (SFX?.buzz)   SFX.buzz();   else tone(280,90,'sawtooth',.18); };\r\n  const sfxGood   = ()=>{ if (SFX?.coin)   SFX.coin();   else { tone(1100,70,'triangle',.22); setTimeout(()=>tone(1400,80,'triangle',.22),80); } };\r\n  const sfxBad    = ()=>{ if (SFX?.fail)   SFX.fail();   else tone(220,140,'sawtooth',.22); };\r\n  const sfxSolved = ()=>{ if (SFX?.win)    SFX.win();    else { tone(660,120,'sine',.22); setTimeout(()=>tone(880,120,'sine',.22),110); setTimeout(()=>tone(1046,180,'sine',.24),240); } };\r\n  const sfxBeep   = (i)=>{ if (SFX?.beep)  SFX.beep();   else tone(520+i*140,110,'sine',.22); };\r\n  const sfxReady  = ()=>{ if (SFX?.ready)  SFX.ready();  else tone(980,130,'triangle',.22); };\r\n\r\n  \/* \u0411\u0435\u0439\u0434\u0436 \u00abNivel\u00bb *\/\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('[data-sdk-nivel]');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.setAttribute('data-sdk-nivel','');\r\n    lvlBadge.style.cssText='margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setNivelBadge = (n, tot)=>{ if(lvlBadge) lvlBadge.textContent = `Nivel: ${n}\/${tot}`; };\r\n\r\n  \/* \u041b\u043e\u0433\u0438\u043a\u0430 *\/\r\n  const STAGES = [\r\n    { name:'F\u00e1cil',   size:4, br:2, bc:2, maxpuntos:80  },\r\n    { name:'Medio',   size:6, br:2, bc:3, maxpuntos:180 },\r\n    { name:'Dif\u00edcil', size:9, br:3, bc:3, maxpuntos:400 }\r\n  ];\r\n\r\n  const wrap     = document.getElementById('sdk-wrap');\r\n  const instr    = document.getElementById('sdk-instr');\r\n  const btnStart = document.getElementById('sdk-start');\r\n  const boardEl  = document.getElementById('sdk-board');\r\n  const grid     = document.getElementById('sdk-grid');\r\n  const gridWrap = document.getElementById('sdk-grid-wrap');\r\n  const pad      = document.getElementById('sdk-pad');\r\n  const btnErase = document.getElementById('sdk-erase');\r\n  const cntBox   = document.getElementById('sdk-count');\r\n  const cntNum   = document.getElementById('sdk-count-num');\r\n\r\n  \/\/ \u043f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u0435\u0440\u0436\u0438\u043c \u043a\u0432\u0430\u0434\u0440\u0430\u0442\r\n  function fitSquare(){\r\n    const w = gridWrap.clientWidth;\r\n    grid.style.height = w + 'px';\r\n  }\r\n  new ResizeObserver(()=>fitSquare()).observe(gridWrap);\r\n  window.addEventListener('orientationchange', fitSquare);\r\n  fitSquare();\r\n\r\n  let stageIdx=0, puzzle=[], solution=[], editable=[], selected=null, stageStart=0;\r\n  const wait = ms=>new Promise(r=>setTimeout(r,ms));\r\n\r\n  function generarSudoku(n, br, bc){\r\n    const pattern=(r,c)=>((r*bc + Math.floor(r\/br) + c) % n)+1;\r\n    const base = Array.from({length:n},(_,r)=> Array.from({length:n},(_,c)=>pattern(r,c)));\r\n    const shuffle=a=>{ for(let i=a.length-1;i>0;i--){ const j=(Math.random()*(i+1))|0; [a[i],a[j]]=[a[j],a[i]]; } };\r\n    const rowBlocks = Array.from({length:n\/br},(_,b)=>{ const rows=Array.from({length:br},(_,i)=>b*br+i); shuffle(rows); return rows; });\r\n    shuffle(rowBlocks); const rowIdx=rowBlocks.flat();\r\n    const colBlocks = Array.from({length:n\/bc},(_,b)=>{ const cols=Array.from({length:bc},(_,i)=>b*bc+i); shuffle(cols); return cols; });\r\n    shuffle(colBlocks); const colIdx=colBlocks.flat();\r\n    const sol = Array.from({length:n},(_,ri)=> Array.from({length:n},(_,ci)=> base[rowIdx[ri]][colIdx[ci]]));\r\n    const puz = sol.map(r=>r.slice());\r\n    let holes = Math.floor(n*n*0.45 + Math.random()*n);\r\n    while(holes>0){ const r=(Math.random()*n)|0, c=(Math.random()*n)|0; if(puz[r][c]!==0){ puz[r][c]=0; holes--; } }\r\n    const ed = Array.from({length:n},(_,r)=> Array.from({length:n},(_,c)=> puz[r][c]===0 ));\r\n    return { puzzle:puz, solution:sol, editable:ed };\r\n  }\r\n\r\n  function blinkCell(cell, ok=true){\r\n    const bg = ok ? '#d1fae5' : '#fee2e2';\r\n    const base = cell.dataset.editable==='1' ? '#fafdff' : '#f3f4f6';\r\n    cell.style.transition='background-color .12s';\r\n    cell.style.backgroundColor = bg;\r\n    setTimeout(()=>{ cell.style.backgroundColor = base; }, 160);\r\n  }\r\n\r\n  function drawGrid(stage){\r\n    const n = stage.size;\r\n    wrap.classList.toggle('sdk-n4', n===4);\r\n    wrap.classList.toggle('sdk-n6', n===6);\r\n    grid.style.gridTemplate = `repeat(${n},1fr)\/repeat(${n},1fr)`;\r\n    grid.innerHTML='';\r\n\r\n    const font = n<=6 ? '1.6em' : '1.15em';\r\n\r\n    for(let r=0;r<n;r++){\r\n      for(let c=0;c<n;c++){\r\n        const v = puzzle[r][c];\r\n        const cell = document.createElement('button');\r\n        cell.type='button';\r\n        cell.dataset.r=r; cell.dataset.c=c;\r\n        cell.dataset.editable = editable[r][c] ? '1' : '0';\r\n        cell.style.cssText = `\r\n          background:${editable[r][c]?'#fafdff':'#eef2ff'};\r\n          color:${editable[r][c]?'#0f172a':'#0b1220'};\r\n          border:none; border-radius:6px; display:flex; align-items:center; justify-content:center;\r\n          font-weight:900; font-size:${font}; cursor:${editable[r][c]?'pointer':'default'};\r\n          box-shadow: inset 0 0 0 2px #e5e7eb;\r\n        `;\r\n        if (r!==n-1 && ((r+1)%stage.br===0)) cell.style.boxShadow += ', 0 4px 0 0 #d3eaff';\r\n        if (c!==n-1 && ((c+1)%stage.bc===0)) cell.style.boxShadow += ', 4px 0 0 0 #d3eaff';\r\n\r\n        cell.textContent = v? v : '';\r\n        if (selected && selected[0]===r && selected[1]===c){\r\n          cell.style.outline = '3px solid #38bdf8';\r\n          cell.style.background = '#e3fff4';\r\n        }\r\n        cell.onclick = ()=>{\r\n          if (!editable[r][c]) return;\r\n          selected=[r,c]; sfxPick(); drawGrid(stage);\r\n        };\r\n        grid.appendChild(cell);\r\n      }\r\n    }\r\n  }\r\n\r\n  function drawPad(stage){\r\n    const n = stage.size;\r\n    pad.innerHTML='';\r\n    for(let k=1;k<=n;k++){\r\n      const b=document.createElement('button');\r\n      b.className='sdk-key';\r\n      b.textContent=k;\r\n      b.onclick=()=>{\r\n        sfxClick();\r\n        if(!selected) return;\r\n        const [r,c]=selected; if(!editable[r][c]) return;\r\n        puzzle[r][c]=k; selected=null;\r\n        const cell = grid.querySelector(`[data-r=\"${r}\"][data-c=\"${c}\"]`);\r\n        if (solution[r][c]===k){ sfxGood(); blinkCell(cell,true); } else { sfxBad(); blinkCell(cell,false); }\r\n        drawGrid(stage);\r\n        checkFinish(stage);\r\n      };\r\n      pad.appendChild(b);\r\n    }\r\n  }\r\n\r\n  document.getElementById('sdk-erase').onclick = ()=>{\r\n    sfxErase();\r\n    if(!selected) return;\r\n    const [r,c]=selected; if(!editable[r][c]) return;\r\n    puzzle[r][c]=0; selected=null;\r\n    drawGrid(STAGES[stageIdx]);\r\n  };\r\n\r\n  function checkFinish(stage){\r\n    const n = stage.size;\r\n    for(let r=0;r<n;r++) for(let c=0;c<n;c++){\r\n      if (puzzle[r][c]!==solution[r][c]) return;\r\n    }\r\n    const t = Math.round((performance.now() - stageStart)\/1000);\r\n    const pts = Math.max(1, stage.maxpuntos - t);\r\n    game.addScore(pts);\r\n    sfxSolved();\r\n    showToast(`\u00a1Nivel completado!<br><small>${stage.name} \u00b7 +${pts} pts \u00b7 ${zFmtMMSS(t)}<\/small>`, 950)\r\n      .then(()=> nextStage());\r\n  }\r\n\r\n  function showToast(html, ms=700){\r\n    cntNum.innerHTML = `<div style=\"font-size:20px;line-height:1.25;\">${html}<\/div>`;\r\n    cntBox.style.display='flex';\r\n    return new Promise(res=> setTimeout(()=>{ cntBox.style.display='none'; cntNum.textContent=''; res(); }, ms));\r\n  }\r\n\r\n  async function nextStage(){\r\n    if (stageIdx >= STAGES.length-1){\r\n      await showToast('<b>\u00a1Todos los niveles completados!<\/b>', 900);\r\n      await saveProgress();\r\n      await game.endGame({save:true});\r\n      return;\r\n    }\r\n    cntBox.style.display='flex';\r\n    for (let s=3; s>=1; s--){ cntNum.textContent=s; sfxBeep(4-s); await wait(800); }\r\n    cntNum.textContent='\u00a1Vamos!'; sfxReady(); await wait(450);\r\n    cntBox.style.display='none';\r\n    stageIdx++; buildStage(stageIdx);\r\n  }\r\n\r\n  function buildStage(idx){\r\n    const st = STAGES[idx];\r\n    const data = generarSudoku(st.size, st.br, st.bc);\r\n    puzzle=data.puzzle; solution=data.solution; editable=data.editable; selected=null;\r\n\r\n    boardEl.style.display = 'block';  \/\/ <- \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u043e\u043a\u0430\u0437 \u043f\u043e\u043b\u044f\r\n    drawGrid(st);\r\n    drawPad(st);\r\n    stageStart = performance.now();\r\n    setNivelBadge(idx+1, STAGES.length);\r\n    fitSquare();                      \/\/ <- \u0441\u0440\u0430\u0437\u0443 \u0432\u044b\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u044b\u0441\u043e\u0442\u0443\r\n  }\r\n\r\n  async function saveProgress(){\r\n    try{\r\n      const recEl = document.querySelector('[data-zg-record]');\r\n      const prev  = Number(recEl?.textContent || 0);\r\n      if (game.score > prev){ await zSaveProgress(usuarioId,11,game.score,game.elapsed); if (recEl) recEl.textContent = game.score; }\r\n      else                  { await zSaveProgress(usuarioId,11,prev,game.elapsed); }\r\n      const total = await zLoadTotalTime(usuarioId,11);\r\n      const totalEl = document.querySelector('[data-zg-total]'); if (totalEl) totalEl.textContent = zFmtMMSS(total);\r\n    }catch(_){}\r\n  }\r\n\r\n  \/\/ \u0441\u0442\u0430\u0440\u0442\r\n  btnStart.onclick = async ()=>{\r\n    try{ window.SFX?.unlock?.(); }catch(_){}\r\n    sfxClick();\r\n    btnStart.disabled = true;\r\n    instr.style.display = 'none';\r\n    game.setScore(0);\r\n    game.startTimer();\r\n    stageIdx=0;\r\n\r\n    \/\/ \u0432\u0438\u0434\u0438\u043c\u044b\u0439 3-2-1\r\n    cntBox.style.display='flex';\r\n    for (let s=3; s>=1; s--){ cntNum.textContent=s; sfxBeep(4-s); await wait(800); }\r\n    cntNum.textContent='\u00a1Vamos!'; sfxReady(); await wait(450);\r\n    cntBox.style.display='none';\r\n\r\n    buildStage(stageIdx);\r\n  };\r\n\r\n  game.showStart(false);\r\n  game.showContinue(false);\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-43f978e elementor-widget elementor-widget-html\" data-id=\"43f978e\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_SecuenciasLogicas(){\r\n  const game = crearMarcoJuego({ gameId: 12, titulo: 'Secuencias l\u00f3gicas', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f (\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430)\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Observa la secuencia y toca la opci\u00f3n que la contin\u00faa.<br>\r\n    Acierto: <b>+10<\/b> \u00b7 Error: <b>\u22125<\/b>.<br>\r\n    Pulsa <b>Empezar<\/b> para comenzar.\r\n  `);\r\n  game.showStart(true);      \/\/ \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \u0421\u0442\u0430\u0440\u0442 \u0441\u0440\u0430\u0437\u0443\r\n  game.showContinue(false);  \/\/ \u044d\u0442\u0430 \u0438\u0433\u0440\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0411\u0415\u0417 \"Continuar\"\r\n\r\n  \/\/ \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 SFX \u0441 \u0444\u043e\u043b\u0431\u044d\u043a\u043e\u043c \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n  const tone=(f=660,ms=130,type='sine',vol=.2)=>{\r\n    try{\r\n      if (window.SFX?.tone){ SFX.tone(f,ms,type,vol); return; }\r\n      const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;\r\n      tone.ctx=tone.ctx||new AC();\r\n      const c=tone.ctx,o=c.createOscillator(),g=c.createGain();\r\n      o.type=type; o.frequency.value=f; g.gain.value=vol; o.connect(g); g.connect(c.destination);\r\n      o.start(); setTimeout(()=>{ try{o.stop();o.disconnect();g.disconnect();}catch(e){}; },ms);\r\n    }catch(e){}\r\n  };\r\n  const sfx={\r\n    unlock(){ try{ SFX?.unlock?.(); }catch(e){} },\r\n    click(){  SFX?.click?SFX.click():tone(760,70,'square',.16); },\r\n    ok(){     SFX?.success?SFX.success():(tone(980,100,'sine',.22),setTimeout(()=>tone(1240,120,'sine',.22),110)); },\r\n    fail(){   SFX?.fail?SFX.fail():tone(220,170,'sawtooth',.24); },\r\n    beep(n){  SFX?.beep?SFX.beep():tone(520+n*140,110,'sine',.22); },\r\n    ready(){  SFX?.ready?SFX.ready():tone(980,130,'triangle',.22); }\r\n  };\r\n\r\n  \/\/ \u041f\u0443\u043b \u0437\u0430\u0434\u0430\u0447 (\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0411\u0415\u0417 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439)\r\n  const POOL = [\r\n    { pregunta: \"2, 4, 6, 8, ?\", opciones: [\"9\", \"10\", \"12\", \"8\"], correcta: \"10\", explicacion: \"La serie aumenta de 2 en 2: 2, 4, 6, 8, 10.\" },\r\n    { pregunta: \"1, 4, 9, 16, ?\", opciones: [\"20\", \"24\", \"25\", \"36\"], correcta: \"25\", explicacion: \"Son cuadrados perfectos: 1\u00b2, 2\u00b2, 3\u00b2, 4\u00b2, 5\u00b2 = 25.\" },\r\n    { pregunta: \"5, 10, 20, 40, ?\", opciones: [\"45\", \"60\", \"80\", \"100\"], correcta: \"80\", explicacion: \"Cada n\u00famero es el doble del anterior.\" },\r\n    { pregunta: \"1, 3, 6, 10, ?\", opciones: [\"14\", \"15\", \"16\", \"21\"], correcta: \"15\", explicacion: \"Suma acumulativa: +2, +3, +4, +5...\" },\r\n    { pregunta: \"100, 90, 80, 70, ?\", opciones: [\"60\", \"75\", \"50\", \"65\"], correcta: \"60\", explicacion: \"La serie baja de 10 en 10.\" },\r\n    { pregunta: \"rojo, naranja, amarillo, verde, ?\", opciones: [\"azul\", \"morado\", \"rosa\", \"blanco\"], correcta: \"azul\", explicacion: \"Colores del arco\u00edris.\" },\r\n    { pregunta: \"Lunes, Mi\u00e9rcoles, Viernes, ?\", opciones: [\"S\u00e1bado\", \"Domingo\", \"Lunes\", \"Martes\"], correcta: \"Domingo\", explicacion: \"D\u00edas alternos.\" },\r\n    { pregunta: \"Primavera, Verano, Oto\u00f1o, ?\", opciones: [\"Invierno\", \"Verano\", \"Oto\u00f1o\", \"Primavera\"], correcta: \"Invierno\", explicacion: \"Estaciones del a\u00f1o.\" },\r\n    { pregunta: \"A, C, E, G, ?\", opciones: [\"F\", \"H\", \"K\", \"J\"], correcta: \"H\", explicacion: \"Saltando una letra.\" },\r\n    { pregunta: \"uno, tres, cinco, siete, ?\", opciones: [\"nueve\", \"once\", \"seis\", \"ocho\"], correcta: \"nueve\", explicacion: \"Impares en espa\u00f1ol.\" },\r\n    { pregunta: \"\ud83c\udf4f, \ud83c\udf4e, \ud83c\udf4f, \ud83c\udf4e, ?\", opciones: [\"\ud83c\udf4f\", \"\ud83c\udf4e\", \"\ud83c\udf4c\", \"\ud83c\udf50\"], correcta: \"\ud83c\udf4f\", explicacion: \"Alternancia.\" },\r\n    { pregunta: \"\ud83d\udc36, \ud83d\udc31, \ud83d\udc36, \ud83d\udc31, ?\", opciones: [\"\ud83d\udc2d\", \"\ud83d\udc31\", \"\ud83d\udc36\", \"\ud83e\udd8a\"], correcta: \"\ud83d\udc36\", explicacion: \"Alternancia.\" },\r\n    { pregunta: \"\ud83d\udc20, \ud83d\udc1f, \ud83d\udc20, \ud83d\udc1f, ?\", opciones: [\"\ud83d\udc1f\", \"\ud83d\udc20\", \"\ud83d\udc21\", \"\ud83d\udc2c\"], correcta: \"\ud83d\udc20\", explicacion: \"Alternancia.\" },\r\n    { pregunta: \"\u2600\ufe0f, \ud83c\udf24\ufe0f, \u26c5, \ud83c\udf25\ufe0f, ?\", opciones: [\"\u2601\ufe0f\", \"\ud83c\udf27\ufe0f\", \"\ud83c\udf26\ufe0f\", \"\ud83c\udf19\"], correcta: \"\u2601\ufe0f\", explicacion: \"M\u00e1s nubes.\" },\r\n    { pregunta: \"\ud83e\udd5a, \ud83d\udc23, \ud83d\udc25, ?\", opciones: [\"\ud83d\udc25\", \"\ud83d\udc14\", \"\ud83d\udc24\", \"\ud83e\udd5a\"], correcta: \"\ud83d\udc14\", explicacion: \"Ciclo gallina.\" },\r\n    { pregunta: \"\ud83c\udf31, \ud83c\udf3f, \ud83c\udf33, ?\", opciones: [\"\ud83c\udf32\", \"\ud83c\udf4e\", \"\ud83c\udf42\", \"\ud83c\udf37\"], correcta: \"\ud83c\udf32\", explicacion: \"Crecimiento.\" },\r\n    { pregunta: \"\ud83d\udc76, \ud83d\udc66, \ud83d\udc68, ?\", opciones: [\"\ud83d\udc74\", \"\ud83d\udc67\", \"\ud83e\uddd1\", \"\ud83d\udc69\"], correcta: \"\ud83d\udc74\", explicacion: \"Etapas de la vida.\" },\r\n    { pregunta: \"\ud83d\udeb2, \ud83d\ude97, \ud83d\ude8c, ?\", opciones: [\"\ud83d\ude84\", \"\ud83d\ude95\", \"\u2708\ufe0f\", \"\ud83d\ude82\"], correcta: \"\ud83d\ude84\", explicacion: \"Por velocidad.\" },\r\n    { pregunta: \"\ud83c\udf11, \ud83c\udf13, \ud83c\udf15, ?\", opciones: [\"\ud83c\udf17\", \"\ud83c\udf12\", \"\ud83c\udf18\", \"\ud83c\udf11\"], correcta: \"\ud83c\udf11\", explicacion: \"Fases lunares.\" },\r\n    { pregunta: \"Ma\u00f1ana, Tarde, Noche, ?\", opciones: [\"Madrugada\", \"D\u00eda\", \"Amanecer\", \"Mediod\u00eda\"], correcta: \"Madrugada\", explicacion: \"Partes del d\u00eda.\" },\r\n    { pregunta: \"huevo, pollo, gallina, ?\", opciones: [\"huevo\", \"pollo\", \"gallina\", \"granero\"], correcta: \"huevo\", explicacion: \"Ciclo.\" },\r\n    { pregunta: \"pez, anzuelo, ca\u00f1a, ?\", opciones: [\"pescador\", \"red\", \"barco\", \"plomo\"], correcta: \"pescador\", explicacion: \"Cadena l\u00f3gica.\" },\r\n    { pregunta: \"semilla, planta, flor, ?\", opciones: [\"fruto\", \"hoja\", \"ra\u00edz\", \"\u00e1rbol\"], correcta: \"fruto\", explicacion: \"Ciclo.\" },\r\n    { pregunta: \"piedra, tijera, papel, ?\", opciones: [\"piedra\", \"tijera\", \"papel\", \"fuego\"], correcta: \"piedra\", explicacion: \"Juego.\" },\r\n    { pregunta: \"ma\u00f1ana, mediod\u00eda, tarde, ?\", opciones: [\"noche\", \"madrugada\", \"amanecer\", \"atardecer\"], correcta: \"noche\", explicacion: \"Partes del d\u00eda.\" },\r\n    { pregunta: \"B, D, F, H, ?\", opciones: [\"J\", \"I\", \"K\", \"M\"], correcta: \"J\", explicacion: \"Salto de 1 letra.\" },\r\n    { pregunta: \"C, F, I, L, ?\", opciones: [\"M\", \"N\", \"O\", \"P\"], correcta: \"O\", explicacion: \"+3 posiciones.\" },\r\n    { pregunta: \"12, 24, 48, ?\", opciones: [\"60\", \"72\", \"96\", \"84\"], correcta: \"96\", explicacion: \"Dobles.\" },\r\n    { pregunta: \"7, 14, 21, ?\", opciones: [\"24\", \"28\", \"35\", \"30\"], correcta: \"28\", explicacion: \"+7.\" },\r\n    { pregunta: \"Martes, Jueves, S\u00e1bado, ?\", opciones: [\"Domingo\", \"Lunes\", \"Martes\", \"Viernes\"], correcta: \"Lunes\", explicacion: \"D\u00edas alternos.\" },\r\n    { pregunta: \"\ud83d\ude00, \ud83d\ude01, \ud83d\ude02, ?\", opciones: [\"\ud83e\udd23\", \"\ud83d\ude05\", \"\ud83d\ude07\", \"\ud83d\ude0a\"], correcta: \"\ud83e\udd23\", explicacion: \"M\u00e1s risa.\" },\r\n    { pregunta: \"verde, amarillo, rojo, ?\", opciones: [\"verde\", \"rojo\", \"azul\", \"amarillo\"], correcta: \"verde\", explicacion: \"Sem\u00e1foro.\" },\r\n    { pregunta: \"invierno, primavera, verano, ?\", opciones: [\"oto\u00f1o\", \"verano\", \"invierno\", \"primavera\"], correcta: \"oto\u00f1o\", explicacion: \"Estaciones.\" },\r\n    { pregunta: \"\ud83c\udf7c, \ud83c\udf7d\ufe0f, \ud83c\udf4f, ?\", opciones: [\"\ud83e\udd55\", \"\ud83c\udf4e\", \"\ud83c\udf5e\", \"\ud83c\udf70\"], correcta: \"\ud83c\udf4e\", explicacion: \"Alimentos.\" },\r\n    { pregunta: \"cero, dos, cuatro, seis, ?\", opciones: [\"ocho\", \"nueve\", \"diez\", \"doce\"], correcta: \"ocho\", explicacion: \"Pares.\" },\r\n    { pregunta: \"Domingo, Martes, Jueves, ?\", opciones: [\"Viernes\", \"S\u00e1bado\", \"Lunes\", \"Mi\u00e9rcoles\"], correcta: \"S\u00e1bado\", explicacion: \"D\u00edas alternos.\" }\r\n  ];\r\n\r\n  \/\/ \u042f\u0434\u0440\u043e (\u043f\u0440\u044f\u0447\u0435\u043c \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430) + \u043e\u0432\u0435\u0440\u043b\u0435\u0439 \u043e\u0442\u0441\u0447\u0451\u0442\u0430 \u0438 \u0444\u0438\u043a\u0441 \u0448\u0438\u0440\u0438\u043d\u044b\/\u0433\u043e\u0440\u0441\u043a\u0440\u043e\u043b\u043b\u0430\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .zg-root,.zg-core-host,[data-zg-core-host]{ overflow-x:hidden; } \/* \u0431\u0435\u0437 \u0433\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043a\u0440\u043e\u043b\u043b\u0430 *\/\r\n      #seq-wrap{ width:min(920px,85vw); margin:0 auto; display:none; }\r\n      #seq-q{ text-align:center; font-size:2rem; font-weight:800; color:#0f172a; margin:6px 0 18px; overflow-wrap:anywhere; }\r\n      #seq-opts{ display:grid; gap:14px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); align-items:stretch; justify-items:center; }\r\n      #seq-opts button{\r\n        width:100%; min-height:64px; padding:14px 10px; border:2px solid #93c5fd; border-radius:14px;\r\n        background:#fff; color:#0f172a; font-weight:900; cursor:pointer; transition:.12s; box-shadow:0 1px 6px #0000000d;\r\n        white-space:normal; overflow-wrap:anywhere; text-align:center; line-height:1.15;\r\n      }\r\n      #seq-opts button:disabled{ opacity:.9; }\r\n      #seq-msg{ text-align:center; min-height:44px; margin-top:12px; color:#334155; font-size:1.06em; }\r\n\r\n      \/* \u0441\u0447\u0451\u0442\u0447\u0438\u043a 3-2-1 \u043f\u043e\u0432\u0435\u0440\u0445 *\/\r\n      #seq-count{ position:fixed; inset:0; display:none; align-items:center; justify-content:center; z-index:9999; }\r\n      #seq-count .bubble{ background:#ffffffee; border-radius:16px; padding:12px 28px; font-size:2.1em; font-weight:900; color:#0b3a47;\r\n                          box-shadow:0 6px 28px rgba(0,0,0,.18); }\r\n    <\/style>\r\n\r\n    <div id=\"seq-wrap\">\r\n      <div id=\"seq-q\"><\/div>\r\n      <div id=\"seq-opts\"><\/div>\r\n      <div id=\"seq-msg\" aria-live=\"polite\"><\/div>\r\n    <\/div>\r\n    <div id=\"seq-count\"><div class=\"bubble\" id=\"seq-count-num\">3<\/div><\/div>\r\n  `;\r\n\r\n  \/\/ \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b\r\n  const wrap   = game.coreEl.querySelector('#seq-wrap');\r\n  const qEl    = game.coreEl.querySelector('#seq-q');\r\n  const oEl    = game.coreEl.querySelector('#seq-opts');\r\n  const msgEl  = game.coreEl.querySelector('#seq-msg');\r\n  const cntBox = game.coreEl.querySelector('#seq-count');\r\n  const cntNum = game.coreEl.querySelector('#seq-count-num');\r\n\r\n  \/\/ \u0431\u0435\u0439\u0434\u0436 \"Nivel\" \u0432 HUD\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('[data-seq-nivel]');\r\n  const setNivelBadge = (curr,total)=>{\r\n    if (!scoreBox) return;\r\n    if (!lvlBadge){\r\n      lvlBadge = document.createElement('span');\r\n      lvlBadge.setAttribute('data-seq-nivel','');\r\n      lvlBadge.style.cssText='margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n      scoreBox.appendChild(lvlBadge);\r\n    }\r\n    lvlBadge.textContent = `N: ${curr}\/${total}`;\r\n  };\r\n\r\n  \/\/ \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\r\n  let order=[], idx=0;\r\n  const shuffle = a => a.sort(()=>Math.random()-0.5);\r\n  const wait = ms => new Promise(r=>setTimeout(r,ms));\r\n  const looksEmoji = s => \/[\\p{Extended_Pictographic}]\/u.test(s);\r\n\r\n  \/\/ \u0443\u0442\u0438\u043b\u0438\u0442\u044b\r\n  const paintMsg = html => { msgEl.innerHTML = html || ''; };\r\n\r\n  async function countdown3(){\r\n    cntBox.style.display='flex';\r\n    for (let n=7;n>=1;n--){ cntNum.textContent=String(n); sfx.beep(4-n); await wait(800); }\r\n    cntNum.textContent='\u00a1Vamos!'; sfx.ready(); await wait(450);\r\n    cntBox.style.display='none';\r\n  }\r\n\r\n  function renderQuestion(){\r\n    if (wrap.style.display==='none') wrap.style.display='block';\r\n\r\n    const q = POOL[ order[idx] ];\r\n    qEl.textContent = q.pregunta;\r\n    setNivelBadge(idx+1, order.length);\r\n    oEl.innerHTML = '';\r\n\r\n    const opts = q.opciones.slice(); shuffle(opts);\r\n    opts.forEach(op=>{\r\n      const b=document.createElement('button');\r\n      b.textContent = op;\r\n      if (looksEmoji(op)) b.style.fontSize='1.8rem';\r\n      b.onclick = async ()=>{\r\n        sfx.click();\r\n        [...oEl.children].forEach(x=>x.disabled=true);\r\n\r\n        const correct = (op === q.correcta);\r\n        if (correct){\r\n          game.addScore(10);\r\n          b.style.background='#dcfce7'; b.style.borderColor='#86efac';\r\n          paintMsg(`<span style=\"color:#16a34a;font-weight:700;\">\u00a1Correcto! (+10)<\/span><br>${q.explicacion}`);\r\n          sfx.ok();\r\n        } else {\r\n          b.style.background='#fee2e2'; b.style.borderColor='#fca5a5';\r\n          game.setScore(Math.max(0, game.score-5));\r\n          \/\/ \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u0438\u043c \u0432\u0435\u0440\u043d\u044b\u0439\r\n          [...oEl.children].forEach(x=>{ if (x.textContent===q.correcta){ x.style.background='#dcfce7'; x.style.borderColor='#86efac'; } });\r\n          paintMsg(`<span style=\"color:#dc2626;font-weight:700;\">Incorrecto (\u22125)<\/span><br>${q.explicacion}`);\r\n          sfx.fail();\r\n        }\r\n\r\n        \/\/ \u0432\u043c\u0435\u0441\u0442\u043e \u043a\u043d\u043e\u043f\u043a\u0438 \"Continuar\" \u2014 \u0430\u0432\u0442\u043e-\u043e\u0442\u0441\u0447\u0451\u0442 \u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439\r\n        await countdown3();\r\n        nextStep();\r\n      };\r\n      oEl.appendChild(b);\r\n    });\r\n\r\n    \/\/ \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0438\u0433\u0440\u044b \u0432\u0435\u0440\u0445\u043d\u044e\u044e \u043f\u043e\u043b\u043e\u0441\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\r\n    game.setMessage('');\r\n  }\r\n\r\n  function nextStep(){\r\n    idx++;\r\n    if (idx >= order.length){\r\n      \/\/ (\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 \u043a\u0430\u043a \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0438\u0433\u0440\u0430\u0445)\r\n      game.endGame({save:true, messageHTML:`<b>\u00a1Juego terminado!<\/b> Puntaje total: <b>${game.score}<\/b>.`});\r\n    } else {\r\n      paintMsg('');\r\n      renderQuestion();\r\n    }\r\n  }\r\n\r\n  \/\/ HUD\r\n  game.onStart(async ()=>{\r\n    sfx.unlock();\r\n    game.showStart(false);\r\n    game.setScore(0);\r\n\r\n    order = Array.from({length: POOL.length}, (_,i)=>i);\r\n    shuffle(order);\r\n    idx = 0;\r\n\r\n    wrap.style.display='block';\r\n    paintMsg('');\r\n    game.startTimer();\r\n\r\n    \/\/ \u043e\u0442\u0441\u0447\u0451\u0442 \u043f\u0435\u0440\u0435\u0434 \u043f\u0435\u0440\u0432\u044b\u043c \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u043c\r\n    await countdown3();\r\n    renderQuestion();\r\n  });\r\n\r\n  \/\/ \"Continuar\" \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-338f321 elementor-widget elementor-widget-html\" data-id=\"338f321\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Clasificacion(){\r\n  const game = crearMarcoJuego({ gameId: 13, titulo: 'Clasificaci\u00f3n', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar<\/b><br>\r\n    Toca un elemento y luego una tarjeta de categor\u00eda para soltarlo\r\n    (en ordenador tambi\u00e9n puedes arrastrar).<br>\r\n    Correcto: <b>+10<\/b> por ronda \u00b7 Error inmediato: <b>\u22122<\/b>.\r\n  `);\r\n  game.showContinue(false);\r\n\r\n  \/\/ \u2500\u2500\u2500 SFX (\u0441 \u0444\u043e\u043b\u043b\u0431\u0435\u043a\u043e\u043c) \u2500\u2500\u2500\r\n  const tone=(f=660,ms=120,type='sine',vol=.2)=>{\r\n    try{\r\n      if (window.SFX?.tone){ SFX.tone(f,ms,type,vol); return; }\r\n      const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;\r\n      tone.ctx=tone.ctx||new AC();\r\n      const c=tone.ctx,o=c.createOscillator(),g=c.createGain();\r\n      o.type=type; o.frequency.value=f; g.gain.value=vol; o.connect(g); g.connect(c.destination);\r\n      o.start(); setTimeout(()=>{ try{o.stop();o.disconnect();g.disconnect();}catch(e){}; },ms);\r\n    }catch(e){}\r\n  };\r\n  const sfx={\r\n    unlock(){ try{ SFX?.unlock?.(); }catch(e){} },\r\n    click(){  SFX?.click?SFX.click():tone(760,70,'square',.16); },\r\n    ok(){     SFX?.success?SFX.success():(tone(980,110,'sine',.22),setTimeout(()=>tone(1240,120,'sine',.22),110)); },\r\n    fail(){   SFX?.fail?SFX.fail():tone(220,170,'sawtooth',.24); },\r\n    beep(n){  SFX?.beep?SFX.beep():tone(520+n*140,110,'sine',.22); },\r\n    ready(){  SFX?.ready?SFX.ready():tone(980,140,'triangle',.22); }\r\n  };\r\n\r\n  \/\/ \u0422\u0432\u043e\u0439 POOL \u2014 \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439\r\n  const POOL = [\r\n    { pregunta:\"Clasifica los objetos en la categor\u00eda correcta:\", elementos:[\"\ud83c\udf4f\",\"\ud83c\udf4c\",\"\ud83e\udd66\",\"\ud83c\udf5e\"], categorias:[ {nombre:\"Fruta\",items:[\"\ud83c\udf4f\",\"\ud83c\udf4c\"]},{nombre:\"Verdura\",items:[\"\ud83e\udd66\"]},{nombre:\"Pan\",items:[\"\ud83c\udf5e\"]} ], explicacion:\"\ud83c\udf4f y \ud83c\udf4c son frutas, \ud83e\udd66 es verdura, \ud83c\udf5e es pan.\" },\r\n    { pregunta:\"Arrastra los animales a su grupo:\", elementos:[\"\ud83d\udc36\",\"\ud83d\udc31\",\"\ud83d\udc20\",\"\ud83d\udc26\"], categorias:[ {nombre:\"Mam\u00edfero\",items:[\"\ud83d\udc36\",\"\ud83d\udc31\"]},{nombre:\"Ave\",items:[\"\ud83d\udc26\"]},{nombre:\"Pez\",items:[\"\ud83d\udc20\"]} ], explicacion:\"\ud83d\udc36 y \ud83d\udc31 son mam\u00edferos, \ud83d\udc26 es ave, \ud83d\udc20 es pez.\" },\r\n    { pregunta:\"Clasifica los transportes seg\u00fan su tipo:\", elementos:[\"\ud83d\ude97\",\"\ud83d\udeb4\",\"\ud83d\ude86\",\"\ud83d\udea2\"], categorias:[ {nombre:\"Terrestre\",items:[\"\ud83d\ude97\",\"\ud83d\udeb4\",\"\ud83d\ude86\"]},{nombre:\"Mar\u00edtimo\",items:[\"\ud83d\udea2\"]} ], explicacion:\"\ud83d\ude97, \ud83d\udeb4 y \ud83d\ude86 son terrestres, \ud83d\udea2 es mar\u00edtimo.\" },\r\n    { pregunta:\"Clasifica los alimentos:\", elementos:[\"\ud83e\udd69\",\"\ud83c\udf4e\",\"\ud83e\udd55\",\"\ud83c\udf6a\"], categorias:[ {nombre:\"Carne\",items:[\"\ud83e\udd69\"]},{nombre:\"Fruta\",items:[\"\ud83c\udf4e\"]},{nombre:\"Verdura\",items:[\"\ud83e\udd55\"]},{nombre:\"Dulce\",items:[\"\ud83c\udf6a\"]} ], explicacion:\"\ud83e\udd69 es carne, \ud83c\udf4e es fruta, \ud83e\udd55 es verdura, \ud83c\udf6a es dulce.\" },\r\n    { pregunta:\"Agrupa los objetos seg\u00fan su uso:\", elementos:[\"\ud83e\udea5\",\"\ud83c\udf7d\ufe0f\",\"\ud83d\udecf\ufe0f\",\"\ud83d\udebd\"], categorias:[ {nombre:\"Higiene\",items:[\"\ud83e\udea5\",\"\ud83d\udebd\"]},{nombre:\"Dormir\",items:[\"\ud83d\udecf\ufe0f\"]},{nombre:\"Comida\",items:[\"\ud83c\udf7d\ufe0f\"]} ], explicacion:\"\ud83e\udea5 y \ud83d\udebd son para la higiene, \ud83d\udecf\ufe0f para dormir, \ud83c\udf7d\ufe0f para comer.\" },\r\n    { pregunta:\"Clasifica los objetos del clima:\", elementos:[\"\u2600\ufe0f\",\"\ud83c\udf27\ufe0f\",\"\u2744\ufe0f\",\"\ud83c\udf2c\ufe0f\"], categorias:[ {nombre:\"Soleado\",items:[\"\u2600\ufe0f\"]},{nombre:\"Lluvia\",items:[\"\ud83c\udf27\ufe0f\"]},{nombre:\"Nieve\",items:[\"\u2744\ufe0f\"]},{nombre:\"Viento\",items:[\"\ud83c\udf2c\ufe0f\"]} ], explicacion:\"\u2600\ufe0f sol, \ud83c\udf27\ufe0f lluvia, \u2744\ufe0f nieve, \ud83c\udf2c\ufe0f viento.\" },\r\n    { pregunta:\"Agrupa los instrumentos musicales:\", elementos:[\"\ud83e\udd41\",\"\ud83c\udfb8\",\"\ud83c\udfbb\",\"\ud83c\udfa4\"], categorias:[ {nombre:\"Cuerda\",items:[\"\ud83c\udfb8\",\"\ud83c\udfbb\"]},{nombre:\"Percusi\u00f3n\",items:[\"\ud83e\udd41\"]},{nombre:\"Voz\",items:[\"\ud83c\udfa4\"]} ], explicacion:\"\ud83c\udfb8 y \ud83c\udfbb son de cuerda, \ud83e\udd41 percusi\u00f3n, \ud83c\udfa4 la voz.\" },\r\n    { pregunta:\"Clasifica seg\u00fan el color:\", elementos:[\"\ud83c\udf45\",\"\ud83c\udf4b\",\"\ud83c\udf4f\",\"\ud83c\udf46\"], categorias:[ {nombre:\"Rojo\",items:[\"\ud83c\udf45\"]},{nombre:\"Amarillo\",items:[\"\ud83c\udf4b\"]},{nombre:\"Verde\",items:[\"\ud83c\udf4f\"]},{nombre:\"Morado\",items:[\"\ud83c\udf46\"]} ], explicacion:\"\ud83c\udf45 rojo, \ud83c\udf4b amarillo, \ud83c\udf4f verde, \ud83c\udf46 morado.\" },\r\n    { pregunta:\"Clasifica los animales seg\u00fan d\u00f3nde viven:\", elementos:[\"\ud83d\udc2c\",\"\ud83d\udc13\",\"\ud83d\udc3b\",\"\ud83d\udc1f\"], categorias:[ {nombre:\"Mar\",items:[\"\ud83d\udc2c\",\"\ud83d\udc1f\"]},{nombre:\"Tierra\",items:[\"\ud83d\udc3b\"]},{nombre:\"Granja\",items:[\"\ud83d\udc13\"]} ], explicacion:\"\ud83d\udc2c y \ud83d\udc1f mar, \ud83d\udc3b tierra, \ud83d\udc13 granja.\" },\r\n    { pregunta:\"Agrupa los objetos seg\u00fan la \u00e9poca del a\u00f1o:\", elementos:[\"\ud83c\udf83\",\"\ud83c\udf85\",\"\ud83c\udf3b\",\"\ud83d\udc23\"], categorias:[ {nombre:\"Primavera\",items:[\"\ud83c\udf3b\",\"\ud83d\udc23\"]},{nombre:\"Oto\u00f1o\",items:[\"\ud83c\udf83\"]},{nombre:\"Invierno\",items:[\"\ud83c\udf85\"]} ], explicacion:\"\ud83c\udf3b y \ud83d\udc23 primavera, \ud83c\udf83 oto\u00f1o, \ud83c\udf85 invierno.\" }\r\n  ];\r\n\r\n  \/\/ \u2500\u2500\u2500 UI \u2500\u2500\u2500\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .zg-root,.zg-core-host,[data-zg-core-host]{ overflow-x:hidden; }\r\n      .cl-wrap{width:min(980px,85vw);margin:0 auto;display:none}\r\n      .cl-title{font-weight:900;color:#0f172a;text-align:center;margin:2px 0 8px;font-size:1rem;}\r\n      .cl-card{background:#fff;border:2px solid #c7e9f1;border-radius:12px;padding:8px;box-shadow:0 1px 8px #0001;}\r\n      .cl-label{font-weight:800;color:#0f172a;text-align:center;margin-bottom:4px;font-size:.95rem;}\r\n      .cl-zone{min-height:88px;padding:10px;border:2px dashed #8fd2ee;border-radius:10px;\r\n               background:#f0f8ff;display:flex;flex-wrap:wrap;gap:6px;align-items:flex-start;justify-content:center;transition:.12s}\r\n      .cl-item{display:inline-flex;align-items:center;justify-content:center;\r\n               min-width:54px;min-height:54px;padding:6px 10px;font-size:1.9rem;font-weight:800;\r\n               background:#fff;border:2px solid #93c5fd;border-radius:10px;user-select:none;\r\n               touch-action:manipulation;box-shadow:0 1px 6px #0000000d;cursor:pointer;transition:.12s}\r\n      .cl-item.sel{outline:4px solid #0ea5e9;outline-offset:2px}\r\n      .cl-item.lock{background:#eafff4;border-color:#86efac;cursor:default}\r\n      .cl-grid{display:grid;gap:10px;margin-top:10px;grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}\r\n      .cl-hide-instr .zg-panel .zg-msg{display:none!important}\r\n      @media (max-width:520px){\r\n        .cl-item{min-width:50px;min-height:50px;font-size:1.7rem}\r\n        .cl-zone{min-height:80px}\r\n      }\r\n      \/* \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u043f\u043e\u0434 \u043f\u0430\u043d\u0435\u043b\u044c\u044e Puntos \u2014 \u043d\u0435 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 *\/\r\n      #cl-count{position:fixed; top:96px; left:50%; transform:translateX(-50%);\r\n                display:none; align-items:center; justify-content:center; z-index:1000; pointer-events:none}\r\n      #cl-count .bubble{background:#ffffffee;border-radius:16px;padding:10px 22px;font-size:1.8em;font-weight:900;color:#0b3a47;box-shadow:0 6px 28px rgba(0,0,0,.18)}\r\n    <\/style>\r\n    <div class=\"cl-wrap\" id=\"cl-wrap\">\r\n      <div id=\"cl-q\" class=\"cl-title\"><\/div>\r\n      <div class=\"cl-card\"><div id=\"cl-bag\" class=\"cl-zone\" aria-label=\"Sin clasificar\"><\/div><\/div>\r\n      <div id=\"cl-cats\" class=\"cl-grid\"><\/div>\r\n    <\/div>\r\n    <div id=\"cl-count\"><div class=\"bubble\" id=\"cl-count-num\">3<\/div><\/div>\r\n  `;\r\n\r\n  \/\/ \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b\r\n  const rootEl = game.coreEl.closest('.zg-root');\r\n  const wrapEl = document.getElementById('cl-wrap');\r\n  const qEl    = document.getElementById('cl-q');\r\n  const bagEl  = document.getElementById('cl-bag');\r\n  const catsEl = document.getElementById('cl-cats');\r\n  const cntBox = document.getElementById('cl-count');\r\n  const cntNum = document.getElementById('cl-count-num');\r\n\r\n  \/\/ \u0431\u0435\u0439\u0434\u0436 \u00abNivel\u00bb \u0441\u043f\u0440\u0430\u0432\u0430 \u043e\u0442 \u00abPuntos\u00bb\r\n  const scoreBox = rootEl?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('[data-cl-nivel]');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.setAttribute('data-cl-nivel','');\r\n    lvlBadge.style.cssText = 'margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    lvlBadge.textContent = 'N: 1\/'+POOL.length;\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setNivelBadge = (n, total)=>{ if (lvlBadge) lvlBadge.textContent = `N: ${n}\/${total}`; };\r\n\r\n  const isTouch = ('ontouchstart' in window) || navigator.maxTouchPoints>0 || matchMedia('(pointer:coarse)').matches;\r\n\r\n  \/\/ state\r\n  let order=[], idx=0, selected=null, lockCount=0, expectMap=null, totalThisRound=0;\r\n  const shuffle = a => a.sort(()=>Math.random()-0.5);\r\n  const deselect = ()=>{ if(selected){ selected.classList.remove('sel'); selected=null; } };\r\n  const wait = ms => new Promise(r=>setTimeout(r,ms));\r\n\r\n  async function countdown3(){\r\n    cntBox.style.display='flex';\r\n    for(let n=7;n>=1;n--){ cntNum.textContent=String(n); sfx.beep(4-n); await wait(800); }\r\n    cntNum.textContent='\u00a1Vamos!'; sfx.ready(); await wait(450);\r\n    cntBox.style.display='none';\r\n  }\r\n\r\n  function checkWin(){\r\n    if (lockCount !== totalThisRound) return false;\r\n    if (bagEl.children.length !== 0)  return false;\r\n    return true;\r\n  }\r\n\r\n  function makeItem(sym){\r\n    const el=document.createElement('div');\r\n    el.className='cl-item';\r\n    el.textContent=sym;\r\n    el.dataset.item=sym;\r\n\r\n    el.addEventListener('click', e=>{\r\n      e.stopPropagation();\r\n      if (el.classList.contains('lock')) return;\r\n      sfx.click();\r\n      if (selected===el){ deselect(); return; }\r\n      deselect(); selected=el; el.classList.add('sel');\r\n    });\r\n\r\n    if (!isTouch){\r\n      el.setAttribute('draggable','true');\r\n      el.addEventListener('dragstart', e=>{ selected=el; e.dataTransfer.setData('text\/plain', sym); el.style.opacity=.6; });\r\n      el.addEventListener('dragend',   ()=>{ el.style.opacity=''; });\r\n    }\r\n    return el;\r\n  }\r\n\r\n  function placeSelectedIn(zone, q){\r\n    if (!selected) return;\r\n\r\n    if (zone === bagEl){\r\n      zone.appendChild(selected);\r\n      selected.classList.remove('sel');\r\n      selected = null;\r\n      return;\r\n    }\r\n\r\n    const catName = zone.dataset.cat;\r\n    const sym     = selected.dataset.item;\r\n    const expectCat = expectMap.get(sym);\r\n\r\n    if (expectCat === catName){\r\n      selected.classList.remove('sel');\r\n      selected.classList.add('lock');\r\n      selected.draggable = false;\r\n      zone.appendChild(selected);\r\n      selected = null;\r\n      sfx.ok();\r\n      lockCount++;\r\n\r\n      if (checkWin()){\r\n        game.addScore(10);\r\n        \/\/ \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u0435 \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u0432\u0435\u0440\u0445\u0443 \u0432\u043c\u0435\u0441\u0442\u043e \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u2014 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f\r\n        qEl.innerHTML = `<span style=\"color:#16a34a;font-weight:700;\">\u00a1Correcto! (+10)<\/span> ${q.explicacion}`;\r\n        (async()=>{ await countdown3(); nextRound(); })();\r\n      }\r\n    }else{\r\n      game.setScore(Math.max(0, game.score-2));\r\n      sfx.fail();\r\n      selected.style.transform='translateY(-3px)'; selected.style.filter='contrast(1.15)';\r\n      setTimeout(()=>{ selected.style.transform=''; selected.style.filter=''; bagEl.appendChild(selected); },120);\r\n      selected.classList.remove('sel');\r\n      selected = null;\r\n    }\r\n  }\r\n\r\n  function makeDropZone(zone, q){\r\n    zone.addEventListener('click', ()=> placeSelectedIn(zone, q));\r\n    if (!isTouch){\r\n      zone.addEventListener('dragover', e=>{ e.preventDefault(); zone.style.background='#e9fff5'; });\r\n      zone.addEventListener('dragleave',   ()=>{ zone.style.background='#f0f8ff'; });\r\n      zone.addEventListener('drop', e=>{\r\n        e.preventDefault();\r\n        zone.style.background='#f0f8ff';\r\n        placeSelectedIn(zone, q);\r\n      });\r\n    }\r\n  }\r\n\r\n  function renderRound(){\r\n    const q = POOL[ order[idx] ];\r\n    qEl.textContent = q.pregunta;\r\n    setNivelBadge(idx+1, order.length);\r\n\r\n    expectMap = new Map();\r\n    q.categorias.forEach(c=> c.items.forEach(sym=> expectMap.set(sym, c.nombre)));\r\n    lockCount = 0;\r\n    totalThisRound = q.elementos.length;\r\n\r\n    bagEl.innerHTML='';\r\n    makeDropZone(bagEl, q);\r\n    const items = q.elementos.slice(); shuffle(items);\r\n    items.forEach(sym=> bagEl.appendChild( makeItem(sym) ));\r\n\r\n    catsEl.innerHTML='';\r\n    const cats = q.categorias.map(c=>({nombre:c.nombre, items:c.items.slice()}));\r\n    shuffle(cats).forEach(cat=>{\r\n      const card=document.createElement('div'); card.className='cl-card';\r\n      const lab = document.createElement('div'); lab.className='cl-label'; lab.textContent=cat.nombre;\r\n      const dz  = document.createElement('div'); dz.className='cl-zone'; dz.dataset.cat=cat.nombre;\r\n      makeDropZone(dz, q);\r\n      card.addEventListener('click', ()=> placeSelectedIn(dz, q));\r\n      card.appendChild(lab); card.appendChild(dz);\r\n      catsEl.appendChild(card);\r\n    });\r\n\r\n    game.setMessage('');\r\n  }\r\n\r\n  function nextRound(){\r\n    idx++;\r\n    if (idx >= order.length){\r\n      game.endGame({save:true, messageHTML:`<b>\u00a1Juego terminado!<\/b> Puntos: <b>${game.score}<\/b>.`});\r\n    } else {\r\n      renderRound();\r\n    }\r\n  }\r\n\r\n  \/\/ HUD\r\n  game.onStart(()=>{\r\n    sfx.unlock();\r\n    rootEl?.classList.add('cl-hide-instr');\r\n    game.showStart(false);\r\n    game.setScore(0);\r\n\r\n    wrapEl.style.display='block';\r\n    order = Array.from({length: POOL.length}, (_,i)=>i); shuffle(order);\r\n    idx = 0;\r\n    renderRound();\r\n    game.startTimer();\r\n  });\r\n\r\n  \/\/ \u00abContinuar\u00bb \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-906fdd9 elementor-widget elementor-widget-html\" data-id=\"906fdd9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_ReaccionRapida(){\r\n  const game = crearMarcoJuego({ gameId: 14, titulo: 'Reacci\u00f3n r\u00e1pida', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u2500\u2500 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u2500\u2500\r\n  const NIVELES = 10;              \/\/ \u0431\u044b\u043b\u043e RONDAS\r\n  const READ_SECONDS = 5;\r\n  const PAUSA_RESULTADO = 900;     \/\/ \u043a\u043e\u0440\u043e\u0442\u043a\u0430\u044f \u043f\u0430\u0443\u0437\u0430 \u043f\u0435\u0440\u0435\u0434 3-2-1\r\n  const NO_TOCAR_MIN = 2200;\r\n  const NO_TOCAR_MAX = 3600;\r\n\r\n  \/\/ \u2500\u2500 \u0434\u0430\u043d\u043d\u044b\u0435 \u2500\u2500\r\n  const COLORES = [\r\n    {nombre:'rojo',    color:'#e74c3c'},\r\n    {nombre:'azul',    color:'#3498db'},\r\n    {nombre:'amarillo',color:'#f1c40f'},\r\n    {nombre:'verde',   color:'#27ae60'},\r\n  ];\r\n  const NUMS = ['1','2','3','4'];\r\n  const FIGS = [\r\n    {nombre:'C\u00edrculo',   emoji:'\u26aa\ufe0f'},\r\n    {nombre:'Cuadrado',  emoji:'\u2b1c\ufe0f'},\r\n    {nombre:'Tri\u00e1ngulo', emoji:'\ud83d\udd3a'},\r\n    {nombre:'Estrella',  emoji:'\u2b50\ufe0f'},\r\n  ];\r\n  const TIPOS = ['color','numero','figura','no-tocar'];\r\n\r\n  \/\/ \u2500\u2500 SFX (+ \u0444\u043e\u043b\u043b\u0431\u0435\u043a) \u2500\u2500\r\n  const tone=(f=660,ms=120,type='sine',vol=.2)=>{\r\n    try{\r\n      if (window.SFX?.tone){ SFX.tone(f,ms,type,vol); return; }\r\n      const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;\r\n      tone.ctx=tone.ctx||new AC();\r\n      const c=tone.ctx,o=c.createOscillator(),g=c.createGain();\r\n      o.type=type; o.frequency.value=f; g.gain.value=vol; o.connect(g); g.connect(c.destination);\r\n      o.start(); setTimeout(()=>{ try{o.stop();o.disconnect();g.disconnect();}catch(e){}; },ms);\r\n    }catch(e){}\r\n  };\r\n  const sfx={\r\n    unlock(){ try{ SFX?.unlock?.(); }catch(e){} },\r\n    bg(kind){ try{ SFX?.musicLoop?.(kind||'quiz'); }catch(e){} },\r\n    bgStop(){ try{ SFX?.musicStop?.(); SFX?.loopStop?.('*'); }catch(e){} },\r\n    tick(){   SFX?.ui?SFX.ui():tone(820,70,'square',.14); },\r\n    whoosh(){ SFX?.whoosh?SFX.whoosh():tone(520,90,'sawtooth',.12); },\r\n    click(){  SFX?.click?SFX.click():tone(760,70,'square',.16); },\r\n    ok(){     SFX?.success?SFX.success():(tone(980,110,'sine',.22),setTimeout(()=>tone(1240,120,'sine',.22),110)); },\r\n    fail(){   SFX?.fail?SFX.fail():tone(240,170,'sawtooth',.24); },\r\n    beep(n){  SFX?.beep?SFX.beep():tone(520+n*140,110,'sine',.22); },\r\n    ready(){  SFX?.ready?SFX.ready():tone(980,140,'triangle',.22); },\r\n    sparkle(){SFX?.sparkle?SFX.sparkle():tone(1100,90,'sine',.18); }\r\n  };\r\n\r\n  \/\/ \u2500\u2500 \u0432\u0435\u0440\u0441\u0442\u043a\u0430 \u2500\u2500\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .zg-root,.zg-core-host,[data-zg-core-host]{ overflow-x:hidden; }\r\n      #rr-wrap{ display:none; width:min(920px,96vw); margin:0 auto; text-align:center }\r\n      .rr-card{ max-width:740px; margin:0 auto; background:#fff; border:2px solid #dbeafe; border-radius:16px; padding:14px 16px; box-shadow:0 2px 10px #0001 }\r\n      .rr-text{ font-size:1.18em; line-height:1.35; color:#0f172a }\r\n      .rr-count{ margin-top:8px; font-size:2.2rem; font-weight:900; color:#0ea5e9 }\r\n      .rr-btns{ display:flex; flex-wrap:wrap; gap:12px; justify-content:center; margin-top:10px }\r\n      .rr-status{ font-size:1.04em; min-height:2em; margin-top:12px }\r\n      \/* \u0441\u0447\u0451\u0442\u0447\u0438\u043a \u043c\u0435\u0436\u0434\u0443 \u0443\u0440\u043e\u0432\u043d\u044f\u043c\u0438 \u2014 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u0439, \u043f\u043e\u0434 HUD, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0435\u0442 *\/\r\n      #rr-next { position:fixed; top:96px; left:50%; transform:translateX(-50%); display:none; align-items:center; justify-content:center; z-index:1000; pointer-events:none }\r\n      #rr-next .bubble{ background:#ffffffee; border-radius:16px; padding:10px 22px; font-size:1.8em; font-weight:900; color:#0b3a47; box-shadow:0 6px 28px rgba(0,0,0,.18) }\r\n    <\/style>\r\n    <div id=\"rr-wrap\">\r\n      <div class=\"rr-card\">\r\n        <div id=\"rr-instr-text\" class=\"rr-text\"><\/div>\r\n        <div id=\"rr-countdown\" class=\"rr-count\" aria-live=\"polite\"><\/div>\r\n      <\/div>\r\n      <div id=\"rr-buttons\" class=\"rr-btns\"><\/div>\r\n      <div id=\"rr-status\" class=\"rr-status\"><\/div>\r\n    <\/div>\r\n    <div id=\"rr-next\"><div class=\"bubble\" id=\"rr-next-num\">3<\/div><\/div>\r\n  `;\r\n\r\n  \/\/ \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u0430\u044f \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    En cada <b>nivel<\/b> lee la instrucci\u00f3n (p. ej. \u00abpulsa el color azul\u00bb). Tienes\r\n    <b>${READ_SECONDS}s<\/b> para leer; el contador est\u00e1 debajo. Despu\u00e9s aparecer\u00e1n los botones.<br>\r\n    A veces ver\u00e1s <b style=\"color:#b91c1c\">\u00ab\u00a1No pulses ning\u00fan bot\u00f3n!\u00bb<\/b>: espera sin tocar nada.<br>\r\n    Niveles: <b>${NIVELES}<\/b>. Pulsa <b>Empezar<\/b> para iniciar.\r\n  `);\r\n\r\n  \/\/ \u0431\u0435\u0439\u0434\u0436 \u00abNivel\u00bb \u0432 HUD\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('[data-rr-nivel]');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.setAttribute('data-rr-nivel','');\r\n    lvlBadge.style.cssText='margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    lvlBadge.textContent = 'N: 1\/'+NIVELES;\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setNivelHUD = (n)=>{ if (lvlBadge) lvlBadge.textContent = `N: ${n+1}\/${NIVELES}`; };\r\n\r\n  \/\/ \u0443\u0442\u0438\u043b\u0438\u0442\u044b\r\n  const isTouch = ('ontouchstart' in window) || navigator.maxTouchPoints>0 || matchMedia('(pointer:coarse)').matches;\r\n  const btnCssBase = `\r\n    font-weight:800;border:none;border-radius:16px;outline:none;cursor:pointer;\r\n    box-shadow:0 2px 12px #00000012;transition:.15s;touch-action:manipulation;\r\n  `;\r\n  const $  = sel => game.coreEl.querySelector(sel);\r\n  const $$ = sel => Array.from(game.coreEl.querySelectorAll(sel));\r\n  const shuffle = a => a.map(v=>[Math.random(),v]).sort((x,y)=>x[0]-y[0]).map(x=>x[1]);\r\n  const puntosPorReaccion = rt => Math.max(1, Math.min(20, Math.round((5000 - rt)\/200)));\r\n  const wait = (ms)=> new Promise(r=>setTimeout(r,ms));\r\n\r\n  let nivel = 0, roundLocked = false, appearTime = 0, tipoActual = '';\r\n  let readCountdownId = null;\r\n\r\n  function setInstr(html){ $('#rr-instr-text').innerHTML = html||''; }\r\n  function setCd(v){ $('#rr-countdown').textContent = v>0 ? v : ''; }\r\n  function setStatus(html){ $('#rr-status').innerHTML = html||''; }\r\n  function clearStatus(){ setStatus(''); }\r\n  function clearButtons(){ $('#rr-buttons').innerHTML = ''; }\r\n\r\n  function startReadCountdown(sec, onEnd){\r\n    clearInterval(readCountdownId);\r\n    let t = sec;\r\n    setCd(t);\r\n    sfx.bg('quiz');\r\n    readCountdownId = setInterval(()=>{\r\n      t--; setCd(t); sfx.tick();\r\n      if (t<=0){ clearInterval(readCountdownId); sfx.whoosh(); onEnd && onEnd(); }\r\n    }, 1000);\r\n  }\r\n\r\n  async function countdownNext(){\r\n    const box = $('#rr-next'), num = $('#rr-next-num');\r\n    box.style.display='flex';\r\n    for(let n=3; n>=1; n--){ num.textContent=String(n); sfx.beep(4-n); await wait(820); }\r\n    num.textContent='\u00a1Vamos!'; sfx.ready(); await wait(480);\r\n    box.style.display='none';\r\n  }\r\n\r\n  async function onAnswered(){\r\n    if (roundLocked) return;\r\n    roundLocked = true;\r\n    sfx.bgStop();\r\n    $$('#rr-buttons button').forEach(b=>b.disabled = true);\r\n    await wait(PAUSA_RESULTADO);\r\n    if (nivel < NIVELES-1){\r\n      await countdownNext();               \/\/ \u043e\u0442\u0441\u0447\u0451\u0442 \u0432\u043c\u0435\u0441\u0442\u043e Continue\r\n      nextRound();\r\n    } else {\r\n      nextRound();\r\n    }\r\n  }\r\n\r\n  function nextRound(){\r\n    nivel++;\r\n    if (nivel < NIVELES){\r\n      renderRound();\r\n    } else {\r\n      sfx.sparkle();\r\n      game.endGame({save:true, messageHTML:`<b>\u00a1Juego terminado!<\/b> Puntos: <b>${game.score}<\/b>.`});\r\n    }\r\n  }\r\n\r\n  function renderRound(){\r\n    roundLocked = false;\r\n    clearButtons(); clearStatus(); setCd(''); setNivelHUD(nivel);\r\n    tipoActual = TIPOS[Math.floor(Math.random()*TIPOS.length)];\r\n\r\n    if (tipoActual === 'color'){\r\n      const correctIdx = Math.floor(Math.random()*COLORES.length);\r\n      const correct = COLORES[correctIdx];\r\n      setInstr(`Pulsa el color <b style=\"color:${correct.color};text-transform:uppercase\">${correct.nombre}<\/b>.<br><small>Los botones aparecer\u00e1n cuando termine el conteo.<\/small>`);\r\n      startReadCountdown(READ_SECONDS, ()=>{\r\n        const cont = $('#rr-buttons'); clearButtons(); appearTime = Date.now(); sfx.whoosh();\r\n        shuffle(COLORES.map((c,i)=>({c,i}))).forEach(({c,i})=>{\r\n          const btn = document.createElement('button');\r\n          btn.textContent = c.nombre.toUpperCase();\r\n          btn.style.cssText = `\r\n            ${btnCssBase} color:#fff; background:${c.color};\r\n            font-size:${isTouch?'1.15rem':'1.05rem'}; padding:${isTouch?'16px 26px':'12px 20px'}; min-width:120px;\r\n          `;\r\n          btn.onpointerdown = ()=> sfx.click();\r\n          btn.onclick = ()=>{\r\n            if (roundLocked) return;\r\n            const rt = Date.now()-appearTime;\r\n            const ok = (i===correctIdx);\r\n            if (ok){ const add = puntosPorReaccion(rt); game.addScore(add); sfx.ok();\r\n              setStatus(`<span style=\"color:#16a34a;font-weight:700;\">\u00a1Correcto!<\/span> Tiempo: <b>${rt} ms<\/b> (+${add})`);\r\n            } else { sfx.fail(); setStatus(`<span style=\"color:#dc2626;font-weight:700;\">\u00a1Incorrecto!<\/span>`); }\r\n            onAnswered();\r\n          };\r\n          cont.appendChild(btn);\r\n        });\r\n      });\r\n\r\n    } else if (tipoActual === 'numero'){\r\n      const correctIdx = Math.floor(Math.random()*NUMS.length);\r\n      const correct = NUMS[correctIdx];\r\n      setInstr(`Pulsa el n\u00famero <b>${correct}<\/b>.<br><small>Los botones aparecer\u00e1n cuando termine el conteo.<\/small>`);\r\n      startReadCountdown(READ_SECONDS, ()=>{\r\n        const cont = $('#rr-buttons'); clearButtons(); appearTime = Date.now(); sfx.whoosh();\r\n        shuffle(NUMS.map((n,i)=>({n,i}))).forEach(({n,i})=>{\r\n          const btn = document.createElement('button');\r\n          btn.textContent = n;\r\n          btn.style.cssText = `\r\n            ${btnCssBase} background:#fff; color:#0f172a; border:2px solid #8ddfff;\r\n            font-size:${isTouch?'1.2rem':'1.05rem'}; padding:${isTouch?'16px 24px':'12px 18px'}; min-width:100px;\r\n          `;\r\n          btn.onpointerdown = ()=> sfx.click();\r\n          btn.onclick = ()=>{\r\n            if (roundLocked) return;\r\n            const rt = Date.now()-appearTime;\r\n            const ok = (i===correctIdx);\r\n            if (ok){ const add = puntosPorReaccion(rt); game.addScore(add); sfx.ok();\r\n              setStatus(`<span style=\"color:#16a34a;font-weight:700;\">\u00a1Correcto!<\/span> Tiempo: <b>${rt} ms<\/b> (+${add})`);\r\n            } else { sfx.fail(); setStatus(`<span style=\"color:#dc2626;font-weight:700;\">\u00a1Incorrecto!<\/span>`); }\r\n            onAnswered();\r\n          };\r\n          cont.appendChild(btn);\r\n        });\r\n      });\r\n\r\n    } else if (tipoActual === 'figura'){\r\n      const correctIdx = Math.floor(Math.random()*FIGS.length);\r\n      const correct = FIGS[correctIdx];\r\n      setInstr(`Pulsa el <b>${correct.nombre.toUpperCase()} ${correct.emoji}<\/b>.<br><small>Los botones aparecer\u00e1n cuando termine el conteo.<\/small>`);\r\n      startReadCountdown(READ_SECONDS, ()=>{\r\n        const cont = $('#rr-buttons'); clearButtons(); appearTime = Date.now(); sfx.whoosh();\r\n        shuffle(FIGS.map((f,i)=>({f,i}))).forEach(({f,i})=>{\r\n          const btn = document.createElement('button');\r\n          btn.innerHTML = `${f.emoji} <span style=\"font-size:.9em\">${f.nombre}<\/span>`;\r\n          btn.style.cssText = `\r\n            ${btnCssBase} background:#f3fcfa; color:#0f172a; border:2px solid #4ea4d9;\r\n            font-size:${isTouch?'1.2rem':'1.05rem'}; padding:${isTouch?'16px 24px':'12px 18px'}; min-width:130px;\r\n          `;\r\n          btn.onpointerdown = ()=> sfx.click();\r\n          btn.onclick = ()=>{\r\n            if (roundLocked) return;\r\n            const rt = Date.now()-appearTime;\r\n            const ok = (i===correctIdx);\r\n            if (ok){ const add = puntosPorReaccion(rt); game.addScore(add); sfx.ok();\r\n              setStatus(`<span style=\"color:#16a34a;font-weight:700;\">\u00a1Correcto!<\/span> Tiempo: <b>${rt} ms<\/b> (+${add})`);\r\n            } else { sfx.fail(); setStatus(`<span style=\"color:#dc2626;font-weight:700;\">\u00a1Incorrecto!<\/span>`); }\r\n            onAnswered();\r\n          };\r\n          cont.appendChild(btn);\r\n        });\r\n      });\r\n\r\n    } else { \/\/ no-tocar\r\n      setInstr(`<span style=\"color:#b91c1c;font-weight:700;\">\u00a1No pulses ning\u00fan bot\u00f3n!<\/span><br><small>Solo espera a que termine el conteo.<\/small>`);\r\n      startReadCountdown(READ_SECONDS, ()=>{\r\n        const cont = $('#rr-buttons'); clearButtons(); appearTime = Date.now(); sfx.whoosh();\r\n        shuffle(COLORES).forEach(col=>{\r\n          const btn = document.createElement('button');\r\n          btn.textContent = col.nombre.toUpperCase();\r\n          btn.style.cssText = `\r\n            ${btnCssBase} color:#fff; background:${col.color};\r\n            font-size:${isTouch?'1.05rem':'0.98rem'}; padding:${isTouch?'14px 22px':'10px 18px'}; min-width:110px; opacity:.9;\r\n          `;\r\n          btn.onpointerdown = ()=> sfx.click();\r\n          btn.onclick = ()=>{\r\n            if (roundLocked) return;\r\n            sfx.fail();\r\n            setStatus(`<span style=\"color:#dc2626;font-weight:700;\">\u00a1Incorrecto!<\/span> (No deb\u00edas tocar nada)`);\r\n            onAnswered();\r\n          };\r\n          cont.appendChild(btn);\r\n        });\r\n        const waitMs = NO_TOCAR_MIN + Math.floor(Math.random()*(NO_TOCAR_MAX-NO_TOCAR_MIN));\r\n        setTimeout(()=>{\r\n          if (!roundLocked){\r\n            game.addScore(3);\r\n            sfx.ok();\r\n            setStatus(`<span style=\"color:#16a34a;font-weight:700;\">\u00a1Correcto!<\/span> No tocaste nada (+3)`);\r\n            onAnswered();\r\n          }\r\n        }, waitMs);\r\n      });\r\n    }\r\n  }\r\n\r\n  \/\/ \u0441\u0442\u0430\u0440\u0442 HUD\r\n  game.onStart(()=>{\r\n    sfx.unlock();\r\n    game.showStart(false);\r\n    game.showContinue(false);\r\n    game.setScore(0);\r\n    document.getElementById('rr-wrap').style.display = 'block';\r\n    game.setMessage('');\r\n    nivel = 0;\r\n    setNivelHUD(nivel);\r\n    setInstr('Lee la instrucci\u00f3n. El contador est\u00e1 debajo.');\r\n    setCd(''); clearButtons(); clearStatus();\r\n    game.startTimer();\r\n    setTimeout(renderRound, 350);\r\n  });\r\n\r\n  \/\/ \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0444\u043e\u043d\u043e\u0432\u043e\u0439 \u043c\u0443\u0437\u044b\u043a\u0438 \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435\r\n  game.onExit?.(()=> sfx.bgStop());\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-43392c9 elementor-widget elementor-widget-html\" data-id=\"43392c9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_NumerosRapidos(){\r\n  const game = crearMarcoJuego({ gameId: 15, titulo: 'N\u00fameros r\u00e1pidos', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\r\n  const NIVELES = 10;           \/\/ \u0431\u044b\u043b\u043e RONDAS\r\n  const READ_SECONDS = 5;       \/\/ \u043e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043e\u0442\u0441\u0447\u0451\u0442 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f\r\n  const PAUSA_RESULTADO = 1500; \/\/ \u043f\u0430\u0443\u0437\u0430 \u043f\u043e\u0441\u043b\u0435 \u043e\u0442\u0432\u0435\u0442\u0430\r\n\r\n  \/\/ ===== SFX (SFX.* \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c, \u0438\u043d\u0430\u0447\u0435 WebAudio-\u0444\u043e\u043b\u043b\u0431\u0435\u043a)\r\n  const tone=(f=660,ms=120,type='sine',vol=.2)=>{\r\n    try{\r\n      if (window.SFX?.tone){ SFX.tone(f,ms,type,vol); return; }\r\n      const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;\r\n      tone.ctx=tone.ctx||new AC();\r\n      const c=tone.ctx,o=c.createOscillator(),g=c.createGain();\r\n      o.type=type; o.frequency.value=f; g.gain.value=vol; o.connect(g); g.connect(c.destination);\r\n      o.start(); setTimeout(()=>{ try{o.stop();o.disconnect();g.disconnect();}catch(e){}; },ms);\r\n    }catch(e){}\r\n  };\r\n  const sfx={\r\n    unlock(){ try{ SFX?.unlock?.(); }catch(e){} },\r\n    bg(kind){ try{ SFX?.musicLoop?.(kind||'quiz'); }catch(e){} },\r\n    bgStop(){ try{ SFX?.musicStop?.(); SFX?.loopStop?.('*'); }catch(e){} },\r\n    tick(){   SFX?.ui?SFX.ui():tone(820,70,'square',.14); },\r\n    whoosh(){ SFX?.whoosh?SFX.whoosh():tone(520,90,'sawtooth',.12); },\r\n    click(){  SFX?.click?SFX.click():tone(760,70,'square',.16); },\r\n    ok(){     SFX?.success?SFX.success():(tone(980,110,'sine',.22),setTimeout(()=>tone(1240,120,'sine',.22),110)); },\r\n    fail(){   SFX?.fail?SFX.fail():tone(240,170,'sawtooth',.24); },\r\n    ready(){  SFX?.ready?SFX.ready():tone(980,140,'triangle',.22); },\r\n    sparkle(){SFX?.sparkle?SFX.sparkle():tone(1100,90,'sine',.18); }\r\n  };\r\n\r\n  \/\/ \u0420\u0430\u0437\u043c\u0435\u0442\u043a\u0430 \u044f\u0434\u0440\u0430\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .zg-root,.zg-core-host,[data-zg-core-host]{ overflow-x:hidden; }\r\n      #nr-wrap{ width:min(920px,85vw); margin:0 auto; position:relative; }\r\n      #nr-count-overlay{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; pointer-events:none; z-index:40; }\r\n      #nr-count{ width:120px; height:120px; border-radius:999px; background:#0ea5e9; color:#fff;\r\n                 display:flex; align-items:center; justify-content:center; font-size:2.6rem; font-weight:900;\r\n                 box-shadow:0 12px 30px #0ea5e944; transform:scale(1); opacity:1; transition:.25s; }\r\n    <\/style>\r\n\r\n    <div id=\"nr-wrap\">\r\n      <!-- \u043f\u0440\u043e\u0433\u0440\u0435\u0441\u0441 \u0432\u043d\u0443\u0442\u0440\u0438 \u0438\u0433\u0440\u044b \u043f\u0440\u044f\u0447\u0435\u043c (\u0442\u0435\u043f\u0435\u0440\u044c \u0432 HUD \u0431\u0435\u0439\u0434\u0436 Nivel) -->\r\n      <div id=\"nr-progress\" style=\"display:none;\">Nivel 1\/${NIVELES}<\/div>\r\n\r\n      <div id=\"nr-count-overlay\"><div id=\"nr-count\"><\/div><\/div>\r\n\r\n      <div id=\"nr-op\" style=\"font-size:2.2em;font-weight:800;text-align:center;margin:22px 0 18px;min-height:1.4em;color:#0f172a;\"><\/div>\r\n\r\n      <div id=\"nr-buttons\" style=\"display:flex;flex-wrap:wrap;gap:16px;justify-content:center;min-height:64px;\"><\/div>\r\n\r\n      <div id=\"nr-status\" style=\"text-align:center;font-size:1.08em;min-height:2.2em;margin-top:18px;\"><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0432 \u043f\u0430\u043d\u0435\u043b\u0438 \u0441\u043f\u0440\u0430\u0432\u0430 \u043e\u0442 Puntos\r\n  game.setMessage(`\r\n    <div style=\"line-height:1.35\">\r\n      Lee con calma. Tras el contador aparecer\u00e1 el ejercicio con 4 respuestas; elige la correcta.\r\n    <\/div>\r\n  `);\r\n\r\n  \/\/ \u0411\u0435\u0439\u0434\u0436 \u00abNivel\u00bb \u0432 HUD\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('[data-nr-nivel]');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.setAttribute('data-nr-nivel','');\r\n    lvlBadge.style.cssText = 'margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    lvlBadge.textContent = 'N: 1\/'+NIVELES;\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setNivelBadge = (i)=>{ if (lvlBadge) lvlBadge.textContent = `N: ${i+1}\/${NIVELES}`; };\r\n\r\n  \/\/ \u0423\u0442\u0438\u043b\u0438\u0442\u044b\r\n  const $  = sel => game.coreEl.querySelector(sel);\r\n  const $$ = sel => Array.from(game.coreEl.querySelectorAll(sel));\r\n  const isTouch = ('ontouchstart' in window) || navigator.maxTouchPoints>0 || matchMedia('(pointer:coarse)').matches;\r\n  const btnBase = `font-weight:800;border:none;border-radius:16px;outline:none;cursor:pointer;box-shadow:0 2px 12px #00000012;transition:.15s;touch-action:manipulation;`;\r\n  const shuffle = a => a.map(v=>[Math.random(),v]).sort((x,y)=>x[0]-y[0]).map(x=>x[1]);\r\n  const puntosPorReaccion = rt => Math.max(1, Math.min(20, Math.round((7000 - rt)\/300)));\r\n\r\n  let nivel = 0, appearTime = 0, countdownId = null, bloqueado = false;\r\n\r\n  function setOp(t){ $('#nr-op').textContent = t||''; }\r\n  function setStatus(h){ $('#nr-status').innerHTML = h||''; }\r\n  function clearButtons(){ $('#nr-buttons').innerHTML=''; }\r\n\r\n  \/\/ \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430\r\n  function generarOperacion(){\r\n    const tipos=['+','-','\u00d7'];\r\n    const tipo = tipos[Math.floor(Math.random()*tipos.length)];\r\n    let a,b,correcta;\r\n\r\n    if (tipo==='+'){ a=6+Math.floor(Math.random()*45); b=1+Math.floor(Math.random()*20); correcta=a+b; }\r\n    else if (tipo==='-'){ a=15+Math.floor(Math.random()*36); b=1+Math.floor(Math.random()*(a-1)); correcta=a-b; }\r\n    else { a=2+Math.floor(Math.random()*10); b=2+Math.floor(Math.random()*8); correcta=a*b; }\r\n\r\n    const opciones = new Set([correcta]);\r\n    while(opciones.size<4){\r\n      const jitter=(Math.random()<0.5?-1:1)*(2+Math.floor(Math.random()*6)); \/\/ \u00b12..\u00b17\r\n      const cand=correcta+jitter; if(cand>0) opciones.add(cand);\r\n    }\r\n    return { texto:`${a} ${tipo} ${b} = ?`, correcta, opciones: shuffle([...opciones]) };\r\n  }\r\n\r\n  \/\/ \u041f\u043e\u043a\u0430\u0437 \u043a\u043d\u043e\u043f\u043e\u043a \u043e\u0442\u0432\u0435\u0442\u043e\u0432\r\n  function mostrarBotones(op){\r\n    clearButtons(); bloqueado=false; appearTime=Date.now(); sfx.whoosh();\r\n    const cont = $('#nr-buttons');\r\n    op.opciones.forEach(val=>{\r\n      const btn = document.createElement('button');\r\n      btn.textContent = val;\r\n      btn.style.cssText = `\r\n        ${btnBase} background:#fff;color:#0f172a;border:2px solid #8ddfff;\r\n        font-size:${isTouch?'1.4rem':'1.25rem'}; padding:${isTouch?'18px 30px':'14px 24px'};\r\n        min-width:130px;`;\r\n      btn.onpointerdown = ()=> sfx.click();\r\n      btn.onclick=()=>{\r\n        if(bloqueado) return; bloqueado=true;\r\n        const rt = Date.now()-appearTime;\r\n        const ok = (val===op.correcta);\r\n        if(ok){\r\n          const add = puntosPorReaccion(rt);\r\n          game.addScore(add);\r\n          sfx.ok();\r\n          setStatus(`<span style=\"color:#16a34a;font-weight:700;\">\u00a1Correcto!<\/span> Tiempo: <b>${rt} ms<\/b> (+${add})`);\r\n        }else{\r\n          sfx.fail();\r\n          setStatus(`<span style=\"color:#dc2626;font-weight:700;\">\u00a1Incorrecto!<\/span> Respuesta: <b>${op.correcta}<\/b>`);\r\n        }\r\n        $$('#nr-buttons button').forEach(b=>b.disabled=true);\r\n        setTimeout(siguiente, PAUSA_RESULTADO);\r\n      };\r\n      cont.appendChild(btn);\r\n    });\r\n  }\r\n\r\n  \/\/ \u0426\u0435\u043d\u0442\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 \u043e\u0442\u0441\u0447\u0451\u0442 (\u0441 \u043e\u0437\u0432\u0443\u0447\u043a\u043e\u0439 \u0438 \u0444\u043e\u043d\u043e\u0432\u043e\u0439 \u00ab\u043a\u0432\u0438\u0437\u044b\u00bb)\r\n  function startCountdown(seconds, done){\r\n    const circle = $('#nr-count');\r\n    const overlay = $('#nr-count-overlay');\r\n    overlay.style.display = 'flex';\r\n    let t = seconds;\r\n    circle.textContent = t;\r\n    clearInterval(countdownId);\r\n    sfx.bg('quiz');\r\n    countdownId = setInterval(()=>{\r\n      t--;\r\n      sfx.tick();\r\n      if(t>0){ circle.textContent=t; }\r\n      else{\r\n        clearInterval(countdownId);\r\n        sfx.ready(); sfx.bgStop();\r\n        \/\/ \u043f\u043b\u0430\u0432\u043d\u043e \u0441\u043a\u0440\u044b\u0442\u044c\r\n        circle.style.opacity='0'; circle.style.transform='scale(0.85)';\r\n        setTimeout(()=>{ overlay.style.display='none'; circle.style.opacity='1'; circle.style.transform='scale(1)'; }, 260);\r\n        done && done();\r\n      }\r\n    },1000);\r\n  }\r\n\r\n  function renderRound(){\r\n    setNivelBadge(nivel);\r\n    setStatus('');\r\n    setOp('');\r\n    clearButtons();\r\n\r\n    const op = generarOperacion();\r\n    startCountdown(READ_SECONDS, ()=>{\r\n      setOp(op.texto);\r\n      mostrarBotones(op);\r\n    });\r\n  }\r\n\r\n  function siguiente(){\r\n    nivel++;\r\n    if(nivel < NIVELES){\r\n      renderRound();\r\n    }else{\r\n      sfx.sparkle();\r\n      game.endGame({save:true, messageHTML:`<b>\u00a1Juego terminado!<\/b> Puntos: <b>${game.score}<\/b>.`});\r\n    }\r\n  }\r\n\r\n  \/\/ HUD\r\n  game.onStart(()=>{\r\n    sfx.unlock();\r\n    game.showStart(false);\r\n    game.showContinue(false);\r\n    game.setScore(0);\r\n    nivel=0;\r\n    setStatus(''); setOp(''); clearButtons();\r\n    game.startTimer();\r\n    renderRound();\r\n  });\r\n\r\n  \/\/ \u041d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0432\u0443\u043a\r\n  game.onExit?.(()=> sfx.bgStop());\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-b63998f elementor-widget elementor-widget-html\" data-id=\"b63998f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Dibuja(){\r\n  const game = crearMarcoJuego({ gameId:16, titulo:'Dibuja', bg:'#e4f7fa' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Dibuja la figura siguiendo la sombra azul.<br>\r\n    Usa <b>Pincel<\/b> \/ <b>Goma<\/b>, ajusta el <b>tama\u00f1o<\/b> y pulsa <b>Evaluar<\/b>.<br>\r\n    Tras evaluar, aparecer\u00e1 un <b>conteo 3-2-1<\/b> y pasas al siguiente dibujo.\r\n  `);\r\n\r\n  \/\/ ---- SFX (\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 window.SFX \u0438 \u0441 WebAudio-\u0444\u043e\u043b\u043b\u0431\u0435\u043a\u043e\u043c)\r\n  const tone=(f=660,ms=120,type='sine',vol=.22)=>{\r\n    try{\r\n      if (window.SFX?.tone){ SFX.tone(f,ms,type,vol); return; }\r\n      const AC=window.AudioContext||window.webkitAudioContext; if(!AC) return;\r\n      tone.ctx=tone.ctx||new AC();\r\n      const c=tone.ctx,o=c.createOscillator(),g=c.createGain();\r\n      o.type=type; o.frequency.value=f; g.gain.value=vol; o.connect(g); g.connect(c.destination);\r\n      o.start(); setTimeout(()=>{ try{o.stop();o.disconnect();g.disconnect();}catch(_){ } },ms);\r\n    }catch(_){}\r\n  };\r\n  const Snd={\r\n    click(){ SFX?.click?SFX.click():tone(760,70,'square',.16); },\r\n    ui(){ SFX?.ui?SFX.ui():tone(900,55,'square',.14); },\r\n    tick(){ SFX?.ui?SFX.ui():tone(820,70,'square',.14); },\r\n    ready(){ SFX?.ready?SFX.ready():tone(980,140,'triangle',.24); },\r\n    ok(){ SFX?.success?SFX.success():(tone(980,110,'sine',.22),setTimeout(()=>tone(1240,120,'sine',.22),110)); },\r\n    whoosh(){ SFX?.whoosh?SFX.whoosh():tone(520,110,'sawtooth',.16); },\r\n    clear(){ this.whoosh(); tone(300,120,'triangle',.14); },\r\n    penDn(){ tone(600,60,'sine',.12); },\r\n    penUp(){ tone(500,60,'triangle',.10); },\r\n    scratch(){ tone(1800,40,'sawtooth',.07); },     \/\/ \u043a\u0438\u0441\u0442\u044c\r\n    erase(){ tone(420,40,'square',.08); },         \/\/ \u043b\u0430\u0441\u0442\u0438\u043a\r\n  };\r\n\r\n  \/\/ \u042f\u0434\u0440\u043e (\u043f\u043e\u0441\u043b\u0435 Empezar)\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .zg-draw-core{display:none; gap:16px; align-items:flex-start; justify-content:center;}\r\n      .zg-draw-wrap{position:relative; background:#fff; border-radius:16px; box-shadow:0 0 24px 2px #ddd; padding:12px;}\r\n      .zg-draw-title{font-weight:800;margin:2px 0 10px 4px;}\r\n      .zg-draw-area{display:flex; gap:16px; align-items:flex-start;}\r\n      .zg-draw-cv{position:relative}\r\n      .zg-draw-cv canvas{max-width:100%; border-radius:14px; border:2px solid #e9eef6; touch-action:none;}\r\n      #zg-target{position:absolute; inset:0; z-index:1;}\r\n      #zg-canvas{position:relative; z-index:2;}\r\n\r\n      \/* \u043a\u0430\u0440\u0442\u043e\u0447\u043a\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 *\/\r\n      #zg-result{position:absolute; inset:0; z-index:3; display:none; align-items:center; justify-content:center; pointer-events:none;}\r\n      .zg-result-card{background:#ffffffee; border:2px solid #c7f0d8; border-radius:16px; padding:14px 18px; font-size:1.06em; font-weight:700; color:#135; box-shadow:0 6px 18px #00000012; text-align:center; max-width:min(90%,520px);}\r\n\r\n      \/* \u043f\u043e\u0432\u0435\u0440\u0445 \u0432\u0441\u0435\u0433\u043e \u2014 \u043a\u0440\u0443\u0433\u043b\u044b\u0439 \u043e\u0442\u0441\u0447\u0451\u0442 *\/\r\n      #zg-count-ov{position:absolute; inset:0; display:none; align-items:center; justify-content:center; pointer-events:none; z-index:4;}\r\n      #zg-count{width:120px; height:120px; border-radius:999px; background:#0ea5e9; color:#fff;\r\n                display:flex; align-items:center; justify-content:center; font-size:2.6rem; font-weight:900;\r\n                box-shadow:0 12px 30px #0ea5e944; transform:scale(1); opacity:1; transition:.25s;}\r\n\r\n      \/* \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b *\/\r\n      .zg-draw-tools{display:flex; flex-direction:column; gap:12px; min-width:170px}\r\n      .zg-tools-row{display:flex; gap:10px; flex-wrap:wrap; align-items:center}\r\n      .zg-chip{display:inline-flex; align-items:center; gap:8px; padding:9px 12px; border-radius:12px; border:2px solid #e7e7ee; background:#fff; font-weight:800; cursor:pointer; user-select:none}\r\n      .zg-chip.active{outline:3px solid #c8f1df}\r\n      .zg-btn{padding:9px 12px; border-radius:12px; border:2px solid #e7e7ee; background:#f6f6f9; font-weight:800; cursor:pointer}\r\n      .zg-range{width:160px}\r\n\r\n      @media (max-width: 720px){\r\n        .zg-draw-core{flex-direction:column}\r\n        .zg-draw-area{flex-direction:column}\r\n        .zg-draw-tools{flex-direction:row; flex-wrap:wrap; justify-content:center; min-width:0}\r\n        .zg-range{width:140px}\r\n      }\r\n    <\/style>\r\n\r\n    <div class=\"zg-draw-core\" id=\"zg-core\">\r\n      <div class=\"zg-draw-wrap\">\r\n        <div class=\"zg-draw-title\">Dibuja: <span id=\"zg-obj\">\u2014<\/span><\/div>\r\n        <div class=\"zg-draw-area\">\r\n          <div class=\"zg-draw-cv\">\r\n            <canvas id=\"zg-target\" width=\"720\" height=\"480\"><\/canvas>\r\n            <canvas id=\"zg-canvas\" width=\"720\" height=\"480\"><\/canvas>\r\n            <div id=\"zg-result\"><\/div>\r\n            <div id=\"zg-count-ov\"><div id=\"zg-count\"><\/div><\/div>\r\n          <\/div>\r\n\r\n          <aside class=\"zg-draw-tools\">\r\n            <div class=\"zg-tools-row\">\r\n              <div id=\"zg-tool-brush\"  class=\"zg-chip active\">Pincel<\/div>\r\n              <div id=\"zg-tool-eraser\" class=\"zg-chip\">Goma<\/div>\r\n              <button id=\"zg-clear\" class=\"zg-btn\">Limpiar<\/button>\r\n            <\/div>\r\n            <div class=\"zg-tools-row\" style=\"gap:12px;\">\r\n              <div style=\"font-weight:800; min-width:68px;\">Tama\u00f1o<\/div>\r\n              <input id=\"zg-size\" type=\"range\" min=\"4\" max=\"28\" value=\"12\" class=\"zg-range\">\r\n            <\/div>\r\n          <\/aside>\r\n        <\/div>\r\n      <\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  \/\/ \u041a\u043d\u043e\u043f\u043a\u0430 Evaluar \u0432 HUD (\u0440\u044f\u0434\u043e\u043c \u0441 \u043e\u0447\u043a\u0430\u043c\u0438) \u2014 \u0421\u041a\u0420\u042b\u0422\u0410 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let btnEvalHud = scoreBox?.querySelector('[data-eval-hud]');\r\n  if (!btnEvalHud && scoreBox){\r\n    btnEvalHud = document.createElement('button');\r\n    btnEvalHud.setAttribute('data-eval-hud','');\r\n    btnEvalHud.textContent = 'Evaluar';\r\n    btnEvalHud.style.cssText = `\r\n      margin-left:8px; padding:6px 12px; border-radius:10px; border:none;\r\n      background:#22c55e; color:#fff; font-weight:800; cursor:pointer; display:none;\r\n    `;\r\n    scoreBox.appendChild(btnEvalHud);\r\n  }\r\n\r\n  \/\/ \u0420\u0430\u0443\u043d\u0434\u044b\r\n  const rondas = [\r\n    {id:\"corazon\", nombre:\"Coraz\u00f3n\"},\r\n    {id:\"estrella\", nombre:\"Estrella\"},\r\n    {id:\"casa\", nombre:\"Casa\"},\r\n    {id:\"arbol\", nombre:\"\u00c1rbol\"},\r\n    {id:\"sonrisa\", nombre:\"Sonrisa\"},\r\n    {id:\"coche\", nombre:\"Coche\"},\r\n    {id:\"flor\", nombre:\"Flor\"},\r\n    {id:\"gato\", nombre:\"Gato\"},\r\n  ];\r\n  const total = rondas.length;\r\n\r\n  \/\/ \u0425\u043e\u043b\u0441\u0442\u044b\r\n  const cvs = document.getElementById('zg-canvas');\r\n  const tgt = document.getElementById('zg-target');\r\n  const ctx = cvs.getContext('2d');\r\n  const tctx = tgt.getContext('2d');\r\n  const resultBox = document.getElementById('zg-result');\r\n\r\n  let erasing=false, drawing=false, last=null, roundStart=0, ronda=0, evaluated=false;\r\n  let scratchAt=0, countdownId=null;\r\n\r\n  \/\/ \u2014 helpers \u2014\r\n  function getPos(e){\r\n    const r=cvs.getBoundingClientRect();\r\n    const x=((e.touches?e.touches[0].clientX:e.clientX)-r.left)*(cvs.width\/r.width);\r\n    const y=((e.touches?e.touches[0].clientY:e.clientY)-r.top)*(cvs.height\/r.height);\r\n    return {x,y};\r\n  }\r\n  function canvasHasInk(){\r\n    const data = ctx.getImageData(0,0,cvs.width,cvs.height).data;\r\n    for (let i=3;i<data.length;i+=4) if (data[i]>10) return true;\r\n    return false;\r\n  }\r\n  function updateEvalVisibility(){\r\n    if (!btnEvalHud) return;\r\n    const show = canvasHasInk() && !evaluated;\r\n    btnEvalHud.style.display = show ? '' : 'none';\r\n    btnEvalHud.disabled = !show;\r\n    btnEvalHud.style.opacity = show ? '1' : '.6';\r\n  }\r\n\r\n  function lineTo(p){\r\n    const size=+document.getElementById('zg-size').value;\r\n    ctx.lineCap='round'; ctx.lineJoin='round';\r\n    ctx.globalCompositeOperation=erasing?'destination-out':'source-over';\r\n    ctx.strokeStyle='#111'; ctx.lineWidth=size;\r\n    ctx.beginPath(); ctx.moveTo(last.x,last.y); ctx.lineTo(p.x,p.y); ctx.stroke();\r\n    last=p;\r\n\r\n    \/\/ \u00ab\u0446\u0430\u0440\u0430\u043f\u0430\u043d\u044c\u0435\u00bb\/\u043b\u0430\u0441\u0442\u0438\u043a \u0441 \u0442\u0440\u043e\u0442\u0442\u043b\u0438\u043d\u0433\u043e\u043c\r\n    const now=Date.now();\r\n    if (now - scratchAt > 110){\r\n      erasing ? Snd.erase() : Snd.scratch();\r\n      scratchAt = now;\r\n    }\r\n  }\r\n  const start=e=>{drawing=true; last=getPos(e); scratchAt=0; Snd.penDn(); e.preventDefault();};\r\n  const move =e=>{if(!drawing) return; lineTo(getPos(e)); e.preventDefault();};\r\n  const end  =()=>{ if(!drawing) return; drawing=false; last=null; Snd.penUp(); updateEvalVisibility(); };\r\n\r\n  cvs.addEventListener('mousedown',start);\r\n  cvs.addEventListener('mousemove',move);\r\n  cvs.addEventListener('mouseup',end);\r\n  cvs.addEventListener('mouseleave',end);\r\n  cvs.addEventListener('touchstart',start,{passive:false});\r\n  cvs.addEventListener('touchmove',move,{passive:false});\r\n  cvs.addEventListener('touchend',end);\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b\r\n  const chipBrush  = document.getElementById('zg-tool-brush');\r\n  const chipEraser = document.getElementById('zg-tool-eraser');\r\n  chipBrush.onclick  = ()=>{ erasing=false; chipBrush.classList.add('active'); chipEraser.classList.remove('active'); Snd.click(); };\r\n  chipEraser.onclick = ()=>{ erasing=true;  chipEraser.classList.add('active'); chipBrush.classList.remove('active'); Snd.click(); };\r\n  document.getElementById('zg-size').addEventListener('input', ()=> Snd.ui());\r\n  document.getElementById('zg-clear').onclick = ()=>{\r\n    ctx.clearRect(0,0,cvs.width,cvs.height);\r\n    updateEvalVisibility();\r\n    Snd.clear();\r\n  };\r\n\r\n  \/\/ \u041a\u043e\u043d\u0442\u0443\u0440\u044b-\u043c\u0438\u0448\u0435\u043d\u0438\r\n  function drawTargetShape(id){\r\n    tctx.clearRect(0,0,tgt.width,tgt.height);\r\n    tctx.save();\r\n    const W=tgt.width, H=tgt.height, s=Math.min(W,H)*0.7, cx=W\/2, cy=H\/2;\r\n    tctx.translate(cx,cy);\r\n    tctx.lineCap='round'; tctx.lineJoin='round';\r\n    tctx.strokeStyle='rgba(60,90,150,0.75)'; tctx.fillStyle='rgba(60,90,150,0.12)'; tctx.lineWidth=3;\r\n    const poly=(pts,close=true)=>{tctx.beginPath();tctx.moveTo(pts[0][0],pts[0][1]);for(let i=1;i<pts.length;i++)tctx.lineTo(pts[i][0],pts[i][1]);if(close)tctx.closePath();tctx.fill();tctx.stroke();};\r\n    if(id==='corazon'){\r\n      tctx.beginPath();\r\n      for(let t=-Math.PI;t<=Math.PI;t+=0.02){\r\n        const x=16*Math.sin(t)**3, y=-(13*Math.cos(t)-5*Math.cos(2*t)-2*Math.cos(3*t)-Math.cos(4*t));\r\n        const X=x*(s\/40), Y=y*(s\/40);\r\n        if(t===-Math.PI) tctx.moveTo(X,Y); else tctx.lineTo(X,Y);\r\n      }\r\n      tctx.closePath(); tctx.fill(); tctx.stroke();\r\n    } else if(id==='estrella'){\r\n      const R=s*0.45, r=s*0.2, n=5, pts=[]; for(let i=0;i<n*2;i++){const a=-Math.PI\/2+i*Math.PI\/n; const rr=(i%2? r:R); pts.push([rr*Math.cos(a), rr*Math.sin(a)]);} poly(pts);\r\n    } else if(id==='casa'){\r\n      poly([[-s*0.35, s*0.15],[s*0.35, s*0.15],[s*0.35,-s*0.15],[-s*0.35,-s*0.15]]);\r\n      tctx.beginPath(); tctx.moveTo(-s*0.4,-s*0.15); tctx.lineTo(0,-s*0.45); tctx.lineTo(s*0.4,-s*0.15); tctx.closePath(); tctx.fill(); tctx.stroke();\r\n    } else if(id==='arbol'){\r\n      tctx.fillRect(-s*0.06, s*0.05, s*0.12, s*0.28); tctx.strokeRect(-s*0.06, s*0.05, s*0.12, s*0.28);\r\n      tctx.beginPath(); tctx.arc(0,-s*0.1,s*0.28,0,Math.PI*2); tctx.fill(); tctx.stroke();\r\n    } else if(id==='sonrisa'){\r\n      tctx.beginPath(); tctx.arc(0,0,s*0.35,0,Math.PI*2); tctx.stroke();\r\n      tctx.beginPath(); tctx.arc(-s*0.12,-s*0.08,s*0.03,0,Math.PI*2); tctx.fill();\r\n      tctx.beginPath(); tctx.arc( s*0.12,-s*0.08,s*0.03,0,Math.PI*2); tctx.fill();\r\n      tctx.beginPath(); tctx.arc(0,s*0.08,s*0.16,0,Math.PI); tctx.stroke();\r\n    } else if(id==='coche'){\r\n      poly([[-s*0.32, s*0.05],[s*0.32, s*0.05],[s*0.28,-s*0.08],[-s*0.12,-s*0.08],[-s*0.22,0]]);\r\n      tctx.beginPath(); tctx.arc(-s*0.18,s*0.07,s*0.07,0,Math.PI*2); tctx.fill(); tctx.stroke();\r\n      tctx.beginPath(); tctx.arc( s*0.18,s*0.07,s*0.07,0,Math.PI*2); tctx.fill(); tctx.stroke();\r\n    } else if(id==='flor'){\r\n      const R=s*0.12; for(let i=0;i<6;i++){const a=i*Math.PI\/3; tctx.beginPath(); tctx.arc(R*Math.cos(a),R*Math.sin(a),R,0,Math.PI*2); tctx.fill(); tctx.stroke();}\r\n      tctx.beginPath(); tctx.arc(0,0,R,0,Math.PI*2); tctx.fill(); tctx.stroke();\r\n    } else if(id==='gato'){\r\n      tctx.beginPath(); tctx.arc(0,0,s*0.3,0,Math.PI*2); tctx.fill(); tctx.stroke();\r\n      poly([[-s*0.18,-s*0.2],[-s*0.06,-s*0.45],[ s*0.02,-s*0.22]]);\r\n      poly([[ s*0.18,-s*0.2],[ s*0.06,-s*0.45],[-s*0.02,-s*0.22]]);\r\n      tctx.beginPath(); tctx.arc(-s*0.1,-s*0.04,s*0.025,0,Math.PI*2); tctx.fill();\r\n      tctx.beginPath(); tctx.arc( s*0.1,-s*0.04,s*0.025,0,Math.PI*2); tctx.fill();\r\n      tctx.beginPath(); tctx.arc(0,s*0.02,s*0.02,0,Math.PI*2); tctx.fill();\r\n      for(let k=-1;k<=1;k+=2){ tctx.beginPath(); tctx.moveTo(0,s*0.02); tctx.lineTo(k*s*0.18,0); tctx.stroke();\r\n        tctx.beginPath(); tctx.moveTo(0,s*0.02); tctx.lineTo(k*s*0.18, s*0.06); tctx.stroke();\r\n        tctx.beginPath(); tctx.moveTo(0,s*0.02); tctx.lineTo(k*s*0.18,-s*0.06); tctx.stroke(); }\r\n    }\r\n    tctx.restore();\r\n  }\r\n\r\n  \/\/ \u041e\u0446\u0435\u043d\u043a\u0430\r\n  function evaluar(){\r\n    const drawn=ctx.getImageData(0,0,cvs.width,cvs.height).data;\r\n    const mask =tctx.getImageData(0,0,tgt.width,tgt.height).data;\r\n    let hit=0, ink=0;\r\n    for(let i=3;i<drawn.length;i+=4){\r\n      const a=drawn[i];\r\n      const alpha=drawn[i+3];\r\n      if(alpha>10){ ink++; if(mask[i+3]>10 || (mask[i]+mask[i+1]+mask[i+2])>30) hit++; }\r\n    }\r\n    const overlap=ink?Math.round(hit\/ink*100):0;\r\n    const tRound=Math.floor((Date.now()-roundStart)\/1000);\r\n    const speedBonus=Math.max(0,10-Math.min(10,tRound));\r\n    const pts=Math.max(1,Math.round(overlap\/4))+speedBonus;\r\n    return {overlap,tRound,pts};\r\n  }\r\n\r\n  function showResult({overlap,tRound,pts}){\r\n    tgt.style.opacity = '0.08';\r\n    cvs.style.opacity = '0.08';\r\n    resultBox.innerHTML = `\r\n      <div class=\"zg-result-card\">\r\n        <div style=\"font-size:1.1em;color:#22803b;margin-bottom:6px;\">\u00a1Bien hecho!<\/div>\r\n        Coincidencia: <b>${overlap}%<\/b> \u00b7 Tiempo: <b>${tRound}s<\/b> \u00b7 +<b>${pts}<\/b> pts\r\n      <\/div>`;\r\n    resultBox.style.display='flex';\r\n  }\r\n  function hideResult(){ resultBox.style.display='none'; tgt.style.opacity='1'; cvs.style.opacity='1'; }\r\n\r\n  \/\/ \u041e\u0442\u0441\u0447\u0451\u0442 (\u043f\u043e\u0432\u0435\u0440\u0445 \u0445\u043e\u043b\u0441\u0442\u0430)\r\n  function startCountdown(sec, cb){\r\n    const ov=document.getElementById('zg-count-ov');\r\n    const circle=document.getElementById('zg-count');\r\n    let t=sec;\r\n    ov.style.display='flex';\r\n    circle.textContent=t;\r\n    clearInterval(countdownId);\r\n    countdownId=setInterval(()=>{\r\n      t--; Snd.tick();\r\n      if(t>0){ circle.textContent=t; }\r\n      else{\r\n        clearInterval(countdownId);\r\n        Snd.ready();\r\n        circle.style.opacity='0'; circle.style.transform='scale(0.85)';\r\n        setTimeout(()=>{ ov.style.display='none'; circle.style.opacity='1'; circle.style.transform='scale(1)'; }, 240);\r\n        cb && cb();\r\n      }\r\n    },1000);\r\n  }\r\n\r\n  \/\/ \u0420\u0435\u043d\u0434\u0435\u0440 \u0440\u0430\u0443\u043d\u0434\u0430\r\n  function renderRonda(){\r\n    const objetivo=rondas[ronda];\r\n    document.getElementById('zg-obj').textContent=`${objetivo.nombre} (${ronda+1}\/${total})`;\r\n    hideResult();\r\n    ctx.clearRect(0,0,cvs.width,cvs.height);\r\n    drawTargetShape(objetivo.id);\r\n    evaluated=false;\r\n    updateEvalVisibility();\r\n    roundStart=Date.now();\r\n  }\r\n\r\n  \/\/ \u041f\u043e\u0441\u043b\u0435 \u043e\u0446\u0435\u043d\u043a\u0438 \u2192 \u0430\u0432\u0442\u043e-\u043e\u0442\u0441\u0447\u0451\u0442 \u0438 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439\r\n  function nextRound(){\r\n    ronda++;\r\n    if(ronda<total){\r\n      startCountdown(3, renderRonda);\r\n    }else{\r\n      if (btnEvalHud) btnEvalHud.remove();\r\n      game.endGame({save:true, messageHTML:`\u00a1Juego terminado! Dibujos: <b>${total}<\/b>`});\r\n    }\r\n  }\r\n\r\n  \/\/ \u041a\u043b\u0438\u043a \u043f\u043e Evaluar (HUD)\r\n  function doEvaluate(){\r\n    if(evaluated || !canvasHasInk()) return;\r\n    evaluated=true;\r\n    const res=evaluar();\r\n    game.addScore(res.pts);\r\n    Snd.ok();\r\n    showResult(res);\r\n    updateEvalVisibility();\r\n    setTimeout(()=> startCountdown(3, nextRound), 900);\r\n  }\r\n  if (btnEvalHud) btnEvalHud.onclick = ()=>{ Snd.click(); doEvaluate(); };\r\n\r\n  \/\/ \u0421\u0442\u0430\u0440\u0442\r\n  game.onStart(()=>{\r\n    game.showStart(false);\r\n    game.showContinue(false);     \/\/ \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\r\n    game.setScore(0);\r\n    game.startTimer();\r\n    document.getElementById('zg-core').style.display='flex';\r\n    game.setMessage('');\r\n    ronda=0;\r\n    startCountdown(3, renderRonda); \/\/ \u0441\u0442\u0430\u0440\u0442\u043e\u0432\u044b\u0439 \u043e\u0442\u0441\u0447\u0451\u0442\r\n  });\r\n\r\n  \/\/ \u00abContinue\u00bb \u043d\u0435 \u043d\u0443\u0436\u0435\u043d\r\n  game.onContinue(()=>{});\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-d812b64 elementor-widget elementor-widget-html\" data-id=\"d812b64\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_NotasMusicales(){\r\n  const game = crearMarcoJuego({ gameId: 17, titulo: 'Notas musicales' });\r\n\r\n  \/\/ ----- viewport lock (iOS zoom)\r\n  let _nmPrevViewportTag = document.querySelector('meta[name=\"viewport\"]');\r\n  let _nmPrevViewportContent = _nmPrevViewportTag ? _nmPrevViewportTag.getAttribute('content') : '';\r\n  function nmLockViewport(){\r\n    const content = 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no';\r\n    if (_nmPrevViewportTag) _nmPrevViewportTag.setAttribute('content', content);\r\n    else { const m=document.createElement('meta'); m.name='viewport'; m.content=content; m.id='nm-viewport-lock'; document.head.appendChild(m); }\r\n  }\r\n  function nmUnlockViewport(){\r\n    document.getElementById('nm-viewport-lock')?.remove();\r\n    if (_nmPrevViewportTag && _nmPrevViewportContent) _nmPrevViewportTag.setAttribute('content', _nmPrevViewportContent);\r\n  }\r\n  nmLockViewport();\r\n  document.querySelector('.zg-exit')?.addEventListener('click', nmUnlockViewport, {once:true});\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f (\u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430)\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Tras una peque\u00f1a cuenta atr\u00e1s escuchar\u00e1s la(s) nota(s).<br>\r\n    Toca en el piano para elegir la(s) misma(s) nota(s): al tocar suenan.<br>\r\n    R3\u21922 notas \u00b7 R6\u21923 \u00b7 R10\u21924. <b>Repetir<\/b> resta 1 punto. Pulsa <b>Evaluar<\/b>.\r\n  `);\r\n\r\n  \/\/ ---------- UI ----------\r\n  game.coreEl.innerHTML = `\r\n  <style>\r\n    .nm-wrap{\r\n      background:#fff;border-radius:16px;box-shadow:0 1px 14px #0001;\r\n      padding:10px;max-width:min(860px,88vw);margin:0 auto;position:relative;\r\n      touch-action:none;-webkit-text-size-adjust:100%;text-size-adjust:100%;\r\n    }\r\n    .nm-hint{background:#fff8d8;border:1px solid #f6d27d;border-radius:10px;\r\n      padding:6px 10px;margin-bottom:8px;color:#5b4a00;font-weight:600;text-align:center}\r\n\r\n    \/* \u0414\u0412\u0410 \u0420\u042f\u0414\u0410 (3 + 4), \u043e\u0431\u0430 \u043f\u043e \u0446\u0435\u043d\u0442\u0440\u0443 *\/\r\n    .nm-rows{display:flex;flex-direction:column;gap:10px;align-items:center;margin:6px 0}\r\n    .nm-row{display:flex;gap:8px;justify-content:center;align-items:flex-end}\r\n\r\n    .key{\r\n      width:clamp(58px, 13vw, 88px); aspect-ratio:2\/3;\r\n      background:#fff; border:2px solid #e6ecf6; border-bottom:5px solid #dae4f3; border-radius:8px;\r\n      box-shadow:0 2px 6px #eef; display:flex; align-items:flex-end; justify-content:center; padding-bottom:8px;\r\n      font-weight:800; color:#163; cursor:pointer; -webkit-tap-highlight-color:transparent; user-select:none;\r\n      transition: box-shadow .08s, background-color .08s, border-color .08s; font-size:15.5px;\r\n    }\r\n    .key:active{ box-shadow:0 0 0 #eef; }\r\n    .key.sel{ background:#f1fff7;border-color:#94e3c3;color:#267233; }\r\n\r\n    \/* \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u043a\u0430 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0445 \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0435 *\/\r\n    @keyframes nmBlink{\r\n      0%,100%{ background:#fffbe6; border-color:#f59e0b; }\r\n      50%    { background:#dcfce7; border-color:#34d399; box-shadow:0 0 12px 2px rgba(34,197,94,.45); }\r\n    }\r\n    .key.blink{ animation:nmBlink .9s 2; }\r\n\r\n    \/* \u043e\u0442\u0441\u0447\u0451\u0442 \u043f\u043e\u0432\u0435\u0440\u0445 *\/\r\n    .nm-count{position:absolute; inset:0; display:none; align-items:center; justify-content:center; pointer-events:none; z-index:6;}\r\n    .nm-bubble{background:#ffffffdd;border-radius:16px;padding:12px 28px;font-size:2.1em;font-weight:900;color:#0b3a47;box-shadow:0 2px 18px #0001}\r\n\r\n    \/* \u043f\u0430\u043d\u0435\u043b\u044c \u043a\u043d\u043e\u043f\u043e\u043a \u2014 \u0421\u041a\u0420\u042b\u0422\u0410 \u0434\u043e \u0441\u0442\u0430\u0440\u0442\u0430 *\/\r\n    .nm-toolbar{display:none;justify-content:center;gap:10px;flex-wrap:wrap;margin-top:8px}\r\n    .nm-ctrl{font-size:1.02em;padding:9px 14px;border-radius:12px;border:none;cursor:pointer;font-weight:800}\r\n    .nm-ctrl.blue{background:#c6e6ff;color:#134}\r\n    .nm-ctrl.orange{background:#ffa500;color:#fff}\r\n\r\n    .nm-hide-instr .zg-panel .zg-msg{display:none!important}\r\n    @media (max-width:420px){ .key{ font-size:14.5px } }\r\n  <\/style>\r\n\r\n  <div class=\"nm-wrap\">\r\n\r\n\r\n    <div id=\"nm-rows\" class=\"nm-rows\">\r\n      <div id=\"nm-row1\" class=\"nm-row\"><\/div>\r\n      <div id=\"nm-row2\" class=\"nm-row\"><\/div>\r\n    <\/div>\r\n\r\n    <div id=\"nm-msg\" style=\"text-align:center;min-height:1.5em;\"><\/div>\r\n\r\n    <div id=\"nm-toolbar\" class=\"nm-toolbar\">\r\n      <button id=\"nm-replay\"  class=\"nm-ctrl blue\"   disabled>Repetir (\u22121)<\/button>\r\n      <button id=\"nm-evaluar\" class=\"nm-ctrl orange\" disabled>Evaluar<\/button>\r\n    <\/div>\r\n\r\n    <div id=\"nm-count\" class=\"nm-count\"><div class=\"nm-bubble\" id=\"nm-count-num\">3<\/div><\/div>\r\n  <\/div>\r\n  `;\r\n\r\n  \/\/ ==== HUD: \u0431\u0435\u0439\u0434\u0436 Nivel \u0440\u044f\u0434\u043e\u043c \u0441 Puntos ====\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let nivelBadge = scoreBox?.querySelector('[data-nm-nivel]');\r\n  const rondasTotales=10, TEMPO=850, NOTE_MS=800;\r\n  if (!nivelBadge && scoreBox){\r\n    nivelBadge = document.createElement('span');\r\n    nivelBadge.setAttribute('data-nm-nivel','');\r\n    nivelBadge.style.cssText = 'margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    nivelBadge.textContent = `N: 1\/${rondasTotales}`;\r\n    scoreBox.appendChild(nivelBadge);\r\n  }\r\n  const setNivelHUD = n => { if (nivelBadge) nivelBadge.textContent = `N: ${n}\/${rondasTotales}`; };\r\n\r\n  \/\/ ----- Datos\r\n  const notas = [\r\n    {nom:'Do',freq:261.63},{nom:'Re',freq:293.66},{nom:'Mi',freq:329.63},\r\n    {nom:'Fa',freq:349.23},{nom:'Sol',freq:392.00},{nom:'La',freq:440.00},\r\n    {nom:'Si',freq:493.88},\r\n  ];\r\n\r\n  \/\/ ----- Estado \/ refs\r\n  let ronda=0, objetivo=[], selected=new Set();\r\n  let playing=false, answered=false, answerStartTs=0;\r\n\r\n  const elMsg    = document.getElementById('nm-msg');\r\n  const elReplay = document.getElementById('nm-replay');\r\n  const elEval   = document.getElementById('nm-evaluar');\r\n  const rowsWrap = document.getElementById('nm-rows');\r\n  const row1El   = document.getElementById('nm-row1');\r\n  const row2El   = document.getElementById('nm-row2');\r\n  const toolbar  = document.getElementById('nm-toolbar');\r\n  const cntWrap  = document.getElementById('nm-count');\r\n  const cntNum   = document.getElementById('nm-count-num');\r\n  const hintEl   = document.getElementById('nm-hint');\r\n\r\n  \/\/ \u0431\u043b\u043e\u043a \u0434\u0430\u0431\u043b-\u0442\u0430\u043f \u0437\u0443\u043c\u0430\r\n  let _nmLastTouch = 0;\r\n  game.coreEl.addEventListener('touchend', (e) => {\r\n    const now = Date.now();\r\n    if (now - _nmLastTouch <= 350) e.preventDefault();\r\n    _nmLastTouch = now;\r\n  }, { passive:false });\r\n\r\n  \/\/ ----- Audio helpers -----\r\n  let audioCtx=null;\r\n  function ensureAudio(){ if(!audioCtx) audioCtx=new (window.AudioContext||window.webkitAudioContext)(); }\r\n  function beep(freq, ms=NOTE_MS, type='sine', vol=0.25){\r\n    ensureAudio();\r\n    const now=audioCtx.currentTime, osc=audioCtx.createOscillator(), gain=audioCtx.createGain();\r\n    osc.type=type; osc.frequency.setValueAtTime(freq, now);\r\n    gain.gain.setValueAtTime(0, now);\r\n    gain.gain.linearRampToValueAtTime(vol, now+0.02);\r\n    gain.gain.linearRampToValueAtTime(0.0, now+ms\/1000);\r\n    osc.connect(gain); gain.connect(audioCtx.destination);\r\n    osc.start(now); osc.stop(now+ms\/1000+0.02);\r\n  }\r\n  const sfxTick = ()=> beep(880,110,'square',0.22);            \/\/ 3-2-1\r\n  const sfxGo   = ()=> beep(523.25,180,'triangle',0.26);       \/\/ \u00ab\u00a1Escucha!\u00bb\r\n  const sfxOK   = ()=> { beep(659.25,120,'sine',0.25); setTimeout(()=>beep(880,150,'sine',0.25),120); };\r\n  const sfxFail = ()=> beep(196.00,260,'sawtooth',0.22);\r\n\r\n  const wait=ms=>new Promise(r=>setTimeout(r,ms));\r\n  async function playSequence(seq){ playing=true; for(const n of seq){ beep(n.freq,NOTE_MS); await wait(TEMPO); } playing=false; }\r\n\r\n  \/\/ helpers\r\n  const allKeys = () => rowsWrap.querySelectorAll('.key');\r\n\r\n  \/\/ ----- Piano -----\r\n  function renderKeys(){\r\n    row1El.innerHTML=''; row2El.innerHTML='';\r\n    const mk = (n) => {\r\n      const b=document.createElement('div');\r\n      b.className='key'; b.textContent=n.nom; b.dataset.note=n.nom;\r\n      b.onpointerdown = () => {\r\n        if (playing || answered) return;\r\n        beep(n.freq, Math.min(320, NOTE_MS));\r\n        const need = notesNeeded();\r\n        if (need === 1){\r\n          const cur = [...selected][0] || null;\r\n          if (cur !== n.nom){\r\n            selected.clear();\r\n            allKeys().forEach(k=>k.classList.remove('sel'));\r\n            selected.add(n.nom); b.classList.add('sel');\r\n          }\r\n        } else {\r\n          if (selected.has(n.nom)){ selected.delete(n.nom); b.classList.remove('sel'); }\r\n          else if (selected.size < need){ selected.add(n.nom); b.classList.add('sel'); }\r\n        }\r\n        elEval.disabled = (selected.size !== need);\r\n      };\r\n      return b;\r\n    };\r\n    \/\/ 3 \u0441\u0432\u0435\u0440\u0445\u0443, 4 \u0441\u043d\u0438\u0437\u0443\r\n    const order = [0,1,2, 3,4,5,6]; \/\/ Do Re Mi | Fa Sol La Si\r\n    row1El.appendChild( mk(notas[order[0]]) );\r\n    row1El.appendChild( mk(notas[order[1]]) );\r\n    row1El.appendChild( mk(notas[order[2]]) );\r\n    for(let i=3;i<order.length;i++) row2El.appendChild( mk(notas[order[i]]) );\r\n  }\r\n\r\n  function notesNeeded(){ if(ronda>=9) return 4; if(ronda>=5) return 3; if(ronda>=2) return 2; return 1; }\r\n\r\n  function makeTarget(){\r\n    const need=notesNeeded(); const pool=[...notas]; objetivo=[];\r\n    for(let i=0;i<need;i++){ const k=(Math.random()*pool.length)|0; objetivo.push(pool.splice(k,1)[0]); }\r\n  }\r\n\r\n  \/\/ ----- \u041e\u0442\u0441\u0447\u0451\u0442 (\u0441 \u043e\u0437\u0432\u0443\u0447\u043a\u043e\u0439) -----\r\n  async function startCountdown(){\r\n    cntWrap.style.display='flex';\r\n    for(let x=3;x>=1;x--){ cntNum.textContent=x; sfxTick(); await wait(820); }\r\n    cntNum.textContent='\u00a1Escucha!'; sfxGo(); await wait(520);\r\n    cntWrap.style.display='none';\r\n  }\r\n\r\n  async function startRoundPlay(){\r\n    await startCountdown();\r\n    await playSequence(objetivo);\r\n    answerStartTs=Date.now();\r\n    elReplay.disabled=false;\r\n    elMsg.textContent=`Selecciona ${notesNeeded()} nota(s) y pulsa Evaluar.`;\r\n  }\r\n\r\n  function resetSelections(){\r\n    selected.clear();\r\n    allKeys().forEach(k=>k.classList.remove('sel','blink'));\r\n    elEval.disabled=true; elReplay.disabled=true;\r\n  }\r\n\r\n  function renderRound(newTarget=true){\r\n    setNivelHUD(ronda+1);\r\n    elMsg.textContent=''; answered=false;\r\n    resetSelections();\r\n    if (newTarget) makeTarget();\r\n    startRoundPlay();\r\n  }\r\n\r\n  \/\/ ----- Botones -----\r\n  elReplay.onclick=()=>{ if(playing||answered) return; game.addScore(-1); playSequence(objetivo); };\r\n\r\n  elEval.onclick=async ()=>{\r\n    if (playing||answered) return;\r\n    const need=notesNeeded();\r\n    if (selected.size!==need){ elMsg.innerHTML=`<span style=\"color:#b23;\">Selecciona ${need} nota(s).<\/span>`; return; }\r\n    answered=true; elEval.disabled=true; elReplay.disabled=true;\r\n\r\n    elMsg.textContent='Comparamos: objetivo y tu respuesta...';\r\n    await playSequence(objetivo); await wait(260);\r\n    const userSeq=[...selected].map(nm=>notas.find(n=>n.nom===nm));\r\n    await playSequence(userSeq);\r\n\r\n    const targetSet=new Set(objetivo.map(n=>n.nom));\r\n    const userSet=new Set(selected);\r\n    let ok = targetSet.size===userSet.size;\r\n    if (ok){ for (const nm of targetSet) if (!userSet.has(nm)) { ok=false; break; } }\r\n\r\n    if (ok){\r\n      const rt=Date.now()-answerStartTs;\r\n      const speed=Math.max(0,8-Math.floor(Math.min(rt,5000)\/700));\r\n      const pts=8+speed; game.addScore(pts); sfxOK();\r\n      elMsg.innerHTML=`<span style=\"color:#298d34;font-weight:700;\">\u00a1Correcto!<\/span> +<b>${pts}<\/b> pts`;\r\n      setTimeout(()=>{ ronda++; if (ronda<rondasTotales) renderRound(true); else game.endGame({save:true, messageHTML:`\u00a1Juego terminado! Niveles: <b>${rondasTotales}<\/b>`}); }, 900);\r\n    }else{\r\n      sfxFail();\r\n      objetivo.forEach(n=>{\r\n        const key=[...allKeys()].find(k=>k.dataset.note===n.nom);\r\n        if (key) key.classList.add('blink');\r\n      });\r\n      elMsg.innerHTML=`<span style=\"color:#c33;font-weight:700;\">Incorrecto.<\/span> Repetimos el nivel.`;\r\n      setTimeout(()=>{ answered=false; resetSelections(); renderRound(false); }, 1200);\r\n    }\r\n  };\r\n\r\n  \/\/ ----- START -----\r\n  game.onStart(()=>{\r\n    \/\/ \u0441\u043f\u0440\u044f\u0442\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e \u0438 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0430\u043d\u0435\u043b\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u041f\u041e\u0421\u041b\u0415 \u0441\u0442\u0430\u0440\u0442\u0430\r\n    game.coreEl.closest('.zg-root')?.classList.add('nm-hide-instr');\r\n    game.setMessage(''); if (hintEl) hintEl.style.display='none';\r\n    toolbar.style.display = 'flex';\r\n\r\n    \/\/ \u0441\u0431\u0440\u043e\u0441\r\n    ronda=0; objetivo=[]; selected=new Set(); playing=false; answered=false;\r\n    game.showStart(false); game.showContinue(false); game.setScore(0);\r\n    setNivelHUD(1);\r\n\r\n    game.startTimer();\r\n    renderKeys();\r\n    renderRound(true);\r\n  });\r\n\r\n  game.onContinue(()=>{ \/* \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f *\/ });\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6524364 elementor-widget elementor-widget-html\" data-id=\"6524364\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Quiz(){\r\n  const game = crearMarcoJuego({ gameId: 18, titulo: 'Quiz' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f (ES) \u2014 \u0441\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u043f\u043e\u0441\u043b\u0435 Empezar\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Aparece una pregunta con 4 opciones. Toca tu respuesta.<br>\r\n    Acierto: <b>+10<\/b> \u00b7 Error: <b>\u22125<\/b>. Tras la explicaci\u00f3n,<br>\r\n    ver\u00e1s un <b>conteo 3\u20132\u20131<\/b> y pasar\u00e1 a la siguiente.\r\n  `);\r\n\r\n  \/\/ \u2500\u2500 UI \u2500\u2500\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .q-wrap{background:#fff;border-radius:20px;box-shadow:0 0 24px 2px #ddd;\r\n              padding:16px;max-width:min(760px,96vw);margin:0 auto;position:relative; padding-bottom:64px   }\r\n      .q-step{display:none} \/* \u0448\u0430\u0433 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0432 HUD \u043a\u0430\u043a Nivel *\/\r\n      .q-title{font-size:1.6em;font-weight:800;color:#123;margin:8px 0 16px}\r\n      .q-opts{display:flex;flex-direction:column;gap:12px}\r\n      .q-btn{ text-align:left;padding:12px 16px;font-size:1.05em;border-radius:12px;\r\n              background:#fff;color:#134;border:2px solid #8df;cursor:pointer;font-weight:800;\r\n              box-shadow:0 1px 6px #0001; transition:.08s}\r\n      .q-btn:active{transform:translateY(1px)}\r\n      .q-btn[disabled]{opacity:.85;cursor:default}\r\n      .q-btn.ok{ background:#dcfce7;color:#166534;border-color:#86efac }\r\n      .q-btn.err{ background:#fee2e2;color:#b91c1c;border-color:#fca5a5 }\r\n\r\n      .q-feedback{font-size:1.18em;line-height:1.35;margin:6px 0 10px}\r\n      .q-feedback.ok{color:#166534}\r\n      .q-feedback.err{color:#b91c1c}\r\n\r\n      .qz-hide-instr .zg-msg{ display:none !important; }\r\n\r\n\r\n        \/* \u043e\u0442\u0441\u0447\u0451\u0442 \u0441\u043d\u0438\u0437\u0443, \u043d\u0435 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0442\u0435\u043a\u0441\u0442 *\/\r\n        .q-count{\r\n          position:absolute;\r\n          left:0; right:0; bottom:10px; \r\n          display:none;\r\n          pointer-events:none;\r\n          z-index:10;\r\n          text-align:center;\r\n        }\r\n        .q-bubble{\r\n          display:inline-block;\r\n          background:#ffffffee;\r\n          border-radius:16px;\r\n          padding:10px 32px;\r\n          font-size:1.8em;\r\n          font-weight:900;\r\n          color:#0b3a47;\r\n          box-shadow:0 2px 18px #0001;\r\n        }\r\n        @media (max-width:520px){\r\n          .q-bubble{ font-size:1.6em; padding:8px 18px; }\r\n          .q-wrap{ padding-bottom:72px; } \/* \u043d\u0430 \u0443\u0437\u043a\u0438\u0445 \u044d\u043a\u0440\u0430\u043dax \u2014 \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043c\u0435\u0441\u0442\u0430 *\/\r\n        }\r\n\r\n    <\/style>\r\n    <div class=\"q-wrap\">\r\n      <div id=\"q-step\"  class=\"q-step\"><\/div>\r\n      <div id=\"q-title\" class=\"q-title\"><\/div>\r\n      <div id=\"q-opts\"  class=\"q-opts\"><\/div>\r\n      <div id=\"q-count\" class=\"q-count\"><div class=\"q-bubble\" id=\"q-count-num\">3<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  \/\/ \u2500\u2500 \u0411\u0435\u0439\u0434\u0436 Nivel \u0432 HUD \u0440\u044f\u0434\u043e\u043c \u0441 Puntos \u2500\u2500\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let nivelBadge = scoreBox?.querySelector('[data-quiz-nivel]');\r\n  if (!nivelBadge && scoreBox){\r\n    nivelBadge = document.createElement('span');\r\n    nivelBadge.setAttribute('data-quiz-nivel','');\r\n    nivelBadge.style.cssText = 'margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    nivelBadge.textContent = 'N: 1\/10';\r\n    scoreBox.appendChild(nivelBadge);\r\n  }\r\n  const setNivelHUD = (n,total)=>{ if(nivelBadge) nivelBadge.textContent = `N: ${n}\/${total}`; };\r\n\r\n  \/\/ \u2500\u2500 \u0410\u0443\u0434\u0438\u043e\/SFX \u2500\u2500\r\n  let ac=null; const ctx=()=> ac||(ac=new (window.AudioContext||window.webkitAudioContext)());\r\n  function tone(f=660, ms=120, type='sine', vol=0.22){\r\n    try{\r\n      const c=ctx(), t=c.currentTime, o=c.createOscillator(), g=c.createGain();\r\n      o.type=type; o.frequency.setValueAtTime(f,t);\r\n      g.gain.setValueAtTime(0,t); g.gain.linearRampToValueAtTime(vol,t+0.02);\r\n      g.gain.linearRampToValueAtTime(0,t+ms\/1000);\r\n      o.connect(g); g.connect(c.destination); o.start(t); o.stop(t+ms\/1000+0.02);\r\n    }catch(_){}\r\n  }\r\n  const sfxClick = ()=> tone(520,90,'triangle',.18);\r\n  const sfxGood  = ()=> { tone(740,110,'sine',.24); setTimeout(()=>tone(980,140,'sine',.24),110); };\r\n  const sfxBad   = ()=> tone(180,260,'sawtooth',.22);\r\n  const sfxTick  = ()=> tone(880,110,'square',.22);\r\n  const sfxGo    = ()=> tone(523.25,200,'triangle',.26);\r\n\r\n  \/\/ \u2500\u2500 \u0412\u043e\u043f\u0440\u043e\u0441\u044b \u2500\u2500\r\n  const preguntasBase = [\r\n    { q:\"\u00bfCu\u00e1l es el planeta m\u00e1s grande del Sistema Solar?\", opciones:[\"Marte\",\"J\u00fapiter\",\"Saturno\",\"Neptuno\"], correcta:\"J\u00fapiter\", exp:\"J\u00fapiter es el planeta m\u00e1s grande por di\u00e1metro y masa.\" },\r\n    { q:\"\u00bfEn qu\u00e9 continente est\u00e1 Egipto?\", opciones:[\"Asia\",\"Europa\",\"\u00c1frica\",\"Ocean\u00eda\"], correcta:\"\u00c1frica\", exp:\"Egipto se sit\u00faa en el noreste de \u00c1frica.\" },\r\n    { q:\"\u00bfCu\u00e1ntos lados tiene un hex\u00e1gono?\", opciones:[\"5\",\"6\",\"7\",\"8\"], correcta:\"6\", exp:\"Hexa = seis. Un hex\u00e1gono tiene 6 lados.\" },\r\n    { q:\"\u00bfQui\u00e9n pint\u00f3 la Mona Lisa?\", opciones:[\"Picasso\",\"Van Gogh\",\"Da Vinci\",\"Dal\u00ed\"], correcta:\"Da Vinci\", exp:\"Leonardo da Vinci la pint\u00f3 a inicios del siglo XVI.\" },\r\n    { q:\"\u00bfCu\u00e1l es el s\u00edmbolo qu\u00edmico del agua?\", opciones:[\"H2O\",\"O2\",\"CO2\",\"NaCl\"], correcta:\"H2O\", exp:\"Dos \u00e1tomos de hidr\u00f3geno y uno de ox\u00edgeno.\" },\r\n    { q:\"Capital de Espa\u00f1a:\", opciones:[\"Lisboa\",\"Madrid\",\"Barcelona\",\"Valencia\"], correcta:\"Madrid\", exp:\"Madrid es la capital de Espa\u00f1a.\" },\r\n    { q:\"\u00bfCu\u00e1ntos minutos hay en una hora?\", opciones:[\"30\",\"45\",\"60\",\"120\"], correcta:\"60\", exp:\"Una hora son 60 minutos.\" },\r\n    { q:\"\u00bfCu\u00e1l es el idioma m\u00e1s hablado del mundo (nativos)?\", opciones:[\"Ingl\u00e9s\",\"Chino mandar\u00edn\",\"Hindi\",\"Espa\u00f1ol\"], correcta:\"Chino mandar\u00edn\", exp:\"El mandar\u00edn tiene m\u00e1s hablantes nativos que cualquier otro.\" },\r\n    { q:\"\u00bfQu\u00e9 n\u00famero romano representa el 10?\", opciones:[\"V\",\"X\",\"L\",\"C\"], correcta:\"X\", exp:\"X equivale a 10 en n\u00fameros romanos.\" },\r\n    { q:\"\u00bfCu\u00e1l es el metal cuya abreviatura es Fe?\", opciones:[\"Plata\",\"Hierro\",\"Oro\",\"Cobre\"], correcta:\"Hierro\", exp:\"Fe proviene del lat\u00edn <i>ferrum<\/i>.\" },\r\n    { q:\"\u00bfCu\u00e1l es el oc\u00e9ano m\u00e1s grande?\", opciones:[\"Atl\u00e1ntico\",\"\u00cdndico\",\"Pac\u00edfico\",\"\u00c1rtico\"], correcta:\"Pac\u00edfico\", exp:\"El Pac\u00edfico es el mayor por superficie y volumen.\" },\r\n    { q:\"\u00bfEn qu\u00e9 pa\u00eds naci\u00f3 el tango?\", opciones:[\"Espa\u00f1a\",\"M\u00e9xico\",\"Argentina\",\"Cuba\"], correcta:\"Argentina\", exp:\"Naci\u00f3 en el R\u00edo de la Plata (Argentina y Uruguay).\" },\r\n    { q:\"\u00bfQu\u00e9 animal es un mam\u00edfero?\", opciones:[\"Tibur\u00f3n\",\"Delf\u00edn\",\"Pulpo\",\"Trucha\"], correcta:\"Delf\u00edn\", exp:\"El delf\u00edn es un mam\u00edfero marino.\" },\r\n    { q:\"\u00bfCu\u00e1l es la capital de Francia?\", opciones:[\"Par\u00eds\",\"Lyon\",\"Marsella\",\"Niza\"], correcta:\"Par\u00eds\", exp:\"Par\u00eds es la capital de Francia.\" },\r\n    { q:\"\u00bfCu\u00e1nto es 9 \u00d7 8?\", opciones:[\"64\",\"72\",\"81\",\"98\"], correcta:\"72\", exp:\"9 por 8 es 72.\" },\r\n  ];\r\n  const preguntas = preguntasBase.slice();\r\n  for (let i=preguntas.length-1;i>0;i--){ const j=(Math.random()*(i+1))|0; [preguntas[i],preguntas[j]]=[preguntas[j],preguntas[i]]; }\r\n  const totalRondas = Math.min(10, preguntas.length);\r\n\r\n  \/\/ \u2500\u2500 \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\/refs \u2500\u2500\r\n  let nivel = 0, locked = false;  \/\/ nivel: 0..total-1\r\n  const elTitle = document.getElementById('q-title');\r\n  const elOpts  = document.getElementById('q-opts');\r\n  const hintEl  = document.getElementById('q-hint');\r\n  const cntWrap = document.getElementById('q-count');\r\n  const cntNum  = document.getElementById('q-count-num');\r\n  const rootEl  = game.coreEl.closest('.zg-root');\r\n\r\n  function setNivel(n){ setNivelHUD(n+1, totalRondas); }\r\n\r\n  \/\/ \u041e\u0442\u0441\u0447\u0451\u0442 3\u20132\u20131 \u0441 \u043e\u0437\u0432\u0443\u0447\u043a\u043e\u0439\r\n        async function countdownThen(next){\r\n          cntWrap.style.display = 'block';          \/\/ \u0431\u044b\u043b\u043e 'flex'\r\n          for (let s=5; s>=1; s--){ cntNum.textContent=s; sfxTick(); await new Promise(r=>setTimeout(r,820)); }\r\n          cntNum.textContent='\u00a1Vamos!'; sfxGo(); await new Promise(r=>setTimeout(r,420));\r\n          cntWrap.style.display = 'none';\r\n          next && next();\r\n        }\r\n\r\n\r\n\r\n  function renderPregunta(){\r\n    if (nivel>=totalRondas){\r\n      return game.endGame({save:true, messageHTML:`\u00a1Juego terminado! Preguntas: <b>${totalRondas}<\/b>`});\r\n    }\r\n    locked=false; setNivel(nivel);\r\n    const q = preguntas[nivel];\r\n\r\n    \/\/ \u0412\u043e\u043f\u0440\u043e\u0441\r\n    elTitle.textContent = q.q;\r\n    elOpts.style.display = '';\r\n    elOpts.innerHTML='';\r\n\r\n    q.opciones.forEach(txt=>{\r\n      const btn=document.createElement('button');\r\n      btn.className='q-btn'; btn.textContent=txt;\r\n      btn.onclick=()=>{\r\n        if (locked) return;\r\n        locked=true; sfxClick();\r\n        const correcto = (txt===q.correcta);\r\n        [...elOpts.children].forEach(b=>b.disabled=true);\r\n\r\n        if (correcto){\r\n          btn.classList.add('ok'); sfxGood();\r\n          game.addScore(10);\r\n          elTitle.innerHTML = `<div class=\"q-feedback ok\">\u00a1Correcto! (+10)<\/div><div>${q.exp}<\/div>`;\r\n        } else {\r\n          btn.classList.add('err'); sfxBad();\r\n          game.setScore(Math.max(0, game.score - 5));\r\n          \/\/ \u043f\u043e\u0434\u0441\u0432\u0435\u0442\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\r\n          [...elOpts.children].forEach(b=>{ if (b.textContent===q.correcta) b.classList.add('ok'); });\r\n          elTitle.innerHTML = `<div class=\"q-feedback err\">Incorrecto (\u22125)<\/div><div>Respuesta: <b>${q.correcta}<\/b>.<br>${q.exp}<\/div>`;\r\n        }\r\n\r\n        \/\/ \u043f\u0440\u044f\u0447\u0435\u043c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b \u043d\u0430 \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u044a\u044f\u0441\u043d\u0435\u043d\u0438\u044f\r\n        elOpts.style.display='none';\r\n\r\n        \/\/ \u0432\u043c\u0435\u0441\u0442\u043e \u00abContinuar\u00bb \u2014 \u0430\u0432\u0442\u043e-\u043e\u0442\u0441\u0447\u0451\u0442 \u0438 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\r\n        countdownThen(()=>{\r\n          nivel++;\r\n          elTitle.textContent='';\r\n          elOpts.innerHTML=''; elOpts.style.display='';\r\n          renderPregunta();\r\n        });\r\n      };\r\n      elOpts.appendChild(btn);\r\n    });\r\n  }\r\n\r\n  \/\/ \u0421\u0442\u0430\u0440\u0442 \u2014 \u043f\u0440\u044f\u0447\u0435\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e \u0441\u043b\u0435\u0432\u0430 \u0438 \u0445\u0438\u043d\u0442\r\n    game.onStart(()=>{\r\n      const rootEl = game.coreEl.closest('.zg-root');\r\n      rootEl?.classList.add('qz-hide-instr'); \/\/ \u043a\u043b\u0430\u0441\u0441 \u043d\u0430 \u043a\u043e\u0440\u0435\u043d\u044c\r\n      game.setMessage('');                    \/\/ \u2190 \u0443\u0431\u0440\u0430\u0442\u044c \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e \u043c\u0433\u043d\u043e\u0432\u0435\u043d\u043d\u043e\r\n    \r\n      nivel = 0; locked = false;\r\n      game.setScore(0);\r\n      game.showStart(false);\r\n      game.showContinue(false);\r\n      game.startTimer();\r\n      countdownThen(renderPregunta);\r\n    });\r\n\r\n\r\n  \/\/ HUD \u00abContinuar\u00bb \u043d\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u0435\u043d\r\n  game.onContinue(()=>{ \/* no-op *\/ });\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-21c1582 elementor-widget elementor-widget-html\" data-id=\"21c1582\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_FormaPalabra(){\r\n  const game = crearMarcoJuego({ gameId: 19, titulo: 'Forma la palabra' });\r\n\r\n  \/\/ \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f (\u0443\u0431\u0438\u0440\u0430\u0435\u043c \u043f\u043e\u0441\u043b\u0435 \u0441\u0442\u0430\u0440\u0442\u0430)\r\n  game.setMessage(`\r\n    Forma la palabra a partir de las letras seg\u00fan la pista.<br>\r\n    Toca una letra para a\u00f1adirla y vuelve a tocarla arriba para quitarla.<br>\r\n    Acierto: <b>+pts<\/b> (con bonus por rapidez). Error: <b>\u22125 pts<\/b>.\r\n  `);\r\n\r\n  \/\/ ---------- UI ----------\r\n  game.coreEl.innerHTML = `\r\n    <style>\r\n      .fp-wrap{\r\n        background:#fff;border-radius:24px;box-shadow:0 0 24px 2px #ddd;\r\n        padding:18px 14px;max-width:760px;margin:0 auto;display:none;position:relative;\r\n      }\r\n      .fp-hidden{display:none!important}\r\n      .fp-pista{font-size:1.05em;color:#134;text-align:center;margin-bottom:10px}\r\n      .fp-objetivo{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;min-height:56px;margin:6px 0 12px}\r\n      .fp-banco{display:grid;gap:10px;justify-content:center;justify-items:center}\r\n      .fp-pick{\r\n        min-width:50px;height:50px;border-radius:12px;background:#e9fff6;border:2px solid #8fe3c6;\r\n        color:#155;font-size:1.2em;font-weight:800;cursor:pointer\r\n      }\r\n      .fp-slot{min-width:50px;height:50px;border-radius:12px;background:#f7fbff;border:2px dashed #cfe4ff}\r\n      .fp-bankbtn{\r\n        min-width:46px;height:46px;padding:0 10px;border-radius:10px;font-size:1.2em;font-weight:800;\r\n        cursor:pointer;background:#f3fcfa;color:#0f172a;border:2px solid #48a;transition:.06s;box-shadow:0 1px 6px #0001\r\n      }\r\n      .fp-bankbtn:active{transform:translateY(1px)}\r\n      \/* \u041e\u0442\u0441\u0447\u0451\u0442 \u043f\u043e\u0432\u0435\u0440\u0445 *\/\r\n      .fp-count{position:absolute;inset:0;display:none;align-items:center;justify-content:center;pointer-events:none;z-index:5}\r\n      .fp-bubble{background:#ffffffee;border-radius:16px;padding:12px 28px;font-size:2.1em;font-weight:900;color:#0b3a47;box-shadow:0 2px 18px #0001}\r\n\r\n      \/* \u0421\u043a\u0440\u044b\u0442\u044c \u043b\u0435\u0432\u0443\u044e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e \u043f\u043e\u0441\u043b\u0435 \u0441\u0442\u0430\u0440\u0442\u0430 *\/\r\n      .fp-hide-instr .zg-panel .zg-msg{display:none!important}\r\n    <\/style>\r\n\r\n    <div class=\"fp-wrap\" id=\"fp-wrap\">\r\n      <div id=\"fp-pista\" class=\"fp-pista\"><\/div>\r\n      <div id=\"fp-objetivo\" class=\"fp-objetivo\"><\/div>\r\n      <div id=\"fp-banco\" class=\"fp-banco\"><\/div>\r\n      <div id=\"fp-count\" class=\"fp-count\"><div class=\"fp-bubble\" id=\"fp-count-num\">3<\/div><\/div>\r\n    <\/div>\r\n  `;\r\n\r\n  \/\/ ---------- SFX ----------\r\n  let ac=null; const ctx=()=> ac||(ac=new (window.AudioContext||window.webkitAudioContext)());\r\n  function tone(f=660, ms=120, type='sine', vol=0.22){\r\n    try{\r\n      const c=ctx(), t=c.currentTime, o=c.createOscillator(), g=c.createGain();\r\n      o.type=type; o.frequency.setValueAtTime(f,t);\r\n      g.gain.setValueAtTime(0,t); g.gain.linearRampToValueAtTime(vol,t+0.02);\r\n      g.gain.linearRampToValueAtTime(0,t+ms\/1000);\r\n      o.connect(g); g.connect(c.destination); o.start(t); o.stop(t+ms\/1000+0.02);\r\n    }catch(_){}\r\n  }\r\n  const sfxPick   = ()=> tone(620,90,'triangle',.20);\r\n  const sfxRemove = ()=> tone(420,90,'triangle',.18);\r\n  const sfxGood   = ()=> { tone(740,110,'sine',.26); setTimeout(()=>tone(980,140,'sine',.26),110); };\r\n  const sfxBad    = ()=> tone(180,260,'sawtooth',.22);\r\n  const sfxTick   = ()=> tone(880,110,'square',.22);\r\n  const sfxGo     = ()=> tone(523.25,200,'triangle',.26);\r\n\r\n  \/\/ ---------- Datos ----------\r\n  const banco = [\r\n    { palabra:\"PLATANO\",  pista:\"Fruta amarilla \ud83c\udf4c\" },\r\n    { palabra:\"MANZANA\",  pista:\"Fruta roja o verde \ud83c\udf4f\ud83c\udf4e\" },\r\n    { palabra:\"PERRO\",    pista:\"Animal que ladra \ud83d\udc36\" },\r\n    { palabra:\"GATO\",     pista:\"Animal que ma\u00falla \ud83d\udc31\" },\r\n    { palabra:\"ESCUELA\",  pista:\"Lugar para estudiar \ud83d\udcda\" },\r\n    { palabra:\"VENTANA\",  pista:\"Se abre para ver afuera \ud83e\ude9f\" },\r\n    { palabra:\"ORDENADOR\",pista:\"Tambi\u00e9n \u2018computadora\u2019 \ud83d\udcbb\" },\r\n    { palabra:\"MARIPOSA\", pista:\"Insecto con alas bonitas \ud83e\udd8b\" },\r\n    { palabra:\"CIUDAD\",   pista:\"Lugar con muchos edificios \ud83c\udfd9\ufe0f\" },\r\n    { palabra:\"MUSEO\",    pista:\"Arte y exposiciones \ud83d\uddbc\ufe0f\" },\r\n    { palabra:\"JUEGO\",    pista:\"Lo que est\u00e1s haciendo ahora \ud83c\udfae\" },\r\n    { palabra:\"COCHE\",    pista:\"Veh\u00edculo de 4 ruedas \ud83d\ude97\" },\r\n    { palabra:\"FAMILIA\",  pista:\"Personas que te quieren \ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66\" },\r\n    { palabra:\"AMISTAD\",  pista:\"Relaci\u00f3n de cari\u00f1o \ud83e\udd1d\" },\r\n    { palabra:\"MONTANA\",  pista:\"Muy alta, con cumbres \ud83c\udfd4\ufe0f\" },\r\n  ];\r\n  const shuffle=a=>{for(let i=a.length-1;i>0;i--){const j=(Math.random()*(i+1))|0;[a[i],a[j]]=[a[j],a[i]]}};\r\n  let pool=[...banco]; shuffle(pool);\r\n  const totalRondas=Math.min(10,pool.length);\r\n\r\n  \/\/ ---------- HUD: Nivel badge ----------\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let lvlBadge = scoreBox?.querySelector('[data-fp-nivel]');\r\n  if (!lvlBadge && scoreBox){\r\n    lvlBadge = document.createElement('span');\r\n    lvlBadge.setAttribute('data-fp-nivel','');\r\n    lvlBadge.style.cssText='margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    lvlBadge.textContent = `N: 1\/${totalRondas}`;\r\n    scoreBox.appendChild(lvlBadge);\r\n  }\r\n  const setNivelHUD = (n)=>{ if(lvlBadge) lvlBadge.textContent = `N: ${n}\/${totalRondas}`; };\r\n\r\n  \/\/ ---------- Refs \/ estado ----------\r\n  const wrap       = document.getElementById('fp-wrap');\r\n  const elPista    = document.getElementById('fp-pista');\r\n  const elObjetivo = document.getElementById('fp-objetivo');\r\n  const elBanco    = document.getElementById('fp-banco');\r\n  const cntWrap    = document.getElementById('fp-count');\r\n  const cntNum     = document.getElementById('fp-count-num');\r\n\r\n  const rootEl = game.coreEl.closest('.zg-root');\r\n\r\n  const normaliza  = s=>s.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g,'').toUpperCase().replace(\/\u00d1\/g,'N');\r\n\r\n  let ronda=0;\r\n  let letras=[], mezcla=[], elegidosIdx=[];\r\n  let palabraActual='';\r\n  let inicioRonda=0;\r\n  let erroresRonda=0;\r\n\r\n  \/\/ ---------- layout ----------\r\n  function layoutBanco(){\r\n    const L = mezcla.length;\r\n    const rows = (L<=8)?2:3;\r\n    const cols = Math.max(4, Math.ceil(L\/rows));\r\n    elBanco.style.gridTemplateColumns = `repeat(${cols}, minmax(46px,1fr))`;\r\n  }\r\n\r\n  \/\/ ---------- \u0440\u0435\u043d\u0434\u0435\u0440 ----------\r\n  function renderObjetivo(){\r\n    elObjetivo.innerHTML='';\r\n    elegidosIdx.forEach((idx,pos)=>{\r\n      const b=document.createElement('button');\r\n      b.className='fp-pick';\r\n      b.textContent=mezcla[idx];\r\n      b.title='Quitar';\r\n      b.onclick=()=>{ elegidosIdx.splice(pos,1); sfxRemove(); renderObjetivo(); renderBanco(); };\r\n      elObjetivo.appendChild(b);\r\n    });\r\n    for(let k=elegidosIdx.length;k<letras.length;k++){\r\n      const s=document.createElement('div'); s.className='fp-slot'; elObjetivo.appendChild(s);\r\n    }\r\n  }\r\n  function renderBanco(){\r\n    elBanco.innerHTML='';\r\n    mezcla.forEach((ch,idx)=>{\r\n      const usado=elegidosIdx.includes(idx);\r\n      const b=document.createElement('button');\r\n      b.className='fp-bankbtn';\r\n      b.disabled=usado;\r\n      if(usado){ b.style.background='#f2f6fb'; b.style.border='2px solid #e6eef8'; b.style.color='#9aa6b2'; }\r\n      b.textContent=ch;\r\n      b.onclick=()=>{\r\n        if(usado) return;\r\n        elegidosIdx.push(idx); sfxPick();\r\n        renderObjetivo(); renderBanco();\r\n        tryAutoCheck();                 \/\/ \u2190 \u0430\u0432\u0442\u043e\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\r\n      };\r\n      elBanco.appendChild(b);\r\n    });\r\n  }\r\n\r\n  function prepararRonda(){\r\n    const { palabra, pista } = pool[ronda];\r\n    palabraActual = palabra;\r\n    letras = palabra.split('');\r\n    mezcla = [...letras];\r\n    do{ shuffle(mezcla); }while(mezcla.join('')===letras.join(''));\r\n    elegidosIdx=[];\r\n    inicioRonda = Date.now();\r\n    erroresRonda = 0;\r\n\r\n    elPista.textContent = pista;\r\n    setNivelHUD(ronda+1);\r\n    renderObjetivo(); renderBanco(); layoutBanco();\r\n    wrap.style.display='block';\r\n  }\r\n\r\n  \/\/ ---------- \u043e\u0442\u0441\u0447\u0451\u0442 ----------\r\n  async function countdownThen(next){\r\n    cntWrap.style.display='flex';\r\n    for (let s=3; s>=1; s--){ cntNum.textContent=s; sfxTick(); await new Promise(r=>setTimeout(r,820)); }\r\n    cntNum.textContent='\u00a1Vamos!'; sfxGo(); await new Promise(r=>setTimeout(r,420));\r\n    cntWrap.style.display='none';\r\n    next && next();\r\n  }\r\n\r\n  \/\/ ---------- \u0430\u0432\u0442\u043e\u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 ----------\r\n  function tryAutoCheck(){\r\n    if (elegidosIdx.length !== letras.length) return;\r\n\r\n    const candidata = elegidosIdx.map(i => mezcla[i]).join('');\r\n    const ok = normaliza(candidata) === normaliza(palabraActual);\r\n\r\n    if (ok){\r\n      const tRound = Math.floor((Date.now()-inicioRonda)\/1000);\r\n      const pts = Math.max(1, 20 - tRound - erroresRonda*3);\r\n      game.addScore(pts); sfxGood();\r\n      game.setMessage(`<span style=\"color:#298d34;font-weight:700;\">\u00a1Correcto!<\/span> Palabra: <b>${palabraActual}<\/b>. (+${pts})`);\r\n\r\n      \/\/ \u0430\u0432\u0442\u043e-\u043f\u0435\u0440\u0435\u0445\u043e\u0434 \u0441 \u043e\u0442\u0441\u0447\u0451\u0442\u043e\u043c\r\n      countdownThen(()=>{\r\n        ronda++;\r\n        if (ronda < totalRondas){\r\n          game.setMessage('');\r\n          prepararRonda();\r\n        } else {\r\n          game.endGame({save:true, messageHTML:`\u00a1Juego terminado! Niveles: <b>${totalRondas}<\/b>`});\r\n        }\r\n      });\r\n    } else {\r\n      erroresRonda++; sfxBad();\r\n      game.setScore(Math.max(0, game.score - 5));\r\n      game.setMessage(`<span style=\"color:#c33;font-weight:700;\">Incorrecto.<\/span> \u22125 pts. Int\u00e9ntalo de nuevo.`);\r\n      \/\/ \u0441\u0431\u0440\u043e\u0441 \u0432\u044b\u0431\u043e\u0440\u0430\r\n      elegidosIdx = [];\r\n      renderObjetivo(); renderBanco();\r\n    }\r\n  }\r\n\r\n  \/\/ ---------- START ----------\r\n  game.onStart(()=>{\r\n    \/\/ \u0441\u043f\u0440\u044f\u0447\u0435\u043c \u043b\u0435\u0432\u0443\u044e \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0443\u044e \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e\r\n    rootEl?.classList.add('fp-hide-instr');\r\n    game.setMessage('');\r\n\r\n    game.showStart(false);\r\n    game.showContinue(false);      \/\/ \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c Continue\r\n    game.setScore(0);\r\n    game.startTimer();\r\n\r\n    ronda=0;\r\n    prepararRonda();\r\n  });\r\n\r\n  \/\/ \u041d\u0430 \u0432\u0441\u044f\u043a\u0438\u0439 \u0441\u043b\u0443\u0447\u0430\u0439 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u043c Continue \u0438\u0437 HUD\r\n  game.onContinue(()=>{ \/* no-op *\/ });\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-cb2a8a2 elementor-widget elementor-widget-html\" data-id=\"cb2a8a2\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\nfunction iniciarJuego_Refranes(){\r\n  const game = crearMarcoJuego({ gameId: 20, titulo: 'Refranes' });\r\n\r\n  game.setMessage(`\r\n    <b>C\u00f3mo jugar:<\/b><br>\r\n    Lee el refr\u00e1n con hueco y toca la parte que lo completa.<br>\r\n    Respuesta correcta: <b>+10 pts<\/b>. Tras la explicaci\u00f3n ver\u00e1s un <b>conteo 3\u20132\u20131<\/b> y pasar\u00e1 al siguiente.\r\n  `);\r\n\r\n  \/\/ ---------- UI ----------\r\n  game.coreEl.innerHTML = `\r\n  <style>\r\n    .rf-wrap{\r\n      background:#fff;border-radius:22px;box-shadow:0 0 24px 2px #ddd;\r\n      padding:16px 14px;max-width:760px;margin:0 auto;display:none;position:relative\r\n    }\r\n    .rf-hidden{display:none!important}\r\n    .rf-head{display:none} \/* usamos \u0431\u0435\u0439\u0434\u0436 Nivel \u0432 HUD *\/\r\n\r\n    .rf-q{\r\n      font-size:1.55em;line-height:1.28;color:#134;text-align:center;margin:8px 0 14px;\r\n      word-break:break-word;overflow-wrap:anywhere\r\n    }\r\n    .rf-q .gap{padding:2px 6px;border-bottom:3px solid #bfe6ff}\r\n\r\n    .rf-opts{display:grid;gap:12px;justify-content:center;grid-auto-rows:auto}\r\n    @media (max-width:560px){ .rf-opts{grid-template-columns:repeat(2, minmax(140px,1fr))} .rf-q{font-size:1.35em} }\r\n    @media (min-width:561px){ .rf-opts{grid-template-columns:repeat(2, minmax(220px,1fr))} }\r\n\r\n    .rf-btn{\r\n      box-sizing:border-box;width:100%;\r\n      padding:12px 14px;border-radius:12px;background:#fff;color:#167;border:2px solid #8df;\r\n      font-weight:800;cursor:pointer;text-align:center;white-space:normal;line-height:1.25;\r\n      word-break:break-word;overflow-wrap:anywhere;box-shadow:0 1px 6px #0001;transition:.08s;\r\n      display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:4; overflow:hidden;\r\n      max-height:calc(1.25em * 4 + 0px);\r\n    }\r\n    .rf-btn:active{transform:translateY(1px)}\r\n    .rf-btn:disabled{opacity:.75;cursor:default}\r\n    .rf-ok{background:#caffd8!important;color:#267233!important;border-color:#92e6b8!important}\r\n    .rf-bad{background:#ffcfcf!important;color:#b23!important;border-color:#f3b0b0!important}\r\n\r\n    .rf-msg{min-height:2.2em;font-size:1.06em;text-align:center;margin-top:10px}\r\n\r\n    \/* \u0421\u0427\u0401\u0422\u0427\u0418\u041a \u2014 \u0421\u0422\u0410\u0422\u0418\u0427\u041d\u042b\u0419 \u0411\u041b\u041e\u041a \u041f\u041e\u0414 \u041a\u0410\u0420\u0422\u041e\u0427\u041a\u041e\u0419, \u041d\u0415 \u041f\u041e\u0412\u0415\u0420\u0425 *\/\r\n    .rf-count{display:none;justify-content:center;margin-top:12px}\r\n    .rf-bubble{\r\n      background:#ffffffee;border-radius:16px;padding:10px 24px;\r\n      font-size:2.0em;font-weight:900;color:#0b3a47;box-shadow:0 2px 18px #0001\r\n    }\r\n\r\n    .rf-hide-instr .zg-panel .zg-msg{display:none!important}\r\n  <\/style>\r\n\r\n  <div id=\"rf-wrap\" class=\"rf-wrap\" aria-live=\"polite\">\r\n    <div id=\"rf-head\" class=\"rf-head\">Ronda 1 \/ 10<\/div>\r\n    <div id=\"rf-q\" class=\"rf-q\"><\/div>\r\n    <div id=\"rf-opts\" class=\"rf-opts\"><\/div>\r\n    <div id=\"rf-msg\" class=\"rf-msg\"><\/div>\r\n    <div id=\"rf-count\" class=\"rf-count\"><div class=\"rf-bubble\" id=\"rf-count-num\">3<\/div><\/div>\r\n  <\/div>\r\n  `;\r\n\r\n  \/\/ ---------- HUD: Nivel ----------\r\n  const scoreBox = game.coreEl.closest('.zg-root')?.querySelector('.zg-score');\r\n  let nivelBadge = scoreBox?.querySelector('[data-rf-nivel]');\r\n  const totalRondas = 10;\r\n  if (!nivelBadge && scoreBox){\r\n    nivelBadge = document.createElement('span');\r\n    nivelBadge.setAttribute('data-rf-nivel','');\r\n    nivelBadge.style.cssText='margin-left:8px;background:#cffafe;border:1px solid #a5f3fc;color:#083344;padding:4px 8px;border-radius:10px;font-weight:800;';\r\n    nivelBadge.textContent = `N: 1\/${totalRondas}`;\r\n    scoreBox.appendChild(nivelBadge);\r\n  }\r\n  const setNivelHUD = n => { if (nivelBadge) nivelBadge.textContent = `N: ${n}\/${totalRondas}`; };\r\n\r\n  \/\/ ---------- SFX ----------\r\n  let ac=null; const ctx=()=> ac||(ac=new (window.AudioContext||window.webkitAudioContext)());\r\n  function tone(f=660, ms=120, type='sine', vol=0.22){\r\n    try{\r\n      const c=ctx(), t=c.currentTime, o=c.createOscillator(), g=c.createGain();\r\n      o.type=type; o.frequency.setValueAtTime(f,t);\r\n      g.gain.setValueAtTime(0,t); g.gain.linearRampToValueAtTime(vol,t+0.02);\r\n      g.gain.linearRampToValueAtTime(0,t+ms\/1000);\r\n      o.connect(g); g.connect(c.destination); o.start(t); o.stop(t+ms\/1000+0.02);\r\n    }catch(_){}\r\n  }\r\n  const sfxClick = ()=> tone(520,90,'triangle',.18);\r\n  const sfxGood  = ()=> { tone(740,110,'sine',.26); setTimeout(()=>tone(980,140,'sine',.26),110); };\r\n  const sfxBad   = ()=> tone(180,260,'sawtooth',.22);\r\n  const sfxTick  = ()=> tone(880,110,'square',.22);\r\n  const sfxGo    = ()=> tone(523.25,400,'triangle',.26);\r\n\r\n  \/\/ ---------- \u0411\u0430\u0437\u0430 ----------\r\n  const banco = [\r\n    { q:\"A quien madruga, _____\", a:\"Dios le ayuda\",\r\n      opciones:[\"Dios le ayuda\",\"mal viento le sopla\",\"poco duerme\",\"todo lo pierde\"],\r\n      exp:\"Significa que el esfuerzo temprano suele traer buenos resultados.\" },\r\n    { q:\"No por mucho madrugar, _____\", a:\"amanece m\u00e1s temprano\",\r\n      opciones:[\"amanece m\u00e1s temprano\",\"se trabaja mejor\",\"duermes mejor\",\"todo florece\"],\r\n      exp:\"Hay cosas que no dependen de la prisa.\" },\r\n    { q:\"M\u00e1s vale p\u00e1jaro en mano que _____\", a:\"ciento volando\",\r\n      opciones:[\"ciento volando\",\"dos en el \u00e1rbol\",\"uno cantando\",\"nada en la jaula\"],\r\n      exp:\"Mejor algo seguro que muchas promesas.\" },\r\n    { q:\"A caballo regalado, _____\", a:\"no le mires el diente\",\r\n      opciones:[\"no le mires el diente\",\"dale m\u00e1s comida\",\"no lo montes\",\"v\u00e9ndelo pronto\"],\r\n      exp:\"A lo recibido gratis no se le buscan defectos.\" },\r\n    { q:\"El que mucho abarca, _____\", a:\"poco aprieta\",\r\n      opciones:[\"poco aprieta\",\"todo consigue\",\"nada pierde\",\"nunca falla\"],\r\n      exp:\"Si haces demasiadas cosas a la vez, ninguna sale bien.\" },\r\n    { q:\"En casa de herrero, _____\", a:\"cuchillo de palo\",\r\n      opciones:[\"cuchillo de palo\",\"todo es hierro\",\"no hay cuchillos\",\"fuego sagrado\"],\r\n      exp:\"A veces falta en casa lo que uno domina en su oficio.\" },\r\n    { q:\"Dime con qui\u00e9n andas y _____\", a:\"te dir\u00e9 qui\u00e9n eres\",\r\n      opciones:[\"te dir\u00e9 qui\u00e9n eres\",\"te dar\u00e9 consejos\",\"ver\u00e1s a d\u00f3nde vas\",\"te dir\u00e9 qu\u00e9 temes\"],\r\n      exp:\"Tus compa\u00f1\u00edas reflejan tu car\u00e1cter.\" },\r\n    { q:\"Perro ladrador, _____\", a:\"poco mordedor\",\r\n      opciones:[\"poco mordedor\",\"muy ruidoso\",\"buen amigo\",\"siempre guardi\u00e1n\"],\r\n      exp:\"Quien amenaza mucho suele actuar poco.\" },\r\n    { q:\"No hay mal que por bien _____\", a:\"no venga\",\r\n      opciones:[\"no venga\",\"no dure\",\"se aleje\",\"no cambie\"],\r\n      exp:\"De lo malo puede salir algo bueno.\" },\r\n    { q:\"Al mal tiempo, _____\", a:\"buena cara\",\r\n      opciones:[\"buena cara\",\"buen paraguas\",\"poca prisa\",\"calma tensa\"],\r\n      exp:\"Actitud positiva ante la adversidad.\" },\r\n    { q:\"Ojos que no ven, _____\", a:\"coraz\u00f3n que no siente\",\r\n      opciones:[\"coraz\u00f3n que no siente\",\"alma que no llora\",\"mente que no piensa\",\"boca que no calla\"],\r\n      exp:\"Lo que ignoras no te duele.\" },\r\n    { q:\"Cr\u00eda cuervos y _____\", a:\"te sacar\u00e1n los ojos\",\r\n      opciones:[\"te sacar\u00e1n los ojos\",\"tendr\u00e1s compa\u00f1\u00eda\",\"cantar\u00e1n de noche\",\"no dormir\u00e1s bien\"],\r\n      exp:\"Si alimentas lo malo, te volver\u00e1 en contra.\" },\r\n    { q:\"A mal paso, _____\", a:\"darle prisa\",\r\n      opciones:[\"darle prisa\",\"mala cara\",\"buen consejo\",\"otro paso\"],\r\n      exp:\"Mejor resolver lo dif\u00edcil cuanto antes.\" },\r\n    { q:\"Quien calla, _____\", a:\"otorga\",\r\n      opciones:[\"otorga\",\"sufre\",\"aprende\",\"pierde\"],\r\n      exp:\"El silencio puede interpretarse como aceptaci\u00f3n.\" },\r\n    { q:\"De tal palo, _____\", a:\"tal astilla\",\r\n      opciones:[\"tal astilla\",\"tal rama\",\"tal \u00e1rbol\",\"tal sombra\"],\r\n      exp:\"Los hijos se parecen a los padres.\" },\r\n    { q:\"Haz el bien y _____\", a:\"no mires a qui\u00e9n\",\r\n      opciones:[\"no mires a qui\u00e9n\",\"espera el premio\",\"calla siempre\",\"perdona todo\"],\r\n      exp:\"Haz el bien sin buscar a qui\u00e9n beneficiar.\" },\r\n    { q:\"Camar\u00f3n que se duerme, _____\", a:\"se lo lleva la corriente\",\r\n      opciones:[\"se lo lleva la corriente\",\"pierde el sue\u00f1o\",\"no desayuna\",\"nada despacio\"],\r\n      exp:\"Si te conf\u00edas, te adelantan.\" },\r\n    { q:\"A falta de pan, _____\", a:\"buenas son tortas\",\r\n      opciones:[\"buenas son tortas\",\"mejor es agua\",\"todo es hambre\",\"vino y sal\"],\r\n      exp:\"Conformarse con alternativas cuando falta lo ideal.\" },\r\n    { q:\"No hay peor ciego que _____\", a:\"el que no quiere ver\",\r\n      opciones:[\"el que no quiere ver\",\"el que tarde llega\",\"el que mucho grita\",\"el que no sabe o\u00edr\"],\r\n      exp:\"La peor ceguera es la voluntaria.\" },\r\n    { q:\"Quien a buen \u00e1rbol se arrima, _____\", a:\"buena sombra le cobija\",\r\n      opciones:[\"buena sombra le cobija\",\"buen fruto recoge\",\"mejor canta\",\"mejor corre\"],\r\n      exp:\"Acercarse a quien puede protegerte trae ventajas.\" }\r\n  ];\r\n  const shuffle = a => { for(let i=a.length-1;i>0;i--){ const j=(Math.random()*(i+1))|0; [a[i],a[j]]=[a[j],a[i]]; } };\r\n\r\n  \/\/ ---------- \u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435 \/ refs ----------\r\n  let pool=[...banco]; shuffle(pool); pool=pool.slice(0,totalRondas);\r\n  let ronda=0, answered=false;\r\n\r\n  const rootEl = game.coreEl.closest('.zg-root');\r\n  const wrap   = document.getElementById('rf-wrap');\r\n  const elQ    = document.getElementById('rf-q');\r\n  const elOpts = document.getElementById('rf-opts');\r\n  const elMsg  = document.getElementById('rf-msg');\r\n  const cntWrap= document.getElementById('rf-count');\r\n  const cntNum = document.getElementById('rf-count-num');\r\n\r\n  function showCore(){ wrap.style.display='block'; }\r\n  function hideOpts(){ elOpts.classList.add('rf-hidden'); }\r\n  function showOpts(){ elOpts.classList.remove('rf-hidden'); }\r\n\r\n  \/\/ \u0421\u0447\u0451\u0442\u0447\u0438\u043a (\u043d\u0435 \u043f\u0435\u0440\u0435\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0442\u0435\u043a\u0441\u0442)\r\n  async function countdownThen(next){\r\n    cntWrap.style.display='flex';\r\n    for (let s=6; s>=1; s--){ cntNum.textContent=s; sfxTick(); await new Promise(r=>setTimeout(r,820)); }\r\n    cntNum.textContent='\u00a1Vamos!'; sfxGo(); await new Promise(r=>setTimeout(r,420));\r\n    cntWrap.style.display='none';\r\n    next && next();\r\n  }\r\n\r\n  function renderRound(){\r\n    const { q, a, opciones, exp } = pool[ronda];\r\n    answered=false;\r\n    setNivelHUD(ronda+1);\r\n    elQ.innerHTML = q.replace('_____', '<span class=\"gap\">_____<\/span>');\r\n    elMsg.innerHTML = '';\r\n    showOpts();\r\n    elOpts.innerHTML='';\r\n\r\n    const vars=[...opciones]; shuffle(vars);\r\n    vars.forEach(txt=>{\r\n      const b=document.createElement('button');\r\n      b.className='rf-btn';\r\n      b.textContent=txt;\r\n      b.onclick=()=>{\r\n        if (answered) return;\r\n        answered=true; sfxClick();\r\n        [...elOpts.children].forEach(x=>x.disabled=true);\r\n\r\n        const correct = (txt===a);\r\n        if (correct){ b.classList.add('rf-ok'); sfxGood(); game.addScore(10); }\r\n        else{\r\n          b.classList.add('rf-bad'); sfxBad();\r\n          const right=[...elOpts.children].find(x=>x.textContent===a); if (right) right.classList.add('rf-ok');\r\n        }\r\n\r\n        elQ.innerHTML = q.replace('_____', `<b>${a}<\/b>`);\r\n        elMsg.innerHTML = (correct\r\n          ? `<span style=\"color:#298d34;font-weight:700;\">\u00a1Correcto! (+10)<\/span><br>${exp}`\r\n          : `<span style=\"color:#c33;font-weight:700;\">Incorrecto.<\/span><br>${exp}`\r\n        );\r\n\r\n        hideOpts();\r\n        countdownThen(()=>{\r\n          ronda++;\r\n          if (ronda<totalRondas){ renderRound(); }\r\n          else{\r\n            game.endGame({ save:true, messageHTML:`\u00a1Juego terminado! Niveles: <b>${totalRondas}<\/b>` });\r\n          }\r\n        });\r\n      };\r\n      elOpts.appendChild(b);\r\n    });\r\n  }\r\n\r\n  \/\/ ---------- START ----------\r\n  game.onStart(()=>{\r\n    rootEl?.classList.add('rf-hide-instr'); \/\/ \u0441\u043a\u0440\u044b\u0442\u044c \u043b\u0435\u0432\u0443\u044e \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e\r\n    game.setMessage('');\r\n    game.showStart(false);\r\n    game.showContinue(false);\r\n    game.setScore(0);\r\n    game.startTimer();\r\n\r\n    ronda=0; showCore();\r\n\r\n    \/\/ \u041e\u0442\u0441\u0447\u0451\u0442 \u041f\u0415\u0420\u0415\u0414 \u041f\u0415\u0420\u0412\u042b\u041c \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u043c\r\n    countdownThen(renderRound);\r\n  });\r\n\r\n  game.onContinue(()=>{ \/* \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f *\/ });\r\n}\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-6d4d0bf elementor-widget elementor-widget-html\" data-id=\"6d4d0bf\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script>\r\n\/\/ ===== \u0423\u0422\u0418\u041b\u042b =====\r\nfunction zFmtMMSS(s){ s=Math.max(0,Math.floor(s)); const m=String(Math.floor(s\/60)).padStart(2,'0'); const sec=String(s%60).padStart(2,'0'); return `${m}:${sec}`; }\r\n\r\n\/\/ ===== API \u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e \u0441 \u0442\u0432\u043e\u0438\u043c\u0438 \u044d\u043a\u0448\u0435\u043d\u0430\u043c\u0438) =====\r\nasync function zLoadRecord(userId, gameId){\r\n  const r = await fetch(AJAX_URL,{method:'POST', body:new URLSearchParams({action:'zabalimp_comprobar_record', user_id:userId, game_id:gameId})});\r\n  const j = await r.json(); return Number(j.record||0);\r\n}\r\nasync function zSaveProgress(userId, gameId, score, seconds){\r\n  return fetch(AJAX_URL,{method:'POST', body:new URLSearchParams({action:'zabalimp_guardar_progreso', user_id:userId, game_id:gameId, score:score, tiempo_segundos:seconds})});\r\n}\r\n\r\n\/\/ ===== \u0420\u0435\u0439\u0442\u0438\u043d\u0433 (\u043e\u0436\u0438\u0434\u0430\u0435\u0442 WP-\u044d\u043a\u0448\u0435\u043d 'zabalimp_ranking') =====\r\n\/\/ \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b:\r\n\/\/   game_id   \u2014 \u0447\u0438\u0441\u043b\u043e \u0438\u043b\u0438 '' (\u0432\u0441\u0435 \u0438\u0433\u0440\u044b)\r\n\/\/   q         \u2014 \u0441\u0442\u0440\u043e\u043a\u0430 \u043f\u043e\u0438\u0441\u043a\u0430 (\u043f\u043e \u0438\u0433\u0440\u043e\u043a\u0443\/\u0438\u0433\u0440\u0435)\r\n\/\/   sort      \u2014 'jugador' | 'juego' | 'record' | 'fecha'\r\n\/\/   dir       \u2014 'asc' | 'desc'\r\n\/\/   limit     \u2014 \u043c\u0430\u043a\u0441 \u0441\u0442\u0440\u043e\u043a\r\nasync function zLoadRanking({game_id='', q='', sort='record', dir='desc', limit=100, offset=0} = {}){\r\n  try{\r\n    const params = new URLSearchParams({\r\n      action:'zabalimp_ranking',\r\n      game_id: game_id || '',\r\n      q: q || '',\r\n      sort, dir, limit, offset\r\n    });\r\n    const r = await fetch(AJAX_URL, { method:'POST', body: params });\r\n    const j = await r.json();\r\n    \/\/ \u043e\u0436\u0438\u0434\u0430\u0435\u043c j.rows: [{usuario, juego_id, juego_nombre, record, fecha}]\r\n    return Array.isArray(j?.rows) ? j.rows : [];\r\n  }catch(_){ return []; }\r\n}\r\n\r\n\r\n\/* ====== \u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u044b\u0439 \u0431\u0430\u043d\u043d\u0435\u0440 \u00ab\u041d\u043e\u0432\u044b\u0439 \u0440\u0435\u043a\u043e\u0440\u0434\u00bb ====== *\/\r\nfunction zShowRecordBanner({ gameTitle, oldRecord, newRecord }){\r\n  const ex = document.getElementById('z-record-banner');\r\n  if (ex) ex.remove();\r\n\r\n  const wrap = document.createElement('div');\r\n  wrap.id = 'z-record-banner';\r\n  wrap.style.cssText = `\r\n    position:fixed; inset:0; z-index:10000; background:rgba(0,0,0,.45); display:flex;\r\n    align-items:center; justify-content:center; backdrop-filter:saturate(120%) blur(2px);\r\n  `;\r\n  wrap.innerHTML = `\r\n    <div style=\"\r\n      width:min(680px,94vw); background:#fff; border-radius:18px; padding:18px 20px;\r\n      box-shadow:0 20px 40px #0005; text-align:center;\">\r\n      <div style=\"font-size:3.2rem; line-height:1\">\ud83c\udfc5<\/div>\r\n      <div style=\"font-weight:900; font-size:1.6rem; margin:6px 0 2px;\">\u00a1Nuevo r\u00e9cord!<\/div>\r\n      <div style=\"color:#0f172a; font-weight:800; margin-bottom:8px;\">${gameTitle || 'Juego'}<\/div>\r\n      <div style=\"font-size:1.05rem; color:#334155; margin:6px 0 12px;\">\r\n        Anterior: <b>${oldRecord||0}<\/b> \u2022 Nuevo: <b style=\"color:#0ea5e9\">${newRecord||0}<\/b>\r\n      <\/div>\r\n      <button id=\"z-record-ok\" class=\"z-btn z-btn-primary\" style=\"padding:10px 16px;border-radius:12px;\">\u00a1Genial!<\/button>\r\n    <\/div>\r\n  `;\r\n  document.body.appendChild(wrap);\r\n  document.getElementById('z-record-ok').onclick = ()=> wrap.remove();\r\n\r\n  try{ SFX?.success?.(); }catch(_){}\r\n  setTimeout(()=>{ if (document.getElementById('z-record-banner')) wrap.remove(); }, 4500);\r\n}\r\n\r\n\r\nasync function zLoadTotalTime(userId, gameId){\r\n  const r = await fetch(AJAX_URL,{method:'POST', body:new URLSearchParams({action:'zabalimp_tiempo_total', user_id:userId, game_id:gameId})});\r\n  const j = await r.json(); return Number(j.total||0);\r\n}\r\n\r\n\r\nfunction crearMarcoJuego({gameId, titulo, bg='#e4f7fa', saveOnExit=true}){\r\n  const floater = document.getElementById('trp-floater-ls-current-language'); if (floater) floater.style.display='none';\r\n  document.body.classList.add('zg-lock');\r\n\r\n  let root = document.getElementById('zabalimp-juego-fullscreen');\r\n  if (!root){ root=document.createElement('div'); root.id='zabalimp-juego-fullscreen'; document.body.appendChild(root); }\r\n  root.className='zg-root'; root.style.background=bg;\r\n\r\n  root.innerHTML = `\r\n<style>\r\n.zg-header{\r\n  display:grid !important;\r\n  grid-template-columns:minmax(0,1fr) auto;\r\n  grid-template-rows:auto auto;\r\n  grid-template-areas:\"user title\" \"badges exit\";\r\n  gap:6px 12px; align-items:center;\r\n}\r\n.zg-h-title{ grid-area:title; justify-self:end; font-weight:800; font-size:clamp(18px,2.4vw,24px); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\r\n.zg-badges{ grid-area:badges; display:flex; gap:10px; flex-wrap:wrap; align-items:center; }\r\n.zg-exit{ grid-area:exit; justify-self:end; }\r\n@media (max-width:600px){\r\n  .zg-header{ gap:6px 10px; }\r\n  .zg-h-title{ font-size:clamp(16px,4.2vw,20px); }\r\n  .zg-badges{ gap:6px; }\r\n}\r\n<\/style>\r\n\r\n<div class=\"zg-header\">\r\n  <div class=\"zg-user\">\ud83d\udc64 ${usuarioNombre}<\/div>\r\n  <div class=\"zg-h-title\">${titulo || ''}<\/div>\r\n  <div class=\"zg-badges\">\r\n    <span class=\"zg-badge\">Total: <b data-zg-total>--:--<\/b><\/span>\r\n    <span class=\"zg-badge\">R\u00e9cord: <b data-zg-record>0<\/b><\/span>\r\n  <\/div>\r\n  <button class=\"zg-exit z-btn z-btn-secondary\" type=\"button\">Salir<\/button>\r\n<\/div>\r\n\r\n<div class=\"zg-main\">\r\n  <aside class=\"zg-sidebar\">\r\n    <div class=\"zg-panel\">\r\n      <div class=\"zg-score\">\r\n        <span>Puntos: <span data-zg-score>0<\/span><\/span>\r\n        <span class=\"zg-time-inline\">\u00b7 <span data-zg-time>00:00<\/span><\/span>\r\n        <span class=\"zg-sfx-slot\"><\/span>\r\n      <\/div>\r\n    <\/div>\r\n  <\/aside>\r\n\r\n  <section class=\"zg-core\" data-zg-core>\r\n    <div class=\"zg-core-ui\">\r\n      <div class=\"zg-msg\" data-zg-msg><\/div>\r\n      <div class=\"zg-actions\">\r\n        <button class=\"zg-btn zg-btn-primary\" data-zg-start>Empezar<\/button>\r\n        <button class=\"zg-btn zg-btn-secondary\" data-zg-continue style=\"display:none\">Continuar<\/button>\r\n      <\/div>\r\n      <div class=\"zg-core-host\" data-zg-core-host><\/div>\r\n    <\/div>\r\n  <\/section>\r\n<\/div>\r\n  `;\r\n\r\n  const el = {\r\n    root,\r\n    coreHost: root.querySelector('[data-zg-core-host]'),\r\n    msg: root.querySelector('[data-zg-msg]'),\r\n    startBtn: root.querySelector('[data-zg-start]'),\r\n    contBtn: root.querySelector('[data-zg-continue]'),\r\n    score: root.querySelector('[data-zg-score]'),\r\n    time: root.querySelector('[data-zg-time]'),\r\n    record: root.querySelector('[data-zg-record]'),\r\n    total: root.querySelector('[data-zg-total]'),\r\n    exit: root.querySelector('.zg-exit'),\r\n    sfxSlot: root.querySelector('.zg-sfx-slot'),\r\n  };\r\n\r\n  const state = { gameId, score:0, startTs:null, timer:null, elapsed:0, record:0, finished:false };\r\n\r\n  function zFmtMMSS_local(s){\r\n    s = Math.max(0, Math.floor(s));\r\n    const m = String(Math.floor(s\/60)).padStart(2,'0');\r\n    const sec = String(s%60).padStart(2,'0');\r\n    return m + ':' + sec;\r\n  }\r\n  function tick(){\r\n    const s = Math.floor((Date.now()-state.startTs)\/1000);\r\n    state.elapsed = s;\r\n    if (el.time) el.time.textContent = zFmtMMSS_local(s);\r\n  }\r\n\r\n  const api = {\r\n    setMessage(html){ if (el.msg) el.msg.innerHTML = html||''; },\r\n    setScore(val){ state.score = Math.max(0,Number(val||0)); if (el.score) el.score.textContent = state.score; },\r\n    addScore(delta){ api.setScore(state.score + Number(delta||0)); },\r\n    showContinue(show=true){ if (el.contBtn) el.contBtn.style.display = show? '' : 'none'; },\r\n    showStart(show=true){ if (el.startBtn) el.startBtn.style.display = show? '' : 'none'; },\r\n    coreEl: el.coreHost,\r\n    startTimer(){ state.startTs = Date.now(); clearInterval(state.timer); state.timer = setInterval(tick, 1000); tick(); },\r\n    stopTimer(){ clearInterval(state.timer); },\r\n\r\n    async endGame({save=true, messageHTML=''}={}){\r\n      if (state.finished) return;\r\n      state.finished = true;\r\n      api.stopTimer();\r\n      if (messageHTML) api.setMessage(messageHTML);\r\n\r\n      let broke = false, oldRec = 0, newRec = state.score;\r\n\r\n      if (save){\r\n        if (!state.record) state.record = await zLoadRecord(usuarioId, state.gameId);\r\n        oldRec = state.record;\r\n\r\n        if (state.score > state.record){\r\n          broke = true;\r\n          await zSaveProgress(usuarioId, state.gameId, state.score, state.elapsed);\r\n          state.record = state.score;\r\n          if (el.record) el.record.textContent = state.record;\r\n        } else {\r\n          await zSaveProgress(usuarioId, state.gameId, state.record, state.elapsed);\r\n        }\r\n        const total = await zLoadTotalTime(usuarioId, state.gameId);\r\n        if (el.total) el.total.textContent = zFmtMMSS_local(total);\r\n      }\r\n\r\n      \/\/ \u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0431\u0430\u043d\u043d\u0435\u0440 \u2014 \u0421\u0420\u0410\u0417\u0423, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0438 \u043f\u0440\u0438 \u0432\u044b\u0445\u043e\u0434\u0435\r\n      if (broke){\r\n        zShowRecordBanner({ gameTitle: titulo, oldRecord: oldRec, newRecord: newRec });\r\n        try{ SFX?.success?.(); }catch(_){}\r\n      }\r\n\r\n      api.showStart(true); if (el.startBtn) el.startBtn.textContent='Jugar otra vez';\r\n      api.showContinue(false);\r\n    },\r\n\r\n    onStart(fn){ if (el.startBtn) el.startBtn.onclick = fn; },\r\n    onContinue(fn){ if (el.contBtn) el.contBtn.onclick = fn; },\r\n    get score(){ return state.score; },\r\n    get elapsed(){ return state.elapsed; },\r\n    get record(){ return state.record; },\r\n  };\r\n\r\n  \/\/ \u0432\u044b\u0445\u043e\u0434\r\n  el.exit.onclick = async ()=>{\r\n    if (saveOnExit && !state.finished){\r\n      await api.endGame({save:true});\r\n    }\r\n    document.body.classList.remove('zg-lock');\r\n    const floater2 = document.getElementById('trp-floater-ls-current-language'); if (floater2) floater2.style.display='';\r\n    root.remove();\r\n    const menu = document.getElementById('zabalimp-menu'); if (menu) menu.style.display='';\r\n  };\r\n\r\n  (async ()=>{\r\n    state.record = await zLoadRecord(usuarioId, gameId);\r\n    if (el.record) el.record.textContent = state.record;\r\n    const total = await zLoadTotalTime(usuarioId, gameId);\r\n    if (el.total) el.total.textContent = zFmtMMSS_local(total);\r\n  })();\r\n\r\n  document.addEventListener('visibilitychange', ()=>{ if (document.hidden) api.stopTimer(); else if (!state.finished && state.startTs) state.timer=setInterval(tick,1000); });\r\n\r\n  const sfxParent = el.sfxSlot || el.score;\r\n  if (window.SFX && sfxParent) SFX.injectToggle(sfxParent);\r\n\r\n  return api;\r\n}\r\n<\/script>\r\n\r\n\r\n\r\n\r\n\r\n<script>\r\n\/\/ ===== Mini SoundKit \u2014 \u0435\u0434\u0438\u043d\u044b\u0439 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0438\u0433\u0440 =====\r\n(function(){\r\n  class SoundKit{\r\n    constructor(){\r\n      this.ctx = null;\r\n      this.enabled = JSON.parse(localStorage.getItem('sfx_enabled') ?? 'true');\r\n      this.master = 0.32;\r\n      this.bgm = null;\r\n      this.toggleBtn = null;\r\n    }\r\n    unlock(){ \/\/ \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u00abEmpezar\u00bb\r\n      if(!this.ctx) this.ctx = new (window.AudioContext||window.webkitAudioContext)();\r\n      if(this.ctx.state==='suspended') this.ctx.resume();\r\n    }\r\n    _env(ms, g=0.25){ \/\/ \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043e\u0433\u0438\u0431\u0430\u044e\u0449\u0443\u044e\r\n      const now = this.ctx.currentTime, gain = this.ctx.createGain();\r\n      gain.gain.setValueAtTime(0, now);\r\n      gain.gain.linearRampToValueAtTime(g*this.master, now+0.02);\r\n      gain.gain.linearRampToValueAtTime(0, now+ms\/1000);\r\n      return {gain, when: now, stopAt: now+ms\/1000+0.03};\r\n    }\r\n    tone(freq, ms=120, type='sine', vol=0.24){\r\n      if(!this.enabled) return;\r\n      this.unlock();\r\n      const {gain, when, stopAt} = this._env(ms, vol);\r\n      const osc = this.ctx.createOscillator();\r\n      osc.type=type; osc.frequency.setValueAtTime(freq, when);\r\n      osc.connect(gain); gain.connect(this.ctx.destination);\r\n      osc.start(when); osc.stop(stopAt);\r\n    }\r\n    chord(freqs=[523,659,784], noteMs=90, gapMs=45){\r\n      freqs.forEach((f,i)=> setTimeout(()=>this.tone(f, noteMs, 'sine', 0.22), i*(noteMs+gapMs)));\r\n    }\r\n    gliss(f1=440, f2=220, ms=260){\r\n      if(!this.enabled) return;\r\n      this.unlock();\r\n      const {gain, when, stopAt} = this._env(ms, 0.22);\r\n      const osc = this.ctx.createOscillator();\r\n      osc.type='sawtooth';\r\n      osc.frequency.setValueAtTime(f1, when);\r\n      osc.frequency.linearRampToValueAtTime(f2, when+ms\/1000);\r\n      osc.connect(gain); gain.connect(this.ctx.destination);\r\n      osc.start(when); osc.stop(stopAt);\r\n    }\r\n    click(){ this.tone(880, 40, 'square', 0.14); }\r\n    success(){ this.chord([523,659,784]); }       \/\/ \u0434\u043e-\u043c\u0438-\u0441\u043e\u043b\u044c\r\n    fail(){ this.gliss(392,196,280); }            \/\/ \u043d\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0439 \u00ab\u0431\u0443\u0443\u043c\u00bb\r\n    ui(){ this.tone(1200, 55, 'triangle', 0.12); } \/\/ \u043b\u0451\u0433\u043a\u0438\u0439 UI-\u0442\u0438\u043a\r\n\r\n    \/\/ --- BGM (\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e) ---\r\n    startBgm(url, vol=0.12){\r\n      if(!this.enabled) return;\r\n      this.stopBgm();\r\n      const a = new Audio(url);\r\n      a.loop = true; a.volume = vol; a.crossOrigin='anonymous';\r\n      a.play().catch(()=>{}); this.bgm = a;\r\n    }\r\n    stopBgm(){ if(this.bgm){ this.bgm.pause(); this.bgm=null; } }\r\n\r\n    setEnabled(v){\r\n      this.enabled = v;\r\n      localStorage.setItem('sfx_enabled', JSON.stringify(v));\r\n      if(!v) this.stopBgm();\r\n      this._updateToggle();\r\n    }\r\n    toggle(){ this.setEnabled(!this.enabled); }\r\n\r\n    injectToggle(parent){ \/\/ \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430 \ud83d\udd0a\/\ud83d\udd07 \u0432 HUD\r\n      if(!parent) return;\r\n      if(this.toggleBtn) return;\r\n      const b = document.createElement('button');\r\n      b.title='Sonido';\r\n      b.style.cssText = 'margin-left:8px;padding:6px 10px;border-radius:10px;border:1px solid #e5e7eb;background:#fff;font-weight:800;cursor:pointer;';\r\n      b.onclick = ()=>{ this.toggle(); this.ui(); };\r\n      parent.appendChild(b);\r\n      this.toggleBtn = b;\r\n      this._updateToggle();\r\n    }\r\n    _updateToggle(){\r\n      if(!this.toggleBtn) return;\r\n      this.toggleBtn.textContent = this.enabled ? '\ud83d\udd0a' : '\ud83d\udd07';\r\n      this.toggleBtn.style.opacity = this.enabled ? '1' : '.6';\r\n    }\r\n  }\r\n  window.SFX = new SoundKit();\r\n})();\r\n<\/script>\r\n\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Instalar app Bienvenido\/a a Zaba Limp Tu nombre Entrar Cambiar usuario<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_joinchat":[],"footnotes":""},"class_list":["post-4295","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/zabalimp.es\/eu\/wp-json\/wp\/v2\/pages\/4295","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/zabalimp.es\/eu\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/zabalimp.es\/eu\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/zabalimp.es\/eu\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/zabalimp.es\/eu\/wp-json\/wp\/v2\/comments?post=4295"}],"version-history":[{"count":0,"href":"https:\/\/zabalimp.es\/eu\/wp-json\/wp\/v2\/pages\/4295\/revisions"}],"wp:attachment":[{"href":"https:\/\/zabalimp.es\/eu\/wp-json\/wp\/v2\/media?parent=4295"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}