/* ───────────────────────────────────────────── PLACEMENT TEST — adaptive, in groups of 5 Six bands (A1→C2). Pass a band (4/5) → move up. Fail → the test stops and reports your level. Exposes: window.PlacementTest, window.PLACEMENT_BANDS, window.LEVEL_COLORS, window.LEVEL_NAMES ───────────────────────────────────────────── */ const LEVEL_COLORS = { A1:'#1a7a5e', A2:'#2E8B57', B1:'#2E6DB4', B2:'#C8922A', C1:'#b06520', C2:'#7a2a1f' }; const LEVEL_NAMES = { A1:{ en:'Beginner', ar:'مبتدئ', tr:'Başlangıç' }, A2:{ en:'Elementary', ar:'تأسيسي', tr:'Temel' }, B1:{ en:'Intermediate', ar:'متوسط', tr:'Orta' }, B2:{ en:'Upper-Intermediate', ar:'فوق المتوسط', tr:'Orta-Üstü' }, C1:{ en:'Advanced', ar:'متقدم', tr:'İleri' }, C2:{ en:'Proficient', ar:'إتقان', tr:'Yetkin' }, }; /* skill: vocab | grammar | reading — every band mixes all three */ const PLACEMENT_BANDS = [ { id:'A1', questions:[ { skill:'grammar', q:'I ___ an engineer.', options:['is','am','are','be'], a:1 }, { skill:'vocab', q:'The opposite of “big” is ___.', options:['tall','old','small','fast'], a:2 }, { skill:'grammar', q:'___ name is Ahmed.', options:['My','Me','I','Mine'], a:0 }, { skill:'vocab', q:'You use a ___ to write.', options:['cup','pen','door','chair'], a:1 }, { skill:'reading', passage:'Sara is a student. She studies computer engineering. She has classes every morning.', q:'When does Sara have classes?', options:['At night','On weekends','In the evening','In the morning'], a:3 }, ]}, { id:'A2', questions:[ { skill:'grammar', q:'She ___ to work by bus every day.', options:['go','going','goes','went'], a:2 }, { skill:'vocab', q:'A “deadline” is ___.', options:['a type of meeting','the last day to finish something','a work tool','a long holiday'], a:1 }, { skill:'grammar', q:'There isn\u2019t ___ milk in the fridge.', options:['some','a','many','any'], a:3 }, { skill:'vocab', q:'A “colleague” is a person who ___.', options:['works with you','sells food','drives the bus','lives next door'], a:0 }, { skill:'reading', passage:'Omar works in a factory. Yesterday the main machine stopped, so the team finished work late. Today a technician is coming to repair it.', q:'Why did the team finish late?', options:['Omar was sick','The factory closed early','A machine stopped working','The technician was late'], a:2 }, ]}, { id:'B1', questions:[ { skill:'grammar', q:'I have worked here ___ 2022.', options:['for','since','from','at'], a:1 }, { skill:'vocab', q:'If a plan is “flexible”, it ___.', options:['is very expensive','is already finished','cannot change','can change easily'], a:3 }, { skill:'grammar', q:'If it rains tomorrow, we ___ the test inside.', options:['will do','would do','did','are doing'], a:0 }, { skill:'vocab', q:'To “postpone” a meeting means to ___.', options:['cancel it forever','move it to a later time','start it early','forget about it'], a:1 }, { skill:'reading', passage:'The company introduced a new system to track projects. At first, many employees found it confusing, but after a short training most agreed it saved them time.', q:'How did employees feel at first?', options:['Excited','Bored','Confused','Angry'], a:2 }, ]}, { id:'B2', questions:[ { skill:'grammar', q:'By the time the client arrived, we ___ the demo.', options:['already prepare','have already prepared','had already prepared','are already preparing'], a:2 }, { skill:'vocab', q:'A “bottleneck” in a process is ___.', options:['a point that slows everything down','the fastest step','the final result','an extra budget'], a:0 }, { skill:'grammar', q:'The report ___ by the quality team before it was published.', options:['reviewed','has reviewed','is reviewing','was reviewed'], a:3 }, { skill:'vocab', q:'“Feasible” is closest in meaning to ___.', options:['expensive','achievable','impossible','optional'], a:1 }, { skill:'reading', passage:'Although the prototype passed most tests, engineers remained cautious. The results, while promising, came from a controlled lab environment — and field conditions are rarely so forgiving.', q:'Why are the engineers cautious?', options:['The prototype failed most tests','The lab made mistakes','Real conditions may differ from the lab','The project lost its funding'], a:2 }, ]}, { id:'C1', questions:[ { skill:'vocab', q:'An “inherent” risk is one that ___.', options:['was added by mistake','is a natural part of the situation','can be fully removed','comes only from outside'], a:1 }, { skill:'grammar', q:'___ the budget cuts, the team delivered the project on schedule.', options:['Despite','Although','Because','Unless'], a:0 }, { skill:'vocab', q:'To “corroborate” findings means to ___.', options:['publish them quickly','question their value','summarize them briefly','confirm them with more evidence'], a:3 }, { skill:'grammar', q:'Not until the final tests were completed ___ the results.', options:['the team announced','did the team announce','the team did announce','announced the team'], a:1 }, { skill:'reading', passage:'The committee\u2019s report stops short of assigning blame, noting instead that the failure emerged from an accumulation of minor, individually reasonable decisions — none of which, on its own, would have raised concern.', q:'What does the report say caused the failure?', options:['One person\u2019s clear mistake','Deliberate misconduct','Many small decisions adding up','A single unreasonable decision'], a:2 }, ]}, { id:'C2', questions:[ { skill:'vocab', q:'A “paradigm shift” refers to ___.', options:['a minor technical update','a fundamental change in approach','a return to older methods','a temporary trend'], a:1 }, { skill:'grammar', q:'Had the sensors been calibrated correctly, the anomaly ___ much earlier.', options:['was detected','would be detected','had been detected','would have been detected'], a:3 }, { skill:'vocab', q:'“Ubiquitous” most nearly means ___.', options:['present everywhere','extremely rare','poorly understood','recently invented'], a:0 }, { skill:'vocab', q:'If evidence is “circumstantial”, it ___.', options:['proves guilt definitively','suggests but does not prove directly','was collected at the scene','has been fabricated'], a:1 }, { skill:'reading', passage:'That the model performs well on historical data is, by itself, unremarkable; the more telling question — and the one the authors conspicuously decline to address — is whether its assumptions survive contact with genuinely novel conditions.', q:'What is the author\u2019s criticism?', options:['The model performs poorly on historical data','The assumptions are clearly stated but wrong','The authors address too many questions at once','The authors avoid discussing how the model handles new conditions'], a:3 }, ]}, ]; const PT_PASS = 4; // correct answers (of 5) needed to move up a band const PT_SKILLS = [ { id:'vocab', emoji:'📖', en:'Vocabulary', ar:'المفردات', tr:'Kelime' }, { id:'grammar', emoji:'🧩', en:'Grammar', ar:'القواعد', tr:'Dil bilgisi' }, { id:'reading', emoji:'📕', en:'Reading', ar:'القراءة', tr:'Okuma' }, ]; function PlacementTest({ t, lang, onFinish, onClose }) { const [phase, setPhase] = React.useState('intro'); // intro | quiz | between | result const [bandIdx, setBandIdx] = React.useState(0); const [qIdx, setQIdx] = React.useState(0); const [curCorrect, setCurCorrect] = React.useState(0); const [scores, setScores] = React.useState([]); // [{id, correct, total}] const [skills, setSkills] = React.useState({ vocab:[0,0], grammar:[0,0], reading:[0,0] }); const [picked, setPicked] = React.useState(null); const [result, setResult] = React.useState(null); const band = PLACEMENT_BANDS[bandIdx]; const q = band.questions[qIdx]; const totalQ = PLACEMENT_BANDS.length * 5; const answeredSoFar = bandIdx * 5 + qIdx; const computeResult = (allScores, allSkills) => { let level = 'A1', passedAny = false; allScores.forEach(b => { if (b.correct >= PT_PASS) { level = b.id; passedAny = true; } }); return { level, starter: !passedAny, at: Date.now(), bands: allScores, skills: allSkills, }; }; const finish = (allScores, allSkills) => { const res = computeResult(allScores, allSkills); setResult(res); setPhase('result'); if (onFinish) onFinish(res); }; const answer = (i) => { if (picked !== null) return; setPicked(i); const ok = i === q.a; const nextSkills = { ...skills, [q.skill]: [skills[q.skill][0] + (ok ? 1 : 0), skills[q.skill][1] + 1] }; setSkills(nextSkills); const nextCorrect = curCorrect + (ok ? 1 : 0); setTimeout(() => { setPicked(null); if (qIdx < band.questions.length - 1) { setCurCorrect(nextCorrect); setQIdx(qIdx + 1); } else { const bandScore = { id: band.id, correct: nextCorrect, total: band.questions.length }; const nextScores = [...scores, bandScore]; setScores(nextScores); const passed = nextCorrect >= PT_PASS; if (passed && bandIdx < PLACEMENT_BANDS.length - 1) { setPhase('between'); } else { finish(nextScores, nextSkills); } } }, 420); }; const nextBandGo = () => { setBandIdx(bandIdx + 1); setQIdx(0); setCurCorrect(0); setPhase('quiz'); }; const restart = () => { setBandIdx(0); setQIdx(0); setCurCorrect(0); setScores([]); setSkills({ vocab:[0,0], grammar:[0,0], reading:[0,0] }); setPicked(null); setResult(null); setPhase('quiz'); }; /* ── INTRO ── */ if (phase === 'intro') return (
Nour explains the test
{t('Find your English level','اكتشف مستواك في الإنجليزية','İngilizce seviyeni keşfet')}
{t('A short adaptive test of vocabulary, grammar and reading. It comes in parts of 5 questions — answer 4 correctly and you move up to harder questions. When a part is too hard, the test stops and tells you your level.', 'اختبار قصير متكيّف يقيس المفردات والقواعد والقراءة. يأتي على أجزاء من 5 أسئلة — أجب على 4 بشكل صحيح لتنتقل إلى أسئلة أصعب. عندما يصبح الجزء صعباً، يتوقف الاختبار ويخبرك بمستواك.', 'Kelime, dil bilgisi ve okumayı ölçen kısa, uyarlanabilir bir test. 5 soruluk bölümler halinde gelir — 4 doğru yaparsan daha zor sorulara geçersin. Bölüm zorlaştığında test durur ve seviyeni söyler.')}
🧩 {t('Up to 6 parts','حتى 6 أجزاء','En fazla 6 bölüm')} ⏱ {t('10–15 minutes','10–15 دقيقة','10–15 dakika')} 🎯 {t('Stops automatically','يتوقف تلقائياً','Otomatik durur')}
{t('Tip: don\u2019t guess wildly — an honest result gives you a better learning plan.', 'نصيحة: لا تخمّن عشوائياً — النتيجة الصادقة تمنحك خطة تعلّم أفضل.', 'İpucu: rastgele tahmin etme — dürüst bir sonuç sana daha iyi bir plan verir.')}
); /* ── BETWEEN BANDS ── */ if (phase === 'between') { const nextBand = PLACEMENT_BANDS[bandIdx + 1]; return (
⬆️
{t('Part passed!','اجتزت هذا الجزء!','Bölümü geçtin!')}
{t('You answered','أجبت على','Cevapladın:')} {scores[scores.length-1].correct} / 5 {t('at level','في مستوى','seviyesinde —')} {band.id}.{' '} {t('Moving up to','ننتقل الآن إلى','Sıradaki seviye:')} {nextBand.id} · {lang==='ar'?LEVEL_NAMES[nextBand.id].ar:lang==='tr'?LEVEL_NAMES[nextBand.id].tr:LEVEL_NAMES[nextBand.id].en}.
); } /* ── RESULT ── */ if (phase === 'result' && result) { const lvlName = lang==='ar' ? LEVEL_NAMES[result.level].ar : lang==='tr' ? LEVEL_NAMES[result.level].tr : LEVEL_NAMES[result.level].en; return (
Nour celebrates your result
{t('Your result','نتيجتك','Sonucun')}
{result.level}
{lvlName}
{result.starter ? t('You\u2019re at the very beginning — perfect. Start with the A1 lessons and word cards, and your plan will build from there.', 'أنت في البداية تماماً — ممتاز. ابدأ بدروس A1 وبطاقات الكلمات، وستُبنى خطتك من هناك.', 'Tam başlangıçtasın — harika. A1 dersleri ve kelime kartlarıyla başla; planın buradan gelişecek.') : t('Focus your lessons, reading and vocabulary around this level — and stretch one level up when it feels easy.', 'ركّز دروسك وقراءتك ومفرداتك حول هذا المستوى — وارتقِ مستوى واحداً عندما تشعر بالسهولة.', 'Derslerini, okumanı ve kelimeleri bu seviyeye odakla — kolay gelince bir seviye yukarı çık.')}
{t('Skill breakdown','تفصيل المهارات','Beceri dökümü')}
{PT_SKILLS.map(s => { const [c, tot] = result.skills[s.id]; const pct = tot ? Math.round((c / tot) * 100) : 0; return (
{s.emoji} {t(s.en, s.ar, s.tr)} {c}/{tot}
); })}
{t('Your path through the test','مسارك في الاختبار','Testteki yolun')}
{result.bands.map(b => ( = PT_PASS ? 'done' : '')}> {b.correct >= PT_PASS ? '✓' : '•'} {b.id} — {b.correct}/5 ))}
); } /* ── QUIZ ── */ const skillObj = PT_SKILLS.find(s => s.id === q.skill); return (
📐 {t('Level test','اختبار المستوى','Seviye testi')} · {skillObj.emoji} {t(skillObj.en, skillObj.ar, skillObj.tr)}
{t('Part','الجزء','Bölüm')} {bandIdx + 1} — {band.id} · {t('Question','سؤال','Soru')} {qIdx + 1}/5
{PLACEMENT_BANDS.map((b, i) => ( {i < bandIdx ? '✓ ' : ''}{b.id} ))}
{q.passage &&
{q.passage}
}
{q.q}
{q.options.map((opt, i) => ( ))}
{t('Answers are not shown during the test — keep going!','لا تظهر الإجابات أثناء الاختبار — واصل!','Test sırasında cevaplar gösterilmez — devam et!')}
); } Object.assign(window, { PlacementTest, PLACEMENT_BANDS, LEVEL_COLORS, LEVEL_NAMES });