r
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>RumanoDiario – aprende rumano jugando</title>
<style>
:root{
--duo:#58cc02;--duo-dark:#4caf00;--wrong:#ea2b2b;--bg:#0d3b66;--card:#ffffff;--text:#1c1c1c;
--shadow:0 4px 12px rgba(0,0,0,.25);
font-family:Inter,system-ui,sans-serif;
}
* {box-sizing:border-box;margin:0;padding:0;}
body{background:var(--bg);color:#fff;display:flex;flex-direction:column;min-height:100vh;}
header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.2rem;}
header h1{font-size:1.4rem;}
#headerStats{display:flex;gap:1rem;font-size:1rem;}
main{flex:1;display:flex;align-items:center;justify-content:center;padding:1rem;}
.screen{display:none;width:100%;max-width:640px;}
.screen.active{display:block;}
#lessonGrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;}
.lessonCard{background:var(--card);color:var(--text);border-radius:12px;padding:1rem;text-align:center;cursor:pointer;transition:.2s;box-shadow:var(--shadow);}
.lessonCard:hover{transform:scale(1.05);}
.lessonCard.locked{opacity:.5;cursor:not-allowed;}
#progressBar{height:8px;background:#ffffff33;border-radius:4px;overflow:hidden;margin-bottom:1rem;}
#progressBar div{height:100%;background:var(--duo);width:0%;transition:width .3s;}
.btn{width:100%;border:none;padding:.9rem;font-size:1rem;border-radius:8px;cursor:pointer;font-weight:600;}
.primary{background:var(--duo);color:#fff;}
.primary:disabled{background:#888;}
.success{background:#2ecc71;color:#fff;}
.hidden{display:none;}
.tile{display:inline-block;background:var(--card);color:var(--text);padding:.6rem 1rem;margin:.25rem;border-radius:6px;cursor:pointer;user-select:none;}
.tile.selected{background:var(--duo);color:#fff;}
input[type=text]{width:100%;padding:.8rem;font-size:1rem;border-radius:6px;border:none;}
#exerciseArea{display:flex;flex-direction:column;gap:1rem;margin-bottom:1.2rem;}
.matchRow{display:flex;gap:1rem;flex-wrap:wrap;justify-content:center;}
.matchBtn{background:var(--card);color:var(--text);padding:.6rem 1rem;border-radius:6px;border:none;cursor:pointer;}
.matchBtn.on{background:var(--duo);color:#fff;}
.matchBtn.correct{background:#27ae60;color:#fff;pointer-events:none;}
.matchBtn.wrong{background:var(--wrong);color:#fff;}
</style>
</head>
<body>
<header>
<h1>🇷🇴 RumanoDiario</h1>
<div id="headerStats">
<span id="streak">🔥 0</span>
<span id="xp">⚡ 0 XP</span>
<span id="level">Nivel <b>1</b></span>
</div>
</header>
<main id="screens">
<!-- MENÚ -->
<section id="menu" class="screen active">
<h2>Elige tu lección</h2>
<div id="lessonGrid"></div>
</section>
<!-- LECCIÓN -->
<section id="lesson" class="screen">
<div id="progressBar"><div></div></div>
<div id="questionCounter"></div>
<div id="exerciseArea"></div>
<button id="checkBtn" class="btn primary" disabled>Comprobar</button>
<button id="continueBtn" class="btn success hidden">Continuar</button>
</section>
<!-- RESULTADOS -->
<section id="results" class="screen">
<h2>¡Lección completada!</h2>
<p id="resultsText"></p>
<button class="btn primary" onclick="showScreen('menu')">Menú</button>
<button id="nextLessonBtn" class="btn success hidden">Siguiente lección →</button>
</section>
</main>
<audio id="sndOk" preload="auto">
<source src="data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSuBzvLZiTYIG2m98OScTgwOUarm7blmFgU7k9n1unEiBC13yO/eizEIHWq+8+OWT" type="audio/wav"/>
</audio>
<audio id="sndNo" preload="auto">
<source src="data:audio/wav;base64,UklGRuICAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YcACAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSuBzvLZiTYIG2m98OScTgwOUarm7blmFgU7k9n1unEiBC13yO/eizEIHWq+8+OWT" type="audio/wav"/>
</audio>
<script>
/*********************************************************************
* BASE DE DATOS GRANDE
*********************************************************************/
const LESSONS = [
{
id:1, title:"Saludos", locked:false,
vocab:[
{ro:"bună",es:"hola"},{ro:"salut",es:"hola (informal)"},{ro:"dimineața",es:"buenos días"},
{ro:"ziua",es:"buenas tardes"},{ro:"seara",es:"buenas noches"},{ro:"noapte bună",es:"buenas noches (despedida)"},
{ro:"la revedere",es:"adiós"},{ro:"pe mâine",es:"hasta mañana"},{ro:"cu plăcere",es:"con gusto"}
]
},
{
id:2, title:"Presentaciones", locked:true,
vocab:[
{ro:"numele meu este",es:"mi nombre es"},{ro:"eu sunt",es:"yo soy"},{ro:"încântat",es:"encantado"},
{ro:"ce mai faci?",es:"¿cómo estás?"},{ro:"bine, mulțumesc",es:"bien, gracias"},{ro:"și tu?",es:"¿y tú?"},
{ro:"cât de ani ai?",es:"¿cuántos años tienes?"},{ro:"am 25 de ani",es:"tengo 25 años"}
]
},
{
id:3, title:"Números 0-20", locked:true,
vocab:[
{ro:"zero",es:"cero"},{ro:"unu",es:"uno"},{ro:"doi",es:"dos"},{ro:"trei",es:"tres"},
{ro:"patru",es:"cuatro"},{ro:"cinci",es:"cinco"},{ro:"șase",es:"seis"},{ro:"șapte",es:"siete"},
{ro:"opt",es:"ocho"},{ro:"nouă",es:"nueve"},{ro:"zece",es:"diez"},{ro:"unsprezece",es:"once"},
{ro:"douăsprezece",es:"doce"},{ro:"treisprezece",es:"trece"},{ro:"paisprezece",es:"catorce"},
{ro:"cincisprezece",es:"quince"},{ro:"șaisprezece",es:"dieciséis"},{ro:"șaptesprezece",es:"diecisiete"},
{ro:"optsprezece",es:"dieciocho"},{ro:"nouăsprezece",es:"diecinueve"},{ro:"douăzeci",es:"veinte"}
]
},
{
id:4, title:"Colores", locked:true,
vocab:[
{ro:"roșu",es:"rojo"},{ro:"albastru",es:"azul"},{ro:"galben",es:"amarillo"},
{ro:"verde",es:"verde"},{ro:"negru",es:"negro"},{ro:"alb",es:"blanco"},
{ro:"roz",es:"rosa"},{ro:"mov",es:"morado"},{ro:"portocaliu",es:"naranja"},
{ro:"maro",es:"marrón"},{ro:"gri",es:"gris"}
]
},
{
id:5, title:"Familia", locked:true,
vocab:[
{ro:"mamă",es:"madre"},{ro:"tată",es:"padre"},{ro:"frate",es:"hermano"},
{ro:"soră",es:"hermana"},{ro:"copil",es:"niño/hijo"},{ro:"fiu",es:"hijo"},
{ro:"fiică",es:"hija"},{ro:"bunică",es:"abuela"},{ro:"bunic",es:"abuelo"},
{ro:"soț",es:"esposo"},{ro:"soție",es:"esposa"}
]
},
{
id:6, title:"Comida", locked:true,
vocab:[
{ro:"pâine",es:"pan"},{ro:"lapte",es:"leche"},{ro:"ouă",es:"huevos"},
{ro:"brânză",es:"queso"},{ro:"carne",es:"carne"},{ro:"apa",es:"agua"},
{ro:"vin",es:"vino"},{ro:"fructe",es:"frutas"},{ro:"legume",es:"verduras"},
{ro:"dulce",es:"dulce"},{ro:"sare",es:"sal"},{ro:"piper",es:"pimienta"}
]
},
{
id:7, title:"El tiempo", locked:true,
vocab:[
{ro:"soare",es:"sol"},{ro:"ploaie",es:"lluvia"},{ro:"zăpadă",es:"nieve"},
{ro:"vânt",es:"viento"},{ro:"ceată",es:"niebla"},{ro:"cald",es:"calor"},
{ro:"rece",es:"frío"},{ro:"umbrelă",es:"paraguas"},{ro:"temporatură",es:"temperatura"},
{ro:"grade",es:"grados"},{ro:"astăzi",es:"hoy"},{ro:"mâine",es:"mañana"}
]
},
{
id:8, title:"Verbos comunes", locked:true,
vocab:[
{ro:"a fi",es:"ser/estar"},{ro:"a avea",es:"tener"},{ro:"a face",es:"hacer"},
{ro:"a merge",es:"ir"},{ro:"a veni",es:"venir"},{ro:"a spune",es:"decir"},
{ro:"a vedea",es:"ver"},{ro:"a da",es:"dar"},{ro:"a lua",es:"tomar"},
{ro:"a iubi",es:"amar"},{ro:"a mânca",es:"comer"},{ro:"a bea",es:"beber"}
]
},
{
id:9, title:"Frases útiles", locked:true,
vocab:[
{ro:"unde este toaleta?",es:"¿dónde está el baño?"},{ro:"cât costă?",es:"¿cuánto cuesta?"},
{ro:"nu înțeleg",es:"no entiendo"},{ro:"vorbiți engleză?",es:"¿habla inglés?"},
{ro:"ajutor!",es:"¡ayuda!"},{ro:"sunt pierdut",es:"estoy perdido"},
{ro:"cheia camerei",es:"la llave de la habitación"},{ro:"rezervare",es:"reserva"}
]
},
{
id:10, title:"Viajes", locked:true,
vocab:[
{ro:"autobuz",es:"autobús"},{ro:"tren",es:"tren"},{ro:"avion",es:"avión"},
{ro:"bicicletă",es:"bicicleta"},{ro:"mașină",es:"coche"},{ro:"hotel",es:"hotel"},
{ro:"gară",es:"estación"},{ro:"aeroport",es:"aeropuerto"},{ro:"bilet",es:"billete"},
{ro:"valiză",es:"maleta"},{ro:"hartă",es:"mapa"},{ro:"pașaport",es:"pasaporte"}
]
}
];
/*********************************************************************
* ESTADO
*********************************************************************/
let state = {
currentLesson:null,
currentIndex:0,
exercises:[],
streak:Number(localStorage.getItem("streak")||0),
xp:Number(localStorage.getItem("xp")||0),
level:Number(localStorage.getItem("level")||1),
lastDate:localStorage.getItem("lastDate")||null,
unlocked:JSON.parse(localStorage.getItem("unlocked")||"[1]") // ids desbloqueados
};
/*********************************************************************
* UTILS
*********************************************************************/
const qs=s=>document.querySelector(s);
const qsa=s=>[...document.querySelectorAll(s)];
const rand=(arr)=>arr[Math.floor(Math.random()*arr.length)];
const shuffle=arr=>arr.sort(()=>Math.random()-.5);
function showScreen(name){qsa(".screen").forEach(sc=>sc.classList.remove("active"));qs("#"+name).classList.add("active");}
function play(id){const a=qs(id);a.currentTime=0;a.play();}
function speakRo(text){
if(!('speechSynthesis' in window))return;
const u=new SpeechSynthesisUtterance(text);u.lang="ro-RO";speechSynthesis.speak(u);
}
function saveState(){
localStorage.setItem("streak",state.streak);
localStorage.setItem("xp",state.xp);
localStorage.setItem("level",state.level);
localStorage.setItem("lastDate",state.lastDate);
localStorage.setItem("unlocked",JSON.stringify(state.unlocked));
}
/*********************************************************************
* MENÚ
*********************************************************************/
function renderMenu(){
const grid=qs("#lessonGrid");grid.innerHTML="";
LESSONS.forEach(ls=>{
const div=document.createElement("div");
div.className="lessonCard"+(ls.locked?" locked":"");
div.innerHTML=`<h3>${ls.title}</h3><small>${ls.vocab.length} términos</small>`;
if(!ls.locked)div.onclick=()=>startLesson(ls);
grid.appendChild(div);
});
updateHeader();
}
function updateHeader(){
qs("#streak").textContent="🔥 "+state.streak;
qs("#xp").textContent="⚡ "+state.xp+" XP";
qs("#level b").textContent=state.level;
}
/*********************************************************************
* LECCIÓN
*********************************************************************/
function startLesson(lesson){
state.currentLesson=lesson;
state.currentIndex=0;
buildExercises(lesson);
showScreen("lesson");
renderProgress();
renderExercise();
}
function buildExercises(lesson){
state.exercises=[];
lesson.vocab.forEach(w=>{
state.exercises.push({type:"pickEs",word:w,prompt:`¿Qué significa <b>${w.ro}</b>?`});
state.exercises.push({type:"pickRo",word:w,prompt:`Traduce al rumano: <b>${w.es}</b>`});
state.exercises.push({type:"listen",word:w,prompt:`Escucha y escribe lo que oyes`});
state.exercises.push({type:"order",word:w,prompt:`Ordena la frase`});
state.exercises.push({type:"fill",word:w,prompt:`Completa la palabra`});
});
shuffle(state.exercises);
}
function renderProgress(){
const t=state.exercises.length,d=state.currentIndex;
qs("#progressBar div").style.width=(d/t*100)+"%";
qs("#questionCounter").textContent=`Pregunta ${d+1} de ${t}`;
}
function renderExercise(){
const ex=state.exercises[state.currentIndex];
const area=qs("#exerciseArea");area.innerHTML="";
qs("#checkBtn").classList.remove("hidden");
qs("#continueBtn").classList.add("hidden");
qs("#checkBtn").disabled=true;
const p=document.createElement("p");p.innerHTML=ex.prompt;area.appendChild(p);
switch(ex.type){
case "pickEs":case "pickRo":
const choices=buildChoices(ex);
choices.forEach(ch=>{
const t=document.createElement("span");t.className="tile";t.textContent=ch;
t.onclick=()=>{qsa("#exerciseArea .tile").forEach(x=>x.classList.remove("selected"));t.classList.add("selected");selectedAnswer=ch;qs("#checkBtn").disabled=false;};
area.appendChild(t);
});break;
case "listen":
speakRo(ex.word.ro);
const inp=document.createElement("input");inp.type="text";inp.placeholder="Escribe en rumano…";
inp.oninput=()=>qs("#checkBtn").disabled=!inp.value.trim();
area.appendChild(inp);
qs("#checkBtn").onclick=()=>checkWrite(inp.value.trim(),ex.word.ro);return;
case "order":
const letters=ex.word.ro.split("");shuffle(letters);
const orderArea=document.createElement("div");orderArea.id="orderArea";
letters.forEach(l=>{
const b=document.createElement("button");b.className="matchBtn";b.textContent=l;
b.onclick=()=>{orderAnswer=(orderAnswer||"")+l;b.classList.add("on");qs("#checkBtn").disabled=false;};
orderArea.appendChild(b);
});
area.appendChild(orderArea);
qs("#checkBtn").onclick=()=>checkWrite(orderAnswer,ex.word.ro);return;
case "fill":
const w=ex.word.ro;
const hideIdx=Math.floor(w.length/2);
const shown=w[hideIdx]===" "?w.replace(w.trim().split("")[hideIdx],"_"):w.replace(w[hideIdx],"_");
const fillInp=document.createElement("input");fillInp.type="text";fillInp.placeholder="Completa…";
fillInp.oninput=()=>qs("#checkBtn").disabled=!fillInp.value.trim();
area.innerHTML+=`<p>${shown}</p>`;area.appendChild(fillInp);
qs("#checkBtn").onclick=()=>checkWrite(fillInp.value.trim(),ex.word.ro);return;
}
qs("#checkBtn").onclick=()=>checkChoice(ex.answer);
}
let selectedAnswer=null,orderAnswer="";
function buildChoices(ex){
const right=ex.type==="pickEs"?ex.word.es:ex.word.ro;
const wrongs=LESSONS.flatMap(l=>l.vocab).filter(v=>v.ro!==ex.word.ro).map(v=>ex.type==="pickEs"?v.es:v.ro);
const picks=[right];while(picks.length<4)picks.push(rand(wrongs));return shuffle(picks);
}
function checkChoice(correct){feedback(selectedAnswer===correct);}
function checkWrite(value,correct){feedback(value.toLowerCase()===correct.toLowerCase());}
function feedback(ok){
play(ok?"#sndOk":"#sndNo");
qs("#checkBtn").classList.add("hidden");
qs("#continueBtn").classList.remove("hidden");
if(ok){state.xp+=10;saveState();updateHeader();}
}
qs("#continueBtn").onclick=()=>{
state.currentIndex++;
if(state.currentIndex>=state.exercises.length){
finishLesson();
}else{
renderProgress();
renderExercise();
}
};
function finishLesson(){
const today=new Date().toISOString().slice(0,10);
if(state.lastDate!==today){state.streak++;state.lastDate=today;}
const newLevel=Math.floor(state.xp/100)+1;
if(newLevel>state.level)state.level=newLevel;
saveState();
qs("#resultsText").innerHTML=`¡Bien hecho! Ganaste <b>${state.exercises.length*10} XP</b>. Racha: <b>${state.streak} días</b>.`;
// desbloquear siguiente
const next=LESSONS.find(l=>l.id===state.currentLesson.id+1);
if(next&&!state.unlocked.includes(next.id)){state.unlocked.push(next.id);next.locked=false;saveState();}
qs("#nextLessonBtn").classList.toggle("hidden",!next);
qs("#nextLessonBtn").onclick=()=>startLesson(next);
showScreen("results");
}
/*********************************************************************
* INIT
*********************************************************************/
(function init(){
LESSONS.forEach(ls=>ls.locked=!state.unlocked.includes(ls.id));
renderMenu();
})();
</script>
</body>
</html>