/* ───────────────────────────────────────────── 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 (