@mblinkov/whats-missing 20.0.21 → 20.0.23

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"),r=require("react"),ce=window.origin+"/cloud/speakid/games/whatsmissing/",D=(a,m)=>a.map(([c,s])=>({src:`${ce}${m}/${s.replace(/\s/g,"%20")}.png`,name:s})),me=[["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"]],ue=[["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"]],ge=[["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(me,"food"),animals:D(ue,"animals"),toys:D(ge,"toys")},he=`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),s=require("react"),xe=window.origin+"/cloud/speakid/games/whatsmissing/",q=(a,f)=>a.map(([g,i])=>({src:`${xe}${f}/${i.replace(/\s/g,"%20")}.png`,name:i})),be=[["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"]],we=[["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"]],ve=[["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(be,"food"),animals:q(we,"animals"),toys:q(ve,"toys")},je=`
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=he,document.head.appendChild(a)}const fe={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",...fe}},pe=()=>{const a=document.createElement("style");a.textContent=`
6
+ `;if(typeof document<"u"&&!document.getElementById("spin-keyframes")){const a=document.createElement("style");a.id="spin-keyframes",a.innerHTML=je,document.head.appendChild(a)}const Se={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",...Se}},ze=()=>{const a=document.createElement("style");a.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)},ye=(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 xe(a,m={minDesktopWidth:1200,forceResize:!1}){const c=r.useRef(null),s=m.minDesktopWidth??1200,u=!!m.forceResize;r.useLayoutEffect(()=>{if(typeof window>"u"||!a||!a.current)return;const l=a.current,A=()=>{try{const i=document.body,d=getComputedStyle(i),p=parseFloat(i.style&&i.style.zoom||"");if(p&&!isNaN(p)&&p>0)return p;const v=parseFloat(d.zoom||"");if(v&&!isNaN(v)&&v>0)return v;const k=d.transform||d.webkitTransform||d.mozTransform;if(k&&k!=="none"){const z=k.match(/matrix\(([^)]+)\)/);if(z){const j=z[1].split(",").map(E=>parseFloat(E))[0];if(j&&!isNaN(j))return j}}}catch{}return 1},b=()=>{if(s&&window.innerWidth<s){S();return}const i=A();if(!i||i===1){S();return}c.current||(c.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 d=1/i;u&&(l.style.width=`${i*100}%`,l.style.height=`${i*100}%`),l.style.transform=`scale(${d})`,l.style.transformOrigin="top left",l.style.willChange="transform",typeof process<"u"&&process.env&&process.env.NODE_ENV==="development"&&console.debug("[useNeutralizeBodyZoom] applied scale:",{scale:i,inv:d,forceResize:u})},S=()=>{if(!c.current)return;const i=c.current;i.transform===null?l.style.removeProperty("transform"):l.style.transform=i.transform,i.transformOrigin===null?l.style.removeProperty("transform-origin"):l.style.transformOrigin=i.transformOrigin,i.width===null?l.style.removeProperty("width"):l.style.width=i.width,i.height===null?l.style.removeProperty("height"):l.style.height=i.height,i.willChange===null?l.style.removeProperty("will-change"):l.style.willChange=i.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(i=>{for(const d of i)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 be(){const a=r.useRef(null);xe(a,{minDesktopWidth:1200,forceResize:!1}),r.useEffect(()=>(pe(),()=>{document.body.style.overflow=""}),[]);const[m,c]=r.useState(null),[s,u]=r.useState([]),[l,A]=r.useState(4),[b,S]=r.useState(!1),[x,w]=r.useState(1),[f,i]=r.useState(null),[d,p]=r.useState("ready"),[v,k]=r.useState(3),[z,M]=r.useState(10),[j,E]=r.useState(20),[Q,H]=r.useState(0),[L,F]=r.useState(!1),[B,q]=r.useState(""),[C,W]=r.useState(!1),[N,V]=r.useState(!1),[Y,I]=r.useState(null),[ee,G]=r.useState([]),[te,$]=r.useState([]),[y,ne]=r.useState(!1),[we,Z]=r.useState(1),[_,U]=r.useState(null);r.useEffect(()=>{const t=()=>{const g=window.visualViewport,h=g?Math.round(g.width):window.innerWidth,T=g?Math.round(g.height):window.innerHeight,R=h<768;if(ne(R),R)U(null),Z(1);else{const le=Math.min(h,T),ae=Math.max(360,Math.round(le*.97)),de=Math.min(ae,1e3);U(de),Z(1)}};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 K=t=>[...t].sort(()=>Math.random()-.5).slice(0,6),X=()=>{if(!m)return;const t=K(P[m]);u(t),S(!0),F(!1),w(1),H(0),G([]),$([]),J(t,[],!0)},J=(t=s,o=te,g=!1)=>{const h=K(t.length?t:P[m]);let T=Math.floor(Math.random()*h.length),R=h[T].name,O=0;for(;o.includes(R)&&O<20;)T=Math.floor(Math.random()*h.length),R=h[T].name,O++;u(h),i(T),$([...o,R]),W(!1),q(""),E(20),I(null),g?(p("ready"),k(3),M(10)):(p("memorize"),M(10))};r.useEffect(()=>{if(!(!b||L||C)){if(d==="ready")if(v<=0)p("memorize");else{const t=setTimeout(()=>k(o=>o-1),1e3);return()=>clearTimeout(t)}if(d==="memorize")if(z<=0)p("guess");else{const t=setTimeout(()=>M(o=>o-1),1e3);return()=>clearTimeout(t)}if(d==="guess"){if(j<=0){W(!0),I("wrong");const o=s[f].name;G(g=>[...g,{round:x,answer:B,correct:o,result:"wrong"}]);return}const t=setTimeout(()=>E(o=>o-1),1e3);return()=>clearTimeout(t)}}},[d,v,z,j,b,L,C,s,f,x,B]);const se=()=>{if(f===null||N)return;const t=s[f].name,o=B.toLowerCase().trim();let g="wrong";o===t?(H(h=>h+1),I("correct"),g="correct"):ye(o,t)===1?(H(h=>h+.5),I("almost"),g="almost"):(I("wrong"),g="wrong"),G(h=>[...h,{round:x,answer:o,correct:t,result:g}]),V(!0),setTimeout(()=>{V(!1),W(!0)},600)},oe=()=>x<l?(w(t=>t+1),J()):F(!0),re=()=>{S(!1),F(!1),c(null)},ie=r.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,...l===t?n.gmButtonActive:{}},onClick:()=>A(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: ",l]}),e.jsx("button",{style:n.gmButton,onClick:X,children:"▶ Start game"})]}),L&&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," / ",l]}),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,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:X,children:"🔁 Play again"}),e.jsx("button",{style:n.gmButton,onClick:re,children:"⬅️ Choose theme"})]})]}),b&&!L&&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 (",z,")"]}),d==="guess"&&!C&&e.jsxs("p",{style:n.gmBodyM,children:["⏳ Time left: ",j,"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,o)=>{const g=f===o&&d==="guess"&&!C;return e.jsx("div",{style:{...n.gmCard,...Y==="correct"&&f===o?n.gmCorrect:{},...Y==="wrong"&&f===o?n.gmWrong:{}},children:!g&&e.jsx("img",{src:t.src,alt:t.name,style:{width:"100%",height:"100%",objectFit:"cover"}})},o)})}),e.jsxs("div",{style:{marginTop:16,height:80},children:[d==="guess"&&!C&&e.jsxs("div",{children:[e.jsx("input",{type:"text",placeholder:"Type the missing word",value:B,onChange:t=>q(t.target.value),style:n.gmInput}),e.jsx("button",{style:{...n.gmButton,marginLeft:8},onClick:se,disabled:N,children:N?"...":"Check"})]}),C&&e.jsx("button",{type:"button",style:n.gmButton,onClick:oe,children:"Next round"})]})]})]})})})})}exports.Game=be;exports.themes=P;
10
+ `,document.head.appendChild(a)},Ce=(a,f)=>{const g=Array.from({length:a.length+1},()=>Array(f.length+1).fill(0));for(let i=0;i<=a.length;i++)g[i][0]=i;for(let i=0;i<=f.length;i++)g[0][i]=i;for(let i=1;i<=a.length;i++)for(let u=1;u<=f.length;u++)g[i][u]=a[i-1]===f[u-1]?g[i-1][u-1]:Math.min(g[i-1][u-1],g[i][u-1],g[i-1][u])+1;return g[a.length][f.length]};function ke(a,f={minDesktopWidth:1200,forceResize:!1}){const g=s.useRef(null),i=f.minDesktopWidth??1200,u=!!f.forceResize;s.useLayoutEffect(()=>{if(typeof window>"u"||!a||!a.current)return;const l=a.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 d=document.createElement("div");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);const j=d.getBoundingClientRect();if(document.documentElement.removeChild(d),j&&j.width>0){const k=j.width/100;if(!isNaN(k)&&k>0)return k}}catch{}return 1},L=()=>{if(i&&window.innerWidth<i){v();return}const r=z();if(!r||r===1){v();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;u&&(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:u})},v=()=>{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 R=0;const x=()=>{cancelAnimationFrame(R),R=requestAnimationFrame(L)},M=new MutationObserver(r=>{for(const y of r)if(y.type==="attributes"&&(y.attributeName==="style"||y.attributeName==="class")){x();break}});return M.observe(document.body,{attributes:!0,attributeFilter:["style","class"]}),window.addEventListener("resize",x),x(),()=>{cancelAnimationFrame(R),M.disconnect(),window.removeEventListener("resize",x),v()}},[a,i,u])}function Te(){const a=s.useRef(null),f=s.useRef(null),[g,i]=s.useState(null);ke(a,{minDesktopWidth:1200,forceResize:!1}),s.useEffect(()=>(ze(),()=>{document.body.style.overflow=""}),[]);const[u,l]=s.useState(null),[z,L]=s.useState([]),[v,R]=s.useState(4),[x,M]=s.useState(!1),[r,y]=s.useState(1),[p,C]=s.useState(null),[d,j]=s.useState("ready"),[k,Y]=s.useState(3),[W,N]=s.useState(10),[F,_]=s.useState(20),[oe,O]=s.useState(0),[B,$]=s.useState(!1),[H,Z]=s.useState(""),[T,G]=s.useState(!1),[D,K]=s.useState(!1),[U,E]=s.useState(null),[se,P]=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(m=>{for(const c of m){const h=c.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 m=window.visualViewport,c=m?Math.round(m.width):window.innerWidth,h=m?Math.round(m.height):window.innerHeight,S=820,w=typeof navigator<"u"&&(navigator.maxTouchPoints>0||"ontouchstart"in window),A=c<=S||w&&h>c;if(re(A),A){Q(null);return}const ue=.88,me=1e3,ne=40,ge=Math.max(360,c-ne),he=Math.max(360,h-ne),fe=Math.min(ge,he),pe=Math.max(360,Math.round(fe*ue)),ye=Math.min(pe,me);Q(ye)};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(!u)return;const t=X(V[u]);L(t),M(!0),$(!1),y(1),O(0),P([]),J([]),te(t,[],!0)},te=(t=z,o=ie,m=!1)=>{const c=X(t.length?t:V[u]);let h=Math.floor(Math.random()*c.length),S=c[h].name,w=0;for(;o.includes(S)&&w<20;)h=Math.floor(Math.random()*c.length),S=c[h].name,w++;L(c),C(h),J([...o,S]),G(!1),Z(""),_(20),E(null),m?(j("ready"),Y(3),N(10)):(j("memorize"),N(10))};s.useEffect(()=>{if(!(!x||B||T)){if(d==="ready")if(k<=0)j("memorize");else{const t=setTimeout(()=>Y(o=>o-1),1e3);return()=>clearTimeout(t)}if(d==="memorize")if(W<=0)j("guess");else{const t=setTimeout(()=>N(o=>o-1),1e3);return()=>clearTimeout(t)}if(d==="guess"){if(F<=0){G(!0),E("wrong");const o=z[p].name;P(m=>[...m,{round:r,answer:H,correct:o,result:"wrong"}]);return}const t=setTimeout(()=>_(o=>o-1),1e3);return()=>clearTimeout(t)}}},[d,k,W,F,x,B,T,z,p,r,H]);const le=()=>{if(p===null||D)return;const t=z[p].name,o=H.toLowerCase().trim();let m="wrong";o===t?(O(c=>c+1),E("correct"),m="correct"):Ce(o,t)===1?(O(c=>c+.5),E("almost"),m="almost"):(E("wrong"),m="wrong"),P(c=>[...c,{round:r,answer:o,correct:t,result:m}]),K(!0),setTimeout(()=>{K(!1),G(!0)},600)},ae=()=>r<v?(y(t=>t+1),te()):$(!0),de=()=>{M(!1),$(!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:a,style:{position:"absolute",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:1,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 - 40px)",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,!u&&!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,...v===t?n.gmButtonActive:{}},onClick:()=>R(t),children:t},t))})]})]}),u&&!x&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsxs("h1",{style:n.gmHeadline1,children:["Theme selected: ",u]}),e.jsxs("p",{style:n.gmBodyM,children:["Rounds: ",v]}),e.jsx("button",{style:n.gmButton,onClick:ee,children:"▶ Start game"})]}),B&&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," / ",v]}),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&&!B&&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 (",W,")"]}),d==="guess"&&!T&&e.jsxs("p",{style:n.gmBodyM,children:["⏳ Time left: ",F,"s"]})]}),d!=="ready"&&(()=>{const t=b?12:20,o=32,m=g??I??(b?360:1e3),c=b?2:3,h=Math.max(90,Math.floor((m-o-t*(c-1))/c));return e.jsx("div",{style:{...n.gmGrid,gridTemplateColumns:`repeat(${c}, ${h}px)`,gridAutoRows:`${h}px`,gap:`${t}px`,justifyContent:"center",justifyItems:"center",padding:8,boxSizing:"border-box"},children:z.map((S,w)=>{const A=p===w&&d==="guess"&&!T;return e.jsx("div",{style:{...n.gmCard,width:`${h}px`,height:`${h}px`,padding:b?8:10,boxSizing:"border-box",...U==="correct"&&p===w?n.gmCorrect:{},...U==="wrong"&&p===w?n.gmWrong:{}},children:!A&&e.jsx("img",{src:S.src,alt:S.name,style:{width:"100%",height:"100%",objectFit:"cover",display:"block"}})},w)})})})(),e.jsxs("div",{style:{marginTop:16,height:80},children:[d==="guess"&&!T&&e.jsxs("div",{children:[e.jsx("input",{type:"text",placeholder:"Type the missing word",value:H,onChange:t=>Z(t.target.value),style:n.gmInput}),e.jsx("button",{style:{...n.gmButton,marginLeft:8},onClick:le,disabled:D,children:D?"...":"Check"})]}),T&&e.jsx("button",{type:"button",style:n.gmButton,onClick:ae,children:"Next round"})]})]})]})})})})}exports.Game=Te;exports.themes=V;
@@ -1,9 +1,9 @@
1
- import { jsx as t, jsxs as c, Fragment as ue } from "react/jsx-runtime";
2
- import { useRef as te, useEffect as P, useState as d, useMemo as he, useLayoutEffect as fe } from "react";
3
- const pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (l, g) => l.map(([m, o]) => ({
4
- src: `${pe}${g}/${o.replace(/\s/g, "%20")}.png`,
5
- name: o
6
- })), ye = [
1
+ import { jsx as t, jsxs as m, Fragment as ze } from "react/jsx-runtime";
2
+ import { useRef as _, useState as c, useEffect as V, useLayoutEffect as se, useMemo as Ce } from "react";
3
+ const ke = window.origin + "/cloud/speakid/games/whatsmissing/", Y = (l, f) => l.map(([h, i]) => ({
4
+ src: `${ke}${f}/${i.replace(/\s/g, "%20")}.png`,
5
+ name: i
6
+ })), Se = [
7
7
  ["apple", "apple"],
8
8
  ["banana", "banana"],
9
9
  ["bread", "bread"],
@@ -29,7 +29,7 @@ const pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (l, g) => l
29
29
  ["strawberry", "strawberry"],
30
30
  ["tomato", "tomato"],
31
31
  ["watermelon", "watermelon"]
32
- ], be = [
32
+ ], Te = [
33
33
  ["bear", "bear"],
34
34
  ["cat", "cat"],
35
35
  ["crocodile", "crocodile"],
@@ -49,7 +49,7 @@ const pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (l, g) => l
49
49
  ["tortoise", "tortoise"],
50
50
  ["wolf", "wolf"],
51
51
  ["zebra", "zebra"]
52
- ], we = [
52
+ ], Me = [
53
53
  ["ball", "ball"],
54
54
  ["balloon", "balloon"],
55
55
  ["blocks", "blocks"],
@@ -69,11 +69,11 @@ const pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (l, g) => l
69
69
  ["teddy_bear", "teddy bear"],
70
70
  ["yo-yo", "yo-yo"],
71
71
  ["crayons", "crayons"]
72
- ], ee = {
73
- food: V(ye, "food"),
74
- animals: V(be, "animals"),
75
- toys: V(we, "toys")
76
- }, xe = `
72
+ ], re = {
73
+ food: Y(Se, "food"),
74
+ animals: Y(Te, "animals"),
75
+ toys: Y(Me, "toys")
76
+ }, Re = `
77
77
  @keyframes spin {
78
78
  from { transform: rotate(0deg); }
79
79
  to { transform: rotate(360deg); }
@@ -81,9 +81,9 @@ const pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (l, g) => l
81
81
  `;
82
82
  if (typeof document < "u" && !document.getElementById("spin-keyframes")) {
83
83
  const l = document.createElement("style");
84
- l.id = "spin-keyframes", l.innerHTML = xe, document.head.appendChild(l);
84
+ l.id = "spin-keyframes", l.innerHTML = Re, document.head.appendChild(l);
85
85
  }
86
- const ve = {
86
+ const Ee = {
87
87
  animation: "spin 1.4s linear infinite"
88
88
  }, n = {
89
89
  gmCenterScreen: {
@@ -236,159 +236,175 @@ const ve = {
236
236
  },
237
237
  gmHourglass: {
238
238
  fontSize: "42px",
239
- ...ve
239
+ ...Ee
240
240
  }
241
- }, ke = () => {
241
+ }, Ie = () => {
242
242
  const l = document.createElement("style");
243
243
  l.textContent = `
244
244
  /* scope only inside game root */
245
245
  #whats-missing-root, #whats-missing-root * { box-sizing: border-box; }
246
246
  #whats-missing-root img { max-width:100%; height:auto; display:block; user-select:none; }
247
247
  `, document.head.appendChild(l);
248
- }, ze = (l, g) => {
249
- const m = Array.from({ length: l.length + 1 }, () => Array(g.length + 1).fill(0));
250
- for (let o = 0; o <= l.length; o++) m[o][0] = o;
251
- for (let o = 0; o <= g.length; o++) m[0][o] = o;
252
- for (let o = 1; o <= l.length; o++)
253
- for (let u = 1; u <= g.length; u++)
254
- 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;
255
- return m[l.length][g.length];
248
+ }, Be = (l, f) => {
249
+ const h = Array.from({ length: l.length + 1 }, () => Array(f.length + 1).fill(0));
250
+ for (let i = 0; i <= l.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 <= l.length; i++)
253
+ for (let u = 1; u <= f.length; u++)
254
+ h[i][u] = l[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[l.length][f.length];
256
256
  };
257
- function Ce(l, g = { minDesktopWidth: 1200, forceResize: !1 }) {
258
- const m = te(null), o = g.minDesktopWidth ?? 1200, u = !!g.forceResize;
259
- fe(() => {
257
+ function Le(l, f = { minDesktopWidth: 1200, forceResize: !1 }) {
258
+ const h = _(null), i = f.minDesktopWidth ?? 1200, u = !!f.forceResize;
259
+ se(() => {
260
260
  if (typeof window > "u" || !l || !l.current) return;
261
- const s = l.current, F = () => {
261
+ const s = l.current, S = () => {
262
262
  try {
263
- const i = document.body, a = getComputedStyle(i), y = parseFloat(i.style && i.style.zoom || "");
263
+ const r = document.body, b = getComputedStyle(r), y = parseFloat(r.style && r.style.zoom || "");
264
264
  if (y && !isNaN(y) && y > 0) return y;
265
- const k = parseFloat(a.zoom || "");
266
- if (k && !isNaN(k) && k > 0) return k;
267
- const S = a.transform || a.webkitTransform || a.mozTransform;
268
- if (S && S !== "none") {
269
- const T = S.match(/matrix\(([^)]+)\)/);
270
- if (T) {
271
- const z = T[1].split(",").map((E) => parseFloat(E))[0];
272
- if (z && !isNaN(z)) return z;
273
- }
265
+ const T = parseFloat(b.zoom || "");
266
+ if (T && !isNaN(T) && T > 0) return T;
267
+ const a = document.createElement("div");
268
+ a.style.position = "absolute", a.style.left = "0", a.style.top = "0", a.style.width = "100px", a.style.height = "1px", a.style.visibility = "hidden", a.style.pointerEvents = "none", document.documentElement.appendChild(a);
269
+ const C = a.getBoundingClientRect();
270
+ if (document.documentElement.removeChild(a), C && C.width > 0) {
271
+ const M = C.width / 100;
272
+ if (!isNaN(M) && M > 0) return M;
274
273
  }
275
274
  } catch {
276
275
  }
277
276
  return 1;
278
- }, x = () => {
279
- if (o && window.innerWidth < o) {
280
- C();
277
+ }, H = () => {
278
+ if (i && window.innerWidth < i) {
279
+ z();
281
280
  return;
282
281
  }
283
- const i = F();
284
- if (!i || i === 1) {
285
- C();
282
+ const r = S();
283
+ if (!r || r === 1) {
284
+ z();
286
285
  return;
287
286
  }
288
- m.current || (m.current = {
287
+ h.current || (h.current = {
289
288
  transform: s.style.transform || null,
290
289
  transformOrigin: s.style.transformOrigin || null,
291
290
  width: s.style.width || null,
292
291
  height: s.style.height || null,
293
292
  willChange: s.style.willChange || null
294
293
  });
295
- const a = 1 / i;
296
- u && (s.style.width = `${i * 100}%`, s.style.height = `${i * 100}%`), s.style.transform = `scale(${a})`, s.style.transformOrigin = "top left", s.style.willChange = "transform", typeof process < "u" && process.env && process.env.NODE_ENV === "development" && console.debug("[useNeutralizeBodyZoom] applied scale:", { scale: i, inv: a, forceResize: u });
297
- }, C = () => {
298
- if (!m.current) return;
299
- const i = m.current;
300
- i.transform === null ? s.style.removeProperty("transform") : s.style.transform = i.transform, i.transformOrigin === null ? s.style.removeProperty("transform-origin") : s.style.transformOrigin = i.transformOrigin, i.width === null ? s.style.removeProperty("width") : s.style.width = i.width, i.height === null ? s.style.removeProperty("height") : s.style.height = i.height, i.willChange === null ? s.style.removeProperty("will-change") : s.style.willChange = i.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
+ }, z = () => {
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");
301
300
  };
302
- let w = 0;
303
- const v = () => {
304
- cancelAnimationFrame(w), w = requestAnimationFrame(x);
305
- }, p = new MutationObserver((i) => {
306
- for (const a of i)
307
- if (a.type === "attributes" && (a.attributeName === "style" || a.attributeName === "class")) {
308
- v();
301
+ let E = 0;
302
+ const x = () => {
303
+ cancelAnimationFrame(E), E = requestAnimationFrame(H);
304
+ }, I = new MutationObserver((r) => {
305
+ for (const b of r)
306
+ if (b.type === "attributes" && (b.attributeName === "style" || b.attributeName === "class")) {
307
+ x();
309
308
  break;
310
309
  }
311
310
  });
312
- return p.observe(document.body, { attributes: !0, attributeFilter: ["style", "class"] }), window.addEventListener("resize", v), v(), () => {
313
- cancelAnimationFrame(w), p.disconnect(), window.removeEventListener("resize", v), C();
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), z();
314
313
  };
315
- }, [l, o, u]);
314
+ }, [l, i, u]);
316
315
  }
317
- function Me() {
318
- const l = te(null);
319
- Ce(l, { minDesktopWidth: 1200, forceResize: !1 }), P(() => (ke(), () => {
316
+ function We() {
317
+ const l = _(null), f = _(null), [h, i] = c(null);
318
+ Le(l, { minDesktopWidth: 1200, forceResize: !1 }), V(() => (Ie(), () => {
320
319
  document.body.style.overflow = "";
321
320
  }), []);
322
- const [g, m] = d(null), [o, u] = d([]), [s, F] = d(4), [x, C] = d(!1), [w, v] = d(1), [p, i] = d(null), [a, y] = d("ready"), [k, S] = d(3), [T, B] = d(10), [z, E] = d(20), [ne, W] = d(0), [A, N] = d(!1), [H, Y] = d(""), [R, j] = d(!1), [D, $] = d(!1), [q, L] = d(null), [oe, G] = d([]), [re, Z] = d([]), [b, ie] = d(!1), [Se, _] = d(1), [U, K] = d(null);
323
- P(() => {
321
+ const [u, s] = c(null), [S, H] = c([]), [z, E] = c(4), [x, I] = c(!1), [r, b] = c(1), [y, T] = c(null), [a, C] = c("ready"), [M, Z] = c(3), [j, F] = c(10), [O, K] = c(20), [le, $] = c(0), [A, D] = c(!1), [W, U] = c(""), [R, G] = c(!1), [P, J] = c(!1), [Q, B] = c(null), [ae, q] = 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 d of g) {
329
+ const p = d.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(() => {
324
340
  const e = () => {
325
- const h = window.visualViewport, f = h ? Math.round(h.width) : window.innerWidth, I = h ? Math.round(h.height) : window.innerHeight, M = f < 768;
326
- if (ie(M), M)
327
- K(null), _(1);
328
- else {
329
- const ce = Math.min(f, I), me = Math.max(360, Math.round(ce * 0.97)), ge = Math.min(me, 1e3);
330
- K(ge), _(1);
341
+ const g = window.visualViewport, d = g ? Math.round(g.width) : window.innerWidth, p = g ? Math.round(g.height) : window.innerHeight, k = 820, v = typeof navigator < "u" && (navigator.maxTouchPoints > 0 || "ontouchstart" in window), N = d <= k || v && p > d;
342
+ if (ce(N), N) {
343
+ ee(null);
344
+ return;
331
345
  }
346
+ const pe = 0.88, fe = 1e3, ie = 40, ye = Math.max(360, d - ie), be = Math.max(360, p - ie), xe = Math.min(ye, be), we = Math.max(360, Math.round(xe * pe)), ve = Math.min(we, fe);
347
+ ee(ve);
332
348
  };
333
349
  e(), window.addEventListener("resize", e);
334
- const r = window.visualViewport;
335
- return r && typeof r.addEventListener == "function" && (r.addEventListener("resize", e), r.addEventListener("scroll", e)), () => {
336
- window.removeEventListener("resize", e), r && typeof r.removeEventListener == "function" && (r.removeEventListener("resize", e), r.removeEventListener("scroll", 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));
337
353
  };
338
354
  }, []);
339
- const X = (e) => [...e].sort(() => Math.random() - 0.5).slice(0, 6), J = () => {
340
- if (!g) return;
341
- const e = X(ee[g]);
342
- u(e), C(!0), N(!1), v(1), W(0), G([]), Z([]), Q(e, [], !0);
343
- }, Q = (e = o, r = re, h = !1) => {
344
- const f = X(e.length ? e : ee[g]);
345
- let I = Math.floor(Math.random() * f.length), M = f[I].name, O = 0;
346
- for (; r.includes(M) && O < 20; )
347
- I = Math.floor(Math.random() * f.length), M = f[I].name, O++;
348
- u(f), i(I), Z([...r, M]), j(!1), Y(""), E(20), L(null), h ? (y("ready"), S(3), B(10)) : (y("memorize"), B(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
+ H(e), I(!0), D(!1), b(1), $(0), q([]), X([]), oe(e, [], !0);
359
+ }, oe = (e = S, o = de, g = !1) => {
360
+ const d = te(e.length ? e : re[u]);
361
+ let p = Math.floor(Math.random() * d.length), k = d[p].name, v = 0;
362
+ for (; o.includes(k) && v < 20; )
363
+ p = Math.floor(Math.random() * d.length), k = d[p].name, v++;
364
+ H(d), T(p), X([...o, k]), G(!1), U(""), K(20), B(null), g ? (C("ready"), Z(3), F(10)) : (C("memorize"), F(10));
349
365
  };
350
- P(() => {
366
+ V(() => {
351
367
  if (!(!x || A || R)) {
352
368
  if (a === "ready")
353
- if (k <= 0) y("memorize");
369
+ if (M <= 0) C("memorize");
354
370
  else {
355
- const e = setTimeout(() => S((r) => r - 1), 1e3);
371
+ const e = setTimeout(() => Z((o) => o - 1), 1e3);
356
372
  return () => clearTimeout(e);
357
373
  }
358
374
  if (a === "memorize")
359
- if (T <= 0) y("guess");
375
+ if (j <= 0) C("guess");
360
376
  else {
361
- const e = setTimeout(() => B((r) => r - 1), 1e3);
377
+ const e = setTimeout(() => F((o) => o - 1), 1e3);
362
378
  return () => clearTimeout(e);
363
379
  }
364
380
  if (a === "guess") {
365
- if (z <= 0) {
366
- j(!0), L("wrong");
367
- const r = o[p].name;
368
- G((h) => [
369
- ...h,
370
- { round: w, answer: H, correct: r, result: "wrong" }
381
+ if (O <= 0) {
382
+ G(!0), B("wrong");
383
+ const o = S[y].name;
384
+ q((g) => [
385
+ ...g,
386
+ { round: r, answer: W, correct: o, result: "wrong" }
371
387
  ]);
372
388
  return;
373
389
  }
374
- const e = setTimeout(() => E((r) => r - 1), 1e3);
390
+ const e = setTimeout(() => K((o) => o - 1), 1e3);
375
391
  return () => clearTimeout(e);
376
392
  }
377
393
  }
378
- }, [a, k, T, z, x, A, R, o, p, w, H]);
379
- const se = () => {
380
- if (p === null || D) return;
381
- const e = o[p].name, r = H.toLowerCase().trim();
382
- let h = "wrong";
383
- r === e ? (W((f) => f + 1), L("correct"), h = "correct") : ze(r, e) === 1 ? (W((f) => f + 0.5), L("almost"), h = "almost") : (L("wrong"), h = "wrong"), G((f) => [...f, { round: w, answer: r, correct: e, result: h }]), $(!0), setTimeout(() => {
384
- $(!1), j(!0);
394
+ }, [a, M, j, O, x, A, R, S, y, r, W]);
395
+ const me = () => {
396
+ if (y === null || P) return;
397
+ const e = S[y].name, o = W.toLowerCase().trim();
398
+ let g = "wrong";
399
+ o === e ? ($((d) => d + 1), B("correct"), g = "correct") : Be(o, e) === 1 ? ($((d) => d + 0.5), B("almost"), g = "almost") : (B("wrong"), g = "wrong"), q((d) => [...d, { round: r, answer: o, correct: e, result: g }]), J(!0), setTimeout(() => {
400
+ J(!1), G(!0);
385
401
  }, 600);
386
- }, le = () => w < s ? (v((e) => e + 1), Q()) : N(!0), ae = () => {
387
- C(!1), N(!1), m(null);
388
- }, de = he(
402
+ }, ue = () => r < z ? (b((e) => e + 1), oe()) : D(!0), ge = () => {
403
+ I(!1), D(!1), s(null);
404
+ }, he = Ce(
389
405
  () => (
390
406
  // ensure logo is positioned inside the square container
391
- /* @__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: [
392
408
  /* @__PURE__ */ t("source", { srcSet: window.origin + "/cloud/speakid/games/whatsmissing/logo.svg", type: "image/svg+xml" }),
393
409
  /* @__PURE__ */ t("img", { src: window.origin + "/cloud/speakid/games/whatsmissing/logo.png", alt: "SPEAKID Logo", style: n.gmLogoImg, loading: "lazy" })
394
410
  ] }) })
@@ -400,106 +416,111 @@ function Me() {
400
416
  {
401
417
  ref: l,
402
418
  style: {
403
- width: "100%",
404
- height: "100vh",
419
+ // center relative to viewport so site layout doesn't push the game off-center
420
+ position: "absolute",
421
+ inset: 0,
405
422
  display: "flex",
406
423
  justifyContent: "center",
407
424
  alignItems: "center",
408
425
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
409
426
  transition: "background 0.3s ease",
410
- overflowX: "clip"
427
+ overflow: "hidden",
428
+ zIndex: 1,
429
+ pointerEvents: "auto"
411
430
  },
412
431
  children: /* @__PURE__ */ t(
413
432
  "div",
414
433
  {
434
+ ref: f,
415
435
  style: {
416
- width: b ? "100%" : U || 1e3,
417
- 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 - 40px)",
418
440
  display: "flex",
419
441
  justifyContent: "center",
420
442
  alignItems: "center",
421
443
  overflow: "hidden",
422
- borderRadius: b ? 0 : "20px",
444
+ borderRadius: w ? 0 : "20px",
423
445
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
424
- boxShadow: b ? "none" : "0 0 40px rgba(0,0,0,0.1)",
425
- margin: b ? "0 auto" : "unset",
446
+ boxShadow: w ? "none" : "0 0 40px rgba(0,0,0,0.08)",
426
447
  position: "relative"
427
- // needed so absolute logo is inside the square
428
448
  },
429
449
  children: /* @__PURE__ */ t(
430
450
  "div",
431
451
  {
432
452
  style: {
433
453
  transform: "none",
454
+ transformOrigin: "50% 50%",
434
455
  width: "100%",
435
456
  height: "100%",
436
457
  display: "flex",
437
458
  justifyContent: "center",
438
459
  alignItems: "center"
439
460
  },
440
- children: /* @__PURE__ */ c("div", { id: "whats-missing-root", children: [
441
- !b && de,
442
- !g && !x && /* @__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: [
443
464
  /* @__PURE__ */ t("h1", { style: n.gmHeadline1, children: "WHAT'S MISSING?" }),
444
465
  /* @__PURE__ */ t("p", { style: n.gmBodyM, children: "Select a theme:" }),
445
- /* @__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)) }),
446
- /* @__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: [
447
468
  /* @__PURE__ */ t("p", { style: n.gmBodyS, children: "Choose number of rounds:" }),
448
469
  /* @__PURE__ */ t("div", { style: { display: "flex", gap: 12, marginTop: 8 }, children: [3, 4, 5].map((e) => /* @__PURE__ */ t(
449
470
  "button",
450
471
  {
451
472
  style: {
452
473
  ...n.gmButton,
453
- ...s === e ? n.gmButtonActive : {}
474
+ ...z === e ? n.gmButtonActive : {}
454
475
  },
455
- onClick: () => F(e),
476
+ onClick: () => E(e),
456
477
  children: e
457
478
  },
458
479
  e
459
480
  )) })
460
481
  ] })
461
482
  ] }),
462
- g && !x && /* @__PURE__ */ c("div", { style: n.gmCenterScreen, children: [
463
- /* @__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: [
464
485
  "Theme selected: ",
465
- g
486
+ u
466
487
  ] }),
467
- /* @__PURE__ */ c("p", { style: n.gmBodyM, children: [
488
+ /* @__PURE__ */ m("p", { style: n.gmBodyM, children: [
468
489
  "Rounds: ",
469
- s
490
+ z
470
491
  ] }),
471
- /* @__PURE__ */ t("button", { style: n.gmButton, onClick: J, children: "▶ Start game" })
492
+ /* @__PURE__ */ t("button", { style: n.gmButton, onClick: ne, children: "▶ Start game" })
472
493
  ] }),
473
- A && /* @__PURE__ */ c("div", { style: n.gmCenterScreen, children: [
494
+ A && /* @__PURE__ */ m("div", { style: n.gmCenterScreen, children: [
474
495
  /* @__PURE__ */ t("h1", { style: n.gmHeadline1, children: "Results" }),
475
- /* @__PURE__ */ c("h2", { style: n.gmHeadline3, children: [
496
+ /* @__PURE__ */ m("h2", { style: n.gmHeadline3, children: [
476
497
  "Your score: ",
477
- ne,
498
+ le,
478
499
  " / ",
479
- s
500
+ z
480
501
  ] }),
481
502
  /* @__PURE__ */ t("p", { style: { ...n.gmBodyM, color: "#10b981", marginTop: 12 }, children: "Yahoo! You did it! 🍬✨" }),
482
- /* @__PURE__ */ c("table", { style: n.gmTable, children: [
483
- /* @__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: [
484
505
  /* @__PURE__ */ t("th", { children: "Round" }),
485
506
  /* @__PURE__ */ t("th", { children: "Your Answer" }),
486
507
  /* @__PURE__ */ t("th", { children: "Correct" }),
487
508
  /* @__PURE__ */ t("th", { children: "Result" })
488
509
  ] }) }),
489
- /* @__PURE__ */ t("tbody", { children: oe.map((e, r) => /* @__PURE__ */ c("tr", { children: [
510
+ /* @__PURE__ */ t("tbody", { children: ae.map((e, o) => /* @__PURE__ */ m("tr", { children: [
490
511
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.round }),
491
512
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.answer || "—" }),
492
513
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.correct }),
493
514
  /* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.result === "correct" ? "✔ Correct" : e.result === "almost" ? "◐ Almost (0.5)" : "✘ Wrong" })
494
- ] }, r)) })
515
+ ] }, o)) })
495
516
  ] }),
496
- /* @__PURE__ */ c("div", { style: { display: "flex", gap: 12, marginTop: 24 }, children: [
497
- /* @__PURE__ */ t("button", { style: n.gmButton, onClick: J, children: "🔁 Play again" }),
498
- /* @__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" })
499
520
  ] })
500
521
  ] }),
501
- x && !A && /* @__PURE__ */ c("div", { style: n.gmGameLayout, children: [
502
- /* @__PURE__ */ c(
522
+ x && !A && /* @__PURE__ */ m("div", { style: n.gmGameLayout, children: [
523
+ /* @__PURE__ */ m(
503
524
  "div",
504
525
  {
505
526
  style: {
@@ -510,70 +531,79 @@ function Me() {
510
531
  alignItems: "center"
511
532
  },
512
533
  children: [
513
- a === "ready" && /* @__PURE__ */ c(ue, { children: [
534
+ a === "ready" && /* @__PURE__ */ m(ze, { children: [
514
535
  /* @__PURE__ */ t("h1", { style: { ...n.gmHeadline1, color: "#ec4c44" }, children: "GET READY" }),
515
536
  /* @__PURE__ */ t("div", { style: n.gmHourglass, children: "⏳" })
516
537
  ] }),
517
- a === "memorize" && /* @__PURE__ */ c("p", { style: { ...n.gmBodyM, color: "#10b981" }, children: [
538
+ a === "memorize" && /* @__PURE__ */ m("p", { style: { ...n.gmBodyM, color: "#10b981" }, children: [
518
539
  "MEMORIZE (",
519
- T,
540
+ j,
520
541
  ")"
521
542
  ] }),
522
- a === "guess" && !R && /* @__PURE__ */ c("p", { style: n.gmBodyM, children: [
543
+ a === "guess" && !R && /* @__PURE__ */ m("p", { style: n.gmBodyM, children: [
523
544
  "⏳ Time left: ",
524
- z,
545
+ O,
525
546
  "s"
526
547
  ] })
527
548
  ]
528
549
  }
529
550
  ),
530
- a !== "ready" && /* @__PURE__ */ t(
531
- "div",
532
- {
533
- style: {
534
- ...n.gmGrid,
535
- gridTemplateColumns: b ? "repeat(2, 1fr)" : "repeat(3, 210px)",
536
- gridAutoRows: b ? "150px" : "210px",
537
- gap: b ? "12px" : "20px",
538
- justifyItems: "center"
539
- },
540
- children: o.map((e, r) => {
541
- const h = p === r && a === "guess" && !R;
542
- return /* @__PURE__ */ t(
543
- "div",
544
- {
545
- style: {
546
- ...n.gmCard,
547
- ...q === "correct" && p === r ? n.gmCorrect : {},
548
- ...q === "wrong" && p === r ? n.gmWrong : {}
549
- },
550
- children: !h && /* @__PURE__ */ t(
551
- "img",
552
- {
553
- src: e.src,
554
- alt: e.name,
555
- style: {
556
- width: "100%",
557
- height: "100%",
558
- objectFit: "cover"
551
+ a !== "ready" && (() => {
552
+ const e = w ? 12 : 20, o = 32, g = h ?? L ?? (w ? 360 : 1e3), d = w ? 2 : 3, p = Math.max(
553
+ 90,
554
+ Math.floor((g - o - e * (d - 1)) / d)
555
+ );
556
+ return /* @__PURE__ */ t(
557
+ "div",
558
+ {
559
+ style: {
560
+ ...n.gmGrid,
561
+ gridTemplateColumns: `repeat(${d}, ${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((k, v) => {
570
+ const N = y === v && a === "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 === v ? n.gmCorrect : {},
581
+ ...Q === "wrong" && y === v ? n.gmWrong : {}
582
+ },
583
+ children: !N && /* @__PURE__ */ t(
584
+ "img",
585
+ {
586
+ src: k.src,
587
+ alt: k.name,
588
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
559
589
  }
560
- }
561
- )
562
- },
563
- r
564
- );
565
- })
566
- }
567
- ),
568
- /* @__PURE__ */ c("div", { style: { marginTop: 16, height: 80 }, children: [
569
- a === "guess" && !R && /* @__PURE__ */ c("div", { children: [
590
+ )
591
+ },
592
+ v
593
+ );
594
+ })
595
+ }
596
+ );
597
+ })(),
598
+ /* @__PURE__ */ m("div", { style: { marginTop: 16, height: 80 }, children: [
599
+ a === "guess" && !R && /* @__PURE__ */ m("div", { children: [
570
600
  /* @__PURE__ */ t(
571
601
  "input",
572
602
  {
573
603
  type: "text",
574
604
  placeholder: "Type the missing word",
575
- value: H,
576
- onChange: (e) => Y(e.target.value),
605
+ value: W,
606
+ onChange: (e) => U(e.target.value),
577
607
  style: n.gmInput
578
608
  }
579
609
  ),
@@ -581,13 +611,13 @@ function Me() {
581
611
  "button",
582
612
  {
583
613
  style: { ...n.gmButton, marginLeft: 8 },
584
- onClick: se,
585
- disabled: D,
586
- children: D ? "..." : "Check"
614
+ onClick: me,
615
+ disabled: P,
616
+ children: P ? "..." : "Check"
587
617
  }
588
618
  )
589
619
  ] }),
590
- 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" })
591
621
  ] })
592
622
  ] })
593
623
  ] })
@@ -599,6 +629,6 @@ function Me() {
599
629
  );
600
630
  }
601
631
  export {
602
- Me as Game,
603
- ee as themes
632
+ We as Game,
633
+ re as themes
604
634
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblinkov/whats-missing",
3
- "version": "20.0.21",
3
+ "version": "20.0.23",
4
4
  "main": "dist/whats-missing.umd.js",
5
5
  "module": "dist/whats-missing.es.js",
6
6
  "types": "dist/index.d.ts",
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,39 +214,65 @@ 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
246
  const resize = () => {
217
- // Prefer visualViewport when available — it's more accurate under page zoom
218
247
  const vv = (window as any).visualViewport;
219
248
  const vw = vv ? Math.round(vv.width) : window.innerWidth;
220
249
  const vh = vv ? Math.round(vv.height) : window.innerHeight;
221
250
 
222
- const mobile = vw < 768;
223
- // tablet flag kept for possible future use
224
- const tablet = vw >= 768 && vw < 1200;
251
+ // treat narrow screens as mobile.
252
+ // Also treat portrait tablets (iPad portrait) as mobile:
253
+ // - use a slightly larger mobile breakpoint (820px),
254
+ // - or if device is touch-capable and height > width (portrait).
255
+ const MOBILE_BREAKPOINT = 820;
256
+ const isTouchDevice = typeof navigator !== "undefined" && (navigator.maxTouchPoints > 0 || "ontouchstart" in window);
257
+ const mobile = vw <= MOBILE_BREAKPOINT || (isTouchDevice && vh > vw);
225
258
 
226
- // treat only phones as "mobile" so tablets use the desktop (square/centered) layout
227
259
  setIsMobile(mobile);
228
260
 
229
261
  if (mobile) {
230
- // mobile: fluid full-width layout
231
262
  setContainerSize(null);
232
- setScale(1);
233
- } else {
234
- // tablets and desktops: fit into a centered square (max 1000)
235
- const safeFactor = 0.97;
236
- const maxSquare = 1000;
237
- const available = Math.min(vw, vh);
238
- const sizePx = Math.max(360, Math.round(available * safeFactor)); // don't go too small
239
- const finalSize = Math.min(sizePx, maxSquare);
240
- setContainerSize(finalSize);
241
- setScale(1);
263
+ return;
242
264
  }
265
+
266
+ // tablets & desktop: conservative square
267
+ const safeFactor = 0.88; // leave margins for browser UI
268
+ const maxSquare = 1000;
269
+ const gutter = 40;
270
+ const availableW = Math.max(360, vw - gutter);
271
+ const availableH = Math.max(360, vh - gutter);
272
+ const available = Math.min(availableW, availableH);
273
+ const sizePx = Math.max(360, Math.round(available * safeFactor));
274
+ const finalSize = Math.min(sizePx, maxSquare);
275
+ setContainerSize(finalSize);
243
276
  };
244
277
 
245
278
  // run once
@@ -397,34 +430,41 @@ export default function Game() {
397
430
  <div
398
431
  ref={containerRef}
399
432
  style={{
400
- width: "100%",
401
- height: "100vh",
433
+ // center relative to viewport so site layout doesn't push the game off-center
434
+ position: "absolute",
435
+ inset: 0,
402
436
  display: "flex",
403
437
  justifyContent: "center",
404
438
  alignItems: "center",
405
439
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
406
440
  transition: "background 0.3s ease",
407
- overflowX: "clip"
441
+ overflow: "hidden",
442
+ zIndex: 1,
443
+ pointerEvents: "auto",
408
444
  }}
409
445
  >
410
446
  <div
447
+ // visible square that should fit on tablets
448
+ ref={squareRef}
411
449
  style={{
412
- width: isMobile ? "100%" : containerSize || 1000,
413
- height: isMobile ? "100%" : containerSize || 1000,
450
+ width: isMobile ? "100%" : containerSize ? `${containerSize}px` : "1000px",
451
+ height: isMobile ? "100%" : containerSize ? `${containerSize}px` : "1000px",
452
+ maxWidth: "calc(100vw - 24px)",
453
+ maxHeight: "calc(100vh - 40px)",
414
454
  display: "flex",
415
455
  justifyContent: "center",
416
456
  alignItems: "center",
417
457
  overflow: "hidden",
418
458
  borderRadius: isMobile ? 0 : "20px",
419
459
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
420
- boxShadow: isMobile ? "none" : "0 0 40px rgba(0,0,0,0.1)",
421
- margin: isMobile ? "0 auto" : "unset",
422
- position: "relative", // needed so absolute logo is inside the square
460
+ boxShadow: isMobile ? "none" : "0 0 40px rgba(0,0,0,0.08)",
461
+ position: "relative",
423
462
  }}
424
463
  >
425
464
  <div
426
465
  style={{
427
466
  transform: "none",
467
+ transformOrigin: "50% 50%",
428
468
  width: "100%",
429
469
  height: "100%",
430
470
  display: "flex",
@@ -543,50 +583,58 @@ export default function Game() {
543
583
  <p style={styles.gmBodyM}>⏳ Time left: {timeLeft}s</p>
544
584
  )}
545
585
  </div>
546
- {phase !== "ready" && (
547
- <div
548
- style={{
549
- ...styles.gmGrid,
550
- gridTemplateColumns: isMobile
551
- ? "repeat(2, 1fr)"
552
- : "repeat(3, 210px)",
553
- gridAutoRows: isMobile ? "150px" : "210px",
554
- gap: isMobile ? "12px" : "20px",
555
- justifyItems: "center",
556
- }}
557
- >
558
- {files.map((file, i) => {
559
- const isHidden =
560
- hiddenIndex === i && phase === "guess" && !answered;
561
- return (
562
- <div
563
- key={i}
564
- style={{
565
- ...styles.gmCard,
566
- ...(result === "correct" && hiddenIndex === i
567
- ? styles.gmCorrect
568
- : {}),
569
- ...(result === "wrong" && hiddenIndex === i
570
- ? styles.gmWrong
571
- : {}),
572
- }}
573
- >
574
- {!isHidden && (
575
- <img
576
- src={file.src}
577
- alt={file.name}
578
- style={{
579
- width: "100%",
580
- height: "100%",
581
- objectFit: "cover",
582
- }}
583
- />
584
- )}
585
- </div>
586
- );
587
- })}
588
- </div>
589
- )}
586
+ {phase !== "ready" && (() => {
587
+ // compute card size from real measured square size (fallback to containerSize)
588
+ const gap = isMobile ? 12 : 20;
589
+ const innerGutters = 32; // logo / paddings estimation
590
+ const effective = squareSize ?? containerSize ?? (isMobile ? 360 : 1000);
591
+ const columns = isMobile ? 2 : 3;
592
+ const cardSize = Math.max(
593
+ 90,
594
+ Math.floor((effective - innerGutters - gap * (columns - 1)) / columns)
595
+ );
596
+
597
+ return (
598
+ <div
599
+ style={{
600
+ ...styles.gmGrid,
601
+ gridTemplateColumns: `repeat(${columns}, ${cardSize}px)`,
602
+ gridAutoRows: `${cardSize}px`,
603
+ gap: `${gap}px`,
604
+ justifyContent: "center",
605
+ justifyItems: "center",
606
+ padding: 8,
607
+ boxSizing: "border-box",
608
+ }}
609
+ >
610
+ {files.map((file, i) => {
611
+ const isHidden = hiddenIndex === i && phase === "guess" && !answered;
612
+ return (
613
+ <div
614
+ key={i}
615
+ style={{
616
+ ...styles.gmCard,
617
+ width: `${cardSize}px`,
618
+ height: `${cardSize}px`,
619
+ padding: isMobile ? 8 : 10,
620
+ boxSizing: "border-box",
621
+ ...(result === "correct" && hiddenIndex === i ? styles.gmCorrect : {}),
622
+ ...(result === "wrong" && hiddenIndex === i ? styles.gmWrong : {}),
623
+ }}
624
+ >
625
+ {!isHidden && (
626
+ <img
627
+ src={file.src}
628
+ alt={file.name}
629
+ style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }}
630
+ />
631
+ )}
632
+ </div>
633
+ );
634
+ })}
635
+ </div>
636
+ );
637
+ })()}
590
638
  <div style={{ marginTop: 16, height: 80 }}>
591
639
  {phase === "guess" && !answered && (
592
640
  <div>