Enlace a la aplicación html
Aplicación html en un Site
Código html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>¡Aprendo los Colores! 🎨</title>
<style>
/* ============================================================
TOKENS
============================================================ */
:root{
--sky-top:#79c6ff;
--sky-bottom:#ffe3a3;
--card-bg:#ffffff;
--ink:#2b2140;
--ink-soft:#5b5470;
--accent:#ff6fb5;
--accent-2:#7c3aed;
--success:#2dc653;
--error:#e63946;
--radius-lg:28px;
--radius-md:18px;
--shadow:0 10px 26px rgba(43,33,64,.18);
--shadow-soft:0 4px 12px rgba(43,33,64,.12);
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}
body.contraste{
--sky-top:#000000;
--sky-bottom:#1a1a1a;
--card-bg:#ffffff;
--ink:#000000;
--ink-soft:#000000;
--shadow:0 0 0 4px #000000;
--shadow-soft:0 0 0 3px #000000;
}
/* ============================================================
RESET / BASE
============================================================ */
*{box-sizing:border-box; -webkit-tap-highlight-color:transparent; user-select:none;}
html,body{height:100%; margin:0; overscroll-behavior:none;}
body{
font-family:var(--font);
color:var(--ink);
background:linear-gradient(180deg,var(--sky-top) 0%, var(--sky-bottom) 100%);
min-height:100vh;
touch-action:manipulation;
overflow-x:hidden;
}
button{font-family:inherit;}
.hidden{display:none !important;}
/* ============================================================
LAYOUT SHELL
============================================================ */
#app{
min-height:100vh;
display:flex;
flex-direction:column;
padding:12px;
gap:12px;
}
/* top bar */
#topbar{
display:flex;
align-items:center;
justify-content:space-between;
background:rgba(255,255,255,.55);
backdrop-filter:blur(6px);
border-radius:var(--radius-md);
padding:8px 14px;
box-shadow:var(--shadow-soft);
}
#topbar h1{
font-size:clamp(1.1rem,3vw,1.7rem);
margin:0;
display:flex;
align-items:center;
gap:8px;
}
.icon-btn{
border:none;
background:var(--card-bg);
border-radius:50%;
width:52px;
height:52px;
font-size:1.5rem;
display:flex;
align-items:center;
justify-content:center;
box-shadow:var(--shadow-soft);
cursor:pointer;
transition:transform .15s ease;
}
.icon-btn:active{transform:scale(.88);}
#topbar .controls{display:flex; gap:8px;}
#scoreBadge{
background:linear-gradient(135deg,#ffd60a,#fb8500);
border-radius:999px;
padding:8px 16px;
font-weight:800;
font-size:clamp(.9rem,2.4vw,1.2rem);
box-shadow:var(--shadow-soft);
color:#3a2600;
}
/* generic screen */
.screen{
flex:1;
display:flex;
flex-direction:column;
gap:14px;
animation:fadeIn .35s ease;
}
@keyframes fadeIn{from{opacity:0; transform:translateY(8px);} to{opacity:1; transform:translateY(0);}}
.backBtn{
align-self:flex-start;
border:none;
background:var(--card-bg);
border-radius:999px;
padding:10px 20px;
font-size:1.1rem;
font-weight:800;
color:var(--ink);
box-shadow:var(--shadow-soft);
cursor:pointer;
}
.backBtn:active{transform:scale(.94);}
.promptCard{
background:var(--card-bg);
border-radius:var(--radius-lg);
padding:18px 22px;
text-align:center;
box-shadow:var(--shadow);
font-size:clamp(1.2rem,4vw,2rem);
font-weight:800;
}
.promptCard b{color:var(--accent-2);}
/* ============================================================
MENU
============================================================ */
#screen-menu{justify-content:flex-start;}
#hero{
background:var(--card-bg);
border-radius:var(--radius-lg);
padding:22px;
text-align:center;
box-shadow:var(--shadow);
position:relative;
overflow:hidden;
}
#hero h2{
margin:0 0 4px 0;
font-size:clamp(1.4rem,5vw,2.4rem);
color:var(--ink);
}
#hero p{margin:0; color:var(--ink-soft); font-size:clamp(.9rem,2.6vw,1.15rem);}
#sunMascot{width:90px; height:90px; margin:0 auto 6px; display:block;}
.difficultyRow{
display:flex;
gap:8px;
justify-content:center;
margin-top:14px;
flex-wrap:wrap;
}
.diffBtn{
border:3px solid transparent;
background:#f1eefc;
border-radius:999px;
padding:8px 18px;
font-weight:800;
font-size:1rem;
cursor:pointer;
color:var(--ink);
}
.diffBtn.active{
border-color:var(--accent-2);
background:#e6defc;
}
#gameGrid{
display:grid;
grid-template-columns:repeat(auto-fit, minmax(150px,1fr));
gap:14px;
}
.gameCard{
background:var(--card-bg);
border:none;
border-radius:var(--radius-lg);
padding:18px 10px;
display:flex;
flex-direction:column;
align-items:center;
gap:8px;
box-shadow:var(--shadow);
cursor:pointer;
font-weight:800;
font-size:1rem;
color:var(--ink);
transition:transform .15s ease;
}
.gameCard:active{transform:scale(.95);}
.gameCard .emoji{font-size:2.6rem;}
/* ============================================================
GAME 1 - Encuentra el color
============================================================ */
#circleField{
flex:1;
display:flex;
flex-wrap:wrap;
align-content:center;
justify-content:center;
gap:22px;
padding:10px;
}
.colorCircle{
width:clamp(64px,16vw,110px);
height:clamp(64px,16vw,110px);
border-radius:50%;
border:4px solid rgba(0,0,0,.08);
box-shadow:var(--shadow-soft);
cursor:pointer;
transition:transform .12s ease;
}
.colorCircle:active{transform:scale(.9);}
.colorCircle.wrongShake{animation:shake .4s ease;}
.colorCircle.correctPop{animation:pop .5s ease;}
@keyframes shake{
0%,100%{transform:translateX(0);}
20%{transform:translateX(-10px);}
40%{transform:translateX(10px);}
60%{transform:translateX(-8px);}
80%{transform:translateX(8px);}
}
@keyframes pop{
0%{transform:scale(1);}
40%{transform:scale(1.35);}
100%{transform:scale(0);}
}
/* ============================================================
GAME 2 - De que color es
============================================================ */
#objectStage{
display:flex;
align-items:center;
justify-content:center;
padding:14px;
}
#objectStage svg{width:min(220px,45vw); height:min(220px,45vw); filter:drop-shadow(var(--shadow-soft));}
#colorOptions{
display:grid;
grid-template-columns:repeat(auto-fit, minmax(110px,1fr));
gap:14px;
padding:0 8px 14px;
}
.colorOptionBtn{
border:none;
border-radius:var(--radius-md);
padding:16px 8px;
font-weight:800;
font-size:1rem;
cursor:pointer;
box-shadow:var(--shadow-soft);
color:#fff;
text-shadow:0 1px 3px rgba(0,0,0,.35);
}
.colorOptionBtn.light{color:#2b2140; text-shadow:none;}
.colorOptionBtn:active{transform:scale(.93);}
/* ============================================================
GAME 3 - Memoria
============================================================ */
#memStats{
display:flex;
gap:10px;
justify-content:center;
flex-wrap:wrap;
}
#memStats span{
background:var(--card-bg);
border-radius:999px;
padding:6px 16px;
font-weight:800;
box-shadow:var(--shadow-soft);
font-size:.95rem;
}
#memoryGrid{
flex:1;
display:grid;
gap:10px;
padding:6px;
align-content:center;
justify-content:center;
}
.memCard{
width:clamp(52px,15vw,90px);
height:clamp(52px,15vw,90px);
border-radius:14px;
cursor:pointer;
box-shadow:var(--shadow-soft);
background:linear-gradient(135deg,#7c3aed,#ff6fb5);
display:flex;
align-items:center;
justify-content:center;
font-size:1.4rem;
transition:transform .15s ease;
}
.memCard.flipped, .memCard.matched{background:none;}
.memCard.matched{opacity:.35; cursor:default;}
.memCard:active{transform:scale(.93);}
/* ============================================================
GAME 4 - Explota el color
============================================================ */
#bubbleArea{
flex:1;
position:relative;
overflow:hidden;
background:linear-gradient(180deg,rgba(255,255,255,.25),rgba(255,255,255,0));
border-radius:var(--radius-lg);
min-height:320px;
}
.bubble{
position:absolute;
border-radius:50%;
box-shadow:0 4px 10px rgba(0,0,0,.15), inset -6px -6px 14px rgba(0,0,0,.12), inset 6px 6px 10px rgba(255,255,255,.4);
cursor:pointer;
border:3px solid rgba(0,0,0,.06);
}
.bubble.popped{animation:pop .35s ease forwards;}
/* ============================================================
GAME 5 - Colorea
============================================================ */
#drawingTabs{
display:flex;
gap:8px;
justify-content:center;
flex-wrap:wrap;
}
.tabBtn{
border:none;
background:var(--card-bg);
border-radius:999px;
padding:8px 16px;
font-weight:800;
cursor:pointer;
box-shadow:var(--shadow-soft);
}
.tabBtn.active{background:var(--accent-2); color:#fff;}
#colorStage{
flex:1;
display:flex;
align-items:center;
justify-content:center;
}
#colorStage svg{width:min(320px,80vw); height:min(320px,60vh); filter:drop-shadow(var(--shadow-soft));}
.colorable{cursor:pointer; transition:fill .15s ease;}
#palette{
display:flex;
gap:8px;
flex-wrap:wrap;
justify-content:center;
padding:10px;
background:var(--card-bg);
border-radius:var(--radius-md);
box-shadow:var(--shadow-soft);
margin:0 8px;
}
.swatch{
width:40px; height:40px;
border-radius:50%;
border:3px solid #fff;
box-shadow:0 0 0 2px rgba(0,0,0,.08);
cursor:pointer;
}
.swatch.selected{box-shadow:0 0 0 4px var(--ink);}
#eraseBtn{
border:none;
border-radius:999px;
padding:10px 18px;
font-weight:800;
background:#f1eefc;
box-shadow:var(--shadow-soft);
cursor:pointer;
align-self:center;
}
/* ============================================================
CELEBRATION OVERLAY
============================================================ */
#celebrate{
position:fixed; inset:0;
display:flex; align-items:center; justify-content:center;
pointer-events:none;
z-index:50;
}
#celebrate .msg{
background:linear-gradient(135deg,#ffd60a,#ff6fb5);
padding:22px 40px;
border-radius:999px;
font-size:clamp(1.4rem,5vw,2.4rem);
font-weight:900;
color:#3a1e00;
box-shadow:0 14px 34px rgba(0,0,0,.3);
animation:celebratePop .9s ease forwards;
}
@keyframes celebratePop{
0%{transform:scale(.3) rotate(-8deg); opacity:0;}
25%{transform:scale(1.15) rotate(3deg); opacity:1;}
40%{transform:scale(1) rotate(0);}
85%{transform:scale(1) rotate(0); opacity:1;}
100%{transform:scale(.7) translateY(-40px); opacity:0;}
}
@media (max-width:480px){
#topbar h1 span.longlabel{display:none;}
}
</style>
</head>
<body>
<div id="app">
<!-- ============ TOP BAR ============ -->
<div id="topbar">
<h1>🌈 <span class="longlabel">¡Aprendo los Colores!</span></h1>
<div class="controls">
<div id="scoreBadge">⭐ <span id="scoreVal">0</span></div>
<button class="icon-btn" id="soundToggleBtn" title="Sonido">🔊</button>
<button class="icon-btn" id="contrastToggleBtn" title="Alto contraste">🌓</button>
</div>
</div>
<!-- ============ MENU ============ -->
<div class="screen" id="screen-menu">
<div id="hero">
<svg id="sunMascot" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g id="sunRays" stroke="#FFD60A" stroke-width="8" stroke-linecap="round">
<line x1="50" y1="4" x2="50" y2="16"/>
<line x1="50" y1="84" x2="50" y2="96"/>
<line x1="4" y1="50" x2="16" y2="50"/>
<line x1="84" y1="50" x2="96" y2="50"/>
<line x1="16" y1="16" x2="24" y2="24"/>
<line x1="76" y1="76" x2="84" y2="84"/>
<line x1="84" y1="16" x2="76" y2="24"/>
<line x1="16" y1="84" x2="24" y2="76"/>
</g>
<circle cx="50" cy="50" r="26" fill="#FFD60A"/>
<circle cx="41" cy="46" r="3.4" fill="#3a2600"/>
<circle cx="59" cy="46" r="3.4" fill="#3a2600"/>
<path id="sunMouth" d="M40 58 Q50 66 60 58" stroke="#3a2600" stroke-width="3.4" fill="none" stroke-linecap="round"/>
</svg>
<h2>¡Vamos a jugar con los colores!</h2>
<p>Elige un juego para empezar</p>
<div class="difficultyRow" id="difficultyRow">
<button class="diffBtn" data-diff="facil">Fácil (4)</button>
<button class="diffBtn" data-diff="medio">Medio (8)</button>
<button class="diffBtn" data-diff="dificil">Difícil (11)</button>
</div>
</div>
<div id="gameGrid">
<button class="gameCard" data-target="game1"><span class="emoji">👉</span>Encuentra el color</button>
<button class="gameCard" data-target="game2"><span class="emoji">🍓</span>¿De qué color es?</button>
<button class="gameCard" data-target="game3"><span class="emoji">🧠</span>Memoria de colores</button>
<button class="gameCard" data-target="game4"><span class="emoji">🎈</span>Explota el color</button>
<button class="gameCard" data-target="game5"><span class="emoji">🖌️</span>Colorea</button>
</div>
</div>
<!-- ============ GAME 1: Encuentra el color ============ -->
<div class="screen hidden" id="screen-game1">
<button class="backBtn" data-back>⬅ Volver</button>
<div class="promptCard" id="game1Prompt">Toca el color</div>
<div id="circleField"></div>
</div>
<!-- ============ GAME 2: De que color es ============ -->
<div class="screen hidden" id="screen-game2">
<button class="backBtn" data-back>⬅ Volver</button>
<div class="promptCard">¿De qué color es esto?</div>
<div id="objectStage"></div>
<div id="colorOptions"></div>
</div>
<!-- ============ GAME 3: Memoria ============ -->
<div class="screen hidden" id="screen-game3">
<button class="backBtn" data-back>⬅ Volver</button>
<div id="memStats">
<span>🔄 Movimientos: <b id="memMoves">0</b></span>
<span>✅ Parejas: <b id="memPairs">0</b>/<b id="memTotal">0</b></span>
<span>⏱️ Tiempo: <b id="memTime">0</b>s</span>
</div>
<div id="memoryGrid"></div>
</div>
<!-- ============ GAME 4: Explota el color ============ -->
<div class="screen hidden" id="screen-game4">
<button class="backBtn" data-back>⬅ Volver</button>
<div class="promptCard" id="game4Prompt">Explota las burbujas</div>
<div id="bubbleArea"></div>
</div>
<!-- ============ GAME 5: Colorea ============ -->
<div class="screen hidden" id="screen-game5">
<button class="backBtn" data-back>⬅ Volver</button>
<div id="drawingTabs"></div>
<div id="colorStage"></div>
<div id="palette"></div>
<button id="eraseBtn">🧽 Borrar todo</button>
</div>
</div>
<div id="celebrate"></div>
<script>
/* ============================================================
SECTION 0: ESTADO GLOBAL Y DATOS DE COLORES
============================================================ */
const COLORS = [
{id:'rojo', name:'Rojo', hex:'#E63946', light:false},
{id:'azul', name:'Azul', hex:'#1D4ED8', light:false},
{id:'amarillo', name:'Amarillo', hex:'#FFD60A', light:true},
{id:'verde', name:'Verde', hex:'#2DC653', light:false},
{id:'naranja', name:'Naranja', hex:'#FB8500', light:false},
{id:'morado', name:'Morado', hex:'#7C3AED', light:false},
{id:'rosa', name:'Rosa', hex:'#FF6FB5', light:true},
{id:'marron', name:'Marrón', hex:'#8B5E34', light:false},
{id:'negro', name:'Negro', hex:'#1A1A1A', light:false},
{id:'blanco', name:'Blanco', hex:'#FFFFFF', light:true},
{id:'gris', name:'Gris', hex:'#9CA3AF', light:true},
];
const DIFFICULTY_COUNTS = {facil:4, medio:8, dificil:11};
const state = {
score:0,
streak:0,
difficulty:'facil',
sound:true,
contrast:false,
};
function colorPool(){
return COLORS.slice(0, DIFFICULTY_COUNTS[state.difficulty]);
}
function byId(id){ return COLORS.find(c=>c.id===id); }
function randInt(n){ return Math.floor(Math.random()*n); }
function pickRandom(arr){ return arr[randInt(arr.length)]; }
function shuffle(arr){
for(let i=arr.length-1;i>0;i--){
const j = randInt(i+1);
[arr[i],arr[j]] = [arr[j],arr[i]];
}
return arr;
}
/* ============================================================
SECTION 1: AUDIO (Web Audio API - sin archivos externos)
============================================================ */
let audioCtx = null;
function ensureAudio(){
if(!audioCtx){
try{ audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
catch(e){ audioCtx = null; }
}
if(audioCtx && audioCtx.state === 'suspended'){ audioCtx.resume(); }
}
function tone(freq, start, dur, type='sine', gainPeak=0.18){
if(!state.sound || !audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.value = freq;
gain.gain.setValueAtTime(0, audioCtx.currentTime+start);
gain.gain.linearRampToValueAtTime(gainPeak, audioCtx.currentTime+start+0.02);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime+start+dur);
osc.connect(gain).connect(audioCtx.destination);
osc.start(audioCtx.currentTime+start);
osc.stop(audioCtx.currentTime+start+dur+0.02);
}
function playSuccess(){
ensureAudio();
tone(523.25,0,.14,'triangle');
tone(659.25,.10,.14,'triangle');
tone(783.99,.20,.22,'triangle');
}
function playError(){
ensureAudio();
tone(220,0,.16,'sawtooth',0.10);
tone(180,.12,.2,'sawtooth',0.10);
}
function playPop(){
ensureAudio();
tone(660,0,.08,'square',0.10);
}
function playFanfare(){
ensureAudio();
[523.25,659.25,783.99,1046.5].forEach((f,i)=>tone(f,i*0.11,.2,'triangle',0.16));
}
/* ============================================================
SECTION 2: NAVEGACION ENTRE PANTALLAS
============================================================ */
const screens = {
menu: document.getElementById('screen-menu'),
game1: document.getElementById('screen-game1'),
game2: document.getElementById('screen-game2'),
game3: document.getElementById('screen-game3'),
game4: document.getElementById('screen-game4'),
game5: document.getElementById('screen-game5'),
};
let activeIntervals = [];
function clearActiveIntervals(){
activeIntervals.forEach(id=>clearInterval(id));
activeIntervals = [];
}
function showScreen(name){
clearActiveIntervals();
Object.values(screens).forEach(s=>s.classList.add('hidden'));
screens[name].classList.remove('hidden');
if(name==='game1') startGame1();
if(name==='game2') startGame2();
if(name==='game3') startGame3();
if(name==='game4') startGame4();
if(name==='game5') startGame5();
}
document.querySelectorAll('.gameCard').forEach(btn=>{
btn.addEventListener('click', ()=>{ ensureAudio(); showScreen(btn.dataset.target); });
});
document.querySelectorAll('[data-back]').forEach(btn=>{
btn.addEventListener('click', ()=> showScreen('menu'));
});
/* ============================================================
SECTION 3: CONTROLES SUPERIORES (sonido, contraste, dificultad)
============================================================ */
const soundBtn = document.getElementById('soundToggleBtn');
soundBtn.addEventListener('click', ()=>{
state.sound = !state.sound;
soundBtn.textContent = state.sound ? '🔊' : '🔇';
if(state.sound){ ensureAudio(); playPop(); }
});
const contrastBtn = document.getElementById('contrastToggleBtn');
contrastBtn.addEventListener('click', ()=>{
state.contrast = !state.contrast;
document.body.classList.toggle('contraste', state.contrast);
});
document.querySelectorAll('.diffBtn').forEach(btn=>{
btn.addEventListener('click', ()=>{
document.querySelectorAll('.diffBtn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
state.difficulty = btn.dataset.diff;
});
});
document.querySelector('.diffBtn[data-diff="facil"]').classList.add('active');
/* ============================================================
SECTION 4: PUNTUACION Y CELEBRACION
============================================================ */
const scoreVal = document.getElementById('scoreVal');
const celebrateEl = document.getElementById('celebrate');
const CHEERS = ['¡Muy bien!','¡Fantástico!','¡Eres un artista!','¡Sigue así!','¡Genial!','¡Lo has clavado!'];
function addPoint(){
state.score++;
state.streak++;
scoreVal.textContent = state.score;
if(state.streak>0 && state.streak % 5 === 0){
celebrate();
}
}
function resetStreakOnError(){
// el fallo no resta puntos, simplemente no suma
}
function celebrate(){
playFanfare();
const msg = document.createElement('div');
msg.className = 'msg';
msg.textContent = pickRandom(CHEERS) + ' 🎉';
celebrateEl.innerHTML = '';
celebrateEl.appendChild(msg);
setTimeout(()=>{ celebrateEl.innerHTML=''; }, 900);
}
function pulseMascotHappy(){
const mouth = document.getElementById('sunMouth');
if(mouth){ mouth.setAttribute('d','M38 60 Q50 74 62 60'); setTimeout(()=>mouth.setAttribute('d','M40 58 Q50 66 60 58'),500); }
}
/* ============================================================
SECTION 5: JUEGO 1 - ENCUENTRA EL COLOR
============================================================ */
const g1Prompt = document.getElementById('game1Prompt');
const circleField = document.getElementById('circleField');
let g1Target = null;
function startGame1(){ newGame1Question(); }
function newGame1Question(){
const pool = colorPool();
const target = pickRandom(pool);
g1Target = target.id;
g1Prompt.innerHTML = 'Toca el color <b>'+target.name+'</b>';
const count = 4 + randInt(5); // 4 a 8
let choices = [target];
while(choices.length < count){
choices.push(pickRandom(pool));
}
shuffle(choices);
circleField.innerHTML = '';
choices.forEach(c=>{
const el = document.createElement('div');
el.className = 'colorCircle';
el.style.background = c.hex;
if(c.id==='blanco'){ el.style.border = '4px solid #cfd3da'; }
el.dataset.color = c.id;
el.setAttribute('role','button');
el.setAttribute('aria-label', c.name);
el.addEventListener('click', ()=> handleGame1Click(el, c.id));
circleField.appendChild(el);
});
}
function handleGame1Click(el, colorId){
ensureAudio();
if(colorId === g1Target){
el.classList.add('correctPop');
playSuccess();
addPoint();
pulseMascotHappy();
setTimeout(newGame1Question, 480);
}else{
el.classList.add('wrongShake');
playError();
setTimeout(()=>el.classList.remove('wrongShake'), 420);
}
}
/* ============================================================
SECTION 6: JUEGO 2 - DE QUE COLOR ES
============================================================ */
// Siluetas SVG neutras (sin revelar el color real) para que el niño
// deba identificar el color correcto por conocimiento del objeto.
const SILHOUETTES = {
sol: `<svg viewBox="0 0 100 100"><g stroke="#8892a6" stroke-width="7" stroke-linecap="round">
<line x1="50" y1="6" x2="50" y2="18"/><line x1="50" y1="82" x2="50" y2="94"/>
<line x1="6" y1="50" x2="18" y2="50"/><line x1="82" y1="50" x2="94" y2="50"/>
<line x1="19" y1="19" x2="27" y2="27"/><line x1="73" y1="73" x2="81" y2="81"/>
<line x1="81" y1="19" x2="73" y2="27"/><line x1="19" y1="81" x2="27" y2="73"/></g>
<circle cx="50" cy="50" r="24" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/></svg>`,
manzana: `<svg viewBox="0 0 100 100"><path d="M50 30 C30 24 18 44 22 62 C25 78 38 90 50 90 C62 90 75 78 78 62 C82 44 70 24 50 30 Z" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<path d="M50 30 C48 20 52 12 60 8" fill="none" stroke="#6b4a2b" stroke-width="4" stroke-linecap="round"/>
<ellipse cx="60" cy="18" rx="8" ry="4" fill="#f2f2f2" stroke="#8892a6" stroke-width="3" transform="rotate(-30 60 18)"/></svg>`,
rana: `<svg viewBox="0 0 100 100"><ellipse cx="50" cy="60" rx="34" ry="26" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<circle cx="34" cy="34" r="11" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<circle cx="66" cy="34" r="11" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<circle cx="34" cy="34" r="4" fill="#2b2140"/><circle cx="66" cy="34" r="4" fill="#2b2140"/>
<path d="M32 66 Q50 78 68 66" stroke="#8892a6" stroke-width="4" fill="none" stroke-linecap="round"/></svg>`,
cielo: `<svg viewBox="0 0 100 100"><rect x="6" y="10" width="88" height="80" rx="10" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<circle cx="72" cy="34" r="12" fill="#fff" stroke="#8892a6" stroke-width="3"/>
<ellipse cx="34" cy="60" rx="20" ry="10" fill="#fff" stroke="#8892a6" stroke-width="3"/></svg>`,
nube: `<svg viewBox="0 0 100 100"><ellipse cx="38" cy="58" rx="22" ry="16" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<ellipse cx="62" cy="52" rx="26" ry="20" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<ellipse cx="50" cy="66" rx="30" ry="14" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/></svg>`,
platano: `<svg viewBox="0 0 100 100"><path d="M28 78 C20 60 26 30 50 18 C48 22 44 30 46 36 C52 30 66 26 76 30 C64 34 56 44 54 54 C52 68 42 78 28 78 Z" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/></svg>`,
fresa: `<svg viewBox="0 0 100 100"><path d="M50 30 C24 30 16 56 30 76 C38 88 62 88 70 76 C84 56 76 30 50 30 Z" fill="#f2f2f2" stroke="#8892a6" stroke-width="4"/>
<path d="M36 28 L50 16 L64 28 Z" fill="#f2f2f2" stroke="#8892a6" stroke-width="3"/>
<circle cx="42" cy="46" r="2.4" fill="#8892a6"/><circle cx="58" cy="46" r="2.4" fill="#8892a6"/>
<circle cx="50" cy="58" r="2.4" fill="#8892a6"/><circle cx="40" cy="66" r="2.4" fill="#8892a6"/>
<circle cx="60" cy="66" r="2.4" fill="#8892a6"/></svg>`,
};
const OBJECTS = [
{name:'el Sol', key:'sol', color:'amarillo'},
{name:'la Manzana', key:'manzana', color:'rojo'},
{name:'la Rana', key:'rana', color:'verde'},
{name:'el Cielo', key:'cielo', color:'azul'},
{name:'la Nube', key:'nube', color:'blanco'},
{name:'el Plátano', key:'platano', color:'amarillo'},
{name:'la Fresa', key:'fresa', color:'rojo'},
];
const objectStage = document.getElementById('objectStage');
const colorOptions = document.getElementById('colorOptions');
let g2Correct = null;
function startGame2(){ newGame2Question(); }
function newGame2Question(){
const pool = colorPool();
const validObjects = OBJECTS.filter(o=> pool.some(c=>c.id===o.color));
const obj = pickRandom(validObjects.length? validObjects : OBJECTS);
g2Correct = obj.color;
objectStage.innerHTML = SILHOUETTES[obj.key];
const correctColor = byId(obj.color);
let distractors = pool.filter(c=>c.id!==obj.color);
shuffle(distractors);
let opts = [correctColor, ...distractors.slice(0,3)];
while(opts.length<4 && pool.length>=4){ opts.push(pickRandom(pool)); }
shuffle(opts);
colorOptions.innerHTML = '';
opts.forEach(c=>{
const btn = document.createElement('button');
btn.className = 'colorOptionBtn' + (c.light ? ' light' : '');
btn.style.background = c.hex;
if(c.id==='blanco'){ btn.style.border='3px solid #cfd3da'; }
btn.textContent = c.name;
btn.addEventListener('click', ()=> handleGame2Click(btn, c.id));
colorOptions.appendChild(btn);
});
}
function handleGame2Click(btn, colorId){
ensureAudio();
if(colorId === g2Correct){
playSuccess();
addPoint();
pulseMascotHappy();
btn.classList.add('correctPop');
setTimeout(newGame2Question, 500);
}else{
playError();
btn.classList.add('wrongShake');
setTimeout(()=>btn.classList.remove('wrongShake'), 420);
}
}
/* ============================================================
SECTION 7: JUEGO 3 - MEMORIA DE COLORES
============================================================ */
const memoryGrid = document.getElementById('memoryGrid');
const memMoves = document.getElementById('memMoves');
const memPairs = document.getElementById('memPairs');
const memTotal = document.getElementById('memTotal');
const memTime = document.getElementById('memTime');
let mem = { cards:[], flipped:[], moves:0, pairs:0, total:0, timer:null, seconds:0, lock:false };
function startGame3(){
const pairCounts = {facil:4, medio:6, dificil:8};
const nPairs = pairCounts[state.difficulty];
const pool = COLORS.slice(0, Math.max(nPairs, DIFFICULTY_COUNTS[state.difficulty])).slice(0,nPairs);
let deck = [];
pool.forEach(c=>{ deck.push({...c}); deck.push({...c}); });
shuffle(deck);
mem = { cards:deck, flipped:[], moves:0, pairs:0, total:nPairs, timer:null, seconds:0, lock:false };
memMoves.textContent = 0;
memPairs.textContent = 0;
memTotal.textContent = nPairs;
memTime.textContent = 0;
const cols = nPairs <= 4 ? 4 : (nPairs <= 6 ? 4 : 4);
memoryGrid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
memoryGrid.innerHTML = '';
deck.forEach((c,idx)=>{
const card = document.createElement('div');
card.className = 'memCard';
card.dataset.idx = idx;
card.textContent = '?';
card.style.color = '#fff';
card.style.fontWeight = '900';
card.addEventListener('click', ()=> flipMemCard(card, idx));
memoryGrid.appendChild(card);
});
}
function startMemTimer(){
if(mem.timer) return;
const id = setInterval(()=>{
mem.seconds++;
memTime.textContent = mem.seconds;
}, 1000);
mem.timer = id;
activeIntervals.push(id);
}
function flipMemCard(card, idx){
if(mem.lock || card.classList.contains('flipped') || card.classList.contains('matched')) return;
ensureAudio();
startMemTimer();
const c = mem.cards[idx];
card.classList.add('flipped');
card.style.background = c.hex;
card.textContent = '';
if(c.id==='blanco'){ card.style.border='3px solid #cfd3da'; }
mem.flipped.push({card, idx, c});
if(mem.flipped.length === 2){
mem.moves++;
memMoves.textContent = mem.moves;
mem.lock = true;
const [a,b] = mem.flipped;
if(a.c.id === b.c.id){
playSuccess();
a.card.classList.add('matched');
b.card.classList.add('matched');
mem.pairs++;
memPairs.textContent = mem.pairs;
addPoint();
mem.flipped = [];
mem.lock = false;
if(mem.pairs === mem.total){
clearInterval(mem.timer);
setTimeout(celebrate, 200);
}
}else{
playError();
setTimeout(()=>{
a.card.classList.remove('flipped');
b.card.classList.remove('flipped');
a.card.style.background = '';
b.card.style.background = '';
a.card.textContent = '?';
b.card.textContent = '?';
mem.flipped = [];
mem.lock = false;
}, 850);
}
}
}
/* ============================================================
SECTION 8: JUEGO 4 - EXPLOTA EL COLOR
============================================================ */
const bubbleArea = document.getElementById('bubbleArea');
const g4Prompt = document.getElementById('game4Prompt');
let g4 = { target:null, remaining:0, raf:null, bubbles:[] };
function startGame4(){ newGame4Round(); loopGame4(); }
function newGame4Round(){
bubbleArea.innerHTML = '';
const pool = colorPool();
const target = pickRandom(pool);
g4.target = target.id;
g4Prompt.innerHTML = 'Explota todas las burbujas <b>'+target.name.toLowerCase()+'</b>s';
const total = 10;
const targetCount = 4 + randInt(2); // 4-5 objetivo
let colorsForBubbles = [];
for(let i=0;i<targetCount;i++) colorsForBubbles.push(target);
while(colorsForBubbles.length < total){
const c = pickRandom(pool);
colorsForBubbles.push(c);
}
shuffle(colorsForBubbles);
g4.remaining = targetCount;
g4.bubbles = [];
const areaW = bubbleArea.clientWidth || 320;
const areaH = bubbleArea.clientHeight || 320;
colorsForBubbles.forEach((c,i)=>{
const size = 46 + randInt(30);
const el = document.createElement('div');
el.className = 'bubble';
el.style.width = size+'px';
el.style.height = size+'px';
el.style.background = c.hex;
if(c.id==='blanco'){ el.style.borderColor = '#cfd3da'; }
const x = randInt(Math.max(areaW-size,40));
el.style.left = x+'px';
el.style.top = (areaH + randInt(300)) + 'px';
el.dataset.color = c.id;
el.addEventListener('click', ()=> popBubble(el, bubbleObj));
bubbleArea.appendChild(el);
const bubbleObj = { el, speed: 0.4 + Math.random()*0.6, y: parseFloat(el.style.top), popped:false };
g4.bubbles.push(bubbleObj);
});
}
function popBubble(el, obj){
if(obj.popped) return;
ensureAudio();
if(el.dataset.color === g4.target){
obj.popped = true;
el.classList.add('popped');
playPop();
addPoint();
g4.remaining--;
setTimeout(()=>el.remove(), 300);
if(g4.remaining <= 0){
setTimeout(()=>{ celebrate(); newGame4Round(); }, 350);
}
}else{
playError();
el.style.transform = 'scale(0.9)';
setTimeout(()=>{ el.style.transform=''; }, 150);
}
}
function loopGame4(){
function step(){
const areaH = bubbleArea.clientHeight || 320;
g4.bubbles.forEach(b=>{
if(b.popped) return;
b.y -= b.speed;
if(b.y < -80){ b.y = areaH + randInt(60); }
b.el.style.top = b.y+'px';
});
if(!screens.game4.classList.contains('hidden')){
g4.raf = requestAnimationFrame(step);
}
}
cancelAnimationFrame(g4.raf);
g4.raf = requestAnimationFrame(step);
}
/* ============================================================
SECTION 9: JUEGO 5 - COLOREA
============================================================ */
const DRAWINGS = {
casa: {
label:'🏠 Casa',
svg:`<svg viewBox="0 0 200 200">
<polygon id="p_tejado" class="colorable" points="20,90 100,30 180,90" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<rect id="p_pared" class="colorable" x="40" y="90" width="120" height="90" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<rect id="p_puerta" class="colorable" x="90" y="130" width="30" height="50" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<rect id="p_ventana" class="colorable" x="55" y="105" width="26" height="26" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<rect id="p_chimenea" class="colorable" x="140" y="45" width="16" height="34" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
</svg>`
},
flor: {
label:'🌸 Flor',
svg:`<svg viewBox="0 0 200 200">
<rect id="p_tallo" class="colorable" x="94" y="120" width="12" height="60" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<ellipse id="p_hoja" class="colorable" cx="80" cy="150" rx="18" ry="9" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<circle id="p_petalo1" class="colorable" cx="100" cy="60" r="22" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<circle id="p_petalo2" class="colorable" cx="72" cy="82" r="22" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<circle id="p_petalo3" class="colorable" cx="128" cy="82" r="22" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<circle id="p_centro" class="colorable" cx="100" cy="82" r="18" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
</svg>`
},
pez: {
label:'🐟 Pez',
svg:`<svg viewBox="0 0 200 200">
<ellipse id="p_cuerpo" class="colorable" cx="100" cy="100" rx="60" ry="38" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<polygon id="p_cola" class="colorable" points="160,100 195,72 195,128" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<polygon id="p_aleta" class="colorable" points="95,120 115,120 105,150" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<circle cx="70" cy="90" r="5" fill="#2b2140"/>
</svg>`
},
coche: {
label:'🚗 Coche',
svg:`<svg viewBox="0 0 200 200">
<rect id="p_carroceria" class="colorable" x="20" y="110" width="160" height="45" rx="14" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<path id="p_techo" class="colorable" d="M55 110 L75 75 H125 L145 110 Z" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<rect id="p_ventanilla" class="colorable" x="85" y="82" width="30" height="24" fill="#ffffff" stroke="#2b2140" stroke-width="3"/>
<circle id="p_rueda1" class="colorable" cx="55" cy="158" r="16" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<circle id="p_rueda2" class="colorable" cx="150" cy="158" r="16" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
</svg>`
},
mariposa: {
label:'🦋 Mariposa',
svg:`<svg viewBox="0 0 200 200">
<line x1="100" y1="60" x2="100" y2="150" stroke="#2b2140" stroke-width="5"/>
<ellipse id="p_ala1" class="colorable" cx="65" cy="85" rx="38" ry="28" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<ellipse id="p_ala2" class="colorable" cx="135" cy="85" rx="38" ry="28" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<ellipse id="p_ala3" class="colorable" cx="75" cy="130" rx="26" ry="20" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<ellipse id="p_ala4" class="colorable" cx="125" cy="130" rx="26" ry="20" fill="#ffffff" stroke="#2b2140" stroke-width="4"/>
<line x1="94" y1="62" x2="80" y2="45" stroke="#2b2140" stroke-width="3" stroke-linecap="round"/>
<line x1="106" y1="62" x2="120" y2="45" stroke="#2b2140" stroke-width="3" stroke-linecap="round"/>
</svg>`
},
};
const drawingTabs = document.getElementById('drawingTabs');
const colorStage = document.getElementById('colorStage');
const paletteEl = document.getElementById('palette');
const eraseBtn = document.getElementById('eraseBtn');
let g5Selected = COLORS[0].id;
let g5Current = 'casa';
function startGame5(){
drawingTabs.innerHTML = '';
Object.keys(DRAWINGS).forEach(key=>{
const btn = document.createElement('button');
btn.className = 'tabBtn' + (key===g5Current ? ' active':'');
btn.textContent = DRAWINGS[key].label;
btn.addEventListener('click', ()=>{
g5Current = key;
document.querySelectorAll('.tabBtn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
renderDrawing();
});
drawingTabs.appendChild(btn);
});
paletteEl.innerHTML = '';
COLORS.forEach(c=>{
const sw = document.createElement('div');
sw.className = 'swatch' + (c.id===g5Selected ? ' selected':'');
sw.style.background = c.hex;
if(c.id==='blanco'){ sw.style.boxShadow='0 0 0 2px #cfd3da'; }
sw.title = c.name;
sw.addEventListener('click', ()=>{
g5Selected = c.id;
document.querySelectorAll('.swatch').forEach(s=>s.classList.remove('selected'));
sw.classList.add('selected');
});
paletteEl.appendChild(sw);
});
eraseBtn.onclick = ()=>{
colorStage.querySelectorAll('.colorable').forEach(el=> el.setAttribute('fill','#ffffff'));
ensureAudio(); playPop();
};
renderDrawing();
}
function renderDrawing(){
colorStage.innerHTML = DRAWINGS[g5Current].svg;
colorStage.querySelectorAll('.colorable').forEach(el=>{
el.addEventListener('click', ()=>{
const c = byId(g5Selected);
el.setAttribute('fill', c.hex);
ensureAudio(); playPop();
});
});
}
/* ============================================================
INICIO
============================================================ */
showScreen('menu');
</script>
</body>
</html>