QiRa

by GeniusIsMe โ€ข Kira Cepat, Jadi Hebat

QiRa mascot and logo

QiRa Checkup
Kenal Pasti Kelemahan
Ulang Kaji Tepat

QiRa ialah maths weakness detector dan progress tracker untuk ibu bapa. Buat akaun percuma untuk kenal pasti kelemahan anak, simpan rekod perkembangan matematik mereka, dan terus beri laluan ulang kaji melalui congak, game, worksheet serta video bimbingan Sir Edy.

QiRa Checkup

Pilih jenis checkup yang sesuai untuk anak.

QiRa Asas Checkup

Pilih satu checkup asas. Setiap checkup ada 20 soalan dan dinilai melalui skor serta masa.

Congak

Pilih jenis congak. Semua latihan rekod masa supaya ibu bapa nampak anak laju atau perlahan.

Congak

Pilih bentuk soalan dan mula latihan.

Game

Pilih mod permainan matematik.

Time Challenge

Jawab sebanyak mungkin sebelum masa tamat.

Game

Battle

Battle

Time Bomb

Pilih versi permainan. Setiap giliran pilih 1, 2 atau 3 langkah.

Time Bomb

Juara Congak

Bila nombor giliran termasuk dalam sifir 3, pemain mesti sebut atau tekan โ€œCONGAKโ€ dengan cepat. Kalau tersilap sebut nombor biasa, markah akan terjejas.

QiRa mascot and logo
GeniusIsMe logo
Powered by GeniusIsMeKira Cepat, Jadi Hebat
`;w.document.open();w.document.write(html.replace(/<\/script/gi,'<\\/script'));w.document.close();setTimeout(()=>{try{w.focus()}catch(e){}},300)} function renderParentDashboard(){ let area=$('parentDashboardArea');if(!area)return; if(QIRA.user && QIRA.store && !QIRA.store.activeParentId){QIRA.store.activeParentId=QIRA.user.id;try{qiraSaveStore()}catch(e){}} if(!QIRA.progressChildId){try{QIRA.progressChildId=localStorage.getItem('qira_progress_child_id')||null}catch(e){}} if(!QIRA.progressChildSnapshot){try{let snap=localStorage.getItem('qira_progress_child_snapshot');if(snap)QIRA.progressChildSnapshot=JSON.parse(snap)}catch(e){}} let kids=qiraChildren(); let active=qiraProgressChild(kids); let view=QIRA.progressView||'summary'; let hasProfile=!!active || kids.length>0 || !!QIRA.progressChildSnapshot || !!QIRA.progressChildId; let shouldLoad=qiraCloudConfigured() && (!QIRA.progressSessionChecked || QIRA.cloudHydratingChildren || QIRA.progressBootstrapRunning) && !hasProfile; if(shouldLoad && !QIRA.progressBootstrapRunning){setTimeout(()=>qiraEnsureProgressSessionAndChildren(),50)} let emptyHtml=shouldLoad ? `
Sedang memuat profil anak dan progress dari QiRa Cloud...
` : `
Belum ada profil anak. Cipta akaun atau tambah profil dahulu untuk mula simpan progress.
`; let content=''; if(hasProfile){ if(!active){content=`
Sedang memuat profil anak yang dipilih dari QiRa Cloud...
`; if(!QIRA.progressBootstrapRunning)setTimeout(()=>qiraEnsureProgressSessionAndChildren(),50);} else content=qiraRenderViewTabs()+(view==='summary'?qiraRenderSummary(kids,active):view==='full'?qiraRenderFullHistory(active):qiraRenderCharts(active)); }else content=emptyHtml; area.innerHTML=`
๐Ÿ“Š Progress Anak

Pantau progress anak, history lengkap, chart dan cadangan langkah seterusnya.

${content}`; } function qiraActivityReportHtml(activityId){ let a=QIRA.store.activities.find(x=>x.id===activityId); if(!a)return ''; let child=QIRA.store.children.find(c=>c.id===a.childId)||{}; let attempts=QIRA.store.attempts.filter(x=>x.activityId===activityId); let reportTitle=qiraActivityLabel(a); let reportDate=qiraFmtDateTime(a.createdAt); let logo='qira_logo_worksheet.png'; let rows=attempts.map((x,i)=>` ${i+1} ${qiraEsc(x.question)}
${qiraEsc(x.topic||'')} ${qiraEsc(x.userAnswer)} ${qiraEsc(x.correctAnswer)} ${x.isCorrect?'Betul':'Salah'} ${x.timeSeconds}s `).join(''); return ` QiRa Report - ${qiraEsc(reportTitle)}

QiRa Report

QiRa by GeniusIsMe โ€ข Kira Cepat, Jadi Hebat

${qiraEsc(reportTitle)} โ€ข ${qiraEsc(child.nickname||'Anak')}

Tarikh: ${reportDate}
Skor: ${a.score}/${a.total} โ€ข ${a.accuracy}%
Tempoh: ${qiraFmtDuration(a.durationSeconds)}
Purata masa: ${a.avgTime}s / soalan
${(a.winner||a.loser)?`
Pemenang: ${qiraEsc(a.winner||'-')}
Kalah: ${qiraEsc(a.loser||'-')}
`:''} ${qiraPlayerDetailsPlain(a)?`
Maklumat pemain: ${qiraEsc(qiraPlayerDetailsPlain(a))}
`:''}
Weakness / Nota: ${qiraWeaknessText(a).replace(/
/g,' โ€ข ').replace(/<[^>]+>/g,'')}

Semakan Jawapan

${attempts.length?`${rows}
NoSoalanJawapan AnakJawapan BetulStatusMasa
`:`
Report penuh hanya tersedia untuk aktiviti baharu yang ada tracking soalan.
`}
`; } function qiraDownloadActivityReport(activityId){qiraPrintActivityReport(activityId)} function qiraPrintActivityReport(activityId){let html=qiraActivityReportHtml(activityId);if(!html){qiraNotice('Report tidak ditemui.');return}let w=window.open('','_blank');if(!w){qiraNotice('Browser menyekat tab baharu. Sila benarkan pop-up untuk cetak atau simpan PDF.');return}w.document.open();w.document.write(html.replace(/<\/script/gi,'<\/script'));w.document.close();setTimeout(()=>{try{w.focus();w.print()}catch(e){}},500)} function qiraOpenActivityReport(activityId){ try{ if(!activityId){qiraNotice('Report aktiviti tidak ditemui.');return} let acts=qiraDedupeActivities(QIRA.store.activities||[]); let a=acts.find(x=>String(x.id)===String(activityId)||String(x.cloudId||'')===String(activityId)); if(a){ // ensure the activity exists in the canonical store so qiraShowActivityReport can find it let exists=(QIRA.store.activities||[]).some(x=>String(x.id)===String(a.id)); if(!exists) QIRA.store.activities.push(a); qiraShowActivityReport(a.id); return; } qiraNotice('Report aktiviti tidak ditemui. Sila refresh progress dan cuba lagi.'); }catch(err){ console.error('QiRa activity report open failed',err); qiraNotice('Report aktiviti belum dapat dibuka. Sila cuba sekali lagi.'); } } function qiraShowActivityReport(activityId){let a=QIRA.store.activities.find(x=>x.id===activityId);let child=a?QIRA.store.children.find(c=>c.id===a.childId):qiraChild();let attempts=QIRA.store.attempts.filter(x=>x.activityId===activityId);let area=$('parentDashboardArea');if(!area||!a)return;let reportTitle=qiraActivityLabel(a);let reportDate=qiraFmtDateTime(a.createdAt);area.innerHTML=`

${qiraEsc(reportTitle)}${child?.nickname?` โ€ข ${qiraEsc(child.nickname)}`:''}

QiRa by GeniusIsMe โ€ข Kira Cepat, Jadi Hebat

Report dijana: ${reportDate}

๐Ÿ“‹ Report Penuh
${a.accuracy||0}%Skor
${a.avgTime??0}sPurata masa
${qiraFmtDuration(a.durationSeconds)}Tempoh
${(a.winner||a.loser)?`
${qiraEsc(a.winner||'-')}Pemenang
${qiraEsc(a.loser||'-')}Kalah
`:''}${qiraPlayerDetailsHtml(a)}

Tarikh aktiviti: ${qiraFmtDateTime(a.createdAt)}
Weakness / Nota:
${qiraWeaknessText(a)}

Semakan Jawapan

${attempts.length?`${attempts.map((x,i)=>``).join('')}
NoSoalanJawapan AnakJawapan BetulStatusMasa
${i+1}${qiraEsc(x.question)}
${qiraEsc(x.topic||'')}
${qiraEsc(x.userAnswer)}${qiraEsc(x.correctAnswer)}${x.isCorrect?'Betul':'Salah'}${x.timeSeconds}s
`:'
Report penuh hanya tersedia untuk aktiviti baharu yang ada tracking soalan.
'}
`} function openAdminDashboard(){go('adminDashboard');renderAdminDashboard();if(qiraCloudLoggedIn()){qiraAdminLoadAll().then(()=>renderAdminDashboard()).catch(e=>{console.warn(e);renderAdminDashboard(e)})}} function qiraAdminTabButton(key,label){return ``} function qiraProductFormHtml(){let p=qiraAdminFindProduct(QIRA.editProductId)||{};let isEdit=!!QIRA.editProductId;return `

${isEdit?'Edit Produk':'Tambah Produk One-Off'}

MVP fokus kepada produk one-off. Subscription fields disediakan tetapi tidak perlu dipromosi sekarang.

${isEdit?``:''}
`} function qiraAdminProductTypeChanged(){let type=$('qiraProductType')?.value;let billing=$('qiraBillingPeriod');let access=$('qiraAccessDurationType');if(type==='subscription'){if(billing&&billing.value==='none')billing.value='monthly';if(access)access.value='subscription_period'}else{if(billing)billing.value='none';if(access&&access.value==='subscription_period')access.value='lifetime'}} function qiraProductsTableHtml(){let rows=QIRA.adminData.products||[];if(!rows.length)return '
Belum ada produk. Tambah produk pertama dahulu.
';return `${rows.map(p=>``).join('')}
ProdukHargaTypeStatusAccess
${qiraEsc(p.name)}
${qiraEsc(p.description||p.slug||'')}
${qiraMoneyFromCents(p.price_cents)}${qiraEsc(p.product_type)}
${qiraEsc(p.billing_period)}
${p.is_published?'published':'draft'}${qiraEsc(p.access_duration_type)}${p.access_duration_days?`
${p.access_duration_days} hari`:''}
`} function qiraGrantPanelHtml(){let parents=QIRA.adminData.parents||[];return `

Manual Unlock / Grant Access

Untuk MVP, admin boleh beri akses manual selepas bayaran sah/semakan manual. Nanti ToyyibPay webhook akan automate bahagian ini.

${parents.map(p=>`
`} function qiraEntitlementsTableHtml(){let rows=QIRA.adminData.entitlements||[];if(!rows.length)return '
Belum ada entitlement/access.
';return `${rows.map(e=>``).join('')}
ParentProdukStatusAccess
${qiraEsc(e.email||'-')}
${qiraEsc(e.user_id||'')}
${qiraEsc(e.product_name||'-')}${qiraEsc(e.status)}
${qiraEsc(e.entitlement_type)}
${e.starts_at?qiraFmtDateTime(e.starts_at):'-'}
${e.ends_at?'Tamat: '+qiraFmtDateTime(e.ends_at):'Lifetime / tiada tarikh tamat'}
${e.status==='active'?``:''}
`} function renderAdminDashboard(err=null){let area=$('adminDashboardArea');if(!area)return;if(!qiraCloudLoggedIn()){area.innerHTML=`
๐Ÿง  Admin Dashboard

Log masuk diperlukan

Sila log masuk akaun ibu bapa admin dahulu.

`;return}if(!QIRA.adminChecked){area.innerHTML=`
๐Ÿง  Admin Dashboard

Menyemak akses admin...

`;return}if(!QIRA.isAdmin){area.innerHTML=`
๐Ÿง  Admin Dashboard

Akses admin diperlukan

Akaun ini belum didaftarkan sebagai admin aktif.

Pastikan email parent dimasukkan ke table admin_users dengan role super_admin dan is_active=true.

`;return}let products=QIRA.adminData.products||[],parents=QIRA.adminData.parents||[],ents=QIRA.adminData.entitlements||[];let activeEnts=ents.filter(e=>e.status==='active').length;let body='';if(QIRA.adminLoading){body='

Memuat data admin...

Sedang ambil produk, parent dan entitlement dari QiRa Cloud.

'}else if(err){body=`

Data admin belum dapat dimuat

${qiraEsc(err.message||err)}

`}else if(QIRA.adminTab==='products'){body=`${qiraProductFormHtml()}

Senarai Produk

${qiraProductsTableHtml()}
`}else if(QIRA.adminTab==='grant'){body=`${qiraGrantPanelHtml()}

Akses Aktif / Entitlements

${qiraEntitlementsTableHtml()}
`}else{body=`

Semua Entitlements

${qiraEntitlementsTableHtml()}
`}area.innerHTML=`
๐Ÿง  Admin Dashboard

QiRa Admin Control Room

Manual product setup + manual entitlement unlock. Subscription-ready, tapi MVP masih one-off first.

${products.length}Products
${parents.length}Parents
${ents.length}Entitlements
${activeEnts}Active Access
${qiraAdminTabButton('products','Produk')}${qiraAdminTabButton('grant','Manual Unlock')}${qiraAdminTabButton('entitlements','Entitlements')}
${body}
`} function qiraToCsv(rows){if(!rows.length)return '';let keys=[...new Set(rows.flatMap(r=>Object.keys(r)))];let esc=v=>`"${String(typeof v==='object'?JSON.stringify(v):v??'').replace(/"/g,'""')}"`;return [keys.join(','),...rows.map(r=>keys.map(k=>esc(r[k])).join(','))].join('\n')} function qiraExportCsv(type){let rows=QIRA.store[type]||[];let csv=qiraToCsv(rows);let blob=new Blob([csv],{type:'text/csv;charset=utf-8'});let a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`qira_${type}_${new Date().toISOString().slice(0,10)}.csv`;a.click();URL.revokeObjectURL(a.href)} function qiraInitV5(){qiraLoadStore();qiraUpdateChip();qiraUpdateAdminUi();qiraInitCloud();qiraTrackEvent('app_loaded',{version:QIRA_V5,cloud:qiraCloudConfigured()});if(location.hash==='#admin'){setTimeout(()=>openAdminDashboard(),80)}} const NAV=[['progress','๐Ÿ“Š','Progress Anak',()=>openProgressHub()],['checkup','๐Ÿ”','QiRa Checkup',()=>openCheckupSetup()],['home','๐Ÿ ','Home',()=>go('home')],['congak','โšก','Congak',()=>openCongakHome()],['game','๐ŸŽฎ','Game',()=>openGameHome()]]; const CATS={sifir:'Congak Sifir',tambah:'Congak Tambah',tolak:'Congak Tolak',darab:'Congak Darab',bahagi:'Congak Bahagi',anu:'Congak Anu'}; const KINDS=[['direct','Soalan Direct'],['mid','Anu di Tengah'],['front','Anu di Depan'],['mix','Campur']]; const ANU_KINDS=[['mid','Anu di Tengah'],['front','Anu di Depan'],['mix','Campur']]; const SIFIR_SCOPES=[['times','Sifir 0โ€“12'],['square','Nombor Square 1ยฒโ€“12ยฒ'],['easy','Sifir Mudah']]; let APP={screen:'home',stack:[],sound:true,dark:false,activeQuiz:null,timer:null,seconds:0,timeLeft:0,lastKey:'',lastResult:null,congakCat:'tambah',congakKind:'direct',sifirScope:'times',gameMode:'match',raja:null,rajaDifficulty:'medium'}; function go(s,opt={}){if(!SCREENS.includes(s))s='home';if(!opt.replace&&APP.screen!==s)APP.stack.push(APP.screen);APP.screen=s;document.querySelectorAll('.screen').forEach(el=>el.classList.remove('active'));$('screen'+cap(s)).classList.add('active');renderNav();window.scrollTo({top:0,behavior:'smooth'});}function cap(s){return s.charAt(0).toUpperCase()+s.slice(1)} function back(){let prev=APP.stack.pop();go(prev||'home',{replace:true})} /* QiRa V5.4.2 Launch CTA links */ const QIRA_LINKS={ whatsapp:'https://wa.me/60177905156?text=Hai%20Sir%20Edy%2C%20saya%20baru%20cuba%20QiRa%20dan%20berminat%20untuk%20tahu%20pakej%20yang%20sesuai%20untuk%20anak%20saya.', package:'https://wa.me/60177905156?text=Hai%20Sir%20Edy%2C%20saya%20baru%20cuba%20QiRa%20dan%20berminat%20untuk%20tahu%20pakej%20yang%20sesuai%20untuk%20anak%20saya.', exam:'https://wa.me/60177905156?text=Hai%20Sir%20Edy%2C%20saya%20baru%20cuba%20QiRa%20dan%20berminat%20untuk%20tahu%20pakej%20yang%20sesuai%20untuk%20anak%20saya.', ebook:'https://wa.me/60177905156?text=Hai%20Sir%20Edy%2C%20saya%20baru%20cuba%20QiRa%20dan%20berminat%20untuk%20tahu%20pakej%20yang%20sesuai%20untuk%20anak%20saya.' }; function qiraOpenLink(key){ window.open(QIRA_LINKS[key]||QIRA_LINKS.whatsapp,'_blank'); } function qiraFreeEbook(){qiraOpenLink('ebook')} function qiraWhatsApp(){qiraOpenLink('whatsapp')} function qiraOpenExam(){qiraOpenLink('exam')} function qiraOpenPackage(){openPackage()} function renderNav(){let active=(APP.screen.includes('checkup')||APP.screen.includes('asas'))?'checkup':APP.screen.includes('congak')||APP.screen==='quiz'&&APP.activeQuiz?.family==='congak'?'congak':APP.screen.includes('game')||APP.screen.includes('battle')||APP.screen.includes('timeBomb')||APP.screen.includes('raja')||APP.screen==='matchGame'?'game':(APP.screen.includes('worksheet')||APP.screen==='account'||APP.screen==='parentDashboard')?'progress':APP.screen==='home'?'home':'';$('bottomNav').innerHTML=NAV.map(n=>``).join('')} function openDrawer(){$('drawer').classList.add('open');updateControls()}function closeDrawer(){$('drawer').classList.remove('open')}function setTheme(d){APP.dark=d;document.body.classList.toggle('dark',d);updateControls()}function setSound(v){APP.sound=v;updateControls()}function updateControls(){$('sunBtn')?.classList.toggle('active',!APP.dark);$('moonBtn')?.classList.toggle('active',APP.dark);$('soundOnBtn')?.classList.toggle('active',APP.sound);$('soundOffBtn')?.classList.toggle('active',!APP.sound)} let QIRA_AUDIO_CTX=null; function beep(t='click'){ if(!APP.sound)return; try{ const AudioCtx=window.AudioContext||window.webkitAudioContext; if(!AudioCtx)return; if(!QIRA_AUDIO_CTX)QIRA_AUDIO_CTX=new AudioCtx(); const ac=QIRA_AUDIO_CTX; if(ac.state==='suspended')ac.resume(); const now=ac.currentTime; const play=(freq,start,dur,gain=0.055,wave='triangle')=>{ const o=ac.createOscillator(); const g=ac.createGain(); o.type=wave; o.frequency.setValueAtTime(freq,now+start); g.gain.setValueAtTime(0.0001,now+start); g.gain.linearRampToValueAtTime(gain,now+start+0.012); g.gain.exponentialRampToValueAtTime(0.0001,now+start+dur); o.connect(g); g.connect(ac.destination); o.start(now+start); o.stop(now+start+dur+0.02); }; if(t==='ok'){ play(620,0,0.12,0.060,'triangle'); play(820,0.085,0.16,0.048,'sine'); }else if(t==='bad'){ play(260,0,0.13,0.050,'triangle'); play(190,0.095,0.16,0.042,'sine'); }else{ play(520,0,0.075,0.042,'triangle'); } }catch(e){} } function packageWhatsappUrl(productName=''){let active=qiraChild();let childText=active?` untuk ${active.nickname}`:'';let productText=productName?` Pakej yang saya pilih: ${productName}.`:'';let msg=`Hai Sir Edy, saya baru cuba QiRa${childText} dan berminat untuk tahu pakej yang sesuai untuk anak saya.${productText}`;return `https://wa.me/${WHATSAPP_PHONE}?text=${encodeURIComponent(msg)}`} function qiraPackagePrice(p){let cents=Number(p.price_cents||0);if(!cents)return 'Hubungi kami';return `RM${(cents/100).toFixed(2)}`} function qiraProductAccessText(p){let t=p.access_duration_type||'lifetime';if(t==='fixed_period')return `${p.access_duration_days||0} hari`;if(t==='subscription_period')return 'Ikut tempoh subscription';return 'Lifetime'} function qiraPackageOwned(productId){return (QIRA.myEntitlements||[]).some(e=>String(e.product_id)===String(productId)&&e.status==='active')} async function qiraLoadPackageData(){QIRA.packageLoading=true;renderPackagePage();let products=[];let ents=[];try{ if(QIRA.cloudClient){ let res=await qiraCloudTimeout(QIRA.cloudClient.rpc('qira_public_list_products'),'Muat pakej QiRa',10000); if(res.error)throw res.error; products=res.data||[]; if(qiraCloudLoggedIn()){ let er=await qiraCloudTimeout(QIRA.cloudClient.rpc('qira_my_active_entitlements'),'Muat akses saya',8000); if(!er.error)ents=er.data||[]; } } }catch(e){console.warn('Package RPC failed, falling back',e);try{ if(QIRA.cloudClient){ let direct=await QIRA.cloudClient.from('products').select('id,name,title,slug,description,price_cents,currency,product_type,billing_period,access_duration_type,access_duration_days,is_published,status').eq('status','published').order('created_at',{ascending:false}); if(!direct.error)products=direct.data||[]; } }catch(e2){console.warn('Direct package fallback failed',e2)}} QIRA.packageProducts=products||[];QIRA.myEntitlements=ents||[];QIRA.packageLoading=false;renderPackagePage();} function qiraPackageCard(p,idx=0){let name=p.name||p.title||'Pakej QiRa';let owned=qiraPackageOwned(p.id);let desc=p.description||'Akses bahan pembelajaran QiRa yang dipilih. Untuk MVP, akses dibuka secara manual selepas bayaran disahkan.';let type=p.product_type||'one_time';let billing=p.billing_period||'none';return `
${idx===0?'โญ Cadangan utama':'๐Ÿ“ฆ Pakej'}

${qiraEsc(name)}

${qiraEsc(desc)}
${qiraPackagePrice(p)} ${billing&&billing!=='none'?`/ ${qiraEsc(billing)}`:''}
${qiraEsc(type)}${qiraEsc(qiraProductAccessText(p))}${billing==='none'?'Bayar sekali':qiraEsc(billing)}
${owned?`
โœ… Akses aktif untuk akaun ini
`:''}
`} function qiraPackageDetails(productId){let p=(QIRA.packageProducts||[]).find(x=>String(x.id)===String(productId));if(!p){qiraNotice('Pakej tidak ditemui.');return}let name=p.name||p.title||'Pakej QiRa';qiraNotice(`${name}\n\nHarga: ${qiraPackagePrice(p)}\nAkses: ${qiraProductAccessText(p)}\n\nUntuk MVP, pembelian dibuat melalui WhatsApp dahulu. Admin akan buka akses manual selepas bayaran disahkan.`,'info','Detail Pakej')} function qiraWhatsAppProduct(productName){try{qiraTrackEvent('clicked_package_whatsapp',{product:productName||''});}catch(e){} window.open(packageWhatsappUrl(productName||''),'_blank')} function qiraRenderMyAccess(){let rows=QIRA.myEntitlements||[];if(!qiraCloudLoggedIn())return `

Akses Saya

Log masuk akaun ibu bapa untuk semak pakej yang sudah dibuka.

`;if(!rows.length)return `

Akses Saya

Belum ada akses aktif. Pilih pakej dan WhatsApp kami untuk pembelian manual.
`;return `

Akses Saya

${rows.map(e=>`
${qiraEsc(e.product_name||'Pakej QiRa')}
${qiraEsc(e.entitlement_type||'one_time')} โ€ข ${e.ends_at?'Tamat: '+qiraFmtDateTime(e.ends_at):'Lifetime / tiada tarikh tamat'}
active
`).join('')}
`} function renderPackagePage(){let area=$('packageArea');if(!area)return;let products=QIRA.packageProducts||[];let active=qiraChild();let header=`
๐Ÿงก Pilih Pakej Anak

Bantu Anak Belajar Ikut Kelemahan Yang Dikesan

QiRa bukan sekadar jual kelas. QiRa bantu ibu bapa kenal pasti kelemahan anak, kemudian pilih bahan atau program yang sesuai. Untuk MVP, pembelian dibuat secara manual melalui WhatsApp dan akses dibuka oleh admin selepas bayaran disahkan.

${active?`

Profil aktif: ${qiraEsc(active.nickname)} โ€ข ${qiraEsc(active.year||'-')} โ€ข ${qiraEsc(String(active.age||'-'))} tahun

`:''}
1. Semak kelemahan
2. Pilih pakej
3. Bayar / WhatsApp
4. Akses dibuka
`;let body=QIRA.packageLoading?`

Memuat pakej...

Sedang ambil senarai produk dari QiRa Cloud.

`:(products.length?`
${products.map((p,i)=>qiraPackageCard(p,i)).join('')}
`:`

Produk belum dibuka dalam sistem.

Admin boleh tambah produk di Admin Dashboard. Sementara itu, ibu bapa masih boleh WhatsApp untuk semakan pakej.

`);area.innerHTML=`${header}${body}${qiraRenderMyAccess()}

Nota MVP

Access tidak dibuka hanya kerana parent klik WhatsApp atau return payment page. Akses hanya aktif selepas admin/manual payment confirmation atau webhook pembayaran berjaya pada fasa seterusnya.

`} function openPackage(){try{qiraTrackEvent('opened_package_page',{source:APP.screen});}catch(e){} go('package'); renderPackagePage(); qiraLoadPackageData();} function freebie(){try{qiraTrackEvent('clicked_freebie',{url:'whatsapp'});}catch(e){} window.open(packageWhatsappUrl(),'_blank')} function r(a,b){return Math.floor(Math.random()*(b-a+1))+a}function shuffle(a){return a.map(v=>[Math.random(),v]).sort((x,y)=>x[0]-y[0]).map(x=>x[1])}function gcd(a,b){while(b)[a,b]=[b,a%b];return Math.abs(a)}function fracStr(n,d){let g=gcd(n,d);return `${n/g}/${d/g}`}function fracHtml(s){let [a,b]=String(s).split('/');return `${a}${b}`}function fmt(x){let s=String(x);return s.replace(/(\d+)\/(\d+)/g,(m,a,b)=>fracHtml(`${a}/${b}`)).replace(/\^2/g,'2')}function formatTime(s){s=Math.max(0,Math.floor(s));return Math.floor(s/60)+':'+String(s%60).padStart(2,'0')} function makeOpts(ans){let opts=[String(ans)],num=Number(ans);if(!Number.isNaN(num)){let tries=0;while(opts.length<4&&tries++<40){let d=r(-10,10)||1,v=num+d;if(v>=0&&!opts.includes(String(v)))opts.push(String(v))}}else if(String(ans).includes('/')){let [n,d]=String(ans).split('/').map(Number);for(let k=1;opts.length<4&&k<8;k++){let v=fracStr(Math.max(1,n+r(-2,2)),d);if(!opts.includes(v))opts.push(v)}}while(opts.length<4){let v=String(r(1,99));if(!opts.includes(v))opts.push(v)}return shuffle(opts)}function q(text,ans,topic,key){return{html:text,text:String(text).replace(/<[^>]+>/g,''),ans:String(ans),opts:makeOpts(ans),topic,key:key||String(text)}}function uniquePush(arr,item){if(item.key===APP.lastKey)return false; if(arr.some(x=>x.key===item.key))return false; arr.push(item);APP.lastKey=item.key;return true} function genAdd(kind='direct'){let type=r(1,3),a,b;if(type===1){a=r(1,9);b=r(1,9)}else if(type===2){a=r(10,99);b=r(1,9)}else{a=r(10,99);b=r(10,99)}let ans=a+b;if(kind==='mix')kind=['direct','mid','front'][r(0,2)];if(kind==='mid')return q(`${a} + ? = ${ans}`,b,'Tambah',`addm${a},${b}`);if(kind==='front')return q(`? + ${b} = ${ans}`,a,'Tambah',`addf${a},${b}`);return q(`${a} + ${b} = ?`,ans,'Tambah',`add${a},${b}`)} function genSub(kind='direct'){let type=r(1,3),a,b;if(type===1){a=r(2,9);b=r(1,a)}else if(type===2){a=r(10,99);b=r(1,9)}else{a=r(20,99);b=r(10,a-1)}let ans=a-b;if(kind==='mix')kind=['direct','mid','front'][r(0,2)];if(kind==='mid')return q(`${a} โˆ’ ? = ${ans}`,b,'Tolak',`subm${a},${b}`);if(kind==='front')return q(`? โˆ’ ${b} = ${ans}`,a,'Tolak',`subf${a},${b}`);return q(`${a} โˆ’ ${b} = ?`,ans,'Tolak',`sub${a},${b}`)} function genMul(kind='direct'){let type=r(1,3),a,b;if(type===1){a=r(1,9);b=r(1,9)}else if(type===2){a=r(10,99);b=r(1,9)}else{a=r(10,30);b=r(10,20)}let ans=a*b;if(kind==='mix')kind=['direct','mid','front'][r(0,2)];if(kind==='mid')return q(`${a} ร— ? = ${ans}`,b,'Darab',`mulm${a},${b}`);if(kind==='front')return q(`? ร— ${b} = ${ans}`,a,'Darab',`mulf${a},${b}`);return q(`${a} ร— ${b} = ?`,ans,'Darab',`mul${a},${b}`)} function genDiv(kind='direct'){let type=r(1,3),b,c;if(type===1){b=r(1,9);c=r(1,9)}else if(type===2){b=r(2,9);c=r(2,11)}else{b=r(10,20);c=r(2,9)}let a=b*c;if(kind==='mix')kind=['direct','mid','front'][r(0,2)];if(kind==='mid')return q(`${a} รท ? = ${c}`,b,'Bahagi',`divm${a},${b}`);if(kind==='front')return q(`? รท ${b} = ${c}`,a,'Bahagi',`divf${a},${b}`);return q(`${a} รท ${b} = ?`,c,'Bahagi',`div${a},${b}`)} function genSifir(kind='direct',scope='times'){if(scope==='square'){let n=r(1,12),form=Math.random()<.5?`${n}2 = ?`:`${n} ร— ${n} = ?`;return q(form,n*n,'Nombor Square',`sq${n}${form[0]}`)}if(scope==='easy'){let ans=[6,8,10,12,18,20,24,30,36,40,42,48,56,60,72][r(0,14)],pairs=[];for(let a=1;a<=12;a++)for(let b=1;b<=12;b++)if(a*b===ans)pairs.push([a,b]);let p=pairs[r(0,pairs.length-1)],p2=pairs[r(0,pairs.length-1)];return q(`${p[0]} ร— ${p[1]} = ${ans}, ${p2[0]} ร— ? = ${ans}`,p2[1],'Sifir Mudah',`easy${ans}-${p}-${p2}`)}if(kind==='mix')kind=['direct','mid','front'][r(0,2)];let a,b;if(kind==='direct'){a=r(0,12);b=r(0,12)}else{a=r(1,12);b=r(1,12)}let ans=a*b;if(kind==='mid')return q(`${a} ร— ? = ${ans}`,b,'Sifir',`sifm${a},${b}`);if(kind==='front')return q(`? ร— ${b} = ${ans}`,a,'Sifir',`siff${a},${b}`);return q(`${a} ร— ${b} = ?`,ans,'Sifir',`sif${a},${b}`)} function genAnu(){let f=[genAdd,genSub,genMul,genDiv,(k)=>genSifir(k,'times')][r(0,4)],kind=Math.random()<.5?'mid':'front';return f(kind)}function getQuestion(cat,kind='direct',scope='times'){if(cat==='tambah')return genAdd(kind);if(cat==='tolak')return genSub(kind);if(cat==='darab')return genMul(kind);if(cat==='bahagi')return genDiv(kind);if(cat==='anu')return genAnu();return genSifir(kind,scope)} function genTopic(year,topic){if(topic==='Operasi Asas'){return [genAdd,genSub,genMul,genDiv][r(0,year<3?1:3)]('direct')}if(topic==='Sifir')return genSifir('direct','times');if(topic==='Pecahan'){let d=[4,5,6,7,8][r(0,4)],a=r(1,d-2),b=r(1,d-a-1);return q(`${fracHtml(`${a}/${d}`)} + ${fracHtml(`${b}/${d}`)} = ?`,fracStr(a+b,d),'Pecahan',`frac${a}${b}${d}`)}if(topic==='Perpuluhan'){let a=(r(10,95)/10).toFixed(1),b=(r(5,50)/10).toFixed(1),ans=(+a + +b).toFixed(1);return q(`${a} + ${b} = ?`,ans,'Perpuluhan',`dec${a}${b}`)}if(topic==='Peratus'){let p=[10,20,25,50][r(0,3)],n=[40,60,80,100,120,200][r(0,5)];return q(`${p}% daripada ${n} = ?`,n*p/100,'Peratus',`pct${p}${n}`)}if(topic==='Wang / Masa'){let a=r(2,80),b=r(1,40);return q(`RM${a} + RM${b} = RM?`,a+b,'Wang / Masa',`money${a}${b}`)}if(topic==='Ukuran'){let a=r(10,90),b=r(5,50);return q(`${a} cm + ${b} cm = ? cm`,a+b,'Ukuran',`ukur${a}${b}`)}if(topic==='Ruang / Geometri'){let a=r(2,12),b=r(2,12);return q(`Luas segi empat ${a} cm ร— ${b} cm = ? cmยฒ`,a*b,'Ruang / Geometri',`geo${a}${b}`)}let a=r(2,20),b=r(1,10);return q(`Ali ada ${a} biji guli. Ibu beri ${b} lagi. Berapakah jumlah guli Ali?`,a+b,'Soalan Berayat',`word${a}${b}`)} function checkupTopics(year){if(year<=1)return ['Operasi Asas','Wang / Masa','Ruang / Geometri','Soalan Berayat'];if(year===2)return ['Operasi Asas','Sifir','Wang / Masa','Ruang / Geometri','Soalan Berayat'];if(year===3)return ['Operasi Asas','Sifir','Pecahan','Wang / Masa','Ukuran','Soalan Berayat'];return ['Operasi Asas','Sifir','Pecahan','Perpuluhan','Peratus','Wang / Masa','Ukuran','Ruang / Geometri','Soalan Berayat']} function openCheckupSetup(){go('checkupSetup')} function openExamCheckup(){qiraWhatsApp()} function openAsasHome(){if(!qiraRequireChild('openAsasHome'))return;setupAsasTiles();go('asasHome')} const ASAS_CHECKUPS={ sifir:{title:'QiRa Sifir',icon:'โœ–๏ธ',cls:'yellow',cat:'sifir',kind:'mix',scope:'times',note:'Sifir 0 hingga 12 termasuk soalan direct dan Anu.'}, tambah:{title:'QiRa Tambah',icon:'โž•',cls:'green-soft',cat:'tambah',kind:'mix',scope:'times',note:'Tambah 1 digit dan 2 digit, termasuk carry-over.'}, tolak:{title:'QiRa Tolak',icon:'โž–',cls:'orange-soft',cat:'tolak',kind:'mix',scope:'times',note:'Tolak 1 digit dan 2 digit, termasuk pinjam/regrouping.'}, darab:{title:'QiRa Darab',icon:'โœ–๏ธ',cls:'purple',cat:'darab',kind:'mix',scope:'times',note:'Darab 1 digit dan 2 digit.'}, bahagi:{title:'QiRa Bahagi',icon:'โž—',cls:'cyan',cat:'bahagi',kind:'mix',scope:'times',note:'Bahagi tepat tanpa baki.'}, semua:{title:'QiRa Semua',icon:'๐Ÿ”€',cls:'pink',cat:'semua',kind:'mix',scope:'times',note:'Campuran Sifir, Tambah, Tolak, Darab dan Bahagi.'} }; function setupAsasTiles(){ $('asasTiles').innerHTML=Object.entries(ASAS_CHECKUPS).map(([k,v])=>``).join(''); } function startAsasCheckup(mode){ let cfg=ASAS_CHECKUPS[mode],qs=[],tries=0;APP.lastKey=''; while(qs.length<20&&tries++<600){ let q; if(mode==='semua'){ let pool=['sifir','tambah','tolak','darab','bahagi']; let cat=pool[qs.length%pool.length]; q=getQuestion(cat,cat==='sifir'?'mix':'mix','times'); }else{ q=getQuestion(cfg.cat,cfg.kind,cfg.scope); } uniquePush(qs,q); } runQuiz(cfg.title,shuffle(qs),{family:'checkup',year:null,checkupMode:mode}); } function runQuiz(title,questions,opt={}){clearInterval(APP.timer);APP.activeQuiz={title,questions,i:0,score:0,wrong:[],answers:[],qStart:Date.now(),family:opt.family||'congak',year:opt.year||null,checkupMode:opt.checkupMode||null,mode:'fixed'};APP.seconds=0;go('quiz');renderFixedQuiz();APP.timer=setInterval(()=>{APP.seconds++;let t=$('elapsedTime');if(t)t.textContent=formatTime(APP.seconds)},1000)} function renderFixedQuiz(feedback=''){let qz=APP.activeQuiz;qz.qStart=Date.now();let q=qz.questions[qz.i],pct=Math.round((qz.i/qz.questions.length)*100);let word=q.text.length>65?'word':'';let topicBadge=qz.family==='checkup'?'':`
${q.topic}
`;$('quizArea').innerHTML=`
${qz.i+1}/${qz.questions.length}Progress Anak
${qz.score}Betul
${formatTime(APP.seconds)}Masa
${topicBadge}
${fmt(q.html)}
${q.opts.map(o=>``).join('')}
${feedback}
`} function answerFixed(val){try{document.activeElement&&document.activeElement.blur&&document.activeElement.blur()}catch(e){}let qz=APP.activeQuiz,q=qz.questions[qz.i],spent=Math.max(.1,(Date.now()-(qz.qStart||Date.now()))/1000),ok=String(val)===String(q.ans);qz.answers=qz.answers||[];qz.answers.push({no:qz.i+1,question:q.html,text:q.text,topic:q.topic,ans:String(q.ans),user:String(val),ok,seconds:spent,skill:getQuestionSkill(q)});if(ok){qz.score++;beep('ok')}else{qz.wrong.push(q);beep('bad')}let buttons=[...document.querySelectorAll('.answer')];buttons.forEach(b=>{b.disabled=true;if(b.textContent.trim()===String(q.ans))b.classList.add('correct');else if(b.textContent.trim()===String(val))b.classList.add('wrong')});let finalStep=qz.i+1>=qz.questions.length;$('fb').innerHTML=`
${ok?'Betul!':'Cuba lagi.'} Jawapan: ${fmt(q.ans)}
${(!ok||finalStep)?``:''}`;if(ok&&!finalStep){setTimeout(()=>{if(APP.activeQuiz===qz)nextFixed()},950)}else if(ok&&finalStep){setTimeout(()=>{if(APP.activeQuiz===qz)nextFixed()},950)}} function nextFixed(){let qz=APP.activeQuiz;qz.i++;if(qz.i>=qz.questions.length){clearInterval(APP.timer);showResult(qz)}else renderFixedQuiz()} function digitsOf(n){n=Math.abs(Number(n));return n<10?1:n<100?2:3} function hasMulCarry(a,b){ a=Number(a);b=Number(b); if(!a||!b)return false; if(digitsOf(a)===1&&digitsOf(b)===1)return false; let da=String(Math.abs(a)).split('').map(Number),db=String(Math.abs(b)).split('').map(Number); return da.some(x=>db.some(y=>x*y>=10)); } function getQuestionSkill(q){ let s=(q.text||'').replace(/\s+/g,' ').trim(),topic=q.topic||''; let anu=s.includes('?')&&!s.endsWith('= ?'); if(topic.includes('Sifir')){ let nums=[...s.matchAll(/\d+/g)].map(x=>Number(x[0])); let base=nums.find(n=>n>=1&&n<=12)||0; let type=anu?(s.startsWith('?')?'Anu di depan':'Anu di tengah'):'Direct'; return base?`Sifir ${base} โ€ข ${type}`:`Sifir โ€ข ${type}`; } if(topic==='Tambah'){ let m=s.match(/(\d+|\?)\s*\+\s*(\d+|\?)/); let left=m?m[1]:'',right=m?m[2]:''; let a=left&&left!=='?'?Number(left):0,b=right&&right!=='?'?Number(right):0; if(left==='?')return `Anu + ${digitsOf(b)} digit`; if(right==='?')return `${digitsOf(a)} digit + Anu`; let carry=((a%10)+(b%10)>=10); return `${digitsOf(a)} digit + ${digitsOf(b)} digit โ€ข ${carry?'Dengan mengumpul semula (carry over)':'Tanpa mengumpul semula'}`; } if(topic==='Tolak'){ let m=s.match(/(\d+|\?)\s*โˆ’\s*(\d+|\?)/); let left=m?m[1]:'',right=m?m[2]:''; let a=left&&left!=='?'?Number(left):0,b=right&&right!=='?'?Number(right):0; if(left==='?')return `Anu โˆ’ ${digitsOf(b)} digit`; if(right==='?')return `${digitsOf(a)} digit โˆ’ Anu`; let borrow=(a%10{ let st,cls; if(a.ok&&a.seconds<=target){st='Betul
Laju';cls='status-ok'} else if(a.ok){st='Betul
Perlahan';cls='status-slow';slow[a.skill]=(slow[a.skill]||0)+1} else if(a.seconds<=target*1.5){st='Salah
Cepat';cls='status-guess';weak[a.skill]=(weak[a.skill]||0)+1} else{st='Salah
Perlahan';cls='status-bad';weak[a.skill]=(weak[a.skill]||0)+1} return `
${a.no}
${fmt(a.question)}
${a.skill}
Jawapan anak
${fmt(a.user)}
Betul
${fmt(a.ans)}
${st}
${a.seconds.toFixed(1)}s
`; }).join(''); let weakTop=Object.entries(weak).sort((a,b)=>b[1]-a[1]).slice(0,4); let slowTop=Object.entries(slow).sort((a,b)=>b[1]-a[1]).slice(0,3); let insight=''; if(weakTop.length)insight+=`

Fokus utama: banyak kesilapan pada ${weakTop.map(x=>x[0]).join(', ')}.

`; if(slowTop.length)insight+=`

Perlu lancar: anak betul tetapi perlahan pada ${slowTop.map(x=>x[0]).join(', ')}.

`; if(!insight)insight='

Pemerhatian: jawapan anak agak konsisten. Teruskan latihan pendek untuk kekalkan momentum.

'; let pills=[...weakTop.map(x=>`Salah: ${x[0]} (${x[1]})`),...slowTop.map(x=>`Lambat: ${x[0]} (${x[1]})`)].join(''); return `

Semakan Jawapan

Senarai ini bantu ibu bapa nampak soalan mana yang betul, salah, cepat atau perlahan.

${insight}${pills?`
${pills}
`:''}
No
Soalan
Jawapan Anak
Jawapan Betul
Status
${rows}
`; } function getSpeedTarget(qz){ let t=(qz.title||'').toLowerCase(); if(t.includes('sifir'))return 5; if(t.includes('tambah'))return 7; if(t.includes('tolak'))return 7; if(t.includes('darab'))return 10; if(t.includes('bahagi'))return 10; if(t.includes('semua'))return 8; return 8; } function getAsasGrade(qz,total,score,acc){ let seconds=qz.mode==='time'?(qz.duration||0):APP.seconds; let avg=total?seconds/total:0,target=getSpeedTarget(qz); let speedLabel='Laju',speedPenalty=0; if(avg>target*2){speedLabel='Sangat perlahan';speedPenalty=2} else if(avg>target*1.5){speedLabel='Perlahan';speedPenalty=1} else if(avg>target){speedLabel='Sederhana';speedPenalty=0} let base=acc>=80?'A':acc>=65?'B':acc>=50?'C':'D'; let order=['A','B','C','D'],idx=order.indexOf(base); if(base!=='D')idx=Math.min(3,idx+speedPenalty); let grade=order[idx]; let data={ A:{status:'Kukuh',tone:'grade-a',msg:'Asas anak kukuh dan pantas. Teruskan latihan ringkas untuk kekalkan momentum.'}, B:{status:'Baik',tone:'grade-b',msg:'Asas anak baik, cuma masih ada ruang untuk lebih laju dan konsisten.'}, C:{status:'Perlu Latihan',tone:'grade-c',msg:'Anak boleh bina semula asas ini dengan latihan pendek dan konsisten setiap hari.'}, D:{status:'Perlu Bimbingan',tone:'grade-d',msg:'Ini red alert. Anak perlukan bimbingan asas dahulu sebelum masuk latihan yang lebih mencabar.'} }[grade]; if(acc<50&&avg<=target*1.5)data.msg='Anak menjawab agak cepat, tetapi banyak jawapan salah. Ini mungkin tanda anak meneka atau asas topik ini belum kukuh.'; else if(acc>=80&&avg>target*1.5)data.msg='Anak banyak menjawab dengan betul, tetapi masa masih perlahan. Fokus seterusnya ialah kelajuan dan kefasihan.'; return {grade,avg,target,speedLabel,...data}; } function showResult(qz,extra=''){ clearInterval(APP.timer); let total=qz.mode==='time'?(qz.answered||qz.questions.length):qz.questions.length; let score=qz.score,wrong=Math.max(0,total-score),acc=total?Math.round((score/total)*100):0; let gradeInfo=getAsasGrade(qz,total,score,acc); APP.lastResult={year:qz.year,score,total,grade:gradeInfo.grade}; try{qiraTrackQuizResult(qz,total,score,wrong,acc,gradeInfo)}catch(e){console.warn('QiRa V5 tracking failed',e)} let resultStats=qz.mode==='time'?`
${qz.answered}Dijawab
${score}Betul
${wrong}Salah
`:`
${score}Betul
${wrong}Salah
${formatTime(APP.seconds)}Masa
`; let isCheckup=qz.family==='checkup'; let ulang=isCheckup&&qz.checkupMode?`startAsasCheckup('${qz.checkupMode}')`:(isCheckup?'openCheckupSetup()':(qz.replayAction||(qz.family==='game'?'openGameHome()':'openCongakHome()'))); let actions=isCheckup? ``: ``; $('resultArea').innerHTML=`
GRADE ${gradeInfo.grade}
${score}/${total}
${gradeInfo.status}

Ketepatan ${acc}%

Kelajuan: ${gradeInfo.speedLabel} โ€ข Purata ${gradeInfo.avg.toFixed(1)}s / soalan

${gradeInfo.msg}

${resultStats}${extra}${buildDetailReport(qz,gradeInfo)}

Langkah seterusnya

WhatsApp Sir Edy untuk dapatkan cadangan pakej yang sesuai.

${actions}
`; go('result'); } function setupCongakTiles(){let icons={sifir:'โœ–๏ธ',tambah:'โž•',tolak:'โž–',darab:'โœ–๏ธ',bahagi:'โž—',anu:'โ“'},cls={sifir:'yellow',tambah:'green-soft',tolak:'orange-soft',darab:'purple',bahagi:'cyan',anu:'pink'};$('congakTiles').innerHTML=Object.entries(CATS).map(([k,v])=>``).join('')} function segBuild(id,items,selected,cb){$(id).innerHTML=items.map(x=>``).join('')} function openCongak(cat){APP.congakCat=cat;APP.congakKind=cat==='anu'?'mix':'direct';APP.sifirScope='times';$('congakTitle').textContent=CATS[cat];$('sifirScopeRow').classList.toggle('hidden',cat!=='sifir');segBuild('qKindSeg',cat==='anu'?ANU_KINDS:KINDS,APP.congakKind,'setKind');segBuild('sifirScopeSeg',SIFIR_SCOPES,APP.sifirScope,'setSifirScope');go('congakConfig')} function setKind(k){APP.congakKind=k;segBuild('qKindSeg',APP.congakCat==='anu'?ANU_KINDS:KINDS,k,'setKind')}function setSifirScope(k){APP.sifirScope=k;segBuild('sifirScopeSeg',SIFIR_SCOPES,k,'setSifirScope')} function startCongak(){let n=+$('congakCount').value,qs=[],tries=0;APP.lastKey='';while(qs.lengthincludeAnu||k!=='anu').map(([k,v])=>``).join('')}function fillKindSelect(id,anuOnly=false){$(id).innerHTML=(anuOnly?ANU_KINDS:KINDS).map(k=>``).join('')}function fillScopeSelect(id){$(id).innerHTML=SIFIR_SCOPES.map(k=>``).join('')} function openGameHome(){if(!qiraRequireChild('openGameHome'))return;go('gameHome')}function openCongakHome(){if(!qiraRequireChild('openCongakHome'))return;go('congakHome')}function openWorksheet(){if(!qiraRequireChild('openWorksheet'))return;go('worksheet')} function openTimeConfig(){fillCatSelect('timeCat');fillKindSelect('timeKind');fillScopeSelect('timeScope');$('timeCat').onchange=()=>$('timeScopeWrap').classList.toggle('hidden',$('timeCat').value!=='sifir');$('timeScopeWrap').classList.toggle('hidden',$('timeCat').value!=='sifir');go('timeConfig')} function startTimeChallenge(){let cat=$('timeCat').value,kind=$('timeKind').value,scope=$('timeScope').value,dur=+$('timeDuration').value;APP.activeQuiz={title:'Time Challenge',score:0,wrong:[],answered:0,mode:'time',family:'game',cat,kind,scope};APP.timeLeft=dur;clearInterval(APP.timer);go('quiz');renderTimeQuiz();APP.timer=setInterval(()=>{APP.timeLeft--;updateTime();if(APP.timeLeft<=0){clearInterval(APP.timer);showResult(APP.activeQuiz)}},1000)} function updateTime(){let t=$('remainTime');if(t)t.textContent=formatTime(APP.timeLeft)}function nextTimeQ(){return getQuestion(APP.activeQuiz.cat,APP.activeQuiz.kind,APP.activeQuiz.scope)}function renderTimeQuiz(){let q=nextTimeQ();APP.activeQuiz.current=q;APP.activeQuiz.locked=false;APP.activeQuiz.qStart=Date.now();$('quizArea').innerHTML=`
${APP.activeQuiz.score}Betul
${APP.activeQuiz.wrong.length}Salah
${formatTime(APP.timeLeft)}Baki Masa
${q.topic}
${fmt(q.html)}
${q.opts.map(o=>``).join('')}
`} function answerTime(v){try{document.activeElement&&document.activeElement.blur&&document.activeElement.blur()}catch(e){}let qz=APP.activeQuiz,q=qz.current;if(qz.locked)return;qz.locked=true;qz.answered++;let ok=String(v)===String(q.ans),spent=Math.max(.1,(Date.now()-(qz.qStart||Date.now()))/1000);qz.answers=qz.answers||[];qz.answers.push({no:qz.answered,question:q.html,text:q.text,topic:q.topic,ans:String(q.ans),user:String(v),ok,seconds:spent,skill:getQuestionSkill(q)});if(ok){qz.score++;beep('ok')}else{qz.wrong.push(q);beep('bad')}let buttons=[...document.querySelectorAll('.answer')];buttons.forEach(b=>{b.disabled=true;let txt=b.textContent.trim();if(txt===String(q.ans))b.classList.add('correct');else if(txt===String(v))b.classList.add('wrong')});setTimeout(()=>{if(APP.activeQuiz===qz&&APP.timeLeft>0)renderTimeQuiz()},360)} function openGameConfig(mode){APP.gameMode=mode;fillCatSelect('gameCat');fillKindSelect('gameKind');fillScopeSelect('gameSifirScope');$('gameConfigTitle').textContent='Match Answers';$('gameCat').onchange=()=>$('gameSifirScopeRow').classList.toggle('hidden',$('gameCat').value!=='sifir');$('gameSifirScopeRow').classList.toggle('hidden',$('gameCat').value!=='sifir');go('gameConfig')} function startGameFromConfig(){startMatch($('gameCat').value,$('gameKind').value,$('gameSifirScope').value)} function startMatch(cat,kind,scope){let qs=[],tries=0;APP.lastKey='';while(qs.length<6&&tries++<300)uniquePush(qs,getQuestion(cat,kind,scope));APP.match={cat,kind,scope,qs,answers:shuffle(qs.map(q=>q.ans)),selQ:null,selA:null,done:0,start:Date.now(),pairTries:0,mistakes:0,matched:[],pairStart:null};go('matchGame');renderMatch()} function renderMatch(){let m=APP.match;$('matchArea').innerHTML=`

Match Answers

Padankan soalan dengan jawapan.

${m.qs.map((q,i)=>``).join('')}
${m.answers.map((a,i)=>``).join('')}
`} function pickMatchQ(i){let m=APP.match;if(m.qs[i].done)return;if(m.selQ==null&&m.selA==null)m.pairStart=Date.now();m.selQ=i;document.querySelectorAll('[id^=mq]').forEach(e=>e.classList.remove('sel'));$('mq'+i).classList.add('sel');tryMatch()}function pickMatchA(i){let m=APP.match;m.used=m.used||[];if(m.used.includes(i))return;if(m.selQ==null&&m.selA==null)m.pairStart=Date.now();m.selA=i;document.querySelectorAll('[id^=ma]').forEach(e=>e.classList.remove('sel'));$('ma'+i).classList.add('sel');tryMatch()}function tryMatch(){let m=APP.match;if(m.selQ==null||m.selA==null)return;let q=m.qs[m.selQ],a=$('ma'+m.selA).dataset.a;m.used=m.used||[];m.pairTries=(m.pairTries||0)+1;let spent=Math.max(0.1,(Date.now()-(m.pairStart||m.start||Date.now()))/1000);let qEl=$('mq'+m.selQ),aEl=$('ma'+m.selA);if(String(q.ans)===String(a)){qEl.classList.add('correct-match');aEl.classList.add('correct-match');q.done=true;m.used.push(m.selA);m.done++;m.matched=m.matched||[];m.matched.push({question:q.html,text:q.text,topic:q.topic,ans:String(q.ans),user:String(a),ok:true,seconds:+spent.toFixed(1),skill:getQuestionSkill(q)});m.pairStart=null;beep('ok');setTimeout(()=>{if(m.done>=m.qs.length){APP.seconds=Math.round((Date.now()-m.start)/1000);let trackedTotal=(m.qs.length+(m.mistakes||0));let trackedAcc=trackedTotal?Math.round((m.qs.length/trackedTotal)*100):100;showResult({title:'Match Answers',questions:m.qs,score:m.qs.length,wrong:[],family:'game',cat:m.cat,kind:m.kind,scope:m.scope,answers:m.matched,trackingTotal:trackedTotal,trackingScore:m.qs.length,trackingWrong:m.mistakes||0,trackingAccuracy:trackedAcc,trackingNote:`${m.mistakes||0} salah padanan daripada ${m.pairTries||m.qs.length} cubaan.`,replayAction:`startMatch('${m.cat}','${m.kind}','${m.scope}')`},`
๐Ÿงฉ
Semua padanan selesai!

${formatTime(APP.seconds)} โ€ข ${m.mistakes||0} salah padanan

`);return}m.selQ=null;m.selA=null;renderMatch()},220)}else{m.mistakes=(m.mistakes||0)+1;m.pairStart=null;beep('bad');qEl.classList.add('wrong-match');aEl.classList.add('wrong-match');setTimeout(()=>{m.selQ=null;m.selA=null;renderMatch()},420)}} function openBattle(mode){ APP.battleMode=mode; fillCatSelect('battleCat');fillKindSelect('battleKind');fillScopeSelect('battleSifirScope'); $('battleTitle').textContent=mode==='bot'?'Lawan QiRa':'Lawan Kawan'; $('battleFriendCountRow')?.classList.toggle('hidden',mode!=='friend'); $('battleP2Row')?.classList.toggle('hidden',mode==='bot'); $('battleCat').onchange=()=>$('battleSifirScopeRow').classList.toggle('hidden',$('battleCat').value!=='sifir'); $('battleSifirScopeRow').classList.toggle('hidden',$('battleCat').value!=='sifir'); go('battleConfig') } function startBattle(){ let cat=$('battleCat').value,kind=$('battleKind').value,scope=$('battleSifirScope').value; if(APP.battleMode==='bot')startBot(cat,kind,scope); else startFriend(cat,kind,scope) } function startBot(cat,kind,scope,p1Name){ let p1=(p1Name!==undefined?p1Name:$('p1Name').value.trim())||'Player 1'; APP.bot={cat,kind,scope,player:100,bot:100,i:0,score:0,botScore:0,wrong:[],answers:[],locked:false,names:[p1,'QiRa'],start:Date.now(),tracked:false}; go('quiz');renderBot() } function restartBotBattle(){let b=APP.bot;if(!b)return;startBot(b.cat,b.kind,b.scope,b.names[0])} function renderBot(){ let b=APP.bot,q=getQuestion(b.cat,b.kind,b.scope);b.current=q;b.locked=false;b.qStart=Date.now(); $('quizArea').innerHTML=`
${b.names[0]}
${b.names[1]}
${q.topic}
${fmt(q.html)}
${q.opts.map(o=>``).join('')}
` } function answerBot(v){ try{document.activeElement&&document.activeElement.blur&&document.activeElement.blur()}catch(e){} let b=APP.bot,q=b.current;if(b.locked)return;b.locked=true;b.i++; let ok=String(v)===String(q.ans); let spent=Math.max(0.1,(Date.now()-(b.qStart||Date.now()))/1000); b.answers=b.answers||[];b.answers.push({question:q.html,text:q.text,topic:q.topic,ans:String(q.ans),user:String(v),ok,seconds:+spent.toFixed(1),skill:getQuestionSkill(q)}); if(ok){b.bot=Math.max(0,b.bot-20);b.score++;beep('ok')}else{b.player=Math.max(0,b.player-20);b.botScore=(b.botScore||0)+1;b.wrong.push(q);beep('bad')} let buttons=[...document.querySelectorAll('.answer')]; buttons.forEach(btn=>{btn.disabled=true;let txt=btn.textContent.trim();if(txt===String(q.ans))btn.classList.add('correct');else if(txt===String(v))btn.classList.add('wrong')}); setTimeout(()=>{if(b.player<=0||b.bot<=0){ showBotBattleResult() }else renderBot()},420) } function startFriend(cat,kind,scope){ let n=+$('friendCount').value; APP.friend={cat,kind,scope,n,p:1,names:[$('p1Name').value||'Player 1',$('p2Name').value||'Player 2'],scores:[0,0],wrong:[],answers:[],i:0,locked:false,start:Date.now(),tracked:false}; go('quiz'); showFriendReady(); } function showFriendReady(){ let f=APP.friend;if(!f)return; let idx=f.p-1,name=f.names[idx],turnCls=playerTurnClass(idx),nameCls=playerColorClass(idx); $('quizArea').innerHTML=`
โš”๏ธ

${name}

ARE YOU READY???
`; } function startFriendTurn(){renderFriend()} function renderFriend(){ let f=APP.friend,q=getQuestion(f.cat,f.kind,f.scope),idx=f.p-1; f.current=q;f.locked=false;f.qStart=Date.now(); $('quizArea').innerHTML=`
${f.names[idx]}
${q.topic}
${fmt(q.html)}
${q.opts.map(o=>``).join('')}
`; } function answerFriend(v){ try{document.activeElement&&document.activeElement.blur&&document.activeElement.blur()}catch(e){} let f=APP.friend,q=f.current;if(f.locked)return;f.locked=true; let ok=String(v)===String(q.ans); let spent=Math.max(0.1,(Date.now()-(f.qStart||Date.now()))/1000); f.answers=f.answers||[];f.answers.push({idx:f.p-1,name:f.names[f.p-1],question:q.html,text:q.text,topic:q.topic,ans:String(q.ans),user:String(v),ok,seconds:+spent.toFixed(1),skill:getQuestionSkill(q)}); if(ok){f.scores[f.p-1]++;beep('ok')}else{f.wrong.push(q);beep('bad')} let buttons=[...document.querySelectorAll('.answer')]; buttons.forEach(btn=>{btn.disabled=true;let txt=btn.textContent.trim();if(txt===String(q.ans))btn.classList.add('correct');else if(txt===String(v))btn.classList.add('wrong')}); setTimeout(()=>{ f.i++; if(f.i>=f.n){ if(f.p===1){ f.p=2;f.i=0;showFriendReady() }else{ showFriendBattleResult() } }else renderFriend() },420) } function showFriendBattleResult(){ let f=APP.friend;if(!f)return; let draw=f.scores[0]===f.scores[1]; let winnerIdx=draw?-1:(f.scores[0]>f.scores[1]?0:1); let winnerName=draw?'Kedua-dua pemain':f.names[winnerIdx]; let title=draw?'Kedua-dua Hebat!':`${winnerName} Menang!`; let sub=draw?'Keputusan seri! Kedua-dua pemain menunjukkan usaha yang sangat baik.':`Pertarungan selesai. ${winnerName} mencatat keputusan terbaik untuk pusingan ini.`; let msg1=draw?`${f.names[0]} bermain dengan baik. Teruskan berlatih!`:(winnerIdx===0?`${f.names[0]} sangat fokus dan konsisten. Syabas!`:`${f.names[0]} bermain dengan berani. Cuba lagi, anda semakin bagus!`); let msg2=draw?`${f.names[1]} bermain dengan baik. Teruskan berlatih!`:(winnerIdx===1?`${f.names[1]} sangat fokus dan konsisten. Syabas!`:`${f.names[1]} bermain dengan berani. Cuba lagi, anda semakin bagus!`); if(!f.tracked){f.tracked=true;let p1Attempts=(f.answers||[]).filter(a=>a.idx===0);try{qiraTrackGameResult({title:'Battle: Lawan Kawan',mode:`${f.cat} โ€ข ${f.kind}`,topic:f.cat,total:f.n,score:f.scores[0],wrong:Math.max(0,f.n-f.scores[0]),accuracy:f.n?Math.round((f.scores[0]/f.n)*100):0,durationSeconds:Math.round((Date.now()-(f.start||Date.now()))/1000),result:draw?'Seri':(winnerIdx===0?'Menang':'Kalah'),winner:draw?'Seri':f.names[winnerIdx],loser:draw?'':f.names[1-winnerIdx],outcomeType:draw?'draw':'win_loss',playerName:f.names[0],playerScore:f.scores[0],playerTotal:f.n,opponentName:f.names[1],opponentScore:f.scores[1],opponentTotal:f.n,opponentAccuracy:f.n?Math.round((f.scores[1]/f.n)*100):0,note:`${f.names[0]} ${f.scores[0]}/${f.n} โ€ข ${f.names[1]} ${f.scores[1]}/${f.n}`},p1Attempts)}catch(e){console.warn('Friend battle tracking failed',e)}} let actions=``; $('resultArea').innerHTML=buildPremiumDuelResult({emoji:'๐Ÿ†',kicker:'Battle Result',title,sub,p1Name:f.names[0],p2Name:f.names[1],p1Stat:`${f.scores[0]}/${f.n} betul`,p2Stat:`${f.scores[1]}/${f.n} betul`,p1Msg:msg1,p2Msg:msg2,actions}); go('result'); } function openRajaConfig(){ $('rajaMode').value='bot'; setRajaDifficulty('medium'); updateRajaConfig(); go('rajaConfig'); } function setRajaDifficulty(level){ APP.rajaDifficulty=level; ['Easy','Medium','Hard'].forEach(x=>$('rajaDiff'+x)?.classList.toggle('active',x.toLowerCase()===level)); } function getRajaTimeLimit(level){ return level==='easy'?10:(level==='hard'?3:5); } function updateRajaConfig(){ let isBot=$('rajaMode')?.value==='bot'; $('rajaP2Row')?.classList.toggle('hidden',isBot); } function startRajaCongak(){ let mode=$('rajaMode').value,difficulty=APP.rajaDifficulty||'medium',limit=getRajaTimeLimit(difficulty); let p1=$('rajaP1').value.trim()||'Player 1'; let p2=mode==='bot'?'QiRa':($('rajaP2').value.trim()||'Player 2'); clearRajaTimer(); APP.raja={mode,difficulty,limit,currentDisplay:'0',lastNumber:0,turn:0,names:[p1,p2],history:[],done:false,winner:null,loser:null,reason:'',timer:null,deadline:0,timeLeft:limit,needsReady:mode==='friend',start:Date.now(),tracked:false}; go('rajaGame'); renderRajaCongak(); } function rajaDifficultyLabel(r){return (r.difficulty||'medium').toUpperCase()+` โ€ข ${r.limit}s`} function rajaTurnClass(r,idx){return r.mode==='bot'&&idx===1?'qira-turn':playerTurnClass(idx)} function rajaNameClass(r,idx){return r.mode==='bot'&&idx===1?'player-pink':playerColorClass(idx)} function rajaIsCongakNumber(n){return n>0&&n%3===0} function rajaCorrectAnswer(r){ let next=r.lastNumber+1; if(String(r.currentDisplay).toUpperCase()==='CONGAK')return String(next); return rajaIsCongakNumber(next)?'CONGAK':String(next); } function rajaOptions(r){ let next=r.lastNumber+1; if(String(r.currentDisplay).toUpperCase()==='CONGAK')return [String(r.lastNumber),String(next),'CONGAK']; return [String(next),'CONGAK']; } function rajaScore(r){return Math.max(0,r.lastNumber)} function clearRajaTimer(){ if(APP.raja?.timer){clearInterval(APP.raja.timer);APP.raja.timer=null} } function showRajaReady(){ let r=APP.raja;if(!r)return; clearRajaTimer(); let idx=r.turn,name=r.names[idx]; $('rajaArea').innerHTML=`

${name}

ARE YOU READY???
`; } function startRajaTurn(){ let r=APP.raja;if(!r)return; r.needsReady=false; renderRajaCongak(); } function startRajaTimer(){ let r=APP.raja;if(!r||r.done)return; clearRajaTimer(); if(r.mode==='bot'&&r.turn===1)return; r.deadline=Date.now()+r.limit*1000; r.timeLeft=r.limit; updateRajaTimerUI(); r.timer=setInterval(()=>{ let rr=APP.raja;if(!rr||rr.done){clearRajaTimer();return} rr.timeLeft=Math.max(0,(rr.deadline-Date.now())/1000); updateRajaTimerUI(); if(rr.timeLeft<=0)rajaTimeout(); },100); } function updateRajaTimerUI(){ let r=APP.raja;if(!r)return; let fill=$('rajaTimerFill'),txt=$('rajaTimerText'); if(fill)fill.style.width=`${Math.max(0,Math.min(100,(r.timeLeft/r.limit)*100))}%`; if(txt)txt.textContent=`${r.timeLeft.toFixed(1)}s`; } function rajaTimeout(){ let r=APP.raja;if(!r||r.done)return; clearRajaTimer(); let idx=r.turn; r.loser=r.names[idx]; r.winner=r.names[1-idx]; r.reason=`${r.names[idx]} tidak jawab dalam ${r.limit} saat.`; r.done=true; beep('bad'); renderRajaCongak(); } function renderRajaCongak(){ let r=APP.raja;if(!r)return; clearRajaTimer(); if(r.done){renderRajaResult();return} if(r.mode==='friend'&&r.needsReady){showRajaReady();return} let idx=r.turn,player=r.names[idx],display=String(r.currentDisplay),opts=rajaOptions(r),isCongakDisplay=display.toUpperCase()==='CONGAK';r.answerStart=Date.now(); $('rajaArea').innerHTML=`
${rajaDifficultyLabel(r)}
${display}
${player}
${r.limit.toFixed(1)}s
${opts.map(o=>``).join('')}
`; if(r.mode==='bot'&&idx===1&&!r.done)setTimeout(botRajaMove,520); else startRajaTimer(); } function playRajaCongak(answer){ let r=APP.raja;if(!r||r.done)return; clearRajaTimer(); let idx=r.turn,before=String(r.currentDisplay),correct=rajaCorrectAnswer(r),ok=String(answer)===String(correct),attemptNumber=r.lastNumber+1,spent=Math.max(0.1,(Date.now()-(r.answerStart||Date.now()))/1000); r.history.push({idx,name:r.names[idx],before,number:attemptNumber,answer,correct,ok,seconds:+spent.toFixed(1)}); if(!ok){ r.loser=r.names[idx]; r.winner=r.names[1-idx]; r.reason=`${r.names[idx]} tersalah. Jawapan betul: ${correct}.`; r.done=true;beep('bad');renderRajaCongak();return; } r.lastNumber=attemptNumber; r.currentDisplay=String(answer).toUpperCase()==='CONGAK'?'CONGAK':String(attemptNumber); beep('ok'); r.turn=1-idx; if(r.mode==='friend')r.needsReady=true; renderRajaCongak(); } function botRajaMove(){ let r=APP.raja;if(!r||r.done||r.mode!=='bot'||r.turn!==1)return; let correct=rajaCorrectAnswer(r); let options=rajaOptions(r); // QiRa is sharp, but can slip sometimes so kids can win and brag. let slip=Math.random()<0.12 && r.lastNumber>=6; let answer=correct; if(slip){ let wrongs=options.filter(o=>String(o)!==String(correct)); answer=wrongs.length?wrongs[0]:correct; } playRajaCongak(answer); } function renderRajaResult(){ let r=APP.raja;if(!r)return; clearRajaTimer(); let winnerIsQira=r.winner==='QiRa',finalScore=rajaScore(r),modeText=rajaDifficultyLabel(r); if(!r.tracked){r.tracked=true;let activeIdx=0;let playerAttempts=(r.history||[]).filter(h=>h.idx===activeIdx).map(h=>({topic:'Juara Congak',skill:'Sifir 3 / Multiple of 3',question:`Selepas ${h.before}, pilih jawapan seterusnya`,ans:String(h.correct),user:String(h.answer),ok:!!h.ok,seconds:Number.isFinite(Number(h.seconds))?Number(h.seconds):0.1}));if(r.loser===r.names[activeIdx]&&!playerAttempts.some(a=>!a.ok)){playerAttempts.push({topic:'Juara Congak',skill:'Sifir 3 / Multiple of 3',question:'Giliran terakhir',ans:'Jawab sebelum masa tamat / ikut aturan',user:r.reason||'Timeout',ok:false,seconds:r.limit||0})}let total=playerAttempts.length||1,score=playerAttempts.filter(a=>a.ok).length;try{qiraTrackGameResult({title:'Juara Congak',mode:`${r.mode} โ€ข ${r.difficulty}`,topic:'Juara Congak',total,score,wrong:Math.max(0,total-score),accuracy:total?Math.round((score/total)*100):0,durationSeconds:Math.round((Date.now()-(r.start||Date.now()))/1000),result:r.winner===r.names[activeIdx]?'Menang':'Kalah',winner:r.winner||'',loser:r.loser||'',outcomeType:'win_loss',note:`Final score ${finalScore}. ${r.reason||''}`},playerAttempts)}catch(e){console.warn('Juara Congak tracking failed',e)}} let actions=``; if(r.mode==='bot'){ let playerOk=r.history.filter(h=>h.idx===0&&h.ok).length; let qiraOk=r.history.filter(h=>h.idx===1&&h.ok).length; let noteTitle=winnerIsQira?'QiRa lebih konsisten kali ini':'Syabas, anda menang!'; let noteText=winnerIsQira ?`${r.names[0]} capai final score ${finalScore}. Cuba lagi untuk pecah rekod dan tewaskan QiRa.` :`${r.names[0]} berjaya capai final score ${finalScore}. Memang boleh buat bragging rights!`; let reasonText=r.reason||''; $('rajaArea').innerHTML=`
๐Ÿ†
Juara Congak

${r.winner} Menang!

Mode: ${modeText} โ€ข Final Score: ${finalScore}

${noteTitle}

${noteText}

${reasonText?`

${reasonText}

`:''}
${actions}
`; return; } let winnerIdx=r.names.findIndex(n=>n===r.winner); let p1Ok=r.history.filter(h=>h.idx===0&&h.ok).length,p2Ok=r.history.filter(h=>h.idx===1&&h.ok).length; let noteTitle=winnerIdx===0?`${r.names[0]} juara pusingan ini`:`${r.names[1]} juara pusingan ini`; let noteText=`Mode: ${modeText} โ€ข Final Score: ${finalScore}. ${winnerIdx===0?r.names[1]:r.names[0]} boleh cuba lagi untuk pecahkan skor ini.`; let reasonText=r.reason||''; $('rajaArea').innerHTML=`
๐Ÿ‘‘
Juara Congak

${r.winner} Menang!

Mode: ${modeText} โ€ข Final Score: ${finalScore}

${r.names[0]}${p1Ok} jawapan betul
${r.names[1]}${p2Ok} jawapan betul
${noteTitle}

${noteText}

${reasonText?`

${reasonText}

`:''}
${actions}
`; } function restartRajaCongak(){ let r=APP.raja;if(!r){openRajaConfig();return} clearRajaTimer(); APP.raja={mode:r.mode,difficulty:r.difficulty,limit:r.limit,currentDisplay:'0',lastNumber:0,turn:0,names:r.names,history:[],done:false,winner:null,loser:null,reason:'',timer:null,deadline:0,timeLeft:r.limit,needsReady:r.mode==='friend',start:Date.now(),tracked:false}; renderRajaCongak(); } function openTimeBombHome(){go('timeBombHome')} function openTimeBombConfig(type){ APP.timeBombType=type; $('timeBombConfigTitle').textContent=type==='plus'?'Time Bomb Plus':'Time Bomb Minus'; $('timeBombConfigNote').textContent=type==='plus' ?'Mula daripada 0. Setiap giliran tambah 1, 2 atau 3. Target: 21.' :'Mula daripada 21. Setiap giliran tolak 1, 2 atau 3. Target: 0.'; $('tbPlayMode').value='bot'; updateTimeBombConfig(); go('timeBombConfig'); } function updateTimeBombConfig(){ let isBot=$('tbPlayMode')?.value==='bot'; $('tbP2Row')?.classList.toggle('hidden',isBot); } function startTimeBomb(){ let type=APP.timeBombType||'plus',mode=$('tbPlayMode').value,rule=$('tbRule').value; let p1=$('tbP1').value.trim()||'Player 1'; let p2=mode==='bot'?'QiRa':($('tbP2').value.trim()||'Player 2'); APP.timeBomb={type,mode,rule,current:type==='plus'?0:21,target:type==='plus'?21:0,turn:0,names:[p1,p2],history:[],done:false,winner:null,loser:null,start:Date.now(),moveStart:Date.now(),tracked:false}; go('timeBombGame'); renderTimeBomb(); } function tbValidSteps(tb){ let max=3,steps=[]; for(let s=1;s<=max;s++){ if(tb.type==='plus'&&tb.current+s<=tb.target)steps.push(s); if(tb.type==='minus'&&tb.current-s>=tb.target)steps.push(s); } return steps; } function tbPreview(tb,step){ let arr=[]; for(let i=1;i<=step;i++)arr.push(tb.type==='plus'?tb.current+i:tb.current-i); return arr; } function renderTimeBomb(){ let tb=APP.timeBomb;if(!tb)return; if(tb.done){renderTimeBombResult();return} let steps=tbValidSteps(tb),idx=tb.turn,player=tb.names[idx]; let op=tb.type==='plus'?'+':'โˆ’'; let history=tb.history.slice().reverse().map(h=>{let hIdx=tb.names.findIndex(n=>n===h.name);let rowCls=tb.mode==='friend'?playerTurnClass(hIdx):botTimeBombTurnClass(hIdx);let nameCls=tb.mode==='friend'?playerColorClass(hIdx):botTimeBombNameClass(h.name);return `
${h.name}: ${h.seq.join(', ')}${h.ruleHit?'๐Ÿ’ฃ':''}
`}).join('')||`
Belum ada langkah. ${tb.names[0]} mula dahulu.
`; $('timeBombArea').innerHTML=`
${tb.type==='plus'?'Time Bomb Plus':'Time Bomb Minus'}
${tb.current}
${player}
Target: ${tb.target} โ€ข ${tb.rule==='lose'?'Sampai target kena bom':'Sampai target dapat bom'}
${[1,2,3].map(s=>``).join('')}

Sejarah Kiraan

${history}
`; if(tb.mode==='bot'&&tb.turn===1&&!tb.done)setTimeout(botTimeBombMove,520); } function playTimeBomb(step){ let tb=APP.timeBomb;if(!tb||tb.done)return; if(!tbValidSteps(tb).includes(step))return; let spent=Math.max(0.1,(Date.now()-(tb.moveStart||tb.start||Date.now()))/1000); let seq=tbPreview(tb,step),player=tb.names[tb.turn]; tb.current=seq[seq.length-1]; let hit=tb.current===tb.target; tb.history.push({name:player,seq,ruleHit:hit,seconds:+spent.toFixed(1)}); if(hit){ if(tb.rule==='lose'){tb.loser=player;tb.winner=tb.names[1-tb.turn]} else{tb.winner=player;tb.loser=tb.names[1-tb.turn]} tb.done=true;beep(tb.rule==='win'?'ok':'bad');renderTimeBomb();return; } tb.turn=1-tb.turn; tb.moveStart=Date.now(); beep('click'); renderTimeBomb(); } function botTimeBombMove(){ let tb=APP.timeBomb;if(!tb||tb.done||tb.mode!=='bot'||tb.turn!==1)return; let steps=tbValidSteps(tb); // Simple classroom bot: random, but grabs target when the rule is "dapat bom". let step; if(tb.rule==='win'){ step=steps.find(s=>(tb.type==='plus'?tb.current+s:tb.current-s)===tb.target)||steps[r(0,steps.length-1)]; }else{ // Avoid target if possible when target means losing. let safe=steps.filter(s=>(tb.type==='plus'?tb.current+s:tb.current-s)!==tb.target); step=(safe.length?safe:steps)[r(0,(safe.length?safe:steps).length-1)]; } playTimeBomb(step); } function renderTimeBombResult(){ let tb=APP.timeBomb,modeText=tb.type==='plus'?'Time Bomb Plus':'Time Bomb Minus'; if(!tb.tracked){tb.tracked=true;let p1Moves=(tb.history||[]).filter(h=>h.name===tb.names[0]);let winnerIsP1=tb.winner===tb.names[0];try{qiraTrackGameResult({title:modeText,mode:`${tb.mode} โ€ข rule:${tb.rule}`,topic:modeText,total:1,score:winnerIsP1?1:0,wrong:winnerIsP1?0:1,accuracy:winnerIsP1?100:0,durationSeconds:Math.round((Date.now()-(tb.start||Date.now()))/1000),result:winnerIsP1?'Menang':'Kalah',winner:tb.winner||'',loser:tb.loser||'',outcomeType:'win_loss',playerName:tb.names[0],playerScore:winnerIsP1?1:0,playerTotal:1,opponentName:tb.names[1],opponentScore:winnerIsP1?0:1,opponentTotal:1,opponentAccuracy:winnerIsP1?0:100,note:`${tb.names[0]} moves: ${p1Moves.length}. Winner: ${tb.winner}. Loser: ${tb.loser}.`},p1Moves.map((h,i)=>({topic:modeText,skill:'Counting strategy',question:`Langkah ${i+1}`,ans:h.ruleHit?'Target reached':'Safe move',user:h.seq.join(', '),ok:tb.rule==='win'?h.ruleHit:!h.ruleHit,seconds:Number.isFinite(Number(h.seconds))?Number(h.seconds):0.1})))}catch(e){console.warn('Time Bomb tracking failed',e)}} let hero=tb.rule==='win'?'๐Ÿ†':'๐Ÿ’ฅ'; if(tb.mode==='friend'){ let winnerIdx=tb.names.findIndex(n=>n===tb.winner),loserIdx=winnerIdx===0?1:0; let headline=tb.rule==='lose'?`${tb.winner} Menang!`:`${tb.winner} Dapat Bomb!`; let sub=tb.rule==='lose'?`${tb.winner} berjaya elak bom dan menang pusingan ini.`:`${tb.winner} berjaya capai target dan bomb ${tb.loser}.`; let msg1=winnerIdx===0?`${tb.names[0]} sangat fokus dan bijak membuat pilihan. Syabas!`:`${tb.names[0]} bermain dengan berani. Cuba lagi, anda semakin hebat!`; let msg2=winnerIdx===1?`${tb.names[1]} sangat fokus dan bijak membuat pilihan. Syabas!`:`${tb.names[1]} bermain dengan berani. Cuba lagi, anda semakin hebat!`; let actions=``; let afterHtml=`

Sejarah Kiraan

${tb.history.slice().reverse().map(h=>{let hIdx=tb.names.findIndex(n=>n===h.name);return `
${h.name}: ${h.seq.join(', ')}${h.ruleHit?'๐Ÿ’ฃ':''}
`}).join('')}
`; $('timeBombArea').innerHTML=buildPremiumDuelResult({emoji:'๐Ÿ†',kicker:modeText,title:headline,sub,p1Name:tb.names[0],p2Name:tb.names[1],p1Stat:winnerIdx===0?'Pemenang pusingan':'Pencabar hebat',p2Stat:winnerIdx===1?'Pemenang pusingan':'Pencabar hebat',p1Msg:msg1,p2Msg:msg2,actions,afterHtml}); return; } let winnerIsQira=tb.winner==='QiRa'; let title=`${tb.winner} Menang!`; let sub=`${tb.winner} menang pusingan ni.`; let bombTitle=`${tb.loser} kena bom!`; let bombSub=tb.rule==='lose'?`${tb.winner} berjaya elak bom dan memenangi pusingan ini.`:`${tb.winner} berjaya capai sasaran dan bomb ${tb.loser}.`; let p1Msg=winnerIsQira?`${tb.names[0]} bermain dengan berani. Cuba lagi, anda semakin hebat!`:`${tb.names[0]} sangat fokus dan bijak membuat pilihan. Syabas!`; let p2Msg=winnerIsQira?`QiRa lebih tepat dalam pusingan ini.`:`QiRa beri tentangan yang baik. Teruskan cabaran seterusnya!`; let actions=``; let afterHtml=`

Sejarah Kiraan

${tb.history.slice().reverse().map(h=>{let hIdx=tb.names.findIndex(n=>n===h.name);return `
${h.name}: ${h.seq.join(', ')}${h.ruleHit?'๐Ÿ’ฃ':''}
`}).join('')}
`; $('timeBombArea').innerHTML=buildPremiumQiraResult({kicker:'Lawan QiRa',winnerIsQira,title,sub,bombTitle,bombSub,p1Name:tb.names[0],p2Name:'QiRa',p1Stat:tb.winner===tb.names[0]?'Pemenang pusingan':'Pencabar hebat',p2Stat:winnerIsQira?'Pemenang pusingan':'Pencabar hebat',p1Msg,p2Msg,actions,afterHtml}); } function restartTimeBomb(){ let tb=APP.timeBomb;if(!tb){openTimeBombHome();return} APP.timeBomb={type:tb.type,mode:tb.mode,rule:tb.rule,current:tb.type==='plus'?0:21,target:tb.type==='plus'?21:0,turn:0,names:tb.names,history:[],done:false,winner:null,loser:null,start:Date.now(),moveStart:Date.now(),tracked:false}; renderTimeBomb(); } /* V5.3.1 active-child game names */ function qiraRegisteredParent531(){let p=qiraParent();return p&&!p.guest} function qiraActiveChildName531(){let c=qiraChild();return qiraRegisteredParent531()&&c?c.nickname:''} function qiraRefreshChildNameList531(){let dl=$('qiraChildNameList');if(!dl)return;let active=qiraChild();dl.innerHTML=(qiraChildren()||[]).filter(c=>!active||c.id!==active.id).map(c=>``).join('')} function qiraPrepGameNames531(p1Id,p2Id){qiraRefreshChildNameList531();let activeName=qiraActiveChildName531();let p1=$(p1Id),p2=$(p2Id);if(p1&&activeName&&(!p1.value||p1.value==='Player 1'||p1.dataset.qiraAuto==='1')){p1.value=activeName;p1.dataset.qiraAuto='1'}if(p2){p2.setAttribute('list','qiraChildNameList');if(!p2.value)p2.placeholder='Player 2 / pilih nama anak'}} if(typeof openBattle==='function'&&!window.qiraBattleWrapped531){window.qiraBattleWrapped531=true;const oldOpenBattle531=openBattle;openBattle=function(mode){oldOpenBattle531(mode);setTimeout(()=>qiraPrepGameNames531('p1Name','p2Name'),30)}} if(typeof openTimeBombConfig==='function'&&!window.qiraTimeBombWrapped531){window.qiraTimeBombWrapped531=true;const oldOpenTimeBombConfig531=openTimeBombConfig;openTimeBombConfig=function(type){oldOpenTimeBombConfig531(type);setTimeout(()=>qiraPrepGameNames531('tbP1','tbP2'),30)};const oldUpdateTimeBombConfig531=updateTimeBombConfig;updateTimeBombConfig=function(){oldUpdateTimeBombConfig531();setTimeout(()=>qiraPrepGameNames531('tbP1','tbP2'),10)};const oldStartTimeBomb531=startTimeBomb;startTimeBomb=function(){qiraPrepGameNames531('tbP1','tbP2');oldStartTimeBomb531()}} if(typeof updateRajaConfig==='function'&&!window.qiraRajaWrapped531){window.qiraRajaWrapped531=true;const oldUpdateRajaConfig531=updateRajaConfig;updateRajaConfig=function(){oldUpdateRajaConfig531();setTimeout(()=>qiraPrepGameNames531('rajaP1','rajaP2'),10)};const oldStartRaja531=startRajaCongak;startRajaCongak=function(){qiraPrepGameNames531('rajaP1','rajaP2');oldStartRaja531()}} function qiraWorksheetQuestions(cat,kind,scope,n){ n=Number(n||20); if(cat==='sifir'&&scope==='square'){ let qs=[], cycle=0; while(qs.lengthi+1)); nums.forEach(x=>{ if(qs.length>=n)return; let form=((qs.length+cycle)%2===0)?`${x}2 = ?`:`${x} ร— ${x} = ?`; let item=q(form,x*x,'Nombor Square',`ws-square-${qs.length}-${x}-${cycle}`); qs.push(item); }); cycle++; } return qs; } let qs=[],tries=0;APP.lastKey=''; while(qs.lengthx[0]===scope)[1]}`:'');$('printBar').classList.remove('hidden');$('worksheetPreview').innerHTML=`
${title}
QiRa by GeniusIsMe โ€ข Kira Cepat, Jadi Hebat
Nama
Kelas
Tarikh
Skor
${qs.map((q,i)=>`
${i+1}.
${fmt(q.html).replace('= ?','=')}
`).join('')}
${showKey?`
Kunci Jawapan
${qs.map((q,i)=>`
${i+1}. ${fmt(q.ans)}
`).join('')}
`:''}
`;try{let kindLabel=(KINDS.concat(ANU_KINDS).find(x=>x[0]===kind)||[])[1]||kind;let scopeLabel=(SIFIR_SCOPES.find(x=>x[0]===scope)||[])[1]||scope;qiraTrackWorksheet({title,cat,categoryLabel:CATS[cat]||cat,kind,kindLabel,scope,scopeLabel:cat==='sifir'?scopeLabel:'',total:qs.length,withAnswerKey:showKey})}catch(e){console.warn('QiRa worksheet tracking failed',e)}} function initWorksheet(){fillCatSelect('wsCat');fillKindSelect('wsKind');fillScopeSelect('wsSifirScope');$('wsCat').onchange=()=>{$('wsSifirScopeWrap').classList.toggle('hidden',$('wsCat').value!=='sifir')};$('wsSifirScopeWrap').classList.toggle('hidden',$('wsCat').value!=='sifir')}function init(){qiraInitV5();renderNav();setupCongakTiles();initWorksheet();updateControls()}init(); function saveReport(){ const area = $('resultArea'); if(!area || !area.innerHTML.trim()){ qiraNotice('Report belum tersedia. Sila lengkapkan checkup dahulu.'); return; } const cloned = area.cloneNode(true); cloned.querySelectorAll('button,.result-actions,.print-hide').forEach(el=>el.remove()); const reportContent = cloned.innerHTML; const generatedAt = new Date().toLocaleString('ms-MY',{dateStyle:'medium',timeStyle:'short'}); const reportHtml = ` QiRa Report
QiRa

QiRa Checkup Report

by GeniusIsMe โ€ข Kira Cepat, Jadi Hebat โ€ข ${generatedAt}

${reportContent}
`; const w = window.open('', '_blank'); if(!w){ qiraNotice('Browser menyekat tab baharu. Sila benarkan pop-up untuk simpan report.'); return; } w.document.open(); w.document.write(reportHtml.replace(/<\/script/gi,'<\/script')); w.document.close(); } window.addEventListener('afterprint',()=>document.body.classList.remove('print-result'));