/* quiz.jsx — QuizMode, QuizResults */ // Feature flag — AI tutor (Claude) is disabled in self-hosted builds. // To enable as a Pro/paid feature, see docs/AI_TUTOR_PAID_FEATURE.md const AI_TUTOR_ENABLED = false; function shuffle(arr){return[...arr].sort(()=>Math.random()-0.5);} function normCmd(s){return s.trim().toLowerCase().replace(/\s+/g,' ');} function DomTag({d}){const dom=(window.DOMAINS||[]).find(x=>x.id===d);return {dom?.short||`D${d}`};} function DiffTag({diff,ccnp}){if(ccnp)return CCNP;return {diff===1?'Easy':diff===2?'Med':'Hard'};} function Exhibit({text}){ return(
IOS Terminal Output
{text}
); } function OptBtn({opt,state,onClick}){ const cls=['q-opt',...(state?state.split(' '):[])].filter(Boolean).join(' '); return(); } function MCQ({q,selected,onSelect,submitted}){ return(
{q.opts.map(o=>{ let s=''; if(submitted){s='disabled';if(q.ans.includes(o.id))s='correct disabled';else if(selected.includes(o.id))s='wrong disabled';} else if(selected.includes(o.id))s='selected'; return !submitted&&onSelect(o.id)}/>; })}
); } function MSQ({q,selected,onToggle,submitted}){ return(

▲ Select all that apply

{q.opts.map(o=>{ let s=''; if(submitted){const isA=q.ans.includes(o.id),isSel=selected.includes(o.id);s='disabled';if(isA)s='correct disabled';else if(isSel&&!isA)s='wrong disabled';else if(!isSel&&isA)s='missed disabled';} else if(selected.includes(o.id))s='selected'; return !submitted&&onToggle(o.id)}/>; })}
); } function FITBQ({q,vals,onChange,submitted,correctness}){ return(
{(q.blanks||[]).map((blank,i)=>{ const prompt=i===0?q.fitbPrompt:(q.fitbPrompt2||q.fitbPrompt); let cls='fitb-input';if(submitted)cls+=correctness[i]?' ok':' err'; return(
{prompt} onChange(i,e.target.value)} placeholder="type command…" disabled={submitted} spellCheck={false} autoComplete="off"/>
); })} {submitted&&
{(q.blanks||[]).map((b,i)=>!correctness[i]&&
Line {i+1}: {b}
)}
}
); } function MatchQ({q,termSel,defSel,pairs,onTerm,onDef,defs,submitted}){ return(
Terms — click to select
{q.pairs.map((p,i)=>{const pa=pairs.find(x=>x.ti===i);let cls='match-item';if(termSel===i)cls+=' active';if(pa)cls+=' paired';return( ); })}
Definitions — click to pair
{defs.map((def,i)=>{const pa=pairs.find(x=>x.di===i);let cls='match-item';if(defSel===i)cls+=' active';if(pa)cls+=' paired';return( ); })}
{pairs.length===q.pairs.length&&!submitted&&

✓ All matched — click Submit

} {submitted&&
Match Results
{q.pairs.map((p,i)=>{const pa=pairs.find(x=>x.ti===i);const ok=pa&&defs[pa.di]===p.def;return(
{ok?'✓':'✗'}{p.term} → {p.def}
); })}
}
); } function Feedback({q,selected,correct}){ return(
{correct?'✓ Correct':'✗ Incorrect'}
{q.exp}
{!correct&&q.wrong&&
{selected.filter(s=>!q.ans.includes(s)).map(s=>q.wrong[s]&&(
{s.toUpperCase()}:{q.wrong[s]}
))}
}
); } function HintPanel({q,hintsShown,onHint,chatLog,chatIn,setChatIn,onAsk,loading}){ const logRef=React.useRef(null); React.useEffect(()=>{if(logRef.current)logRef.current.scrollTop=logRef.current.scrollHeight;},[chatLog,loading]); const maxHints=q.hints?.length||0; return(
{AI_TUTOR_ENABLED?'AI Tutor':'Hints'}{AI_TUTOR_ENABLED&&Claude}
{hintsShown>0&&
{q.hints.slice(0,hintsShown).map((h,i)=>
Hint {i+1}/{maxHints}{h}
)}
} {hintsShown
} {AI_TUTOR_ENABLED &&
{chatLog.length===0&&
Ask me anything… I won't reveal the answer until you submit.
} {chatLog.map((m,i)=>
{m.c}
)} {loading&&
thinking…
}