top of page

Dos juegos de multiplicar

  • Foto del escritor: Alejandro Rivero
    Alejandro Rivero
  • 17 ago 2025
  • 13 Min. de lectura

el primer juego te permite ir construyendo las tablas

<!doctype html><html lang="es"><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Tabla de multiplicar 1–10</title> <style> :root{ --bg:#2a1025; /* slate-900 */ --panel:#3b0a2a; /* gray-900 */ --muted:#5b2246; /* gray-800 */ --text:#ffe4f1; /* gray-200 */ --text-dim:#f9a8d4; /* gray-400 */ --accent:#ec4899; /* green-500 */ --accent-2:#f472b6; /* sky-400 */ --danger:#ef4444; /* red-500 */ --cell:#1a0a16; /* dark cell */ --cell-hover:#261023; --filled:#2f0b23; /* faint green */ --grid-gap:6px; --cell-size:64px; --radius:16px; --shadow:0 10px 30px rgba(0,0,0,.35); } *{box-sizing:border-box} html,body{height:100%} body{ margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif; background: radial-gradient(1200px 800px at 20% -10%, #7e2553 0%, transparent 60%), radial-gradient(1000px 800px at 110% 10%, #3b0a2a 0%, transparent 55%), var(--bg); color:var(--text); display:flex; align-items:center; justify-content:center; padding:24px; } .app{ width:min(1120px, 100%); max-height: calc(100vh - 48px); overflow:auto; background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02)); border:1px solid rgba(255,255,255,.08); border-radius:24px; padding:20px 20px 28px; box-shadow:var(--shadow); backdrop-filter: blur(6px); } header{display:flex; align-items:flex-end; justify-content:space-between; gap:16px; flex-wrap:wrap;} h1{margin:0; font-size:clamp(20px, 3.6vw, 28px); letter-spacing:.3px} .hint{color:var(--text-dim); font-size:14px} .grid{ margin-top:16px; display:grid; grid-template-columns: repeat(11, 1fr); gap:var(--grid-gap); align-items:stretch; } .cell, .head{ display:flex; align-items:center; justify-content:center; aspect-ratio:1/1; width:100%; min-width:22px; user-select:none; -webkit-user-select:none; border-radius:12px; border:1px solid rgba(255,255,255,.06); background:var(--cell); font-size: var(--cell-font, 16px); transition: transform .08s ease, background .15s ease, border-color .2s ease, box-shadow .2s ease; } .head{ background:linear-gradient(180deg, rgba(255,255,255,.09), rgba(255,255,255,.04)); font-weight:600 } .corner{ background:transparent; border:1px dashed rgba(255,255,255,.12)} .row-head,.col-head{color:var(--accent-2)} .cell{ cursor:pointer; color:var(--text-dim); font-variant-numeric:tabular-nums; } .cell:hover{ background:var(--cell-hover); transform: translateY(-1px); box-shadow:0 6px 20px rgba(0,0,0,.28)} .cell.filled{ background:linear-gradient(180deg, rgba(236,72,153,.22), rgba(236,72,153,.10)); color:#ffd1e6; border-color: rgba(236,72,153,.45); font-weight:600 } .cell.disabled{ cursor:not-allowed; opacity:.6 } .toolbar{display:flex; gap:10px; align-items:center; margin-top:10px; flex-wrap:wrap} .btn{ appearance:none; border:none; padding:10px 14px; border-radius:12px; color:var(--text); background:var(--muted); cursor:pointer; font-weight:600; box-shadow:var(--shadow); opacity:.95 } .btn:hover{ filter:brightness(1.05) } .btn:active{ transform: translateY(1px) } .btn.reset{ background: linear-gradient(180deg, rgba(239,68,68,.18), rgba(239,68,68,.08)); border:1px solid rgba(239,68,68,.35) } /* Modal */ .overlay{ position:fixed; inset:0; background:rgba(0,0,0,.6); display:none; align-items:center; justify-content:center; z-index:1000; } .overlay.open{ display:flex } .modal{ background:linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.04)); border:1px solid rgba(255,255,255,.15); border-radius:24px; padding:28px 24px; width:min(520px, 92%); text-align:center; box-shadow:var(--shadow)} .equation{ font-size: clamp(40px, 7vw, 72px); line-height:1.15; font-weight:800; letter-spacing:.5px; margin:6px 0 4px } .equation.hidden{ visibility:hidden } .sub{ color:var(--text-dim); margin-top:6px } .actions{ display:none; gap:12px; justify-content:center; margin-top:18px } .actions.show{ display:flex } .btn.primary{ background:linear-gradient(180deg, rgba(236,72,153,.32), rgba(236,72,153,.16)); border:1px solid rgba(236,72,153,.55) } .btn.ghost{ background:linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04)); border:1px solid rgba(255,255,255,.18) } .badge{ display:inline-flex; align-items:center; gap:8px; font-size:12px; color:var(--text-dim); padding:6px 10px; border-radius:999px; background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.12) } .kbd{ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; padding:.5px 6px; background:#00000044; border-radius:6px; border:1px solid #ffffff22 } @media (max-width: 720px){ :root{ --grid-gap:4px } .badge{ display:none } } .badge{ display:none } } </style></head><body> <div class="app" role="application" aria-label="Entrenador de tabla de multiplicar 1 a 10"> <header> <div> <h1>Tabla de multiplicar 1–10</h1> <div class="hint">Haz clic en una casilla <span class="badge">fila ? primer número  •  columna ? segundo número</span></div> </div> <div class="toolbar"> <button class="btn reset" id="reset">Reiniciar tablero</button> </div> </header> <div id="grid" class="grid" aria-label="Cuadrícula 10 por 10 de operaciones"> <!-- Primera fila: esquina vacía + cabeceras de columnas 1..10 --> <div class="head corner" aria-hidden="true"> </div> <script> for(let c=1;c<=10;c++){ document.write(`<div class=\"head col-head\" aria-label=\"Columna ${c}\">${c}</div>`); } </script> <!-- Filas 1..10: cabecera de fila + 10 celdas --> <script> for(let r=1;r<=10;r++){ document.write(`<div class=\"head row-head\" aria-label=\"Fila ${r}\">${r}</div>`); for(let c=1;c<=10;c++){ const id = `${r}x${c}`; document.write(`<button class=\"cell\" id=\"cell-${id}\" data-r=\"${r}\" data-c=\"${c}\" aria-label=\"${r} por ${c}\" title=\"${r} × ${c}\"></button>`); } } </script> </div> </div> <!-- Modal --> <div id="overlay" class="overlay" aria-modal="true" role="dialog" aria-labelledby="eq" aria-describedby="hint"> <div class="modal"> <div id="eq" class="equation"> </div> <div id="hint" class="sub"></div> <div class="actions" id="actions"> <button class="btn primary" id="saveBtn">Guardar</button> <button class="btn ghost" id="cancelBtn">No guardar</button> </div> </div> </div> <script> const grid = document.getElementById('grid'); const overlay = document.getElementById('overlay'); const eqEl = document.getElementById('eq'); const actions = document.getElementById('actions'); const resetBtn = document.getElementById('reset'); const saveBtn = document.getElementById('saveBtn'); const cancelBtn = document.getElementById('cancelBtn'); const root = document.documentElement; let currentTarget = null; // celda activa // ====== Responsividad: ajusta el tamaño de la tipografía según el ancho real de columna ====== function updateResponsiveVars(){ // Calcula el ancho de una columna (11 columnas y 10 huecos) const cw = grid.clientWidth || 0; const styles = getComputedStyle(grid); const gap = parseFloat(styles.gap) || 0; const colW = Math.max(0, (cw - gap * 10) / 11); // Tamaño de fuente dentro de cada celda/cabecera (entre 10px y 28px) const fontPx = Math.max(10, Math.min(28, Math.floor(colW * 0.36))); root.style.setProperty('--cell-font', fontPx + 'px'); } window.addEventListener('resize', updateResponsiveVars); // Observa cambios de tamaño del contenedor new ResizeObserver(updateResponsiveVars).observe(grid); // ====== Parámetros adaptativos de práctica ====== let targetTimeMs = 6000; // tiempo automático para revelar const blinkIntervalMs = 750; // parpadeo más lento let autoTimer = null, blinkTimer = null; let openedAt = 0; // timestamp cuando se abre el modal let revealed = false; // si ya se mostró el resultado let lastResponseTimeMs = null; // último tiempo medido let eqClickHandler = null; // referencia para retirar listener function startBlink(){ stopBlink(); blinkTimer = setInterval(() => { eqEl.classList.toggle('hidden'); }, blinkIntervalMs); } function stopBlink(){ if(blinkTimer){ clearInterval(blinkTimer); blinkTimer = null; } eqEl.classList.remove('hidden'); } function revealResult(prod){ if(revealed) return; revealed = true; clearTimeout(autoTimer); autoTimer = null; stopBlink(); eqEl.textContent = String(prod); actions.classList.add('show'); document.body.style.pointerEvents = ''; if(eqClickHandler){ eqEl.removeEventListener('click', eqClickHandler); eqClickHandler = null; } } function openModalForCell(btn){ const r = Number(btn.dataset.r), c = Number(btn.dataset.c); if(btn.classList.contains('filled')) return; currentTarget = btn; const prod = r * c; eqEl.textContent = `${r} × ${c}`; actions.classList.remove('show'); overlay.classList.add('open'); document.body.style.pointerEvents = 'none'; overlay.style.pointerEvents = 'auto'; // Inicialización de tiempos openedAt = performance.now(); revealed = false; lastResponseTimeMs = null; // Parpadeo lento startBlink(); // Click en el número: si ocurre antes de 6 s, revelar inmediatamente eqClickHandler = () => { if(revealed) return; const elapsed = performance.now() - openedAt; if(elapsed < 6000){ lastResponseTimeMs = elapsed; revealResult(prod); } }; eqEl.addEventListener('click', eqClickHandler); // Revelación automática al objetivo (máx 10 s) const autoMs = Math.min(targetTimeMs, 10000); autoTimer = setTimeout(() => { lastResponseTimeMs = autoMs; // si no hubo clic revealResult(prod); }, autoMs); } function closeModal(){ overlay.classList.remove('open'); actions.classList.remove('show'); clearTimeout(autoTimer); autoTimer = null; stopBlink(); if(eqClickHandler){ eqEl.removeEventListener('click', eqClickHandler); eqClickHandler = null; } currentTarget = null; document.body.style.pointerEvents = ''; } grid.addEventListener('click', (e) => { const btn = e.target.closest('.cell'); if(!btn) return; if(btn.classList.contains('filled')) return; openModalForCell(btn); }); saveBtn.addEventListener('click', () => { if(!currentTarget) return; const r = Number(currentTarget.dataset.r), c = Number(currentTarget.dataset.c); currentTarget.textContent = r*c; currentTarget.classList.add('filled'); // Adaptación lenta hacia el tiempo real observado if(lastResponseTimeMs != null){ // EMA con peso 0.1 targetTimeMs = Math.round( targetTimeMs * 0.9 + lastResponseTimeMs * 0.1 ); // nunca superar 10 s targetTimeMs = Math.min(targetTimeMs, 10000); } closeModal(); }); cancelBtn.addEventListener('click', () => { // Extiende un poco (máx 10 s) si el alumno pulsa "No guardar" targetTimeMs = Math.min(10000, targetTimeMs + 500); closeModal(); }); overlay.addEventListener('click', (e) => { if(e.target === overlay) closeModal(); }); window.addEventListener('keydown', (e) => { if(e.key === 'Escape' && overlay.classList.contains('open')) closeModal(); }); resetBtn.addEventListener('click', () => { document.querySelectorAll('.cell.filled').forEach(el => { el.textContent = ''; el.classList.remove('filled'); }); }); // Inicializa variables responsivas updateResponsiveVars(); </script></body></html>

// Create a separate document for the iframe via a Blob URL document.getElementById('game0').srcdoc = document.getElementById('game0-html').value;

el segundo juego te permite añadir numeros al tablero, superponerlos para que se multipliquen, o llevarlos al area de desmultiplicacion para factorizarlos

<!doctype html><html lang="es"><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Desmultiplicar — Tabla de multiplicar y factorización</title> <style> :root { /* Tema rosa */ --bg:#ffe4f1; /* fondo general rosa claro */ --panel:#ffd1e8; /* panel lateral más intenso */ --stage:#ffe9f3; /* área de juego */ --text:#4a0433; /* texto frambuesa oscuro */ --muted:#8a2c54; --accent:#ec4899; /* fucsia */ /* Tamaños */ --box-size: 54px; /* ~15% menos que 64px */ --gap: 10px; --sidebar-pad:12px; } *{ box-sizing:border-box } html,body{ height:100% } body{ margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Inter,Ubuntu,Arial; background:var(--bg); color:var(--text); -webkit-tap-highlight-color:transparent } /* Dos columnas: menú a la IZQUIERDA, escenario a la DERECHA */ .app{ display:flex; flex-direction:row; align-items:stretch; height:100vh; width:100vw; overflow:hidden } /* Menú: ancho exactamente el de las cajas + padding; nunca más ancho */ .sidebar{ order:1; background:linear-gradient(180deg, var(--panel), #ffd9ee); border-right:1px solid #f0a0c6; padding:var(--sidebar-pad); display:flex; flex-direction:column; gap:12px; flex:0 0 calc(var(--box-size) + var(--sidebar-pad)*2); } .header{ display:flex; align-items:center; justify-content:center; gap:8px } .btn{ width:100%; background:#f8c2da; border:1px solid #db2777; color:#4a0433; padding:8px 12px; border-radius:12px; cursor:pointer; user-select:none; font-weight:700 } .btn:hover{ background:#f6a9ca } /* Paleta: una sola columna fija, mismo tamaño que las cajas del juego */ .palette{ display:flex; flex-direction:column; gap:calc(var(--gap) * 1); align-items:center } .palette .seed{ width:var(--box-size); height:var(--box-size); background:linear-gradient(180deg,#f9a8d4,#f472b6); border:1px solid #db2777; border-radius:12px; display:grid; place-items:center; font-weight:800; font-size: calc(var(--box-size) * 0.5); color:#ffffff; box-shadow:inset 0 0 0 1px rgba(255,255,255,.25),0 2px 10px rgba(219,39,119,.25); touch-action:none; user-select:none } .palette .seed:active{ transform:scale(.98) } .stage-wrap{ order:2; position:relative; flex:1 1 auto; min-width:0 } .stage{ position:relative; height:100%; width:100%; overflow:hidden; background: radial-gradient(1200px 400px at 80% -20%, rgba(236,72,153,.18), transparent 60%), linear-gradient(var(--stage), var(--stage)); } .grid{ position:absolute; inset:0; background-image:linear-gradient(to right, rgba(236,72,153,.09) 1px, transparent 1px), linear-gradient(to bottom, rgba(236,72,153,.09) 1px, transparent 1px); background-size:40px 40px; pointer-events:none } .box{ position:absolute; width: var(--box-size); height: var(--box-size); border-radius:14px; background:linear-gradient(180deg,#f472b6,#db2777); border:1px solid #be185d; display:flex; align-items:center; justify-content:center; font-weight:800; color:#fff; box-shadow:0 10px 20px rgba(219,39,119,.35); touch-action:none; user-select:none; transition:box-shadow .12s ease } .box.dragging{ box-shadow:0 16px 32px rgba(219,39,119,.55) } .box .val{ display:block; padding:0 6px; width:100%; text-align:center; font-size: calc(var(--box-size) * 0.5); line-height:1 } .drop-merge{ outline:2px dashed rgba(236,72,153,.9) } /* Desmultiplicador: arriba a la derecha del escenario */ .device{ position:absolute; top:16px; right:16px; width: calc(var(--box-size) * 3.2); height: calc(var(--box-size) * 2.3); border-radius:16px; border:2px dashed rgba(236,72,153,.6); background:rgba(236,72,153,.12); display:flex; align-items:center; justify-content:center; text-align:center; padding:12px; color:#9d174d; font-weight:800; backdrop-filter:blur(2px); pointer-events:none } /* Fantasma al arrastrar desde la paleta: mismo tamaño y tema rosa */ .ghost{ position:fixed; z-index:50; width:var(--box-size); height:var(--box-size); border-radius:14px; background:linear-gradient(180deg,#f9a8d4,#f472b6); border:1px solid #db2777; display:flex; align-items:center; justify-content:center; font-weight:800; color:#ffffff; opacity:.9; pointer-events:none; box-shadow:0 10px 20px rgba(219,39,119,.35) } </style></head><body> <div class="app"> <aside class="sidebar"> <div class="header"> <button class="btn" id="clearBtn">Limpiar</button> </div> <div class="palette" id="palette" aria-label="Cajitas 0 a 9"></div> </aside> <main class="stage-wrap"> <div class="stage" id="stage"> <div class="grid"></div> <div class="device" id="device">Desmultiplicador</div> </div> </main> </div> <script> (() => { const stage = document.getElementById('stage'); const palette = document.getElementById('palette'); const device = document.getElementById('device'); const clearBtn = document.getElementById('clearBtn'); const sidebar = document.querySelector('.sidebar'); const root = document.documentElement; let idCounter = 0; const PX = (n) => `${n}px`; function stageRect(){ return stage.getBoundingClientRect(); } function clamp(n, min, max){ return Math.max(min, Math.min(max, n)); } function uid(){ return `box-${++idCounter}`; } // Ajuste dinámico: asegurar que caben 10 semillas + botón en vertical function fitSidebar(){ const seedCount = 10; const styles = getComputedStyle(root); const gap = parseFloat(styles.getPropertyValue('--gap')) || 10; const pad = parseFloat(styles.getPropertyValue('--sidebar-pad')) || 12; const headerH = document.querySelector('.header').getBoundingClientRect().height; const avail = sidebar.clientHeight - headerH - pad; // alto usable para semillas const base = 54; // desde :root (15% más pequeño) const sizeFit = Math.floor((avail - (seedCount-1)*gap) / seedCount); const finalSize = Math.max(36, Math.min(base, sizeFit)); root.style.setProperty('--box-size', finalSize + 'px'); } window.addEventListener('resize', fitSidebar); // Utilidades de movimiento y animación function targetWithin(el, dx, dy){ const srect = stageRect(); const tx = clamp(parseInt(el.style.left) + dx, 2, srect.width - el.offsetWidth - 2); const ty = clamp(parseInt(el.style.top) + dy, 2, srect.height - el.offsetHeight - 2); return [tx, ty]; } function animateTo(el, tx, ty, ms=160){ const fromX = parseInt(el.style.left); const fromY = parseInt(el.style.top); const dx = tx - fromX, dy = ty - fromY; return new Promise(resolve=>{ const anim = el.animate([ { transform: 'translate(0px, 0px)' }, { transform: `translate(${dx}px, ${dy}px)` } ], { duration: ms, easing: 'cubic-bezier(.2,.9,.2,1)'}); anim.onfinish = () => { el.style.left = PX(tx); el.style.top = PX(ty); el.style.transform = 'translate(0,0)'; resolve(); }; }); } function nudgeAnimated(el, dx, dy, ms=160){ const [tx, ty] = targetWithin(el, dx, dy); return animateTo(el, tx, ty, ms); } function boxes(){ return Array.from(stage.querySelectorAll('.box')); } const DIRS = [ [-1, 0], [1, 0], [0, -1], [0, 1], // ejes [-1,-1], [1,-1], [-1, 1], [1, 1] // diagonales ]; async function separatePair(a, b){ const step = Math.max(18, Math.floor(a.offsetWidth * 0.55)); let ia = Math.floor(Math.random()*DIRS.length); let ib = Math.floor(Math.random()*DIRS.length); if(ib === ia) ib = (ia + 4) % DIRS.length; let guard = 0; while(overlapArea(a,b) > 0 && guard++ < 8){ await Promise.all([ nudgeAnimated(a, DIRS[ia][0]*step, DIRS[ia][1]*step, 130), nudgeAnimated(b, DIRS[ib][0]*step, DIRS[ib][1]*step, 130) ]); } pop(a); pop(b); } async function resolveAllOverlaps(){ let changed = true; let passes = 0; while(changed && passes++ < 4){ changed = false; const bs = boxes(); for(let i=0;i<bs.length;i++){ for(let j=i+1;j<bs.length;j++){ if(overlapArea(bs[i], bs[j]) > 0){ await separatePair(bs[i], bs[j]); changed = true; } } } } } // --- Paleta: arrastrar semilla desde columna hacia el escenario --- function createSeed(n){ const div = document.createElement('div'); div.className = 'seed'; div.textContent = n; div.setAttribute('role','button'); let ghost = null; let ghostHalf = 35; // fallback function onMove(e){ if(!ghost) return; ghost.style.left = PX(e.clientX - ghostHalf); ghost.style.top = PX(e.clientY - ghostHalf); } function onUp(e){ if(ghost){ ghost.remove(); ghost = null; } window.removeEventListener('pointermove', onMove); window.removeEventListener('pointerup', onUp); const rect = stageRect(); if(e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom){ const x = clamp(e.clientX - rect.left - ghostHalf, 2, rect.width - (ghostHalf*2) - 2); const y = clamp(e.clientY - rect.top - ghostHalf, 2, rect.height - (ghostHalf*2) - 2); createBox(n, x, y); resolveAllOverlaps(); } } div.addEventListener('pointerdown', (e)=>{ e.preventDefault(); ghost = document.createElement('div'); ghost.className = 'ghost'; ghost.textContent = n; document.body.appendChild(ghost); // medir tamaño real ghostHalf = ghost.getBoundingClientRect().width / 2; onMove(e); window.addEventListener('pointermove', onMove, {passive:false}); window.addEventListener('pointerup', onUp, {passive:false}); }); return div; } function createBox(value, x=20, y=20){ const el = document.createElement('div'); el.className = 'box'; el.dataset.id = uid(); el.dataset.value = String(value); el.style.left = PX(x); el.style.top = PX(y); const span = document.createElement('span'); span.className = 'val'; span.textContent = String(value); el.appendChild(span); stage.appendChild(el); makeDraggable(el); pop(el); return el; } function makeDraggable(el){ let offsetX=0, offsetY=0, dragging=false; el.addEventListener('pointerdown', (e)=>{ e.preventDefault(); startDrag(e, el); }); function onPointerMove(e){ if(!dragging) return; const rect = stageRect(); const x = clamp(e.clientX - rect.left - offsetX, 2, rect.width - el.offsetWidth - 2); const y = clamp(e.clientY - rect.top - offsetY, 2, rect.height - el.offsetHeight - 2); el.style.left = PX(x); el.style.top = PX(y); const target = findMergeTarget(el); document.querySelectorAll('.box').forEach(b => b.classList.remove('drop-merge')); if(target) target.classList.add('drop-merge'); } function onPointerUp(e){ if(!dragging) return; dragging = false; el.releasePointerCapture(e.pointerId); el.classList.remove('dragging'); document.querySelectorAll('.box').forEach(b => b.classList.remove('drop-merge')); if(overlaps(el, device, 0.45)){ handleFactorizeDrop(el); cleanup(); return; } const target = findMergeTarget(el); if(target){ combine(el, target); cleanup(); resolveAllOverlaps(); return; } // Si se suelta sin operar, resolver posibles solapes con animación resolveAllOverlaps(); cleanup(); } function cleanup(){ window.removeEventListener('pointermove', onPointerMove); window.removeEventListener('pointerup', onPointerUp); } function startDrag(e){ dragging = true; el.setPointerCapture(e.pointerId); el.classList.add('dragging'); const rect = el.getBoundingClientRect(); const srect = stageRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; const x = clamp(rect.left - srect.left, 2, srect.width - el.offsetWidth - 2); const y = clamp(rect.top - srect.top, 2, srect.height - el.offsetHeight - 2); el.style.left = PX(x); el.style.top = PX(y); window.addEventListener('pointermove', onPointerMove, {passive:false}); window.addEventListener('pointerup', onPointerUp, {passive:false}); } el.startDrag = (e)=> startDrag(e); } function findMergeTarget(el){ const boxes = Array.from(stage.querySelectorAll('.box')).filter(b => b !== el); let best=null, bestArea=0; for(const b of boxes){ const a = overlapArea(el, b); const minArea = 0.45 * Math.min(el.offsetWidth*el.offsetHeight, b.offsetWidth*b.offsetHeight); if(a > minArea && a > bestArea){ best = b; bestArea = a; } } return best; } function overlaps(a, b, threshold=0.3){ const area = overlapArea(a,b); const minArea = threshold * Math.min(a.offsetWidth*a.offsetHeight, b.offsetWidth*b.offsetHeight); return area > minArea; } function overlapArea(a,b){ const ra = a.getBoundingClientRect(); const rb = b.getBoundingClientRect(); const x1 = Math.max(ra.left, rb.left); const y1 = Math.max(ra.top, rb.top); const x2 = Math.min(ra.right, rb.right); const y2 = Math.min(ra.bottom, rb.bottom); const w = Math.max(0, x2-x1); const h = Math.max(0, y2-y1); return w*h; } function combine(a,b){ const va = parseInt(a.dataset.value); const vb = parseInt(b.dataset.value); const product = va * vb; const ax = parseInt(a.style.left), ay = parseInt(a.style.top); const bx = parseInt(b.style.left), by = parseInt(b.style.top); a.remove(); b.remove(); const nx = Math.round((ax + bx)/2); const ny = Math.round((ay + by)/2); const nb = createBox(product, nx, ny); pop(nb); } function pop(el){ el.animate([ { transform: 'scale(0.85)', opacity: .7 }, { transform: 'scale(1.06)', opacity: 1 }, { transform: 'scale(1)', opacity: 1 }, ], { duration: 200, easing: 'cubic-bezier(.2,.9,.2,1)'}); } function slideTo(el, toX, toY, ms=300){ const fromX = parseInt(el.style.left); const fromY = parseInt(el.style.top); const dx = toX - fromX, dy = toY - fromY; return new Promise(resolve=>{ const anim = el.animate([ { transform: 'translate(0px, 0px)' }, { transform: `translate(${dx}px, ${dy}px)` } ], { duration: ms, easing: 'cubic-bezier(.2,.9,.2,1)' }); anim.onfinish = () => { el.style.left = PX(toX); el.style.top = PX(toY); el.style.transform = 'translate(0,0)'; resolve(); }; }); } function handleFactorizeDrop(el){ const n = parseInt(el.dataset.value); const [f1, f2] = pickFactors(n); const srect = stageRect(); const drect = device.getBoundingClientRect(); const boxW = el.offsetWidth, boxH = el.offsetHeight; el.remove(); // Coordenadas relativas del dispositivo respecto al escenario const devLeft = drect.left - srect.left; const devTop = drect.top - srect.top; const devRight = drect.right - srect.left; const devBottom = drect.bottom - srect.top; const devCX = devLeft + (devRight - devLeft)/2; const devCY = devTop + (devBottom - devTop)/2; // Spawn junto al borde del dispositivo const spawnLeftX = clamp(devLeft - boxW - 8, 2, srect.width - boxW - 2); const spawnLeftY = clamp(devCY - boxH/2, 2, srect.height - boxH - 2); const spawnDownX = clamp(devCX - boxW/2, 2, srect.width - boxW - 2); const spawnDownY = clamp(devBottom + 8, 2, srect.height - boxH - 2); const a = createBox(f1, spawnLeftX, spawnLeftY); // saldrá hacia la izquierda const b = createBox(f2, spawnDownX, spawnDownY); // caerá hacia abajo // Destinos separados para evitar coincidencias entre ellos const leftTargetX = clamp(spawnLeftX - Math.max(120, boxW*2), 2, srect.width - boxW - 2); const leftTargetY = spawnLeftY; const downTargetX = spawnDownX; const downTargetY = clamp(spawnDownY + Math.max(120, boxH*2), 2, srect.height - boxH - 2); Promise.all([ slideTo(a, leftTargetX, leftTargetY, 320), slideTo(b, downTargetX, downTargetY, 320) ]).then(()=>{ pop(a); pop(b); resolveAllOverlaps(); }); } function pickFactors(n){ if(n === 0){ return [0, 1 + Math.floor(Math.random()*9)]; } if(n === 1){ return [1, 1]; } const pairs = []; for(let f=2; f*f<=n; f++) if(n % f === 0) pairs.push([f, n/f]); if(pairs.length === 0) return [1, n]; return pairs[Math.floor(Math.random()*pairs.length)]; } // Construir paleta 0..9 (columna fija) for(let i=0;i<=9;i++) palette.appendChild(createSeed(i)); // Ajuste inicial y on-resize fitSidebar(); // Limpiar escenario clearBtn.addEventListener('click', ()=>{ stage.querySelectorAll('.box').forEach(b => b.remove()); }); })(); </script></body></html>

document.getElementById('game').srcdoc = document.getElementById('game-html').value;

Los dos han sido hechos via vibe coding en GPT-5. El primero:

quiero una pagina html que se puede ejecutar en local en cualquier navegador, para aprender la tabla de multiplicar. Debe mostrar la casilla 10x10 como en el juego de barcos, la fila indica el primer numero la columna el segundo, n x m. Al clickar en un caso vacio, un pop up muestra en grande la multiplicacion y luego va flasheando un numero de veces, apropiado a estimular el recuerdo, hasta que finalmente se transmuta en el resultado y saca dos botones: "guardar" "no guardar", si el usuario pulsa guardar la casilla correspondiente se rellena con el resultado.

quita la frase "memoriza mientras parpadea". Ademas, en realidad es "parpadea mientras recuerdas"... ¿hay algun estudio sobre los tiempos minimo y maximo que un estudiante tarda en recordar?_______No hace falta un nuevo boton ya lo se, simplemente si clica en el numero antes de que transcurran seis segundos, mostramos el resultado. Si luego el estudiante pulsa "guarda" podemos ir adaptandoneos, lentamente, a los tiempos de respuesta que tiene. Si pulsa no guardar, podemos extendernos un poco, sin pasar nunca de 10 segundos. Quita tambien la frase "parpadea", pon el parpadeo mas lento (un segundo? 750 ms?), y usa tonos rosa de niña________It works fine but it doesnt adapt well to the size of the screen.

Y el segundo:

quiero una pagina html que se puede ejecutar en local en cualquier navegador, para aprender la tabla de multiplicar y el concepto de factorizar, que llamaremos "desmultiplicar". Debe mostrar en el lado izquierdo diez "cajitas cuadradas" del cero al 9, que se emplean como en el juego de alquimia: sacas al escenario las cajitas que quieras, arrastrando, y si pones una sobre otra se transforma en su producto. Hay un boton "limpiar" que borra todo el escenario, y un dispositivo "desmultiplicador" en el que si arrastras una caja te devuelve dos que sean una descomposicion en dos factores. Si el numero es cero devuelve cero por cualquier numero del uno al nueve, si el numero es uno devuelve uno por uno, si el numero es primo devuelve uno por ese numero. Debe poderse arrastrar con el navegador del movil y adaptarse correctamente al tamaño de pantalla

las cajas de origen deberian estar siempre en una columna en el lado izquierdo, no formando dos filas. No hay que poner explicaciones, ni el "arrastra" ni el funciona en movil. Debe arrastrarse desde la columna al escenario, que esta al lado, no debe aparecer magicamente la caja en el escenario. El desmultiplicador debe estar arriba a la derecha, y su desmultiplicacion se expulsa hacia el escenario, no se mantiene dentro.----------------Cuando el desmultiplicador expulse los cuadros, que lo haga uno por la izquierda y otro por abajo con una animacion, y asi no coinciden. Los cuadros del menu deben tener el mismo tamaño que los del juego, ahora son demasiado grandes (tres veces mas de lado!!!)---------------la animacion es perfecto. Elimina el tituulo "desmultiplicar" en la barra de menu, es confuso tener el mismo concepto en dos sitios. Deja solo el boton "limpiar" en el tamaño que esta. Las cajas deberian superponerse solo cuando el jugador arrastra. Si suelta sin operar o si chocan por culpa del desmultiplcador, deberian resolver la interseccion separandose en direcciones random, incluyendo diagonales.---------------no bajes nunca el area de juego debajo, da flexibilidad a las secciones para que este siempre a la izquierda del menu. Ademas asi nos aseguramos de que el menu no ocupe mas ancho que el de los propios botones.---------------no, no, el menu a la izquierda, disculpa. El tamaño actual es perfecto. Si quieres anima un poco la operacion de separacion de las piezas que intersecten.--------------es aun un poco grande para movil, haz todo un 15% mas pequeño (o toma como referencia el asegurarte de que todos los botones del 0 al 9 mas el de borrar caben en la vertical ofrecida por el movil). Usa fondos rosa para todo el juego.

Entradas recientes

Ver todo
Mass Gap from Kaluza Klein

This is just a series of proposed blog posts from chatGPT, each in separate markdown format See also https://chatgpt.com/c/6953f699-3088-832d-8e4f-9104a9264251

 
 
 
vLLM con ray a mano

#necesarioexport SSL_CERT_FILE=/fs/agustina/arivero/supercomplex/.local/lib/python3.11/site-packages/certifi/cacert.pem export RAY_NODE_MANAGER_HEARTBEAT_TIMEOUT_MILLISECONDS=20000 # 20 seconds expor

 
 
 

Comentarios


Never Miss a Post. Subscribe Now!

I'm a paragraph. Click here to add your own text and edit me. It's easy.

Thanks for submitting!

© 2035 by Kathy Schulders. Powered and secured by Wix

  • Grey Twitter Icon
bottom of page