/* The science path (app/science.py): the unit חידות and its riddle skills, solved by the class together. The host opens a vote and shows its code on the board; students join with the code on their own devices and vote. Every screen asks the server for the room about once a second and a half. Votes stay hidden until the host checks; then the rocket is launched with the majority's fuel: it flies, falls back (too little fuel) or stays on the pad (too heavy). math.js opens this from the subject picker: MathlyScience.show(container, {back}). */ (()=>{ const css=document.createElement("link"); css.rel="stylesheet"; css.href=document.currentScript.src.replace("science.js","science.css"); document.head.append(css); const esc=s=>String(s??"").replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[c])); const fmt=s=>esc(s).replace(/\$([^$]+)\$/g,'$1'); // $…$: math, set left to right const store={get:(k,s=localStorage)=>{ try{ return s.getItem(k); }catch{ return null; } }, set:(k,v,s=localStorage)=>{ try{ s.setItem(k,v); }catch{} }}; // one vote per device: a random id kept on it const VOTER=(()=>{ let id=store.get("mathly.voter"); if(!id||id.length<8){ id=(crypto.randomUUID?.()||String(Math.random()).slice(2)+Date.now()).replace(/-/g,""); store.set("mathly.voter",id); } return id; })(); const hostKey=code=>store.get(`mathly.science.host:${code}`,sessionStorage)||""; const POLL=1500; let box=null, PATH=null, ROOM=null, SHOWN="", timer=null, launched=new Set(), busy=false, msg=""; async function call(url,options={}){ const code=ROOM?.code; const r=await fetch(url,{...options,headers:{"Content-Type":"application/json",...(code?{"X-Host":hostKey(code)}:{})}}); const body=await r.json().catch(()=>({})); if(!r.ok){ const e=new Error(body.detail||`השרת ענה ${r.status}`); e.status=r.status; throw e; } return body; } const SAY={"no vote with this code":"אין הצבעה פתוחה עם הקוד הזה. בדקו את הקוד ונסו שוב.","no votes yet":"עוד אף אחד לא הצביע.", "this riddle is no longer open for votes":"ההצבעה על החידה הזאת כבר נסגרה.","this vote is full":"ההצבעה מלאה.", "too many votes are open right now":"יש עכשיו יותר מדי הצבעות פתוחות. נסו שוב בעוד כמה דקות."}; const say=e=>SAY[e.message]||e.message||"משהו השתבש. נסו שוב."; function stop(){ clearTimeout(timer); timer=null; } const alive=()=>box&&box.isConnected&&box.querySelector(".sci"); function draw(html){ box.innerHTML=`
${html}
`; } const header=(title,sub,act='')=> `

${esc(title)}

${esc(sub)}

${act}
`; async function show(container,opts={}){ if(box!==container){ container.addEventListener("click",onClick); } box=container; stop(); ROOM=null; msg=""; draw(`${header("מדעים","המקצועות שלי")}

טוענים את המסלול…

`); if(!PATH){ try{ PATH=await call("/api/science"); } catch(e){ return draw(`${header("מדעים","המקצועות שלי")}

לא הצלחנו לטעון את המסלול: ${esc(say(e))}

`); } } drawPath(); } function drawPath(){ stop(); ROOM=null; draw(`${header(PATH.subject,"המקצועות שלי")}${PATH.units.map(u=>`

${esc(u.name)}

${esc(u.description||"")}

${PATH.skills.filter(s=>s.unit===u.id).map(s=>``).join("")}
`).join("")}`); } function drawLobby(skillId){ stop(); ROOM=null; const s=PATH.skills.find(x=>x.id===skillId); draw(`${header(s.name,"חידות · מדעים",'')}

פתיחת הצבעה

למורה או למי שמציג על הלוח: פותחים הצבעה, ומקבלים קוד שהכיתה מצטרפת איתו.

הצטרפות להצבעה

הקלידו את הקוד שמופיע על הלוח.

${msg?``:""}`); box.querySelector("[data-sci-join]").onsubmit=e=>{ e.preventDefault(); join(e.target.code.value.trim(),s.id); }; } async function openRoom(skillId){ busy=true; msg=""; drawLobby(skillId); try{ const st=await call("/api/science/rooms",{method:"POST",body:JSON.stringify({skill:skillId})}); store.set(`mathly.science.host:${st.code}`,st.host_key,sessionStorage); ROOM={code:st.code,skill:skillId}; busy=false; render(st); poll(); } catch(e){ busy=false; msg=say(e); drawLobby(skillId); } } async function join(code,skillId){ if(!/^\d{4}$/.test(code)){ msg="הקוד הוא 4 ספרות."; return drawLobby(skillId); } busy=true; msg=""; ROOM={code,skill:skillId}; try{ const st=await call(`/api/science/rooms/${code}?voter=${VOTER}`); busy=false; render(st); poll(); } catch(e){ busy=false; ROOM=null; msg=say(e); drawLobby(skillId); } } function poll(){ stop(); timer=setTimeout(async()=>{ if(!ROOM||!alive()) return stop(); try{ render(await call(`/api/science/rooms/${ROOM.code}?voter=${VOTER}`)); } catch(e){ if(e.status===404){ msg="ההצבעה הסתיימה."; return drawLobby(ROOM.skill); } } poll(); },POLL); } async function act(url,body){ try{ const st=await call(url,{method:"POST",...(body?{body:JSON.stringify(body)}:{})}); ROOM.err=""; render(st,true); } catch(e){ ROOM.err=say(e); render(ROOM.last,true); } } const OUT={flies:["הטיל המריא! 🚀","flies"],falls:["הדלק נגמר באמצע הדרך, והטיל נפל בחזרה","falls"],stuck:["הטיל כבד מדי ולא זז מכן השיגור","stuck"]}; function rocket(st){ const key=`${st.code}:${st.n}`, now=st.phase==="checked"&&!launched.has(key); if(now) launched.add(key); const cls=st.phase!=="checked"?"":`${now?"go":"end"}-${st.outcome}`; return ``; } function render(st,force=false){ if(!ROOM||!st) return; const sig=JSON.stringify(st)+(ROOM.err||""); if(!force&&sig===SHOWN&&alive()) return; SHOWN=sig; ROOM.last=st; const done=st.phase==="checked", total=st.votes, counts=st.counts||[]; const choices=st.choices.map((text,i)=>{ const pct=done&&total?Math.round(100*counts[i]/total):0; const cls=["sci-choice",st.mine===i&&"mine",done&&i===st.picked&&"picked",done&&i===st.right&&"right",st.tie.includes(i)&&"tied"].filter(Boolean).join(" "); return ``; }).join(""); const tie=st.tie.length&&!done?`

תיקו בין ${st.tie.map(i=>esc(st.choices[i])).join(" ובין ")}. אפשר לשנות הצבעה ולבדוק שוב.

`:""; const result=done?`

${OUT[st.outcome][0]}

הרוב בחר ${esc(st.choices[st.picked])} (${counts[st.picked]} מתוך ${total}).

${fmt(st.why)}

${st.picked!==st.right?`

התשובה שהייתה מצליחה: ${esc(st.choices[st.right])}

`:""}
`:""; const host=st.host?`
${done ?(st.nלחידה הבאה':'') :``}
`:""; draw(`${header(st.name,`חידה ${st.n} מתוך ${st.of}`,'')}
${st.host?"הקוד להצטרפות":"קוד ההצבעה"}${esc(st.code)}

${esc(st.title)}

${fmt(st.story)}

${esc(st.question)}

${rocket(st)}${result}
${choices}

${done?`${total} הצביעו`:`${total} ${total===1?"הצביע":"הצביעו"} עד עכשיו · התוצאות ייחשפו בבדיקה`}

${tie}${ROOM.err?``:""}${host}`); } function onClick(e){ const t=e.target.closest("[data-sci]"); if(!t||!box.contains(t)) return; switch(t.dataset.sci){ case "path": return drawPath(); case "lobby": msg=""; return drawLobby(t.dataset.skill); case "open": return openRoom(t.dataset.skill); case "leave": { const s=ROOM?.skill; stop(); ROOM=null; msg=""; return drawLobby(s); } case "vote": return act(`/api/science/rooms/${ROOM.code}/vote`,{voter:VOTER,choice:+t.dataset.choiceI,n:ROOM.last.n}); case "check": return act(`/api/science/rooms/${ROOM.code}/check?voter=${VOTER}`); case "next": return act(`/api/science/rooms/${ROOM.code}/next?voter=${VOTER}`); } } window.MathlyScience={show,stop}; })();