Øv på matte 🧮

Velg et emne og et nivå – og sett i gang!

Velg vanskelighetsgrad

🟢
Mild
Enkle tall, én operasjon. Perfekt for å bygge flyt.
🟡
Medium
To-tre operasjoner, litt større tall.
🌶️
Spicy
Større tall, flere steg, krever konsentrasjon.
🔥
Hot
Maks vanskelighetsgrad. Klarer du alle 15?
0 / 15
Oppgave 1 av 15
0
Riktig
0
Feil
0
Poeng

Riktig
Feil
Poeng
XP opptjent
{ id: 'add-heltall', ikon: '➕', navn: 'Addisjon', info: 'Heltall', farge: 'rgba(129,140,248,1)', gen: { mild: () => { const a=ri(10,90), b=ri(5,50); return { t:`${a} + ${b}`, s:a+b }; }, medium: () => { const a=ri(20,90), b=ri(20,90); return { t:`${a} + ${b}`, s:a+b }; }, spicy: () => { const a=ri(100,900), b=ri(100,600); return { t:`${a} + ${b}`, s:a+b }; }, hot: () => { const a=ri(1000,9000), b=ri(100,999); return { t:`${a} + ${b}`, s:a+b }; }, } }, { id: 'sub-heltall', ikon: '➖', navn: 'Subtraksjon', info: 'Heltall', farge: 'rgba(251,113,133,1)', gen: { mild: () => { const b=ri(5,40), a=ri(b,90); return { t:`${a} − ${b}`, s:a-b }; }, medium: () => { const b=ri(10,80), a=ri(b,150); return { t:`${a} − ${b}`, s:a-b }; }, spicy: () => { const b=ri(50,400), a=ri(b+50,900); return { t:`${a} − ${b}`, s:a-b }; }, hot: () => { const b=ri(100,5000), a=ri(b+100,9999); return { t:`${a} − ${b}`, s:a-b }; }, } }, { id: 'mul-smaa', ikon: '✖️', navn: 'Multiplikasjon', info: 'Gangetabellen', farge: 'rgba(34,197,94,1)', gen: { mild: () => { const a=ri(1,9), b=ri(1,9); return { t:`${a} · ${b}`, s:a*b }; }, medium: () => { const a=ri(1,9), b=ri(1,9), c=ri(1,9); return { t:`${a} · ${b} · ${c}`, s:a*b*c }; }, spicy: () => { const vals=[ri(1,9),ri(1,9),ri(1,9),ri(1,9)]; return { t:vals.join(' · '), s:vals.reduce((x,y)=>x*y,1) }; }, hot: () => { const vals=[ri(1,9),ri(1,9),ri(1,9),ri(1,9),ri(1,9)]; return { t:vals.join(' · '), s:vals.reduce((x,y)=>x*y,1) }; }, } }, { id: 'mul-store', ikon: '🔢', navn: 'Store tall', info: 'Multiplikasjon', farge: 'rgba(234,179,8,1)', gen: { mild: () => { const a=ri(10,30), b=ri(2,9); return { t:`${a} · ${b}`, s:a*b }; }, medium: () => { const a=ri(10,50), b=ri(10,30); return { t:`${a} · ${b}`, s:a*b }; }, spicy: () => { const a=ri(50,200), b=ri(10,50); return { t:`${a} · ${b}`, s:a*b }; }, hot: () => { const a=ri(100,500), b=ri(20,99); return { t:`${a} · ${b}`, s:a*b }; }, } }, { id: 'div-smaa', ikon: '➗', navn: 'Divisjon', info: 'Enkle tall', farge: 'rgba(249,115,22,1)', gen: { mild: () => { const b=ri(2,9), s=ri(1,9); return { t:`${b*s} : ${b}`, s }; }, medium: () => { const b=ri(2,9), s=ri(5,20); return { t:`${b*s} : ${b}`, s }; }, spicy: () => { const b=ri(3,12), s=ri(10,40); return { t:`${b*s} : ${b}`, s }; }, hot: () => { const b=ri(4,15), s=ri(20,80); return { t:`${b*s} : ${b}`, s }; }, } }, { id: 'add-des', ikon: '🔡', navn: 'Desimaltall', info: 'Addisjon & subtraksjon', farge: 'rgba(129,140,248,.7)', gen: { mild: () => { const a=rd(1,1), b=rd(1,1); return { t:`${a.toFixed(1)} + ${b.toFixed(1)}`, s:Math.round((a+b)*10)/10 }; }, medium: () => { const a=rd(2,2), b=rd(2,2); return { t:`${a.toFixed(2)} + ${b.toFixed(2)}`, s:Math.round((a+b)*100)/100 }; }, spicy: () => { const a=rd(2,2), b=rd(2,2); return { t:`${a.toFixed(2)} − ${b.toFixed(2)}`, s:Math.round((a-b)*100)/100 }; }, hot: () => { const a=rd(2,3), b=rd(2,3), c=rd(1,2); const res = Math.round((a+b-c)*1000)/1000; return { t:`${a} + ${b} − ${c}`, s:res }; }, } }, { id: 'neg-tall', ikon: '🌡️', navn: 'Negative tall', info: 'Addisjon & subtraksjon', farge: 'rgba(96,165,250,1)', gen: { mild: () => { const a=ri(-20,20), b=ri(1,20); return { t:`${a} + ${b}`, s:a+b }; }, medium: () => { const a=ri(-30,30), b=ri(-20,20); return { t:`${a} + (${b})`, s:a+b }; }, spicy: () => { const a=ri(-50,50), b=ri(-40,40); return { t:`${a} − (${b})`, s:a-b }; }, hot: () => { const a=ri(-50,50), b=ri(-40,40), c=ri(-30,30); return { t:`${a} + ${b} − (${c})`, s:a+b-c }; }, } }, { id: 'prosent', ikon: '💯', navn: 'Prosent', info: 'Grunnleggende', farge: 'rgba(167,243,208,1)', gen: { mild: () => { const p=[10,20,25,50][ri(0,3)], h=ri(1,10)*100; return { t:`${p}% av ${h}`, s:Math.round(p*h/100) }; }, medium: () => { const p=[5,10,15,20,25,50][ri(0,5)], h=ri(2,20)*10; return { t:`${p}% av ${h}`, s:Math.round(p*h/100) }; }, spicy: () => { const p=ri(1,9)*5, h=ri(10,200); return { t:`${p}% av ${h}`, s:+(p*h/100).toFixed(2) }; }, hot: () => { const h=ri(50,500), del=ri(10,h); return { t:`Hva er ${del} av ${h} i prosent?`, s:Math.round(del/h*100) }; }, } }, ]; // ══════════════════════════════════════════ // HJELPE-FUNKSJONER // ══════════════════════════════════════════ function ri(min, max) { return Math.floor(Math.random()*(max-min+1))+min; } function rd(intMin, desMin) { const h = ri(intMin, intMin+8); const d = ri(desMin===1 ? 1 : 10, desMin===1 ? 9 : 99); return parseFloat(`${h}.${d}`); } // ══════════════════════════════════════════ // TILSTAND // ══════════════════════════════════════════ let totXP = parseInt(localStorage.getItem('matte_xp') || '0'); let totStreak = parseInt(localStorage.getItem('matte_streak') || '0'); let emneProgress = JSON.parse(localStorage.getItem('matte_prog') || '{}'); let aktivtEmne = null; let aktivtNivaa = null; let oppgaver = []; let oppgaveIdx = 0; let riktig = 0; let feil = 0; let poeng = 0; let sisteSvarRiktig = false; // ══════════════════════════════════════════ // INIT // ══════════════════════════════════════════ function init() { oppdaterHeaderStats(); byggEmneGrid(); } function oppdaterHeaderStats() { document.getElementById('xpVis').textContent = totXP; document.getElementById('streakVis').textContent = totStreak; } function byggEmneGrid() { const grid = document.getElementById('emneGrid'); grid.innerHTML = ''; EMNER.forEach(emne => { const prog = emneProgress[emne.id] || 0; const div = document.createElement('div'); div.className = 'emne-kort'; div.style.setProperty('--emne-c', emne.farge); div.innerHTML = ` ${emne.ikon}
${emne.navn}
${emne.info}
`; div.onclick = () => velgEmne(emne); grid.appendChild(div); }); } // ══════════════════════════════════════════ // NAVIGASJON // ══════════════════════════════════════════ function vis(id) { ['velgSkjerm','nivaaSkjerm','treningSkjerm','resultatSkjerm'].forEach(s => document.getElementById(s).style.display = 'none'); document.getElementById(id).style.display = 'block'; } function velgEmne(emne) { aktivtEmne = emne; document.getElementById('nivaaEmneIkon').textContent = emne.ikon; document.getElementById('niveauTittel').textContent = emne.navn + ' – ' + emne.info; vis('nivaaSkjerm'); } function gaaTilEmner() { byggEmneGrid(); vis('velgSkjerm'); } function gaaTilNivaa() { vis('nivaaSkjerm'); } function startTrening(nivaa) { aktivtNivaa = nivaa; oppgaveIdx = 0; riktig = 0; feil = 0; poeng = 0; // Generer 15 oppgaver oppgaver = []; for (let i = 0; i < 15; i++) { oppgaver.push(aktivtEmne.gen[nivaa]()); } // Nullstill UI document.getElementById('riktigTeller').textContent = '0'; document.getElementById('feilTeller').textContent = '0'; document.getElementById('poengTeller').textContent = '0'; document.querySelectorAll('.stjerne').forEach(s => s.classList.remove('aktiv')); // Topplinje const nivaaFarger = { mild:'var(--mild)', medium:'var(--medium)', spicy:'var(--spicy)', hot:'var(--hot)' }; const nivaaNavn = { mild:'🟢 Mild', medium:'🟡 Medium', spicy:'🌶️ Spicy', hot:'🔥 Hot' }; document.getElementById('treningEmneLabel').textContent = aktivtEmne.navn; const badge = document.getElementById('treningNivaaBadge'); badge.textContent = nivaaNavn[nivaa]; badge.style.background = nivaaFarger[nivaa] + '22'; badge.style.color = nivaaFarger[nivaa]; badge.style.border = `1.5px solid ${nivaaFarger[nivaa]}55`; vis('treningSkjerm'); visOppgave(); } function startSammeIgjen() { startTrening(aktivtNivaa); } // ══════════════════════════════════════════ // OPPGAVE-LOGIKK // ══════════════════════════════════════════ function visOppgave() { if (oppgaveIdx >= oppgaver.length) { visResultat(); return; } const opp = oppgaver[oppgaveIdx]; document.getElementById('oppgaveNr').textContent = `Oppgave ${oppgaveIdx+1} av ${oppgaver.length}`; document.getElementById('oppgaveTekst').textContent = opp.t + ' ='; document.getElementById('oppgaveHint').textContent = ''; document.getElementById('svarInput').value = ''; document.getElementById('svarInput').className = 'svar-input'; document.getElementById('svarInput').disabled = false; const fb = document.getElementById('feedbackTekst'); fb.textContent = ''; fb.className = 'feedback-tekst'; const kort = document.getElementById('oppgaveKort'); kort.className = 'oppgave-kort'; // Fremgangsbar const pct = (oppgaveIdx / oppgaver.length) * 100; document.getElementById('fremgangFill').style.width = pct + '%'; document.getElementById('treningFremgang').textContent = `${oppgaveIdx} / ${oppgaver.length}`; setTimeout(() => document.getElementById('svarInput').focus(), 50); } function sjekkSvar() { const input = document.getElementById('svarInput'); if (input.disabled) { neste(); return; } const raaverdi = input.value.replace(',', '.').trim(); if (raaverdi === '') return; const svar = parseFloat(raaverdi); const fasit = oppgaver[oppgaveIdx].s; const kort = document.getElementById('oppgaveKort'); const fb = document.getElementById('feedbackTekst'); const erRiktig = Math.abs(svar - fasit) < 0.001; input.disabled = true; if (erRiktig) { riktig++; poeng += poengForNivaa(aktivtNivaa); totStreak++; sisteSvarRiktig = true; input.className = 'svar-input riktig'; kort.className = 'oppgave-kort riktig'; fb.textContent = ['Riktig! 🎉', 'Supert! ⭐', 'Perfekt! 🔥', 'Bra! 💪'][ri(0,3)]; fb.className = 'feedback-tekst vis riktig'; oppdaterStjerner(); visXpPopup(`+${poengForNivaa(aktivtNivaa)} XP`); } else { feil++; totStreak = 0; sisteSvarRiktig = false; input.className = 'svar-input feil'; kort.className = 'oppgave-kort feil'; fb.textContent = `Feil. Svaret er ${fasit}`; fb.className = 'feedback-tekst vis feil'; } document.getElementById('riktigTeller').textContent = riktig; document.getElementById('feilTeller').textContent = feil; document.getElementById('poengTeller').textContent = poeng; oppdaterHeaderStats(); lagreProgress(); setTimeout(neste, 1200); } function neste() { oppgaveIdx++; visOppgave(); } function hoppOver() { feil++; totStreak = 0; document.getElementById('feilTeller').textContent = feil; oppdaterHeaderStats(); oppgaveIdx++; visOppgave(); } function poengForNivaa(n) { return { mild:5, medium:10, spicy:15, hot:25 }[n] || 10; } function oppdaterStjerner() { // Stjerne for hver 3. riktige (maks 5 stjerner) const antall = Math.min(5, Math.floor(riktig / 3)); for (let i = 1; i <= antall; i++) { const s = document.getElementById('s'+i); if (s && !s.classList.contains('aktiv')) { s.classList.add('aktiv'); } } } // ══════════════════════════════════════════ // RESULTAT // ══════════════════════════════════════════ function visResultat() { // Fremgangsbar 100% document.getElementById('fremgangFill').style.width = '100%'; document.getElementById('treningFremgang').textContent = `${oppgaver.length} / ${oppgaver.length}`; const prosent = Math.round((riktig / oppgaver.length) * 100); const xpOpptjent = poeng; totXP += xpOpptjent; lagreProgress(); // Ikoner let emoji, tittel, sub; if (prosent === 100) { emoji='🏆'; tittel='Perfekt runde!'; sub='Du svarte riktig på alle oppgavene!'; } else if (prosent >= 80) { emoji='🌟'; tittel='Veldig bra!'; sub=`${prosent}% riktige – imponerende!`; } else if (prosent >= 60) { emoji='👍'; tittel='Bra jobbet!'; sub=`${prosent}% riktige. Fortsett å øve!`; } else if (prosent >= 40) { emoji='💪'; tittel='Fortsett!'; sub=`${prosent}% riktige. Du blir bedre!`; } else { emoji='📚'; tittel='Øv mer!'; sub=`${prosent}% riktige. Ta det igjen!`; } document.getElementById('resultatEmoji').textContent = emoji; document.getElementById('resultatTittel').textContent = tittel; document.getElementById('resultatSub').textContent = sub; document.getElementById('resRiktig').textContent = riktig; document.getElementById('resFeil').textContent = feil; document.getElementById('resPoeng').textContent = poeng; document.getElementById('resXP').textContent = '+' + xpOpptjent; // Stjerner (1–5) const stjerner = Math.max(1, Math.round(prosent / 20)); const starDiv = document.getElementById('resultatStjerner'); starDiv.innerHTML = ''; for (let i = 0; i < 5; i++) { starDiv.innerHTML += i < stjerner ? '⭐' : '☆'; } // Lagre best progress for emnet const progKey = aktivtEmne.id; const gammelProg = emneProgress[progKey] || 0; emneProgress[progKey] = Math.max(gammelProg, prosent); localStorage.setItem('matte_prog', JSON.stringify(emneProgress)); vis('resultatSkjerm'); } // ══════════════════════════════════════════ // LAGRING // ══════════════════════════════════════════ function lagreProgress() { localStorage.setItem('matte_xp', totXP); localStorage.setItem('matte_streak', totStreak); oppdaterHeaderStats(); } // ══════════════════════════════════════════ // XP-POPUP // ══════════════════════════════════════════ let xpTimer = null; function visXpPopup(tekst) { const el = document.getElementById('xpPopup'); el.textContent = tekst; el.classList.add('vis'); if (xpTimer) clearTimeout(xpTimer); xpTimer = setTimeout(() => el.classList.remove('vis'), 1500); } // Start init();