@mblinkov/whats-missing 20.0.20 → 20.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),o=require("react"),le=window.origin+"/cloud/speakid/games/whatsmissing/",D=(a,m)=>a.map(([c,s])=>({src:`${le}${m}/${s.replace(/\s/g,"%20")}.png`,name:s})),ae=[["apple","apple"],["banana","banana"],["bread","bread"],["burger","burger"],["cake","cake"],["carrot","carrot"],["cheese","cheese"],["chicken","chicken"],["chocolate","chocolate"],["corn","corn"],["donut","donut"],["fish","fish"],["fries","fries"],["grapes","grapes"],["ice_cream","ice cream"],["milk","milk"],["orange","orange"],["pear","pear"],["pizza","pizza"],["rice","rice"],["sandwich","sandwich"],["soup","soup"],["strawberry","strawberry"],["tomato","tomato"],["watermelon","watermelon"]],de=[["bear","bear"],["cat","cat"],["crocodile","crocodile"],["dolphin","dolphin"],["elephant","elephant"],["fox","fox"],["giraffe","giraffe"],["horse","horse"],["kangaroo","kangaroo"],["lion","lion"],["monkey","monkey"],["owl","owl"],["panda","panda"],["penguin","penguin"],["squirrel","squirrel"],["tiger","tiger"],["tortoise","tortoise"],["wolf","wolf"],["zebra","zebra"]],ce=[["ball","ball"],["balloon","balloon"],["blocks","blocks"],["car","car"],["doll","doll"],["drum","drum"],["guitar","guitar"],["jump_rope","jump rope"],["kite","kite"],["paints","paints"],["plane","plane"],["puzzle","puzzle"],["rocket","rocket"],["scooter","scooter"],["skateboard","skateboard"],["slide","slide"],["teddy_bear","teddy bear"],["yo-yo","yo-yo"],["crayons","crayons"]],P={food:D(ae,"food"),animals:D(de,"animals"),toys:D(ce,"toys")},me=`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),s=require("react"),pe=window.origin+"/cloud/speakid/games/whatsmissing/",q=(d,f)=>d.map(([g,i])=>({src:`${pe}${f}/${i.replace(/\s/g,"%20")}.png`,name:i})),ye=[["apple","apple"],["banana","banana"],["bread","bread"],["burger","burger"],["cake","cake"],["carrot","carrot"],["cheese","cheese"],["chicken","chicken"],["chocolate","chocolate"],["corn","corn"],["donut","donut"],["fish","fish"],["fries","fries"],["grapes","grapes"],["ice_cream","ice cream"],["milk","milk"],["orange","orange"],["pear","pear"],["pizza","pizza"],["rice","rice"],["sandwich","sandwich"],["soup","soup"],["strawberry","strawberry"],["tomato","tomato"],["watermelon","watermelon"]],xe=[["bear","bear"],["cat","cat"],["crocodile","crocodile"],["dolphin","dolphin"],["elephant","elephant"],["fox","fox"],["giraffe","giraffe"],["horse","horse"],["kangaroo","kangaroo"],["lion","lion"],["monkey","monkey"],["owl","owl"],["panda","panda"],["penguin","penguin"],["squirrel","squirrel"],["tiger","tiger"],["tortoise","tortoise"],["wolf","wolf"],["zebra","zebra"]],be=[["ball","ball"],["balloon","balloon"],["blocks","blocks"],["car","car"],["doll","doll"],["drum","drum"],["guitar","guitar"],["jump_rope","jump rope"],["kite","kite"],["paints","paints"],["plane","plane"],["puzzle","puzzle"],["rocket","rocket"],["scooter","scooter"],["skateboard","skateboard"],["slide","slide"],["teddy_bear","teddy bear"],["yo-yo","yo-yo"],["crayons","crayons"]],V={food:q(ye,"food"),animals:q(xe,"animals"),toys:q(be,"toys")},we=`
2
2
  @keyframes spin {
3
3
  from { transform: rotate(0deg); }
4
4
  to { transform: rotate(360deg); }
5
5
  }
6
- `;if(typeof document<"u"&&!document.getElementById("spin-keyframes")){const a=document.createElement("style");a.id="spin-keyframes",a.innerHTML=me,document.head.appendChild(a)}const ue={animation:"spin 1.4s linear infinite"},n={gmCenterScreen:{position:"relative",zIndex:1,minHeight:"100vh",width:"100%",display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center",textAlign:"center",color:"#1f2937",padding:"24px 16px",boxSizing:"border-box",transform:"translateY(20px)"},gmHeadline1:{fontWeight:700,fontSize:"clamp(28px, 4vw, 40px)",lineHeight:"110%"},gmHeadline3:{fontWeight:600,fontSize:"18px",lineHeight:"130%"},gmBodyM:{fontWeight:400,fontSize:"16px",lineHeight:"140%"},gmBodyS:{fontWeight:400,fontSize:"14px",lineHeight:"140%",color:"#6b7280"},gmButton:{fontFamily:'"Geist", system-ui, -apple-system, "Segoe UI", Roboto, Arial, "Noto Sans"',fontWeight:600,fontSize:"16px",padding:"10px 16px",borderRadius:"12px",border:"1px solid #e5e7eb",background:"#ec4c44",color:"#ffffff",cursor:"pointer",boxShadow:"0 6px 18px rgba(236, 76, 68, .18)",transition:"transform .06s ease, box-shadow .2s ease, background .2s ease, opacity .2s ease"},gmButtonActive:{background:"#333",color:"#fff"},gmGameLayout:{position:"relative",width:"100%",maxWidth:"600px",minHeight:"100vh",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",textAlign:"center",color:"#1f2937",padding:"16px 8px",margin:"0 auto"},gmGrid:{width:"100%",maxWidth:"calc(210px * 3 + 20px * 2)",margin:"0 auto",display:"grid",gridTemplateColumns:"repeat(3, 210px)",gridAutoRows:"210px",gap:"20px",justifyContent:"center"},gmCard:{border:"1px solid #e5e7eb",borderRadius:"16px",background:"#f9f9f9",aspectRatio:"1 / 1",display:"flex",alignItems:"center",justifyContent:"center",overflow:"hidden",transition:"transform .2s ease, box-shadow .2s ease, border-color .2s ease"},gmCorrect:{border:"2px solid #10b981",boxShadow:"0 0 18px rgba(16,185,129,.45)",background:"#ecfdf5"},gmWrong:{border:"2px solid #ec4c44",boxShadow:"0 0 18px rgba(236,76,68,.35)",background:"#fef2f2"},gmInput:{padding:"6px 10px",borderRadius:"6px",border:"1px solid #ccc",fontSize:"16px",fontFamily:'"Geist", system-ui',width:"160px"},gmTable:{marginTop:"20px",marginBottom:"32px",borderCollapse:"collapse",width:"100%",maxWidth:"520px",tableLayout:"fixed",textAlign:"center"},gmTableCell:{padding:"8px 12px",borderBottom:"1px solid #e5e7eb",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},gmLogoFixed:{position:"absolute",top:"16px",left:"24px",width:"120px",zIndex:10,pointerEvents:"none",background:"transparent",transform:"none",willChange:"auto"},gmLogoImg:{height:"clamp(28px, 5vw, 40px)",width:"auto",background:"transparent",objectFit:"contain",imageRendering:"auto",transform:"translateZ(0)",backfaceVisibility:"hidden",WebkitFontSmoothing:"antialiased"},gmHourglass:{fontSize:"42px",...ue}},ge=()=>{const a=document.createElement("style");a.textContent=`
6
+ `;if(typeof document<"u"&&!document.getElementById("spin-keyframes")){const d=document.createElement("style");d.id="spin-keyframes",d.innerHTML=we,document.head.appendChild(d)}const ve={animation:"spin 1.4s linear infinite"},n={gmCenterScreen:{position:"relative",zIndex:1,minHeight:"100%",width:"100%",display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center",textAlign:"center",color:"#1f2937",padding:"24px 16px",boxSizing:"border-box",transform:"translateY(20px)"},gmHeadline1:{fontWeight:700,fontSize:"clamp(28px, 4vw, 40px)",lineHeight:"110%"},gmHeadline3:{fontWeight:600,fontSize:"18px",lineHeight:"130%"},gmBodyM:{fontWeight:400,fontSize:"16px",lineHeight:"140%"},gmBodyS:{fontWeight:400,fontSize:"14px",lineHeight:"140%",color:"#6b7280"},gmButton:{fontFamily:'"Geist", system-ui, -apple-system, "Segoe UI", Roboto, Arial, "Noto Sans"',fontWeight:600,fontSize:"16px",padding:"10px 16px",borderRadius:"12px",border:"1px solid #e5e7eb",background:"#ec4c44",color:"#ffffff",cursor:"pointer",boxShadow:"0 6px 18px rgba(236, 76, 68, .18)",transition:"transform .06s ease, box-shadow .2s ease, background .2s ease, opacity .2s ease"},gmButtonActive:{background:"#333",color:"#fff"},gmGameLayout:{position:"relative",width:"100%",maxWidth:"600px",minHeight:"100%",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",textAlign:"center",color:"#1f2937",padding:"16px 8px",margin:"0 auto"},gmGrid:{width:"100%",maxWidth:"calc(210px * 3 + 20px * 2)",margin:"0 auto",display:"grid",gridTemplateColumns:"repeat(3, 210px)",gridAutoRows:"210px",gap:"20px",justifyContent:"center"},gmCard:{border:"1px solid #e5e7eb",borderRadius:"16px",background:"#f9f9f9",aspectRatio:"1 / 1",display:"flex",alignItems:"center",justifyContent:"center",overflow:"hidden",transition:"transform .2s ease, box-shadow .2s ease, border-color .2s ease"},gmCorrect:{border:"2px solid #10b981",boxShadow:"0 0 18px rgba(16,185,129,.45)",background:"#ecfdf5"},gmWrong:{border:"2px solid #ec4c44",boxShadow:"0 0 18px rgba(236,76,68,.35)",background:"#fef2f2"},gmInput:{padding:"6px 10px",borderRadius:"6px",border:"1px solid #ccc",fontSize:"16px",fontFamily:'"Geist", system-ui',width:"160px"},gmTable:{marginTop:"20px",marginBottom:"32px",borderCollapse:"collapse",width:"100%",maxWidth:"520px",tableLayout:"fixed",textAlign:"center"},gmTableCell:{padding:"8px 12px",borderBottom:"1px solid #e5e7eb",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},gmLogoFixed:{position:"absolute",top:"16px",left:"24px",width:"120px",zIndex:10,pointerEvents:"none",background:"transparent",transform:"none",willChange:"auto"},gmLogoImg:{height:"clamp(28px, 5vw, 40px)",width:"auto",background:"transparent",objectFit:"contain",imageRendering:"auto",transform:"translateZ(0)",backfaceVisibility:"hidden",WebkitFontSmoothing:"antialiased"},gmHourglass:{fontSize:"42px",...ve}},je=()=>{const d=document.createElement("style");d.textContent=`
7
7
  /* scope only inside game root */
8
8
  #whats-missing-root, #whats-missing-root * { box-sizing: border-box; }
9
9
  #whats-missing-root img { max-width:100%; height:auto; display:block; user-select:none; }
10
- `,document.head.appendChild(a)},he=(a,m)=>{const c=Array.from({length:a.length+1},()=>Array(m.length+1).fill(0));for(let s=0;s<=a.length;s++)c[s][0]=s;for(let s=0;s<=m.length;s++)c[0][s]=s;for(let s=1;s<=a.length;s++)for(let u=1;u<=m.length;u++)c[s][u]=a[s-1]===m[u-1]?c[s-1][u-1]:Math.min(c[s-1][u-1],c[s][u-1],c[s-1][u])+1;return c[a.length][m.length]};function fe(a,m={minDesktopWidth:1200,forceResize:!1}){const c=o.useRef(null),s=m.minDesktopWidth??1200,u=!!m.forceResize;o.useLayoutEffect(()=>{if(typeof window>"u"||!a||!a.current)return;const i=a.current,L=()=>{try{const r=document.body,d=getComputedStyle(r),p=parseFloat(r.style&&r.style.zoom||"");if(p&&!isNaN(p)&&p>0)return p;const j=parseFloat(d.zoom||"");if(j&&!isNaN(j)&&j>0)return j;const k=d.transform||d.webkitTransform||d.mozTransform;if(k&&k!=="none"){const C=k.match(/matrix\(([^)]+)\)/);if(C){const v=C[1].split(",").map(B=>parseFloat(B))[0];if(v&&!isNaN(v))return v}}}catch{}return 1},b=()=>{if(s&&window.innerWidth<s){S();return}const r=L();if(!r||r===1){S();return}c.current||(c.current={transform:i.style.transform||null,transformOrigin:i.style.transformOrigin||null,width:i.style.width||null,height:i.style.height||null,willChange:i.style.willChange||null});const d=1/r;u&&(i.style.width=`${r*100}%`,i.style.height=`${r*100}%`),i.style.transform=`scale(${d})`,i.style.transformOrigin="top left",i.style.willChange="transform",typeof process<"u"&&process.env&&process.env.NODE_ENV==="development"&&console.debug("[useNeutralizeBodyZoom] applied scale:",{scale:r,inv:d,forceResize:u})},S=()=>{if(!c.current)return;const r=c.current;r.transform===null?i.style.removeProperty("transform"):i.style.transform=r.transform,r.transformOrigin===null?i.style.removeProperty("transform-origin"):i.style.transformOrigin=r.transformOrigin,r.width===null?i.style.removeProperty("width"):i.style.width=r.width,r.height===null?i.style.removeProperty("height"):i.style.height=r.height,r.willChange===null?i.style.removeProperty("will-change"):i.style.willChange=r.willChange,c.current=null,process.env.NODE_ENV==="development"&&console.debug("[useNeutralizeBodyZoom] restored original styles")};let x=0;const w=()=>{cancelAnimationFrame(x),x=requestAnimationFrame(b)},f=new MutationObserver(r=>{for(const d of r)if(d.type==="attributes"&&(d.attributeName==="style"||d.attributeName==="class")){w();break}});return f.observe(document.body,{attributes:!0,attributeFilter:["style","class"]}),window.addEventListener("resize",w),w(),()=>{cancelAnimationFrame(x),f.disconnect(),window.removeEventListener("resize",w),S()}},[a,s,u])}function pe(){const a=o.useRef(null);fe(a,{minDesktopWidth:1200,forceResize:!1}),o.useEffect(()=>(ge(),()=>{document.body.style.overflow=""}),[]);const[m,c]=o.useState(null),[s,u]=o.useState([]),[i,L]=o.useState(4),[b,S]=o.useState(!1),[x,w]=o.useState(1),[f,r]=o.useState(null),[d,p]=o.useState("ready"),[j,k]=o.useState(3),[C,M]=o.useState(10),[v,B]=o.useState(20),[Q,F]=o.useState(0),[A,W]=o.useState(!1),[H,q]=o.useState(""),[z,N]=o.useState(!1),[G,Y]=o.useState(!1),[$,R]=o.useState(null),[ee,O]=o.useState([]),[te,V]=o.useState([]),[y,ne]=o.useState(!1),[ye,Z]=o.useState(1),[_,U]=o.useState(null);o.useEffect(()=>{const t=()=>{const l=window.innerWidth<768;if(ne(l),l)U(null),Z(1);else{const T=Math.min(window.innerWidth,window.innerHeight),I=Math.max(360,Math.round(T*.97)),E=Math.min(I,1e3);U(E),Z(1)}};return t(),window.addEventListener("resize",t),()=>window.removeEventListener("resize",t)},[]);const K=t=>[...t].sort(()=>Math.random()-.5).slice(0,6),X=()=>{if(!m)return;const t=K(P[m]);u(t),S(!0),W(!1),w(1),F(0),O([]),V([]),J(t,[],!0)},J=(t=s,l=te,h=!1)=>{const g=K(t.length?t:P[m]);let T=Math.floor(Math.random()*g.length),I=g[T].name,E=0;for(;l.includes(I)&&E<20;)T=Math.floor(Math.random()*g.length),I=g[T].name,E++;u(g),r(T),V([...l,I]),N(!1),q(""),B(20),R(null),h?(p("ready"),k(3),M(10)):(p("memorize"),M(10))};o.useEffect(()=>{if(!(!b||A||z)){if(d==="ready")if(j<=0)p("memorize");else{const t=setTimeout(()=>k(l=>l-1),1e3);return()=>clearTimeout(t)}if(d==="memorize")if(C<=0)p("guess");else{const t=setTimeout(()=>M(l=>l-1),1e3);return()=>clearTimeout(t)}if(d==="guess"){if(v<=0){N(!0),R("wrong");const l=s[f].name;O(h=>[...h,{round:x,answer:H,correct:l,result:"wrong"}]);return}const t=setTimeout(()=>B(l=>l-1),1e3);return()=>clearTimeout(t)}}},[d,j,C,v,b,A,z,s,f,x,H]);const se=()=>{if(f===null||G)return;const t=s[f].name,l=H.toLowerCase().trim();let h="wrong";l===t?(F(g=>g+1),R("correct"),h="correct"):he(l,t)===1?(F(g=>g+.5),R("almost"),h="almost"):(R("wrong"),h="wrong"),O(g=>[...g,{round:x,answer:l,correct:t,result:h}]),Y(!0),setTimeout(()=>{Y(!1),N(!0)},600)},oe=()=>x<i?(w(t=>t+1),J()):W(!0),re=()=>{S(!1),W(!1),c(null)},ie=o.useMemo(()=>e.jsx("div",{style:{...n.gmLogoFixed,position:"absolute",top:16,left:16,zIndex:30},children:e.jsxs("picture",{children:[e.jsx("source",{srcSet:window.origin+"/cloud/speakid/games/whatsmissing/logo.svg",type:"image/svg+xml"}),e.jsx("img",{src:window.origin+"/cloud/speakid/games/whatsmissing/logo.png",alt:"SPEAKID Logo",style:n.gmLogoImg,loading:"lazy"})]})}),[]);return e.jsx("div",{ref:a,style:{width:"100%",height:"100vh",display:"flex",justifyContent:"center",alignItems:"center",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",transition:"background 0.3s ease",overflowX:"clip"},children:e.jsx("div",{style:{width:y?"100%":_||1e3,height:y?"100%":_||1e3,display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",borderRadius:y?0:"20px",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",boxShadow:y?"none":"0 0 40px rgba(0,0,0,0.1)",margin:y?"0 auto":"unset",position:"relative"},children:e.jsx("div",{style:{transform:"none",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center"},children:e.jsxs("div",{id:"whats-missing-root",children:[!y&&ie,!m&&!b&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"WHAT'S MISSING?"}),e.jsx("p",{style:n.gmBodyM,children:"Select a theme:"}),e.jsx("div",{style:{display:"flex",gap:16},children:["animals","food","toys"].map(t=>e.jsx("button",{style:n.gmButton,onClick:()=>c(t),children:t==="animals"?"🐶 Animals":t==="food"?"🍎 Food":"🧸 Toys"},t))}),e.jsxs("div",{style:{marginTop:24},children:[e.jsx("p",{style:n.gmBodyS,children:"Choose number of rounds:"}),e.jsx("div",{style:{display:"flex",gap:12,marginTop:8},children:[3,4,5].map(t=>e.jsx("button",{style:{...n.gmButton,...i===t?n.gmButtonActive:{}},onClick:()=>L(t),children:t},t))})]})]}),m&&!b&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsxs("h1",{style:n.gmHeadline1,children:["Theme selected: ",m]}),e.jsxs("p",{style:n.gmBodyM,children:["Rounds: ",i]}),e.jsx("button",{style:n.gmButton,onClick:X,children:"▶ Start game"})]}),A&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"Results"}),e.jsxs("h2",{style:n.gmHeadline3,children:["Your score: ",Q," / ",i]}),e.jsx("p",{style:{...n.gmBodyM,color:"#10b981",marginTop:12},children:"Yahoo! You did it! 🍬✨"}),e.jsxs("table",{style:n.gmTable,children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Round"}),e.jsx("th",{children:"Your Answer"}),e.jsx("th",{children:"Correct"}),e.jsx("th",{children:"Result"})]})}),e.jsx("tbody",{children:ee.map((t,l)=>e.jsxs("tr",{children:[e.jsx("td",{style:n.gmTableCell,children:t.round}),e.jsx("td",{style:n.gmTableCell,children:t.answer||"—"}),e.jsx("td",{style:n.gmTableCell,children:t.correct}),e.jsx("td",{style:n.gmTableCell,children:t.result==="correct"?"✔ Correct":t.result==="almost"?"◐ Almost (0.5)":"✘ Wrong"})]},l))})]}),e.jsxs("div",{style:{display:"flex",gap:12,marginTop:24},children:[e.jsx("button",{style:n.gmButton,onClick:X,children:"🔁 Play again"}),e.jsx("button",{style:n.gmButton,onClick:re,children:"⬅️ Choose theme"})]})]}),b&&!A&&e.jsxs("div",{style:n.gmGameLayout,children:[e.jsxs("div",{style:{minHeight:160,display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center"},children:[d==="ready"&&e.jsxs(e.Fragment,{children:[e.jsx("h1",{style:{...n.gmHeadline1,color:"#ec4c44"},children:"GET READY"}),e.jsx("div",{style:n.gmHourglass,children:"⏳"})]}),d==="memorize"&&e.jsxs("p",{style:{...n.gmBodyM,color:"#10b981"},children:["MEMORIZE (",C,")"]}),d==="guess"&&!z&&e.jsxs("p",{style:n.gmBodyM,children:["⏳ Time left: ",v,"s"]})]}),d!=="ready"&&e.jsx("div",{style:{...n.gmGrid,gridTemplateColumns:y?"repeat(2, 1fr)":"repeat(3, 210px)",gridAutoRows:y?"150px":"210px",gap:y?"12px":"20px",justifyItems:"center"},children:s.map((t,l)=>{const h=f===l&&d==="guess"&&!z;return e.jsx("div",{style:{...n.gmCard,...$==="correct"&&f===l?n.gmCorrect:{},...$==="wrong"&&f===l?n.gmWrong:{}},children:!h&&e.jsx("img",{src:t.src,alt:t.name,style:{width:"100%",height:"100%",objectFit:"cover"}})},l)})}),e.jsxs("div",{style:{marginTop:16,height:80},children:[d==="guess"&&!z&&e.jsxs("div",{children:[e.jsx("input",{type:"text",placeholder:"Type the missing word",value:H,onChange:t=>q(t.target.value),style:n.gmInput}),e.jsx("button",{style:{...n.gmButton,marginLeft:8},onClick:se,disabled:G,children:G?"...":"Check"})]}),z&&e.jsx("button",{type:"button",style:n.gmButton,onClick:oe,children:"Next round"})]})]})]})})})})}exports.Game=pe;exports.themes=P;
10
+ `,document.head.appendChild(d)},Se=(d,f)=>{const g=Array.from({length:d.length+1},()=>Array(f.length+1).fill(0));for(let i=0;i<=d.length;i++)g[i][0]=i;for(let i=0;i<=f.length;i++)g[0][i]=i;for(let i=1;i<=d.length;i++)for(let m=1;m<=f.length;m++)g[i][m]=d[i-1]===f[m-1]?g[i-1][m-1]:Math.min(g[i-1][m-1],g[i][m-1],g[i-1][m])+1;return g[d.length][f.length]};function ze(d,f={minDesktopWidth:1200,forceResize:!1}){const g=s.useRef(null),i=f.minDesktopWidth??1200,m=!!f.forceResize;s.useLayoutEffect(()=>{if(typeof window>"u"||!d||!d.current)return;const l=d.current,z=()=>{try{const r=document.body,y=getComputedStyle(r),p=parseFloat(r.style&&r.style.zoom||"");if(p&&!isNaN(p)&&p>0)return p;const C=parseFloat(y.zoom||"");if(C&&!isNaN(C)&&C>0)return C;const c=document.createElement("div");c.style.position="absolute",c.style.left="0",c.style.top="0",c.style.width="100px",c.style.height="1px",c.style.visibility="hidden",c.style.pointerEvents="none",document.documentElement.appendChild(c);const S=c.getBoundingClientRect();if(document.documentElement.removeChild(c),S&&S.width>0){const k=S.width/100;if(!isNaN(k)&&k>0)return k}}catch{}return 1},L=()=>{if(i&&window.innerWidth<i){j();return}const r=z();if(!r||r===1){j();return}g.current||(g.current={transform:l.style.transform||null,transformOrigin:l.style.transformOrigin||null,width:l.style.width||null,height:l.style.height||null,willChange:l.style.willChange||null});const y=1/r;m&&(l.style.width=`${r*100}%`,l.style.height=`${r*100}%`),l.style.transformOrigin="50% 50%",l.style.transform=`scale(${y})`,l.style.willChange="transform",typeof process<"u"&&process.env?.NODE_ENV==="development"&&console.debug("[useNeutralizeBodyZoom] applied scale:",{scale:r,inv:y,forceResize:m})},j=()=>{if(!g.current)return;const r=g.current;r.transform===null?l.style.removeProperty("transform"):l.style.transform=r.transform,r.transformOrigin===null?l.style.removeProperty("transform-origin"):l.style.transformOrigin=r.transformOrigin,r.width===null?l.style.removeProperty("width"):l.style.width=r.width,r.height===null?l.style.removeProperty("height"):l.style.height=r.height,r.willChange===null?l.style.removeProperty("will-change"):l.style.willChange=r.willChange,g.current=null,typeof process<"u"&&process.env?.NODE_ENV==="development"&&console.debug("[useNeutralizeBodyZoom] restored original styles")};let M=0;const x=()=>{cancelAnimationFrame(M),M=requestAnimationFrame(L)},T=new MutationObserver(r=>{for(const y of r)if(y.type==="attributes"&&(y.attributeName==="style"||y.attributeName==="class")){x();break}});return T.observe(document.body,{attributes:!0,attributeFilter:["style","class"]}),window.addEventListener("resize",x),x(),()=>{cancelAnimationFrame(M),T.disconnect(),window.removeEventListener("resize",x),j()}},[d,i,m])}function Ce(){const d=s.useRef(null),f=s.useRef(null),[g,i]=s.useState(null);ze(d,{minDesktopWidth:1200,forceResize:!1}),s.useEffect(()=>(je(),()=>{document.body.style.overflow=""}),[]);const[m,l]=s.useState(null),[z,L]=s.useState([]),[j,M]=s.useState(4),[x,T]=s.useState(!1),[r,y]=s.useState(1),[p,C]=s.useState(null),[c,S]=s.useState("ready"),[k,Y]=s.useState(3),[A,W]=s.useState(10),[F,Z]=s.useState(20),[oe,N]=s.useState(0),[H,O]=s.useState(!1),[B,_]=s.useState(""),[R,$]=s.useState(!1),[G,U]=s.useState(!1),[K,E]=s.useState(null),[se,D]=s.useState([]),[ie,J]=s.useState([]),[b,re]=s.useState(!1),[I,Q]=s.useState(null);s.useLayoutEffect(()=>{const t=f.current;if(!t||typeof window>"u")return;let o=null;try{o=new ResizeObserver(u=>{for(const a of u){const h=a.contentRect;i(Math.round(Math.min(h.width,h.height)))}}),o.observe(t),i(Math.round(Math.min(t.clientWidth,t.clientHeight)))}catch{i(Math.round(Math.min(t.clientWidth,t.clientHeight)))}return()=>{o&&o.disconnect()}},[b]),s.useEffect(()=>{const t=()=>{const u=window.visualViewport,a=u?Math.round(u.width):window.innerWidth,h=u?Math.round(u.height):window.innerHeight,w=a<768||a>=768&&a<1200&&h>a;if(re(w),w){Q(null);return}const v=.92,P=1e3,ne=40,me=Math.max(360,a-ne),ue=Math.max(360,h-ne),ge=Math.min(me,ue),he=Math.max(360,Math.round(ge*v)),fe=Math.min(he,P);Q(fe)};t(),window.addEventListener("resize",t);const o=window.visualViewport;return o&&typeof o.addEventListener=="function"&&(o.addEventListener("resize",t),o.addEventListener("scroll",t)),()=>{window.removeEventListener("resize",t),o&&typeof o.removeEventListener=="function"&&(o.removeEventListener("resize",t),o.removeEventListener("scroll",t))}},[]);const X=t=>[...t].sort(()=>Math.random()-.5).slice(0,6),ee=()=>{if(!m)return;const t=X(V[m]);L(t),T(!0),O(!1),y(1),N(0),D([]),J([]),te(t,[],!0)},te=(t=z,o=ie,u=!1)=>{const a=X(t.length?t:V[m]);let h=Math.floor(Math.random()*a.length),w=a[h].name,v=0;for(;o.includes(w)&&v<20;)h=Math.floor(Math.random()*a.length),w=a[h].name,v++;L(a),C(h),J([...o,w]),$(!1),_(""),Z(20),E(null),u?(S("ready"),Y(3),W(10)):(S("memorize"),W(10))};s.useEffect(()=>{if(!(!x||H||R)){if(c==="ready")if(k<=0)S("memorize");else{const t=setTimeout(()=>Y(o=>o-1),1e3);return()=>clearTimeout(t)}if(c==="memorize")if(A<=0)S("guess");else{const t=setTimeout(()=>W(o=>o-1),1e3);return()=>clearTimeout(t)}if(c==="guess"){if(F<=0){$(!0),E("wrong");const o=z[p].name;D(u=>[...u,{round:r,answer:B,correct:o,result:"wrong"}]);return}const t=setTimeout(()=>Z(o=>o-1),1e3);return()=>clearTimeout(t)}}},[c,k,A,F,x,H,R,z,p,r,B]);const le=()=>{if(p===null||G)return;const t=z[p].name,o=B.toLowerCase().trim();let u="wrong";o===t?(N(a=>a+1),E("correct"),u="correct"):Se(o,t)===1?(N(a=>a+.5),E("almost"),u="almost"):(E("wrong"),u="wrong"),D(a=>[...a,{round:r,answer:o,correct:t,result:u}]),U(!0),setTimeout(()=>{U(!1),$(!0)},600)},ae=()=>r<j?(y(t=>t+1),te()):O(!0),de=()=>{T(!1),O(!1),l(null)},ce=s.useMemo(()=>e.jsx("div",{style:{...n.gmLogoFixed,position:"absolute",top:16,left:16,zIndex:30},children:e.jsxs("picture",{children:[e.jsx("source",{srcSet:window.origin+"/cloud/speakid/games/whatsmissing/logo.svg",type:"image/svg+xml"}),e.jsx("img",{src:window.origin+"/cloud/speakid/games/whatsmissing/logo.png",alt:"SPEAKID Logo",style:n.gmLogoImg,loading:"lazy"})]})}),[]);return e.jsx("div",{ref:d,style:{position:"fixed",inset:0,display:"flex",justifyContent:"center",alignItems:"center",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",transition:"background 0.3s ease",overflow:"hidden",zIndex:9999,pointerEvents:"auto"},children:e.jsx("div",{ref:f,style:{width:b?"100%":I?`${I}px`:"1000px",height:b?"100%":I?`${I}px`:"1000px",maxWidth:"calc(100vw - 24px)",maxHeight:"calc(100vh - 24px)",display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",borderRadius:b?0:"20px",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",boxShadow:b?"none":"0 0 40px rgba(0,0,0,0.08)",position:"relative"},children:e.jsx("div",{style:{transform:"none",transformOrigin:"50% 50%",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center"},children:e.jsxs("div",{id:"whats-missing-root",children:[!b&&ce,!m&&!x&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"WHAT'S MISSING?"}),e.jsx("p",{style:n.gmBodyM,children:"Select a theme:"}),e.jsx("div",{style:{display:"flex",gap:16},children:["animals","food","toys"].map(t=>e.jsx("button",{style:n.gmButton,onClick:()=>l(t),children:t==="animals"?"🐶 Animals":t==="food"?"🍎 Food":"🧸 Toys"},t))}),e.jsxs("div",{style:{marginTop:24},children:[e.jsx("p",{style:n.gmBodyS,children:"Choose number of rounds:"}),e.jsx("div",{style:{display:"flex",gap:12,marginTop:8},children:[3,4,5].map(t=>e.jsx("button",{style:{...n.gmButton,...j===t?n.gmButtonActive:{}},onClick:()=>M(t),children:t},t))})]})]}),m&&!x&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsxs("h1",{style:n.gmHeadline1,children:["Theme selected: ",m]}),e.jsxs("p",{style:n.gmBodyM,children:["Rounds: ",j]}),e.jsx("button",{style:n.gmButton,onClick:ee,children:"▶ Start game"})]}),H&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"Results"}),e.jsxs("h2",{style:n.gmHeadline3,children:["Your score: ",oe," / ",j]}),e.jsx("p",{style:{...n.gmBodyM,color:"#10b981",marginTop:12},children:"Yahoo! You did it! 🍬✨"}),e.jsxs("table",{style:n.gmTable,children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Round"}),e.jsx("th",{children:"Your Answer"}),e.jsx("th",{children:"Correct"}),e.jsx("th",{children:"Result"})]})}),e.jsx("tbody",{children:se.map((t,o)=>e.jsxs("tr",{children:[e.jsx("td",{style:n.gmTableCell,children:t.round}),e.jsx("td",{style:n.gmTableCell,children:t.answer||"—"}),e.jsx("td",{style:n.gmTableCell,children:t.correct}),e.jsx("td",{style:n.gmTableCell,children:t.result==="correct"?"✔ Correct":t.result==="almost"?"◐ Almost (0.5)":"✘ Wrong"})]},o))})]}),e.jsxs("div",{style:{display:"flex",gap:12,marginTop:24},children:[e.jsx("button",{style:n.gmButton,onClick:ee,children:"🔁 Play again"}),e.jsx("button",{style:n.gmButton,onClick:de,children:"⬅️ Choose theme"})]})]}),x&&!H&&e.jsxs("div",{style:n.gmGameLayout,children:[e.jsxs("div",{style:{minHeight:160,display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center"},children:[c==="ready"&&e.jsxs(e.Fragment,{children:[e.jsx("h1",{style:{...n.gmHeadline1,color:"#ec4c44"},children:"GET READY"}),e.jsx("div",{style:n.gmHourglass,children:"⏳"})]}),c==="memorize"&&e.jsxs("p",{style:{...n.gmBodyM,color:"#10b981"},children:["MEMORIZE (",A,")"]}),c==="guess"&&!R&&e.jsxs("p",{style:n.gmBodyM,children:["⏳ Time left: ",F,"s"]})]}),c!=="ready"&&(()=>{const t=b?12:20,o=32,u=g??I??(b?360:1e3),a=b?2:3,h=Math.max(90,Math.floor((u-o-t*(a-1))/a));return e.jsx("div",{style:{...n.gmGrid,gridTemplateColumns:`repeat(${a}, ${h}px)`,gridAutoRows:`${h}px`,gap:`${t}px`,justifyContent:"center",justifyItems:"center",padding:8,boxSizing:"border-box"},children:z.map((w,v)=>{const P=p===v&&c==="guess"&&!R;return e.jsx("div",{style:{...n.gmCard,width:`${h}px`,height:`${h}px`,padding:b?8:10,boxSizing:"border-box",...K==="correct"&&p===v?n.gmCorrect:{},...K==="wrong"&&p===v?n.gmWrong:{}},children:!P&&e.jsx("img",{src:w.src,alt:w.name,style:{width:"100%",height:"100%",objectFit:"cover",display:"block"}})},v)})})})(),e.jsxs("div",{style:{marginTop:16,height:80},children:[c==="guess"&&!R&&e.jsxs("div",{children:[e.jsx("input",{type:"text",placeholder:"Type the missing word",value:B,onChange:t=>_(t.target.value),style:n.gmInput}),e.jsx("button",{style:{...n.gmButton,marginLeft:8},onClick:le,disabled:G,children:G?"...":"Check"})]}),R&&e.jsx("button",{type:"button",style:n.gmButton,onClick:ae,children:"Next round"})]})]})]})})})})}exports.Game=Ce;exports.themes=V;
@@ -1,9 +1,9 @@
1
- import { jsx as t, jsxs as c, Fragment as ce } from "react/jsx-runtime";
2
- import { useRef as te, useEffect as P, useState as d, useMemo as me, useLayoutEffect as ge } from "react";
3
- const ue = window.origin + "/cloud/speakid/games/whatsmissing/", Y = (l, g) => l.map(([m, o]) => ({
4
- src: `${ue}${g}/${o.replace(/\s/g, "%20")}.png`,
5
- name: o
6
- })), he = [
1
+ import { jsx as t, jsxs as m, Fragment as we } from "react/jsx-runtime";
2
+ import { useRef as Z, useState as c, useEffect as V, useLayoutEffect as se, useMemo as ve } from "react";
3
+ const ze = window.origin + "/cloud/speakid/games/whatsmissing/", Y = (a, f) => a.map(([h, i]) => ({
4
+ src: `${ze}${f}/${i.replace(/\s/g, "%20")}.png`,
5
+ name: i
6
+ })), Ce = [
7
7
  ["apple", "apple"],
8
8
  ["banana", "banana"],
9
9
  ["bread", "bread"],
@@ -29,7 +29,7 @@ const ue = window.origin + "/cloud/speakid/games/whatsmissing/", Y = (l, g) => l
29
29
  ["strawberry", "strawberry"],
30
30
  ["tomato", "tomato"],
31
31
  ["watermelon", "watermelon"]
32
- ], pe = [
32
+ ], ke = [
33
33
  ["bear", "bear"],
34
34
  ["cat", "cat"],
35
35
  ["crocodile", "crocodile"],
@@ -49,7 +49,7 @@ const ue = window.origin + "/cloud/speakid/games/whatsmissing/", Y = (l, g) => l
49
49
  ["tortoise", "tortoise"],
50
50
  ["wolf", "wolf"],
51
51
  ["zebra", "zebra"]
52
- ], fe = [
52
+ ], Se = [
53
53
  ["ball", "ball"],
54
54
  ["balloon", "balloon"],
55
55
  ["blocks", "blocks"],
@@ -69,27 +69,29 @@ const ue = window.origin + "/cloud/speakid/games/whatsmissing/", Y = (l, g) => l
69
69
  ["teddy_bear", "teddy bear"],
70
70
  ["yo-yo", "yo-yo"],
71
71
  ["crayons", "crayons"]
72
- ], ee = {
73
- food: Y(he, "food"),
74
- animals: Y(pe, "animals"),
75
- toys: Y(fe, "toys")
76
- }, ye = `
72
+ ], re = {
73
+ food: Y(Ce, "food"),
74
+ animals: Y(ke, "animals"),
75
+ toys: Y(Se, "toys")
76
+ }, Me = `
77
77
  @keyframes spin {
78
78
  from { transform: rotate(0deg); }
79
79
  to { transform: rotate(360deg); }
80
80
  }
81
81
  `;
82
82
  if (typeof document < "u" && !document.getElementById("spin-keyframes")) {
83
- const l = document.createElement("style");
84
- l.id = "spin-keyframes", l.innerHTML = ye, document.head.appendChild(l);
83
+ const a = document.createElement("style");
84
+ a.id = "spin-keyframes", a.innerHTML = Me, document.head.appendChild(a);
85
85
  }
86
- const be = {
86
+ const Te = {
87
87
  animation: "spin 1.4s linear infinite"
88
88
  }, n = {
89
89
  gmCenterScreen: {
90
90
  position: "relative",
91
91
  zIndex: 1,
92
- minHeight: "100vh",
92
+ // use percent so the component fits inside the centered square container
93
+ // (100vh caused overflow on tablets because it measured the viewport, not the container)
94
+ minHeight: "100%",
93
95
  width: "100%",
94
96
  display: "flex",
95
97
  flexDirection: "column",
@@ -144,7 +146,8 @@ const be = {
144
146
  position: "relative",
145
147
  width: "100%",
146
148
  maxWidth: "600px",
147
- minHeight: "100vh",
149
+ // should fill the parent square, not the full viewport
150
+ minHeight: "100%",
148
151
  display: "flex",
149
152
  flexDirection: "column",
150
153
  alignItems: "center",
@@ -233,155 +236,175 @@ const be = {
233
236
  },
234
237
  gmHourglass: {
235
238
  fontSize: "42px",
236
- ...be
239
+ ...Te
237
240
  }
238
- }, xe = () => {
239
- const l = document.createElement("style");
240
- l.textContent = `
241
+ }, Re = () => {
242
+ const a = document.createElement("style");
243
+ a.textContent = `
241
244
  /* scope only inside game root */
242
245
  #whats-missing-root, #whats-missing-root * { box-sizing: border-box; }
243
246
  #whats-missing-root img { max-width:100%; height:auto; display:block; user-select:none; }
244
- `, document.head.appendChild(l);
245
- }, we = (l, g) => {
246
- const m = Array.from({ length: l.length + 1 }, () => Array(g.length + 1).fill(0));
247
- for (let o = 0; o <= l.length; o++) m[o][0] = o;
248
- for (let o = 0; o <= g.length; o++) m[0][o] = o;
249
- for (let o = 1; o <= l.length; o++)
250
- for (let u = 1; u <= g.length; u++)
251
- m[o][u] = l[o - 1] === g[u - 1] ? m[o - 1][u - 1] : Math.min(m[o - 1][u - 1], m[o][u - 1], m[o - 1][u]) + 1;
252
- return m[l.length][g.length];
247
+ `, document.head.appendChild(a);
248
+ }, Ee = (a, f) => {
249
+ const h = Array.from({ length: a.length + 1 }, () => Array(f.length + 1).fill(0));
250
+ for (let i = 0; i <= a.length; i++) h[i][0] = i;
251
+ for (let i = 0; i <= f.length; i++) h[0][i] = i;
252
+ for (let i = 1; i <= a.length; i++)
253
+ for (let u = 1; u <= f.length; u++)
254
+ h[i][u] = a[i - 1] === f[u - 1] ? h[i - 1][u - 1] : Math.min(h[i - 1][u - 1], h[i][u - 1], h[i - 1][u]) + 1;
255
+ return h[a.length][f.length];
253
256
  };
254
- function ve(l, g = { minDesktopWidth: 1200, forceResize: !1 }) {
255
- const m = te(null), o = g.minDesktopWidth ?? 1200, u = !!g.forceResize;
256
- ge(() => {
257
- if (typeof window > "u" || !l || !l.current) return;
258
- const i = l.current, E = () => {
257
+ function Ie(a, f = { minDesktopWidth: 1200, forceResize: !1 }) {
258
+ const h = Z(null), i = f.minDesktopWidth ?? 1200, u = !!f.forceResize;
259
+ se(() => {
260
+ if (typeof window > "u" || !a || !a.current) return;
261
+ const s = a.current, S = () => {
259
262
  try {
260
- const r = document.body, a = getComputedStyle(r), y = parseFloat(r.style && r.style.zoom || "");
263
+ const r = document.body, b = getComputedStyle(r), y = parseFloat(r.style && r.style.zoom || "");
261
264
  if (y && !isNaN(y) && y > 0) return y;
262
- const k = parseFloat(a.zoom || "");
263
- if (k && !isNaN(k) && k > 0) return k;
264
- const S = a.transform || a.webkitTransform || a.mozTransform;
265
- if (S && S !== "none") {
266
- const T = S.match(/matrix\(([^)]+)\)/);
267
- if (T) {
268
- const C = T[1].split(",").map((H) => parseFloat(H))[0];
269
- if (C && !isNaN(C)) return C;
270
- }
265
+ const M = parseFloat(b.zoom || "");
266
+ if (M && !isNaN(M) && M > 0) return M;
267
+ const d = document.createElement("div");
268
+ d.style.position = "absolute", d.style.left = "0", d.style.top = "0", d.style.width = "100px", d.style.height = "1px", d.style.visibility = "hidden", d.style.pointerEvents = "none", document.documentElement.appendChild(d);
269
+ const k = d.getBoundingClientRect();
270
+ if (document.documentElement.removeChild(d), k && k.width > 0) {
271
+ const T = k.width / 100;
272
+ if (!isNaN(T) && T > 0) return T;
271
273
  }
272
274
  } catch {
273
275
  }
274
276
  return 1;
275
- }, w = () => {
276
- if (o && window.innerWidth < o) {
277
- z();
277
+ }, B = () => {
278
+ if (i && window.innerWidth < i) {
279
+ C();
278
280
  return;
279
281
  }
280
- const r = E();
282
+ const r = S();
281
283
  if (!r || r === 1) {
282
- z();
284
+ C();
283
285
  return;
284
286
  }
285
- m.current || (m.current = {
286
- transform: i.style.transform || null,
287
- transformOrigin: i.style.transformOrigin || null,
288
- width: i.style.width || null,
289
- height: i.style.height || null,
290
- willChange: i.style.willChange || null
287
+ h.current || (h.current = {
288
+ transform: s.style.transform || null,
289
+ transformOrigin: s.style.transformOrigin || null,
290
+ width: s.style.width || null,
291
+ height: s.style.height || null,
292
+ willChange: s.style.willChange || null
291
293
  });
292
- const a = 1 / r;
293
- u && (i.style.width = `${r * 100}%`, i.style.height = `${r * 100}%`), i.style.transform = `scale(${a})`, i.style.transformOrigin = "top left", i.style.willChange = "transform", typeof process < "u" && process.env && process.env.NODE_ENV === "development" && console.debug("[useNeutralizeBodyZoom] applied scale:", { scale: r, inv: a, forceResize: u });
294
- }, z = () => {
295
- if (!m.current) return;
296
- const r = m.current;
297
- r.transform === null ? i.style.removeProperty("transform") : i.style.transform = r.transform, r.transformOrigin === null ? i.style.removeProperty("transform-origin") : i.style.transformOrigin = r.transformOrigin, r.width === null ? i.style.removeProperty("width") : i.style.width = r.width, r.height === null ? i.style.removeProperty("height") : i.style.height = r.height, r.willChange === null ? i.style.removeProperty("will-change") : i.style.willChange = r.willChange, m.current = null, process.env.NODE_ENV === "development" && console.debug("[useNeutralizeBodyZoom] restored original styles");
294
+ const b = 1 / r;
295
+ u && (s.style.width = `${r * 100}%`, s.style.height = `${r * 100}%`), s.style.transformOrigin = "50% 50%", s.style.transform = `scale(${b})`, s.style.willChange = "transform", typeof process < "u" && process.env?.NODE_ENV === "development" && console.debug("[useNeutralizeBodyZoom] applied scale:", { scale: r, inv: b, forceResize: u });
296
+ }, C = () => {
297
+ if (!h.current) return;
298
+ const r = h.current;
299
+ r.transform === null ? s.style.removeProperty("transform") : s.style.transform = r.transform, r.transformOrigin === null ? s.style.removeProperty("transform-origin") : s.style.transformOrigin = r.transformOrigin, r.width === null ? s.style.removeProperty("width") : s.style.width = r.width, r.height === null ? s.style.removeProperty("height") : s.style.height = r.height, r.willChange === null ? s.style.removeProperty("will-change") : s.style.willChange = r.willChange, h.current = null, typeof process < "u" && process.env?.NODE_ENV === "development" && console.debug("[useNeutralizeBodyZoom] restored original styles");
298
300
  };
299
- let x = 0;
300
- const v = () => {
301
- cancelAnimationFrame(x), x = requestAnimationFrame(w);
302
- }, f = new MutationObserver((r) => {
303
- for (const a of r)
304
- if (a.type === "attributes" && (a.attributeName === "style" || a.attributeName === "class")) {
305
- v();
301
+ let E = 0;
302
+ const x = () => {
303
+ cancelAnimationFrame(E), E = requestAnimationFrame(B);
304
+ }, I = new MutationObserver((r) => {
305
+ for (const b of r)
306
+ if (b.type === "attributes" && (b.attributeName === "style" || b.attributeName === "class")) {
307
+ x();
306
308
  break;
307
309
  }
308
310
  });
309
- return f.observe(document.body, { attributes: !0, attributeFilter: ["style", "class"] }), window.addEventListener("resize", v), v(), () => {
310
- cancelAnimationFrame(x), f.disconnect(), window.removeEventListener("resize", v), z();
311
+ return I.observe(document.body, { attributes: !0, attributeFilter: ["style", "class"] }), window.addEventListener("resize", x), x(), () => {
312
+ cancelAnimationFrame(E), I.disconnect(), window.removeEventListener("resize", x), C();
311
313
  };
312
- }, [l, o, u]);
314
+ }, [a, i, u]);
313
315
  }
314
- function Se() {
315
- const l = te(null);
316
- ve(l, { minDesktopWidth: 1200, forceResize: !1 }), P(() => (xe(), () => {
316
+ function Be() {
317
+ const a = Z(null), f = Z(null), [h, i] = c(null);
318
+ Ie(a, { minDesktopWidth: 1200, forceResize: !1 }), V(() => (Re(), () => {
317
319
  document.body.style.overflow = "";
318
320
  }), []);
319
- const [g, m] = d(null), [o, u] = d([]), [i, E] = d(4), [w, z] = d(!1), [x, v] = d(1), [f, r] = d(null), [a, y] = d("ready"), [k, S] = d(3), [T, A] = d(10), [C, H] = d(20), [ne, N] = d(0), [L, j] = d(!1), [F, $] = d(""), [R, D] = d(!1), [G, q] = d(!1), [V, M] = d(null), [oe, O] = d([]), [re, Z] = d([]), [b, ie] = d(!1), [ke, _] = d(1), [U, K] = d(null);
320
- P(() => {
321
+ const [u, s] = c(null), [S, B] = c([]), [C, E] = c(4), [x, I] = c(!1), [r, b] = c(1), [y, M] = c(null), [d, k] = c("ready"), [T, _] = c(3), [j, F] = c(10), [N, U] = c(20), [le, $] = c(0), [A, O] = c(!1), [W, K] = c(""), [R, G] = c(!1), [D, J] = c(!1), [Q, H] = c(null), [ae, P] = c([]), [de, X] = c([]), [w, ce] = c(!1), [L, ee] = c(null);
322
+ se(() => {
323
+ const e = f.current;
324
+ if (!e || typeof window > "u") return;
325
+ let o = null;
326
+ try {
327
+ o = new ResizeObserver((g) => {
328
+ for (const l of g) {
329
+ const p = l.contentRect;
330
+ i(Math.round(Math.min(p.width, p.height)));
331
+ }
332
+ }), o.observe(e), i(Math.round(Math.min(e.clientWidth, e.clientHeight)));
333
+ } catch {
334
+ i(Math.round(Math.min(e.clientWidth, e.clientHeight)));
335
+ }
336
+ return () => {
337
+ o && o.disconnect();
338
+ };
339
+ }, [w]), V(() => {
321
340
  const e = () => {
322
- const s = window.innerWidth < 768;
323
- if (ie(s), s)
324
- K(null), _(1);
325
- else {
326
- const I = Math.min(window.innerWidth, window.innerHeight), B = Math.max(360, Math.round(I * 0.97)), W = Math.min(B, 1e3);
327
- K(W), _(1);
341
+ const g = window.visualViewport, l = g ? Math.round(g.width) : window.innerWidth, p = g ? Math.round(g.height) : window.innerHeight, v = l < 768 || l >= 768 && l < 1200 && p > l;
342
+ if (ce(v), v) {
343
+ ee(null);
344
+ return;
328
345
  }
346
+ const z = 0.92, q = 1e3, ie = 40, pe = Math.max(360, l - ie), fe = Math.max(360, p - ie), ye = Math.min(pe, fe), be = Math.max(360, Math.round(ye * z)), xe = Math.min(be, q);
347
+ ee(xe);
348
+ };
349
+ e(), window.addEventListener("resize", e);
350
+ const o = window.visualViewport;
351
+ return o && typeof o.addEventListener == "function" && (o.addEventListener("resize", e), o.addEventListener("scroll", e)), () => {
352
+ window.removeEventListener("resize", e), o && typeof o.removeEventListener == "function" && (o.removeEventListener("resize", e), o.removeEventListener("scroll", e));
329
353
  };
330
- return e(), window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
331
354
  }, []);
332
- const X = (e) => [...e].sort(() => Math.random() - 0.5).slice(0, 6), J = () => {
333
- if (!g) return;
334
- const e = X(ee[g]);
335
- u(e), z(!0), j(!1), v(1), N(0), O([]), Z([]), Q(e, [], !0);
336
- }, Q = (e = o, s = re, p = !1) => {
337
- const h = X(e.length ? e : ee[g]);
338
- let I = Math.floor(Math.random() * h.length), B = h[I].name, W = 0;
339
- for (; s.includes(B) && W < 20; )
340
- I = Math.floor(Math.random() * h.length), B = h[I].name, W++;
341
- u(h), r(I), Z([...s, B]), D(!1), $(""), H(20), M(null), p ? (y("ready"), S(3), A(10)) : (y("memorize"), A(10));
355
+ const te = (e) => [...e].sort(() => Math.random() - 0.5).slice(0, 6), ne = () => {
356
+ if (!u) return;
357
+ const e = te(re[u]);
358
+ B(e), I(!0), O(!1), b(1), $(0), P([]), X([]), oe(e, [], !0);
359
+ }, oe = (e = S, o = de, g = !1) => {
360
+ const l = te(e.length ? e : re[u]);
361
+ let p = Math.floor(Math.random() * l.length), v = l[p].name, z = 0;
362
+ for (; o.includes(v) && z < 20; )
363
+ p = Math.floor(Math.random() * l.length), v = l[p].name, z++;
364
+ B(l), M(p), X([...o, v]), G(!1), K(""), U(20), H(null), g ? (k("ready"), _(3), F(10)) : (k("memorize"), F(10));
342
365
  };
343
- P(() => {
344
- if (!(!w || L || R)) {
345
- if (a === "ready")
346
- if (k <= 0) y("memorize");
366
+ V(() => {
367
+ if (!(!x || A || R)) {
368
+ if (d === "ready")
369
+ if (T <= 0) k("memorize");
347
370
  else {
348
- const e = setTimeout(() => S((s) => s - 1), 1e3);
371
+ const e = setTimeout(() => _((o) => o - 1), 1e3);
349
372
  return () => clearTimeout(e);
350
373
  }
351
- if (a === "memorize")
352
- if (T <= 0) y("guess");
374
+ if (d === "memorize")
375
+ if (j <= 0) k("guess");
353
376
  else {
354
- const e = setTimeout(() => A((s) => s - 1), 1e3);
377
+ const e = setTimeout(() => F((o) => o - 1), 1e3);
355
378
  return () => clearTimeout(e);
356
379
  }
357
- if (a === "guess") {
358
- if (C <= 0) {
359
- D(!0), M("wrong");
360
- const s = o[f].name;
361
- O((p) => [
362
- ...p,
363
- { round: x, answer: F, correct: s, result: "wrong" }
380
+ if (d === "guess") {
381
+ if (N <= 0) {
382
+ G(!0), H("wrong");
383
+ const o = S[y].name;
384
+ P((g) => [
385
+ ...g,
386
+ { round: r, answer: W, correct: o, result: "wrong" }
364
387
  ]);
365
388
  return;
366
389
  }
367
- const e = setTimeout(() => H((s) => s - 1), 1e3);
390
+ const e = setTimeout(() => U((o) => o - 1), 1e3);
368
391
  return () => clearTimeout(e);
369
392
  }
370
393
  }
371
- }, [a, k, T, C, w, L, R, o, f, x, F]);
372
- const se = () => {
373
- if (f === null || G) return;
374
- const e = o[f].name, s = F.toLowerCase().trim();
375
- let p = "wrong";
376
- s === e ? (N((h) => h + 1), M("correct"), p = "correct") : we(s, e) === 1 ? (N((h) => h + 0.5), M("almost"), p = "almost") : (M("wrong"), p = "wrong"), O((h) => [...h, { round: x, answer: s, correct: e, result: p }]), q(!0), setTimeout(() => {
377
- q(!1), D(!0);
394
+ }, [d, T, j, N, x, A, R, S, y, r, W]);
395
+ const me = () => {
396
+ if (y === null || D) return;
397
+ const e = S[y].name, o = W.toLowerCase().trim();
398
+ let g = "wrong";
399
+ o === e ? ($((l) => l + 1), H("correct"), g = "correct") : Ee(o, e) === 1 ? ($((l) => l + 0.5), H("almost"), g = "almost") : (H("wrong"), g = "wrong"), P((l) => [...l, { round: r, answer: o, correct: e, result: g }]), J(!0), setTimeout(() => {
400
+ J(!1), G(!0);
378
401
  }, 600);
379
- }, le = () => x < i ? (v((e) => e + 1), Q()) : j(!0), ae = () => {
380
- z(!1), j(!1), m(null);
381
- }, de = me(
402
+ }, ue = () => r < C ? (b((e) => e + 1), oe()) : O(!0), ge = () => {
403
+ I(!1), O(!1), s(null);
404
+ }, he = ve(
382
405
  () => (
383
406
  // ensure logo is positioned inside the square container
384
- /* @__PURE__ */ t("div", { style: { ...n.gmLogoFixed, position: "absolute", top: 16, left: 16, zIndex: 30 }, children: /* @__PURE__ */ c("picture", { children: [
407
+ /* @__PURE__ */ t("div", { style: { ...n.gmLogoFixed, position: "absolute", top: 16, left: 16, zIndex: 30 }, children: /* @__PURE__ */ m("picture", { children: [
385
408
  /* @__PURE__ */ t("source", { srcSet: window.origin + "/cloud/speakid/games/whatsmissing/logo.svg", type: "image/svg+xml" }),
386
409
  /* @__PURE__ */ t("img", { src: window.origin + "/cloud/speakid/games/whatsmissing/logo.png", alt: "SPEAKID Logo", style: n.gmLogoImg, loading: "lazy" })
387
410
  ] }) })
@@ -391,59 +414,64 @@ function Se() {
391
414
  return /* @__PURE__ */ t(
392
415
  "div",
393
416
  {
394
- ref: l,
417
+ ref: a,
395
418
  style: {
396
- width: "100%",
397
- height: "100vh",
419
+ // center relative to viewport so site layout doesn't push the game off-center
420
+ position: "fixed",
421
+ inset: 0,
398
422
  display: "flex",
399
423
  justifyContent: "center",
400
424
  alignItems: "center",
401
425
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
402
426
  transition: "background 0.3s ease",
403
- overflowX: "clip"
427
+ overflow: "hidden",
428
+ zIndex: 9999,
429
+ pointerEvents: "auto"
404
430
  },
405
431
  children: /* @__PURE__ */ t(
406
432
  "div",
407
433
  {
434
+ ref: f,
408
435
  style: {
409
- width: b ? "100%" : U || 1e3,
410
- height: b ? "100%" : U || 1e3,
436
+ width: w ? "100%" : L ? `${L}px` : "1000px",
437
+ height: w ? "100%" : L ? `${L}px` : "1000px",
438
+ maxWidth: "calc(100vw - 24px)",
439
+ maxHeight: "calc(100vh - 24px)",
411
440
  display: "flex",
412
441
  justifyContent: "center",
413
442
  alignItems: "center",
414
443
  overflow: "hidden",
415
- borderRadius: b ? 0 : "20px",
444
+ borderRadius: w ? 0 : "20px",
416
445
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
417
- boxShadow: b ? "none" : "0 0 40px rgba(0,0,0,0.1)",
418
- margin: b ? "0 auto" : "unset",
446
+ boxShadow: w ? "none" : "0 0 40px rgba(0,0,0,0.08)",
419
447
  position: "relative"
420
- // needed so absolute logo is inside the square
421
448
  },
422
449
  children: /* @__PURE__ */ t(
423
450
  "div",
424
451
  {
425
452
  style: {
426
453
  transform: "none",
454
+ transformOrigin: "50% 50%",
427
455
  width: "100%",
428
456
  height: "100%",
429
457
  display: "flex",
430
458
  justifyContent: "center",
431
459
  alignItems: "center"
432
460
  },
433
- children: /* @__PURE__ */ c("div", { id: "whats-missing-root", children: [
434
- !b && de,
435
- !g && !w && /* @__PURE__ */ c("div", { style: n.gmCenterScreen, children: [
461
+ children: /* @__PURE__ */ m("div", { id: "whats-missing-root", children: [
462
+ !w && he,
463
+ !u && !x && /* @__PURE__ */ m("div", { style: n.gmCenterScreen, children: [
436
464
  /* @__PURE__ */ t("h1", { style: n.gmHeadline1, children: "WHAT'S MISSING?" }),
437
465
  /* @__PURE__ */ t("p", { style: n.gmBodyM, children: "Select a theme:" }),
438
- /* @__PURE__ */ t("div", { style: { display: "flex", gap: 16 }, children: ["animals", "food", "toys"].map((e) => /* @__PURE__ */ t("button", { style: n.gmButton, onClick: () => m(e), children: e === "animals" ? "🐶 Animals" : e === "food" ? "🍎 Food" : "🧸 Toys" }, e)) }),
439
- /* @__PURE__ */ c("div", { style: { marginTop: 24 }, children: [
466
+ /* @__PURE__ */ t("div", { style: { display: "flex", gap: 16 }, children: ["animals", "food", "toys"].map((e) => /* @__PURE__ */ t("button", { style: n.gmButton, onClick: () => s(e), children: e === "animals" ? "🐶 Animals" : e === "food" ? "🍎 Food" : "🧸 Toys" }, e)) }),
467
+ /* @__PURE__ */ m("div", { style: { marginTop: 24 }, children: [
440
468
  /* @__PURE__ */ t("p", { style: n.gmBodyS, children: "Choose number of rounds:" }),
441
469
  /* @__PURE__ */ t("div", { style: { display: "flex", gap: 12, marginTop: 8 }, children: [3, 4, 5].map((e) => /* @__PURE__ */ t(
442
470
  "button",
443
471
  {
444
472
  style: {
445
473
  ...n.gmButton,
446
- ...i === e ? n.gmButtonActive : {}
474
+ ...C === e ? n.gmButtonActive : {}
447
475
  },
448
476
  onClick: () => E(e),
449
477
  children: e
@@ -452,47 +480,47 @@ function Se() {
452
480
  )) })
453
481
  ] })
454
482
  ] }),
455
- g && !w && /* @__PURE__ */ c("div", { style: n.gmCenterScreen, children: [
456
- /* @__PURE__ */ c("h1", { style: n.gmHeadline1, children: [
483
+ u && !x && /* @__PURE__ */ m("div", { style: n.gmCenterScreen, children: [
484
+ /* @__PURE__ */ m("h1", { style: n.gmHeadline1, children: [
457
485
  "Theme selected: ",
458
- g
486
+ u
459
487
  ] }),
460
- /* @__PURE__ */ c("p", { style: n.gmBodyM, children: [
488
+ /* @__PURE__ */ m("p", { style: n.gmBodyM, children: [
461
489
  "Rounds: ",
462
- i
490
+ C
463
491
  ] }),
464
- /* @__PURE__ */ t("button", { style: n.gmButton, onClick: J, children: "▶ Start game" })
492
+ /* @__PURE__ */ t("button", { style: n.gmButton, onClick: ne, children: "▶ Start game" })
465
493
  ] }),
466
- L && /* @__PURE__ */ c("div", { style: n.gmCenterScreen, children: [
494
+ A && /* @__PURE__ */ m("div", { style: n.gmCenterScreen, children: [
467
495
  /* @__PURE__ */ t("h1", { style: n.gmHeadline1, children: "Results" }),
468
- /* @__PURE__ */ c("h2", { style: n.gmHeadline3, children: [
496
+ /* @__PURE__ */ m("h2", { style: n.gmHeadline3, children: [
469
497
  "Your score: ",
470
- ne,
498
+ le,
471
499
  " / ",
472
- i
500
+ C
473
501
  ] }),
474
502
  /* @__PURE__ */ t("p", { style: { ...n.gmBodyM, color: "#10b981", marginTop: 12 }, children: "Yahoo! You did it! 🍬✨" }),
475
- /* @__PURE__ */ c("table", { style: n.gmTable, children: [
476
- /* @__PURE__ */ t("thead", { children: /* @__PURE__ */ c("tr", { children: [
503
+ /* @__PURE__ */ m("table", { style: n.gmTable, children: [
504
+ /* @__PURE__ */ t("thead", { children: /* @__PURE__ */ m("tr", { children: [
477
505
  /* @__PURE__ */ t("th", { children: "Round" }),
478
506
  /* @__PURE__ */ t("th", { children: "Your Answer" }),
479
507
  /* @__PURE__ */ t("th", { children: "Correct" }),
480
508
  /* @__PURE__ */ t("th", { children: "Result" })
481
509
  ] }) }),
482
- /* @__PURE__ */ t("tbody", { children: oe.map((e, s) => /* @__PURE__ */ c("tr", { children: [
510
+ /* @__PURE__ */ t("tbody", { children: ae.map((e, o) => /* @__PURE__ */ m("tr", { children: [
483
511
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.round }),
484
512
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.answer || "—" }),
485
513
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.correct }),
486
514
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.result === "correct" ? "✔ Correct" : e.result === "almost" ? "◐ Almost (0.5)" : "✘ Wrong" })
487
- ] }, s)) })
515
+ ] }, o)) })
488
516
  ] }),
489
- /* @__PURE__ */ c("div", { style: { display: "flex", gap: 12, marginTop: 24 }, children: [
490
- /* @__PURE__ */ t("button", { style: n.gmButton, onClick: J, children: "🔁 Play again" }),
491
- /* @__PURE__ */ t("button", { style: n.gmButton, onClick: ae, children: "⬅️ Choose theme" })
517
+ /* @__PURE__ */ m("div", { style: { display: "flex", gap: 12, marginTop: 24 }, children: [
518
+ /* @__PURE__ */ t("button", { style: n.gmButton, onClick: ne, children: "🔁 Play again" }),
519
+ /* @__PURE__ */ t("button", { style: n.gmButton, onClick: ge, children: "⬅️ Choose theme" })
492
520
  ] })
493
521
  ] }),
494
- w && !L && /* @__PURE__ */ c("div", { style: n.gmGameLayout, children: [
495
- /* @__PURE__ */ c(
522
+ x && !A && /* @__PURE__ */ m("div", { style: n.gmGameLayout, children: [
523
+ /* @__PURE__ */ m(
496
524
  "div",
497
525
  {
498
526
  style: {
@@ -503,70 +531,79 @@ function Se() {
503
531
  alignItems: "center"
504
532
  },
505
533
  children: [
506
- a === "ready" && /* @__PURE__ */ c(ce, { children: [
534
+ d === "ready" && /* @__PURE__ */ m(we, { children: [
507
535
  /* @__PURE__ */ t("h1", { style: { ...n.gmHeadline1, color: "#ec4c44" }, children: "GET READY" }),
508
536
  /* @__PURE__ */ t("div", { style: n.gmHourglass, children: "⏳" })
509
537
  ] }),
510
- a === "memorize" && /* @__PURE__ */ c("p", { style: { ...n.gmBodyM, color: "#10b981" }, children: [
538
+ d === "memorize" && /* @__PURE__ */ m("p", { style: { ...n.gmBodyM, color: "#10b981" }, children: [
511
539
  "MEMORIZE (",
512
- T,
540
+ j,
513
541
  ")"
514
542
  ] }),
515
- a === "guess" && !R && /* @__PURE__ */ c("p", { style: n.gmBodyM, children: [
543
+ d === "guess" && !R && /* @__PURE__ */ m("p", { style: n.gmBodyM, children: [
516
544
  "⏳ Time left: ",
517
- C,
545
+ N,
518
546
  "s"
519
547
  ] })
520
548
  ]
521
549
  }
522
550
  ),
523
- a !== "ready" && /* @__PURE__ */ t(
524
- "div",
525
- {
526
- style: {
527
- ...n.gmGrid,
528
- gridTemplateColumns: b ? "repeat(2, 1fr)" : "repeat(3, 210px)",
529
- gridAutoRows: b ? "150px" : "210px",
530
- gap: b ? "12px" : "20px",
531
- justifyItems: "center"
532
- },
533
- children: o.map((e, s) => {
534
- const p = f === s && a === "guess" && !R;
535
- return /* @__PURE__ */ t(
536
- "div",
537
- {
538
- style: {
539
- ...n.gmCard,
540
- ...V === "correct" && f === s ? n.gmCorrect : {},
541
- ...V === "wrong" && f === s ? n.gmWrong : {}
542
- },
543
- children: !p && /* @__PURE__ */ t(
544
- "img",
545
- {
546
- src: e.src,
547
- alt: e.name,
548
- style: {
549
- width: "100%",
550
- height: "100%",
551
- objectFit: "cover"
551
+ d !== "ready" && (() => {
552
+ const e = w ? 12 : 20, o = 32, g = h ?? L ?? (w ? 360 : 1e3), l = w ? 2 : 3, p = Math.max(
553
+ 90,
554
+ Math.floor((g - o - e * (l - 1)) / l)
555
+ );
556
+ return /* @__PURE__ */ t(
557
+ "div",
558
+ {
559
+ style: {
560
+ ...n.gmGrid,
561
+ gridTemplateColumns: `repeat(${l}, ${p}px)`,
562
+ gridAutoRows: `${p}px`,
563
+ gap: `${e}px`,
564
+ justifyContent: "center",
565
+ justifyItems: "center",
566
+ padding: 8,
567
+ boxSizing: "border-box"
568
+ },
569
+ children: S.map((v, z) => {
570
+ const q = y === z && d === "guess" && !R;
571
+ return /* @__PURE__ */ t(
572
+ "div",
573
+ {
574
+ style: {
575
+ ...n.gmCard,
576
+ width: `${p}px`,
577
+ height: `${p}px`,
578
+ padding: w ? 8 : 10,
579
+ boxSizing: "border-box",
580
+ ...Q === "correct" && y === z ? n.gmCorrect : {},
581
+ ...Q === "wrong" && y === z ? n.gmWrong : {}
582
+ },
583
+ children: !q && /* @__PURE__ */ t(
584
+ "img",
585
+ {
586
+ src: v.src,
587
+ alt: v.name,
588
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
552
589
  }
553
- }
554
- )
555
- },
556
- s
557
- );
558
- })
559
- }
560
- ),
561
- /* @__PURE__ */ c("div", { style: { marginTop: 16, height: 80 }, children: [
562
- a === "guess" && !R && /* @__PURE__ */ c("div", { children: [
590
+ )
591
+ },
592
+ z
593
+ );
594
+ })
595
+ }
596
+ );
597
+ })(),
598
+ /* @__PURE__ */ m("div", { style: { marginTop: 16, height: 80 }, children: [
599
+ d === "guess" && !R && /* @__PURE__ */ m("div", { children: [
563
600
  /* @__PURE__ */ t(
564
601
  "input",
565
602
  {
566
603
  type: "text",
567
604
  placeholder: "Type the missing word",
568
- value: F,
569
- onChange: (e) => $(e.target.value),
605
+ value: W,
606
+ onChange: (e) => K(e.target.value),
570
607
  style: n.gmInput
571
608
  }
572
609
  ),
@@ -574,13 +611,13 @@ function Se() {
574
611
  "button",
575
612
  {
576
613
  style: { ...n.gmButton, marginLeft: 8 },
577
- onClick: se,
578
- disabled: G,
579
- children: G ? "..." : "Check"
614
+ onClick: me,
615
+ disabled: D,
616
+ children: D ? "..." : "Check"
580
617
  }
581
618
  )
582
619
  ] }),
583
- R && /* @__PURE__ */ t("button", { type: "button", style: n.gmButton, onClick: le, children: "Next round" })
620
+ R && /* @__PURE__ */ t("button", { type: "button", style: n.gmButton, onClick: ue, children: "Next round" })
584
621
  ] })
585
622
  ] })
586
623
  ] })
@@ -592,6 +629,6 @@ function Se() {
592
629
  );
593
630
  }
594
631
  export {
595
- Se as Game,
596
- ee as themes
632
+ Be as Game,
633
+ re as themes
597
634
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblinkov/whats-missing",
3
- "version": "20.0.20",
3
+ "version": "20.0.22",
4
4
  "main": "dist/whats-missing.umd.js",
5
5
  "module": "dist/whats-missing.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,7 +25,9 @@ export const styles: Record<string, CSSProperties> = {
25
25
  gmCenterScreen: {
26
26
  position: "relative",
27
27
  zIndex: 1,
28
- minHeight: "100vh",
28
+ // use percent so the component fits inside the centered square container
29
+ // (100vh caused overflow on tablets because it measured the viewport, not the container)
30
+ minHeight: "100%",
29
31
  width: "100%",
30
32
  display: "flex",
31
33
  flexDirection: "column",
@@ -100,7 +102,8 @@ export const styles: Record<string, CSSProperties> = {
100
102
  position: "relative",
101
103
  width: "100%",
102
104
  maxWidth: "600px",
103
- minHeight: "100vh",
105
+ // should fill the parent square, not the full viewport
106
+ minHeight: "100%",
104
107
  display: "flex",
105
108
  flexDirection: "column",
106
109
  alignItems: "center",
@@ -203,7 +206,8 @@ export const styles: Record<string, CSSProperties> = {
203
206
  flexDirection: "column",
204
207
  alignItems: "center",
205
208
  justifyContent: "center",
206
- height: "60vh",
209
+ // use percent so the wrapper scales with the container square
210
+ height: "60%",
207
211
  gap: "16px",
208
212
  },
209
213
 
package/src/Game.tsx CHANGED
@@ -13,7 +13,7 @@ type RoundResult = {
13
13
  };
14
14
 
15
15
 
16
- // ✅ базовый reset
16
+ // ✅ базовый reset (ограничен корнем игры)
17
17
  const globalReset = () => {
18
18
  const style = document.createElement("style");
19
19
  style.textContent = `
@@ -46,7 +46,6 @@ export function useNeutralizeBodyZoom(
46
46
  opts: { minDesktopWidth?: number; forceResize?: boolean } = { minDesktopWidth: 1200, forceResize: false }
47
47
  ) {
48
48
  const saved = useRef<Record<string, string | null> | null>(null);
49
- // извлекаем значение, чтобы использовать в deps
50
49
  const minDesktopWidth = opts.minDesktopWidth ?? 1200;
51
50
  const forceResize = !!opts.forceResize;
52
51
 
@@ -57,26 +56,30 @@ export function useNeutralizeBodyZoom(
57
56
  const getBodyScale = (): number => {
58
57
  try {
59
58
  const body = document.body;
60
- const cs = getComputedStyle(body) as CSSStyleDeclaration & {
61
- zoom?: string;
62
- webkitTransform?: string;
63
- mozTransform?: string;
64
- };
59
+ const cs = getComputedStyle(body) as CSSStyleDeclaration & { zoom?: string };
65
60
 
61
+ // 1) existing inline/computed zoom if present
66
62
  const zoomInline = parseFloat((body.style && body.style.zoom) || "");
67
63
  if (zoomInline && !isNaN(zoomInline) && zoomInline > 0) return zoomInline;
68
-
69
64
  const zoomComputed = parseFloat((cs.zoom as string) || "");
70
65
  if (zoomComputed && !isNaN(zoomComputed) && zoomComputed > 0) return zoomComputed;
71
66
 
72
- const tf = cs.transform || cs.webkitTransform || cs.mozTransform;
73
- if (tf && tf !== "none") {
74
- const m = tf.match(/matrix\(([^)]+)\)/);
75
- if (m) {
76
- const parts = m[1].split(",").map((p) => parseFloat(p));
77
- const a = parts[0];
78
- if (a && !isNaN(a)) return a;
79
- }
67
+ // 2) robust probe: insert a 100px element and measure its bounding rect.
68
+ // This detects any effective visual scale applied to the page (transform on ancestors, UA scaling, etc).
69
+ const probe = document.createElement("div");
70
+ probe.style.position = "absolute";
71
+ probe.style.left = "0";
72
+ probe.style.top = "0";
73
+ probe.style.width = "100px";
74
+ probe.style.height = "1px";
75
+ probe.style.visibility = "hidden";
76
+ probe.style.pointerEvents = "none";
77
+ document.documentElement.appendChild(probe);
78
+ const rect = probe.getBoundingClientRect();
79
+ document.documentElement.removeChild(probe);
80
+ if (rect && rect.width > 0) {
81
+ const measured = rect.width / 100;
82
+ if (!isNaN(measured) && measured > 0) return measured;
80
83
  }
81
84
  } catch (e) {
82
85
  // ignore
@@ -109,17 +112,18 @@ export function useNeutralizeBodyZoom(
109
112
 
110
113
  const inv = 1 / scale;
111
114
 
112
- // safer default: не трогаем width/height, только визуально инвертируем внутри.
113
- // если нужен "flow" компенсации, включать forceResize: true
115
+ // safer default: только визуальная компенсация (не трогаем layout), опция forceResize включит width/height
114
116
  if (forceResize) {
115
117
  el.style.width = `${scale * 100}%`;
116
118
  el.style.height = `${scale * 100}%`;
117
119
  }
120
+
121
+ // масштабируем относительно центра, чтобы не сдвигать центрированный контент
122
+ el.style.transformOrigin = "50% 50%";
118
123
  el.style.transform = `scale(${inv})`;
119
- el.style.transformOrigin = "top left";
120
124
  el.style.willChange = "transform";
121
125
 
122
- if (typeof process !== "undefined" && process.env && process.env.NODE_ENV === "development") {
126
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
123
127
  // eslint-disable-next-line no-console
124
128
  console.debug("[useNeutralizeBodyZoom] applied scale:", { scale, inv, forceResize });
125
129
  }
@@ -139,7 +143,7 @@ export function useNeutralizeBodyZoom(
139
143
  if (s.willChange === null) el.style.removeProperty("will-change");
140
144
  else el.style.willChange = s.willChange as string;
141
145
  saved.current = null;
142
- if (process.env.NODE_ENV === "development") {
146
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
143
147
  // eslint-disable-next-line no-console
144
148
  console.debug("[useNeutralizeBodyZoom] restored original styles");
145
149
  }
@@ -176,9 +180,12 @@ export function useNeutralizeBodyZoom(
176
180
 
177
181
 
178
182
  export default function Game() {
179
- const containerRef = useRef<HTMLDivElement | null>(null);
180
- useNeutralizeBodyZoom(containerRef, { minDesktopWidth: 1200, forceResize: false });
181
-
183
+ const containerRef = useRef<HTMLDivElement | null>(null);
184
+ // ref на видимый квадрат (тот, что width/height выставлены в px или %)
185
+ const squareRef = useRef<HTMLDivElement | null>(null);
186
+ const [squareSize, setSquareSize] = useState<number | null>(null);
187
+ useNeutralizeBodyZoom(containerRef, { minDesktopWidth: 1200, forceResize: false });
188
+
182
189
 
183
190
  useEffect(() => {
184
191
  globalReset();
@@ -207,40 +214,80 @@ export default function Game() {
207
214
  const [resultsTable, setResultsTable] = useState<RoundResult[]>([]);
208
215
  const [usedHidden, setUsedHidden] = useState<string[]>([]);
209
216
  const [isMobile, setIsMobile] = useState(false);
210
- const [scale, setScale] = useState(1);
211
217
  const [containerSize, setContainerSize] = useState<number | null>(null);
212
218
 
219
+ // наблюдаем за реальным render-size квадрата — это даёт надежную базу для расчета карточек
220
+ useLayoutEffect(() => {
221
+ const el = squareRef.current;
222
+ if (!el || typeof window === "undefined") return;
223
+ let ro: ResizeObserver | null = null;
224
+ try {
225
+ ro = new ResizeObserver((entries) => {
226
+ for (const e of entries) {
227
+ const cr = e.contentRect;
228
+ setSquareSize(Math.round(Math.min(cr.width, cr.height)));
229
+ }
230
+ });
231
+ ro.observe(el);
232
+ // init
233
+ setSquareSize(Math.round(Math.min(el.clientWidth, el.clientHeight)));
234
+ } catch (err) {
235
+ // ResizeObserver may not be available — fallback to initial measurement
236
+ setSquareSize(Math.round(Math.min(el.clientWidth, el.clientHeight)));
237
+ }
238
+ return () => {
239
+ if (ro) ro.disconnect();
240
+ };
241
+ }, [isMobile]);
242
+
213
243
 
214
244
  // ✅ адаптив под мобилки, планшеты и десктоп
215
245
  useEffect(() => {
216
- const resize = () => {
217
- const mobile = window.innerWidth < 768;
218
- const tablet = window.innerWidth >= 768 && window.innerWidth < 1200;
219
-
220
-
221
- // treat only phones as "mobile" so tablets use the desktop (square/centered) layout
222
- setIsMobile(mobile);
223
-
224
-
225
- if (mobile) {
226
- // mobile: fluid full-width layout
227
- setContainerSize(null);
228
- setScale(1);
229
- } else {
230
- // tablets and desktops: fit into a centered square (max 1000)
231
- const safeFactor = 0.97;
232
- const maxSquare = 1000;
233
- const available = Math.min(window.innerWidth, window.innerHeight);
234
- const sizePx = Math.max(360, Math.round(available * safeFactor)); // don't go too small
235
- const finalSize = Math.min(sizePx, maxSquare);
236
- setContainerSize(finalSize);
237
- setScale(1);
238
- }
239
- };
240
- resize();
241
- window.addEventListener("resize", resize);
242
- return () => window.removeEventListener("resize", resize);
243
- }, []);
246
+ const resize = () => {
247
+ const vv = (window as any).visualViewport;
248
+ const vw = vv ? Math.round(vv.width) : window.innerWidth;
249
+ const vh = vv ? Math.round(vv.height) : window.innerHeight;
250
+
251
+ // treat narrow screens as mobile; portrait tablets also considered mobile
252
+ const mobile = vw < 768 || (vw >= 768 && vw < 1200 && vh > vw);
253
+ setIsMobile(mobile);
254
+
255
+ if (mobile) {
256
+ setContainerSize(null);
257
+ return;
258
+ }
259
+
260
+ // tablets & desktop: conservative square
261
+ const safeFactor = 0.92; // leave margins for browser UI
262
+ const maxSquare = 1000;
263
+ const gutter = 40;
264
+ const availableW = Math.max(360, vw - gutter);
265
+ const availableH = Math.max(360, vh - gutter);
266
+ const available = Math.min(availableW, availableH);
267
+ const sizePx = Math.max(360, Math.round(available * safeFactor));
268
+ const finalSize = Math.min(sizePx, maxSquare);
269
+ setContainerSize(finalSize);
270
+ };
271
+
272
+ // run once
273
+ resize();
274
+
275
+ // listen to both window and visualViewport (if available) since some browsers update only visualViewport on zoom
276
+ window.addEventListener("resize", resize);
277
+ const vv = (window as any).visualViewport;
278
+ if (vv && typeof vv.addEventListener === "function") {
279
+ vv.addEventListener("resize", resize);
280
+ vv.addEventListener("scroll", resize);
281
+ }
282
+
283
+ return () => {
284
+ window.removeEventListener("resize", resize);
285
+ if (vv && typeof vv.removeEventListener === "function") {
286
+ vv.removeEventListener("resize", resize);
287
+ vv.removeEventListener("scroll", resize);
288
+ }
289
+ };
290
+ }, []);
244
291
 
245
292
 
246
293
  const getRandomSix = (arr: ImageItem[]) =>
@@ -377,34 +424,41 @@ export default function Game() {
377
424
  <div
378
425
  ref={containerRef}
379
426
  style={{
380
- width: "100%",
381
- height: "100vh",
427
+ // center relative to viewport so site layout doesn't push the game off-center
428
+ position: "fixed",
429
+ inset: 0,
382
430
  display: "flex",
383
431
  justifyContent: "center",
384
432
  alignItems: "center",
385
433
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
386
434
  transition: "background 0.3s ease",
387
- overflowX: "clip"
435
+ overflow: "hidden",
436
+ zIndex: 9999,
437
+ pointerEvents: "auto",
388
438
  }}
389
439
  >
390
440
  <div
441
+ // visible square that should fit on tablets
442
+ ref={squareRef}
391
443
  style={{
392
- width: isMobile ? "100%" : containerSize || 1000,
393
- height: isMobile ? "100%" : containerSize || 1000,
444
+ width: isMobile ? "100%" : containerSize ? `${containerSize}px` : "1000px",
445
+ height: isMobile ? "100%" : containerSize ? `${containerSize}px` : "1000px",
446
+ maxWidth: "calc(100vw - 24px)",
447
+ maxHeight: "calc(100vh - 24px)",
394
448
  display: "flex",
395
449
  justifyContent: "center",
396
450
  alignItems: "center",
397
451
  overflow: "hidden",
398
452
  borderRadius: isMobile ? 0 : "20px",
399
453
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
400
- boxShadow: isMobile ? "none" : "0 0 40px rgba(0,0,0,0.1)",
401
- margin: isMobile ? "0 auto" : "unset",
402
- position: "relative", // needed so absolute logo is inside the square
454
+ boxShadow: isMobile ? "none" : "0 0 40px rgba(0,0,0,0.08)",
455
+ position: "relative",
403
456
  }}
404
457
  >
405
458
  <div
406
459
  style={{
407
460
  transform: "none",
461
+ transformOrigin: "50% 50%",
408
462
  width: "100%",
409
463
  height: "100%",
410
464
  display: "flex",
@@ -523,50 +577,58 @@ export default function Game() {
523
577
  <p style={styles.gmBodyM}>⏳ Time left: {timeLeft}s</p>
524
578
  )}
525
579
  </div>
526
- {phase !== "ready" && (
527
- <div
528
- style={{
529
- ...styles.gmGrid,
530
- gridTemplateColumns: isMobile
531
- ? "repeat(2, 1fr)"
532
- : "repeat(3, 210px)",
533
- gridAutoRows: isMobile ? "150px" : "210px",
534
- gap: isMobile ? "12px" : "20px",
535
- justifyItems: "center",
536
- }}
537
- >
538
- {files.map((file, i) => {
539
- const isHidden =
540
- hiddenIndex === i && phase === "guess" && !answered;
541
- return (
542
- <div
543
- key={i}
544
- style={{
545
- ...styles.gmCard,
546
- ...(result === "correct" && hiddenIndex === i
547
- ? styles.gmCorrect
548
- : {}),
549
- ...(result === "wrong" && hiddenIndex === i
550
- ? styles.gmWrong
551
- : {}),
552
- }}
553
- >
554
- {!isHidden && (
555
- <img
556
- src={file.src}
557
- alt={file.name}
558
- style={{
559
- width: "100%",
560
- height: "100%",
561
- objectFit: "cover",
562
- }}
563
- />
564
- )}
565
- </div>
566
- );
567
- })}
568
- </div>
569
- )}
580
+ {phase !== "ready" && (() => {
581
+ // compute card size from real measured square size (fallback to containerSize)
582
+ const gap = isMobile ? 12 : 20;
583
+ const innerGutters = 32; // logo / paddings estimation
584
+ const effective = squareSize ?? containerSize ?? (isMobile ? 360 : 1000);
585
+ const columns = isMobile ? 2 : 3;
586
+ const cardSize = Math.max(
587
+ 90,
588
+ Math.floor((effective - innerGutters - gap * (columns - 1)) / columns)
589
+ );
590
+
591
+ return (
592
+ <div
593
+ style={{
594
+ ...styles.gmGrid,
595
+ gridTemplateColumns: `repeat(${columns}, ${cardSize}px)`,
596
+ gridAutoRows: `${cardSize}px`,
597
+ gap: `${gap}px`,
598
+ justifyContent: "center",
599
+ justifyItems: "center",
600
+ padding: 8,
601
+ boxSizing: "border-box",
602
+ }}
603
+ >
604
+ {files.map((file, i) => {
605
+ const isHidden = hiddenIndex === i && phase === "guess" && !answered;
606
+ return (
607
+ <div
608
+ key={i}
609
+ style={{
610
+ ...styles.gmCard,
611
+ width: `${cardSize}px`,
612
+ height: `${cardSize}px`,
613
+ padding: isMobile ? 8 : 10,
614
+ boxSizing: "border-box",
615
+ ...(result === "correct" && hiddenIndex === i ? styles.gmCorrect : {}),
616
+ ...(result === "wrong" && hiddenIndex === i ? styles.gmWrong : {}),
617
+ }}
618
+ >
619
+ {!isHidden && (
620
+ <img
621
+ src={file.src}
622
+ alt={file.name}
623
+ style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
624
+ />
625
+ )}
626
+ </div>
627
+ );
628
+ })}
629
+ </div>
630
+ );
631
+ })()}
570
632
  <div style={{ marginTop: 16, height: 80 }}>
571
633
  {phase === "guess" && !answered && (
572
634
  <div>