@mblinkov/whats-missing 20.0.28 → 20.0.30

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,9 +1,9 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),o=require("react"),me=window.origin+"/cloud/speakid/games/whatsmissing/",P=(i,u)=>i.map(([l,s])=>({src:`${me}${u}/${s.replace(/\s/g,"%20")}.png`,name:s})),ue=[["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"]],ge=[["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"]],he=[["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"]],U={food:P(ue,"food"),animals:P(ge,"animals"),toys:P(he,"toys")},fe=`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),o=require("react"),Se=window.origin+"/cloud/speakid/games/whatsmissing/",Z=(r,m)=>r.map(([l,s])=>({src:`${Se}${m}/${s.replace(/\s/g,"%20")}.png`,name:s})),Ce=[["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"]],ke=[["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"]],ze=[["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"]],_={food:Z(Ce,"food"),animals:Z(ke,"animals"),toys:Z(ze,"toys")},Me=`
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 i=document.createElement("style");i.id="spin-keyframes",i.innerHTML=fe,document.head.appendChild(i)}const pe={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",...pe}},ye=()=>{const i=document.createElement("style");i.textContent=`
6
+ `;if(typeof document<"u"&&!document.getElementById("spin-keyframes")){const r=document.createElement("style");r.id="spin-keyframes",r.innerHTML=Me,document.head.appendChild(r)}const Re={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:"none",minHeight:"100%",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",textAlign:"center",color:"#1f2937",padding:"16px 8px",margin:"0 auto"},gmGrid:{width:"100%",maxWidth:"none",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",...Re}},Te=()=>{const r=document.createElement("style");r.textContent=`
7
7
  #whats-missing-root, #whats-missing-root * { box-sizing: border-box; }
8
8
  #whats-missing-root img { max-width:100%; height:auto; display:block; user-select:none; }
9
- `,document.head.appendChild(i)},xe=(i,u)=>{const l=Array.from({length:i.length+1},()=>Array(u.length+1).fill(0));for(let s=0;s<=i.length;s++)l[s][0]=s;for(let s=0;s<=u.length;s++)l[0][s]=s;for(let s=1;s<=i.length;s++)for(let c=1;c<=u.length;c++)l[s][c]=i[s-1]===u[c-1]?l[s-1][c-1]:Math.min(l[s-1][c-1],l[s][c-1],l[s-1][c])+1;return l[i.length][u.length]};function be(i,u={minDesktopWidth:1200,forceResize:!1}){const l=o.useRef(null),s=u.minDesktopWidth??1200,c=!!u.forceResize;o.useLayoutEffect(()=>{if(typeof window>"u"||!i||!i.current)return;const m=i.current,I=()=>{try{const a=document.body,g=getComputedStyle(a),C=parseFloat(a.style&&a.style.zoom||"");if(C&&!isNaN(C)&&C>0)return C;const w=parseFloat(g.zoom||"");if(w&&!isNaN(w)&&w>0)return w;const h=document.createElement("div");h.style.position="absolute",h.style.left="0",h.style.top="0",h.style.width="100px",h.style.height="1px",h.style.visibility="hidden",document.documentElement.appendChild(h);const y=h.getBoundingClientRect();if(document.documentElement.removeChild(h),y&&y.width>0){const H=y.width/100;if(!isNaN(H)&&H>0)return H}}catch{}return 1},S=()=>{if(s&&window.innerWidth<s){R();return}const a=I();if(!a||a===1){R();return}l.current||(l.current={transform:m.style.transform||null,transformOrigin:m.style.transformOrigin||null,width:m.style.width||null,height:m.style.height||null,willChange:m.style.willChange||null});const g=1/a;c&&(m.style.width=`${a*100}%`,m.style.height=`${a*100}%`),m.style.transformOrigin="50% 50%",m.style.transform=`scale(${g})`,m.style.willChange="transform"},R=()=>{if(!l.current)return;const a=l.current;m.style.transform=a.transform??"",m.style.transformOrigin=a.transformOrigin??"",m.style.width=a.width??"",m.style.height=a.height??"",m.style.willChange=a.willChange??"",l.current=null};let b=0;const k=()=>{cancelAnimationFrame(b),b=requestAnimationFrame(S)},v=new MutationObserver(a=>{for(const g of a)if(g.type==="attributes"&&(g.attributeName==="style"||g.attributeName==="class")){k();break}});return v.observe(document.body,{attributes:!0,attributeFilter:["style","class"]}),window.addEventListener("resize",k),k(),()=>{cancelAnimationFrame(b),v.disconnect(),window.removeEventListener("resize",k),R()}},[i,s,c])}function we({gameCubeSize:i,screenHeight:u,screenWidth:l}){const s=o.useRef(null),c=o.useRef(null),[m,I]=o.useState(null);be(s,{minDesktopWidth:1200,forceResize:!1}),o.useEffect(()=>(ye(),()=>{document.body.style.overflow=""}),[]);const[S,R]=o.useState(null),[b,k]=o.useState([]),[v,a]=o.useState(4),[g,C]=o.useState(!1),[w,h]=o.useState(1),[y,H]=o.useState(null),[x,A]=o.useState("ready"),[V,Z]=o.useState(3),[E,F]=o.useState(10),[$,_]=o.useState(20),[se,G]=o.useState(0),[W,N]=o.useState(!1),[L,K]=o.useState(""),[T,O]=o.useState(!1),[D,J]=o.useState(!1),[Q,B]=o.useState(null),[oe,q]=o.useState([]),[ie,X]=o.useState([]),[j,je]=o.useState(!1);o.useLayoutEffect(()=>{const t=c.current;if(!t||typeof window>"u")return;let r=null;try{r=new ResizeObserver(f=>{for(const d of f){const p=d.contentRect;I(Math.round(Math.min(p.width,p.height)))}}),r.observe(t),I(Math.round(Math.min(t.clientWidth,t.clientHeight)))}catch{I(Math.round(Math.min(t.clientWidth,t.clientHeight)))}return()=>r?.disconnect()},[j]);const ee=t=>[...t].sort(()=>Math.random()-.5).slice(0,6),te=()=>{if(!S)return;const t=ee(U[S]);k(t),C(!0),N(!1),h(1),G(0),q([]),X([]),ne(t,[],!0)},ne=(t=b,r=ie,f=!1)=>{const d=ee(t.length?t:U[S]);let p=Math.floor(Math.random()*d.length),M=d[p].name,z=0;for(;r.includes(M)&&z<20;)p=Math.floor(Math.random()*d.length),M=d[p].name,z++;k(d),H(p),X([...r,M]),O(!1),K(""),_(20),B(null),f?(A("ready"),Z(3),F(10)):(A("memorize"),F(10))};o.useEffect(()=>{if(!(!g||W||T)){if(x==="ready")if(V<=0)A("memorize");else{const t=setTimeout(()=>Z(r=>r-1),1e3);return()=>clearTimeout(t)}if(x==="memorize")if(E<=0)A("guess");else{const t=setTimeout(()=>F(r=>r-1),1e3);return()=>clearTimeout(t)}if(x==="guess"){if($<=0){O(!0),B("wrong");const r=b[y].name;q(f=>[...f,{round:w,answer:L,correct:r,result:"wrong"}]);return}const t=setTimeout(()=>_(r=>r-1),1e3);return()=>clearTimeout(t)}}},[x,V,E,$,g,W,T,b,y,w,L]);const re=()=>{if(y===null||D)return;const t=b[y].name,r=L.toLowerCase().trim();let f="wrong";r===t?(G(d=>d+1),B("correct"),f="correct"):xe(r,t)===1?(G(d=>d+.5),B("almost"),f="almost"):(B("wrong"),f="wrong"),q(d=>[...d,{round:w,answer:r,correct:t,result:f}]),J(!0),setTimeout(()=>{J(!1),O(!0)},600)},le=()=>w<v?(h(t=>t+1),ne()):N(!0),ae=()=>{C(!1),N(!1),R(null)},de=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"})]})}),[]),Y=typeof window<"u"?Math.min((l??window.innerWidth)/1440,(u??window.innerHeight)/900):1;return e.jsx("div",{ref:s,style:{position:"fixed",inset:0,display:"flex",justifyContent:"center",alignItems:"center",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",overflow:"hidden",zIndex:1},children:e.jsx("div",{ref:c,style:{width:j?"100%":`${Math.min(i??1e3,l??1e3,u??1e3)}px`,height:j?"100%":`${Math.min(i??1e3,l??1e3,u??1e3)}px`,borderRadius:j?0:"20px",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",position:"relative",overflow:"hidden",boxShadow:j?"none":"0 0 40px rgba(0,0,0,0.08)",display:"flex",justifyContent:"center",alignItems:"center"},children:e.jsxs("div",{id:"whats-missing-root",style:{transform:`scale(${Y})`,transformOrigin:"center center",width:`${100/Y}%`,height:`${100/Y}%`,display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center"},children:[!j&&de,!S&&!g&&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:()=>R(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:()=>a(t),children:t},t))})]})]}),S&&!g&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsxs("h1",{style:n.gmHeadline1,children:["Theme selected: ",S]}),e.jsxs("p",{style:n.gmBodyM,children:["Rounds: ",v]}),e.jsx("button",{style:n.gmButton,onClick:te,children:"▶ Start game"})]}),W&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"Results"}),e.jsxs("h2",{style:n.gmHeadline3,children:["Your score: ",se," / ",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:oe.map((t,r)=>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"})]},r))})]}),e.jsxs("div",{style:{display:"flex",gap:12,marginTop:24},children:[e.jsx("button",{style:n.gmButton,onClick:te,children:"🔁 Play again"}),e.jsx("button",{style:n.gmButton,onClick:ae,children:"⬅️ Choose theme"})]})]}),g&&!W&&e.jsxs("div",{style:n.gmGameLayout,children:[e.jsxs("div",{style:{minHeight:160,display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center"},children:[x==="ready"&&e.jsxs(e.Fragment,{children:[e.jsx("h1",{style:{...n.gmHeadline1,color:"#ec4c44"},children:"GET READY"}),e.jsx("div",{style:n.gmHourglass,children:"⏳"})]}),x==="memorize"&&e.jsxs("p",{style:{...n.gmBodyM,color:"#10b981"},children:["MEMORIZE (",E,")"]}),x==="guess"&&!T&&e.jsxs("p",{style:n.gmBodyM,children:["⏳ Time left: ",$,"s"]})]}),x!=="ready"&&(()=>{const t=j?12:20,r=32,f=Math.min(c.current?.clientWidth??i??1e3,c.current?.clientHeight??u??1e3,l??1e3),d=j?2:3,p=Math.max(90,Math.floor((f-r-t*(d-1))/d));return e.jsx("div",{style:{...n.gmGrid,gridTemplateColumns:`repeat(${d}, ${p}px)`,gridAutoRows:`${p}px`,gap:`${t}px`,justifyContent:"center",justifyItems:"center",padding:8,boxSizing:"border-box"},children:b.map((M,z)=>{const ce=y===z&&x==="guess"&&!T;return e.jsx("div",{style:{...n.gmCard,width:`${p}px`,height:`${p}px`,padding:j?8:10,boxSizing:"border-box",...Q==="correct"&&y===z?n.gmCorrect:{},...Q==="wrong"&&y===z?n.gmWrong:{}},children:!ce&&e.jsx("img",{src:M.src,alt:M.name,style:{width:"100%",height:"100%",objectFit:"cover",display:"block"}})},z)})})})(),e.jsxs("div",{style:{marginTop:16,height:80},children:[x==="guess"&&!T&&e.jsxs("div",{children:[e.jsx("input",{type:"text",placeholder:"Type the missing word",value:L,onChange:t=>K(t.target.value),style:n.gmInput}),e.jsx("button",{style:{...n.gmButton,marginLeft:8},onClick:re,disabled:D,children:D?"...":"Check"})]}),T&&e.jsx("button",{type:"button",style:n.gmButton,onClick:le,children:"Next round"})]})]})]})})})}exports.Game=we;exports.themes=U;
9
+ `,document.head.appendChild(r)},Ie=(r,m)=>{const l=Array.from({length:r.length+1},()=>Array(m.length+1).fill(0));for(let s=0;s<=r.length;s++)l[s][0]=s;for(let s=0;s<=m.length;s++)l[0][s]=s;for(let s=1;s<=r.length;s++)for(let d=1;d<=m.length;d++)l[s][d]=r[s-1]===m[d-1]?l[s-1][d-1]:Math.min(l[s-1][d-1],l[s][d-1],l[s-1][d])+1;return l[r.length][m.length]};function He(r,m={minDesktopWidth:1200,forceResize:!1}){const l=o.useRef(null),s=m.minDesktopWidth??1200,d=!!m.forceResize;o.useLayoutEffect(()=>{if(typeof window>"u"||!r||!r.current)return;const c=r.current,T=()=>{try{const a=document.body,g=getComputedStyle(a),C=parseFloat(a.style&&a.style.zoom||"");if(C&&!isNaN(C)&&C>0)return C;const w=parseFloat(g.zoom||"");if(w&&!isNaN(w)&&w>0)return w;const h=document.createElement("div");h.style.position="absolute",h.style.left="0",h.style.top="0",h.style.width="100px",h.style.height="1px",h.style.visibility="hidden",document.documentElement.appendChild(h);const p=h.getBoundingClientRect();if(document.documentElement.removeChild(h),p&&p.width>0){const I=p.width/100;if(!isNaN(I)&&I>0)return I}}catch{}return 1},j=()=>{if(s&&window.innerWidth<s){M();return}const a=T();if(!a||a===1){M();return}l.current||(l.current={transform:c.style.transform||null,transformOrigin:c.style.transformOrigin||null,width:c.style.width||null,height:c.style.height||null,willChange:c.style.willChange||null});const g=1/a;d&&(c.style.width=`${a*100}%`,c.style.height=`${a*100}%`),c.style.transformOrigin="50% 50%",c.style.transform=`scale(${g})`,c.style.willChange="transform"},M=()=>{if(!l.current)return;const a=l.current;c.style.transform=a.transform??"",c.style.transformOrigin=a.transformOrigin??"",c.style.width=a.width??"",c.style.height=a.height??"",c.style.willChange=a.willChange??"",l.current=null};let b=0;const v=()=>{cancelAnimationFrame(b),b=requestAnimationFrame(j)},S=new MutationObserver(a=>{for(const g of a)if(g.type==="attributes"&&(g.attributeName==="style"||g.attributeName==="class")){v();break}});return S.observe(document.body,{attributes:!0,attributeFilter:["style","class"]}),window.addEventListener("resize",v),v(),()=>{cancelAnimationFrame(b),S.disconnect(),window.removeEventListener("resize",v),M()}},[r,s,d])}function Be({gameCubeSize:r,screenHeight:m,screenWidth:l}){const s=o.useRef(null),d=o.useRef(null),[c,T]=o.useState(null);He(s,{minDesktopWidth:1200,forceResize:!1}),o.useEffect(()=>(Te(),()=>{document.body.style.overflow=""}),[]);const[j,M]=o.useState(null),[b,v]=o.useState([]),[S,a]=o.useState(4),[g,C]=o.useState(!1),[w,h]=o.useState(1),[p,I]=o.useState(null),[y,A]=o.useState("ready"),[K,J]=o.useState(3),[$,G]=o.useState(10),[N,Q]=o.useState(20),[ce,O]=o.useState(0),[E,D]=o.useState(!1),[W,X]=o.useState(""),[R,q]=o.useState(!1),[Y,ee]=o.useState(!1),[te,H]=o.useState(null),[me,P]=o.useState([]),[ue,ne]=o.useState([]),[k,ge]=o.useState(!1);o.useEffect(()=>{if(typeof window>"u")return;const t=()=>{const i=l??window.innerWidth;ge(i<=768)};return t(),window.addEventListener("resize",t),window.addEventListener("orientationchange",t),()=>{window.removeEventListener("resize",t),window.removeEventListener("orientationchange",t)}},[l]),o.useLayoutEffect(()=>{const t=d.current;if(!t||typeof window>"u")return;let i=null;try{i=new ResizeObserver(f=>{for(const u of f){const x=u.contentRect;T(Math.round(Math.min(x.width,x.height)))}}),i.observe(t),T(Math.round(Math.min(t.clientWidth,t.clientHeight)))}catch{T(Math.round(Math.min(t.clientWidth,t.clientHeight)))}return()=>i?.disconnect()},[k]);const oe=t=>[...t].sort(()=>Math.random()-.5).slice(0,6),se=()=>{if(!j)return;const t=oe(_[j]);v(t),C(!0),D(!1),h(1),O(0),P([]),ne([]),ie(t,[],!0)},ie=(t=b,i=ue,f=!1)=>{const u=oe(t.length?t:_[j]);let x=Math.floor(Math.random()*u.length),z=u[x].name,B=0;for(;i.includes(z)&&B<20;)x=Math.floor(Math.random()*u.length),z=u[x].name,B++;v(u),I(x),ne([...i,z]),q(!1),X(""),Q(20),H(null),f?(A("ready"),J(3),G(10)):(A("memorize"),G(10))};o.useEffect(()=>{if(!(!g||E||R)){if(y==="ready")if(K<=0)A("memorize");else{const t=setTimeout(()=>J(i=>i-1),1e3);return()=>clearTimeout(t)}if(y==="memorize")if($<=0)A("guess");else{const t=setTimeout(()=>G(i=>i-1),1e3);return()=>clearTimeout(t)}if(y==="guess"){if(N<=0){q(!0),H("wrong");const i=b[p].name;P(f=>[...f,{round:w,answer:W,correct:i,result:"wrong"}]);return}const t=setTimeout(()=>Q(i=>i-1),1e3);return()=>clearTimeout(t)}}},[y,K,$,N,g,E,R,b,p,w,W]);const he=()=>{if(p===null||Y)return;const t=b[p].name,i=W.toLowerCase().trim();let f="wrong";i===t?(O(u=>u+1),H("correct"),f="correct"):Ie(i,t)===1?(O(u=>u+.5),H("almost"),f="almost"):(H("wrong"),f="wrong"),P(u=>[...u,{round:w,answer:i,correct:t,result:f}]),ee(!0),setTimeout(()=>{ee(!1),q(!0)},600)},fe=()=>w<S?(h(t=>t+1),ie()):D(!0),pe=()=>{C(!1),D(!1),M(null)},ye=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"})]})}),[]),re=1440,le=900,xe=d.current?.clientWidth??l??(typeof window<"u"?window.innerWidth:re),be=d.current?.clientHeight??m??(typeof window<"u"?window.innerHeight:le),U=Math.min(xe/re,be/le);return e.jsx("div",{ref:s,style:{position:"fixed",inset:0,display:"flex",justifyContent:"center",alignItems:"center",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",overflow:"hidden",zIndex:1},children:e.jsx("div",{ref:d,style:{width:k?"100%":`${Math.min(r??1e3,l??1e3,m??1e3)}px`,height:k?"100%":`${Math.min(r??1e3,l??1e3,m??1e3)}px`,borderRadius:k?0:"20px",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",position:"relative",overflow:"hidden",boxShadow:k?"none":"0 0 40px rgba(0,0,0,0.08)",display:"flex",justifyContent:"center",alignItems:"center"},children:e.jsxs("div",{id:"whats-missing-root",style:{transform:`scale(${U})`,transformOrigin:"center center",width:`${100/U}%`,height:`${100/U}%`,display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center"},children:[!k&&ye,!j&&!g&&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:()=>M(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,...S===t?n.gmButtonActive:{}},onClick:()=>a(t),children:t},t))})]})]}),j&&!g&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsxs("h1",{style:n.gmHeadline1,children:["Theme selected: ",j]}),e.jsxs("p",{style:n.gmBodyM,children:["Rounds: ",S]}),e.jsx("button",{style:n.gmButton,onClick:se,children:"▶ Start game"})]}),E&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"Results"}),e.jsxs("h2",{style:n.gmHeadline3,children:["Your score: ",ce," / ",S]}),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:me.map((t,i)=>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"})]},i))})]}),e.jsxs("div",{style:{display:"flex",gap:12,marginTop:24},children:[e.jsx("button",{style:n.gmButton,onClick:se,children:"🔁 Play again"}),e.jsx("button",{style:n.gmButton,onClick:pe,children:"⬅️ Choose theme"})]})]}),g&&!E&&e.jsxs("div",{style:n.gmGameLayout,children:[e.jsxs("div",{style:{minHeight:160,display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center"},children:[y==="ready"&&e.jsxs(e.Fragment,{children:[e.jsx("h1",{style:{...n.gmHeadline1,color:"#ec4c44"},children:"GET READY"}),e.jsx("div",{style:n.gmHourglass,children:"⏳"})]}),y==="memorize"&&e.jsxs("p",{style:{...n.gmBodyM,color:"#10b981"},children:["MEMORIZE (",$,")"]}),y==="guess"&&!R&&e.jsxs("p",{style:n.gmBodyM,children:["⏳ Time left: ",N,"s"]})]}),y!=="ready"&&(()=>{const t=k?12:20,i=32,f=d.current?.clientWidth??Math.min(r??1e3,l??1e3),u=d.current?.clientHeight??Math.min(r??1e3,m??1e3),x=Math.min(f,u),z=typeof window<"u"?window.innerWidth:l??1e3,B=z<600?110:z<900?130:z<1280?150:180,we=4,ae=2,V=Math.max(ae,Math.min(we,Math.floor((x-i+t)/(B+t))||ae)),je=Math.floor((x-i-t*(V-1))/V),L=Math.max(B,je);return e.jsx("div",{style:{...n.gmGrid,gridTemplateColumns:`repeat(${V}, ${L}px)`,gridAutoRows:`${L}px`,gap:`${t}px`,justifyContent:"center",justifyItems:"center",padding:8,boxSizing:"border-box"},children:b.map((de,F)=>{const ve=p===F&&y==="guess"&&!R;return e.jsx("div",{style:{...n.gmCard,width:`${L}px`,height:`${L}px`,padding:L>=160?10:8,boxSizing:"border-box",...te==="correct"&&p===F?n.gmCorrect:{},...te==="wrong"&&p===F?n.gmWrong:{}},children:!ve&&e.jsx("img",{src:de.src,alt:de.name,style:{width:"100%",height:"100%",objectFit:"cover",display:"block"}})},F)})})})(),e.jsxs("div",{style:{marginTop:16,height:80},children:[y==="guess"&&!R&&e.jsxs("div",{children:[e.jsx("input",{type:"text",placeholder:"Type the missing word",value:W,onChange:t=>X(t.target.value),style:n.gmInput}),e.jsx("button",{style:{...n.gmButton,marginLeft:8},onClick:he,disabled:Y,children:Y?"...":"Check"})]}),R&&e.jsx("button",{type:"button",style:n.gmButton,onClick:fe,children:"Next round"})]})]})]})})})}exports.Game=Be;exports.themes=_;
@@ -1,9 +1,9 @@
1
- import { jsx as n, jsxs as l, Fragment as pe } from "react/jsx-runtime";
2
- import { useRef as V, useState as a, useEffect as ie, useLayoutEffect as se, useMemo as fe } from "react";
3
- const ye = window.origin + "/cloud/speakid/games/whatsmissing/", U = (i, u) => i.map(([s, o]) => ({
4
- src: `${ye}${u}/${o.replace(/\s/g, "%20")}.png`,
1
+ import { jsx as n, jsxs as l, Fragment as Te } from "react/jsx-runtime";
2
+ import { useRef as J, useState as a, useEffect as _, useLayoutEffect as ge, useMemo as Re } from "react";
3
+ const Ie = window.origin + "/cloud/speakid/games/whatsmissing/", K = (r, u) => r.map(([s, o]) => ({
4
+ src: `${Ie}${u}/${o.replace(/\s/g, "%20")}.png`,
5
5
  name: o
6
- })), be = [
6
+ })), He = [
7
7
  ["apple", "apple"],
8
8
  ["banana", "banana"],
9
9
  ["bread", "bread"],
@@ -29,7 +29,7 @@ const ye = window.origin + "/cloud/speakid/games/whatsmissing/", U = (i, u) => i
29
29
  ["strawberry", "strawberry"],
30
30
  ["tomato", "tomato"],
31
31
  ["watermelon", "watermelon"]
32
- ], xe = [
32
+ ], Be = [
33
33
  ["bear", "bear"],
34
34
  ["cat", "cat"],
35
35
  ["crocodile", "crocodile"],
@@ -49,7 +49,7 @@ const ye = window.origin + "/cloud/speakid/games/whatsmissing/", U = (i, u) => i
49
49
  ["tortoise", "tortoise"],
50
50
  ["wolf", "wolf"],
51
51
  ["zebra", "zebra"]
52
- ], we = [
52
+ ], Ae = [
53
53
  ["ball", "ball"],
54
54
  ["balloon", "balloon"],
55
55
  ["blocks", "blocks"],
@@ -69,21 +69,21 @@ const ye = window.origin + "/cloud/speakid/games/whatsmissing/", U = (i, u) => i
69
69
  ["teddy_bear", "teddy bear"],
70
70
  ["yo-yo", "yo-yo"],
71
71
  ["crayons", "crayons"]
72
- ], re = {
73
- food: U(be, "food"),
74
- animals: U(xe, "animals"),
75
- toys: U(we, "toys")
76
- }, ke = `
72
+ ], ue = {
73
+ food: K(He, "food"),
74
+ animals: K(Be, "animals"),
75
+ toys: K(Ae, "toys")
76
+ }, Le = `
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 i = document.createElement("style");
84
- i.id = "spin-keyframes", i.innerHTML = ke, document.head.appendChild(i);
83
+ const r = document.createElement("style");
84
+ r.id = "spin-keyframes", r.innerHTML = Le, document.head.appendChild(r);
85
85
  }
86
- const Ce = {
86
+ const We = {
87
87
  animation: "spin 1.4s linear infinite"
88
88
  }, t = {
89
89
  gmCenterScreen: {
@@ -145,7 +145,8 @@ const Ce = {
145
145
  gmGameLayout: {
146
146
  position: "relative",
147
147
  width: "100%",
148
- maxWidth: "600px",
148
+ // allow the layout to expand within the square container for tablets/laptops
149
+ maxWidth: "none",
149
150
  // should fill the parent square, not the full viewport
150
151
  minHeight: "100%",
151
152
  display: "flex",
@@ -159,9 +160,11 @@ const Ce = {
159
160
  },
160
161
  gmGrid: {
161
162
  width: "100%",
162
- maxWidth: "calc(210px * 3 + 20px * 2)",
163
+ // let the grid use all available space; sizing is controlled in Game.tsx
164
+ maxWidth: "none",
163
165
  margin: "0 auto",
164
166
  display: "grid",
167
+ // defaults; overridden responsively in Game.tsx
165
168
  gridTemplateColumns: "repeat(3, 210px)",
166
169
  gridAutoRows: "210px",
167
170
  gap: "20px",
@@ -236,158 +239,167 @@ const Ce = {
236
239
  },
237
240
  gmHourglass: {
238
241
  fontSize: "42px",
239
- ...Ce
242
+ ...We
240
243
  }
241
- }, ve = () => {
242
- const i = document.createElement("style");
243
- i.textContent = `
244
+ }, Ee = () => {
245
+ const r = document.createElement("style");
246
+ r.textContent = `
244
247
  #whats-missing-root, #whats-missing-root * { box-sizing: border-box; }
245
248
  #whats-missing-root img { max-width:100%; height:auto; display:block; user-select:none; }
246
- `, document.head.appendChild(i);
247
- }, ze = (i, u) => {
248
- const s = Array.from({ length: i.length + 1 }, () => Array(u.length + 1).fill(0));
249
- for (let o = 0; o <= i.length; o++) s[o][0] = o;
249
+ `, document.head.appendChild(r);
250
+ }, je = (r, u) => {
251
+ const s = Array.from({ length: r.length + 1 }, () => Array(u.length + 1).fill(0));
252
+ for (let o = 0; o <= r.length; o++) s[o][0] = o;
250
253
  for (let o = 0; o <= u.length; o++) s[0][o] = o;
251
- for (let o = 1; o <= i.length; o++)
252
- for (let m = 1; m <= u.length; m++)
253
- s[o][m] = i[o - 1] === u[m - 1] ? s[o - 1][m - 1] : Math.min(s[o - 1][m - 1], s[o][m - 1], s[o - 1][m]) + 1;
254
- return s[i.length][u.length];
254
+ for (let o = 1; o <= r.length; o++)
255
+ for (let c = 1; c <= u.length; c++)
256
+ s[o][c] = r[o - 1] === u[c - 1] ? s[o - 1][c - 1] : Math.min(s[o - 1][c - 1], s[o][c - 1], s[o - 1][c]) + 1;
257
+ return s[r.length][u.length];
255
258
  };
256
- function Se(i, u = { minDesktopWidth: 1200, forceResize: !1 }) {
257
- const s = V(null), o = u.minDesktopWidth ?? 1200, m = !!u.forceResize;
258
- se(() => {
259
- if (typeof window > "u" || !i || !i.current) return;
260
- const g = i.current, B = () => {
259
+ function Fe(r, u = { minDesktopWidth: 1200, forceResize: !1 }) {
260
+ const s = J(null), o = u.minDesktopWidth ?? 1200, c = !!u.forceResize;
261
+ ge(() => {
262
+ if (typeof window > "u" || !r || !r.current) return;
263
+ const m = r.current, H = () => {
261
264
  try {
262
- const d = document.body, h = getComputedStyle(d), T = parseFloat(d.style && d.style.zoom || "");
263
- if (T && !isNaN(T) && T > 0) return T;
264
- const k = parseFloat(h.zoom || "");
265
- if (k && !isNaN(k) && k > 0) return k;
266
- const p = document.createElement("div");
267
- p.style.position = "absolute", p.style.left = "0", p.style.top = "0", p.style.width = "100px", p.style.height = "1px", p.style.visibility = "hidden", document.documentElement.appendChild(p);
268
- const b = p.getBoundingClientRect();
269
- if (document.documentElement.removeChild(p), b && b.width > 0) {
270
- const A = b.width / 100;
271
- if (!isNaN(A) && A > 0) return A;
265
+ const d = document.body, h = getComputedStyle(d), M = parseFloat(d.style && d.style.zoom || "");
266
+ if (M && !isNaN(M) && M > 0) return M;
267
+ const v = parseFloat(h.zoom || "");
268
+ if (v && !isNaN(v) && v > 0) return v;
269
+ const f = document.createElement("div");
270
+ f.style.position = "absolute", f.style.left = "0", f.style.top = "0", f.style.width = "100px", f.style.height = "1px", f.style.visibility = "hidden", document.documentElement.appendChild(f);
271
+ const y = f.getBoundingClientRect();
272
+ if (document.documentElement.removeChild(f), y && y.width > 0) {
273
+ const B = y.width / 100;
274
+ if (!isNaN(B) && B > 0) return B;
272
275
  }
273
276
  } catch {
274
277
  }
275
278
  return 1;
276
- }, v = () => {
279
+ }, C = () => {
277
280
  if (o && window.innerWidth < o) {
278
- M();
281
+ R();
279
282
  return;
280
283
  }
281
- const d = B();
284
+ const d = H();
282
285
  if (!d || d === 1) {
283
- M();
286
+ R();
284
287
  return;
285
288
  }
286
289
  s.current || (s.current = {
287
- transform: g.style.transform || null,
288
- transformOrigin: g.style.transformOrigin || null,
289
- width: g.style.width || null,
290
- height: g.style.height || null,
291
- willChange: g.style.willChange || null
290
+ transform: m.style.transform || null,
291
+ transformOrigin: m.style.transformOrigin || null,
292
+ width: m.style.width || null,
293
+ height: m.style.height || null,
294
+ willChange: m.style.willChange || null
292
295
  });
293
296
  const h = 1 / d;
294
- m && (g.style.width = `${d * 100}%`, g.style.height = `${d * 100}%`), g.style.transformOrigin = "50% 50%", g.style.transform = `scale(${h})`, g.style.willChange = "transform";
295
- }, M = () => {
297
+ c && (m.style.width = `${d * 100}%`, m.style.height = `${d * 100}%`), m.style.transformOrigin = "50% 50%", m.style.transform = `scale(${h})`, m.style.willChange = "transform";
298
+ }, R = () => {
296
299
  if (!s.current) return;
297
300
  const d = s.current;
298
- g.style.transform = d.transform ?? "", g.style.transformOrigin = d.transformOrigin ?? "", g.style.width = d.width ?? "", g.style.height = d.height ?? "", g.style.willChange = d.willChange ?? "", s.current = null;
301
+ m.style.transform = d.transform ?? "", m.style.transformOrigin = d.transformOrigin ?? "", m.style.width = d.width ?? "", m.style.height = d.height ?? "", m.style.willChange = d.willChange ?? "", s.current = null;
299
302
  };
300
- let w = 0;
301
- const z = () => {
302
- cancelAnimationFrame(w), w = requestAnimationFrame(v);
303
- }, S = new MutationObserver((d) => {
303
+ let x = 0;
304
+ const k = () => {
305
+ cancelAnimationFrame(x), x = requestAnimationFrame(C);
306
+ }, z = new MutationObserver((d) => {
304
307
  for (const h of d)
305
308
  if (h.type === "attributes" && (h.attributeName === "style" || h.attributeName === "class")) {
306
- z();
309
+ k();
307
310
  break;
308
311
  }
309
312
  });
310
- return S.observe(document.body, { attributes: !0, attributeFilter: ["style", "class"] }), window.addEventListener("resize", z), z(), () => {
311
- cancelAnimationFrame(w), S.disconnect(), window.removeEventListener("resize", z), M();
313
+ return z.observe(document.body, { attributes: !0, attributeFilter: ["style", "class"] }), window.addEventListener("resize", k), k(), () => {
314
+ cancelAnimationFrame(x), z.disconnect(), window.removeEventListener("resize", k), R();
312
315
  };
313
- }, [i, o, m]);
316
+ }, [r, o, c]);
314
317
  }
315
- function Be({
316
- gameCubeSize: i,
318
+ function De({
319
+ gameCubeSize: r,
317
320
  screenHeight: u,
318
321
  screenWidth: s
319
322
  }) {
320
- const o = V(null), m = V(null), [g, B] = a(null);
321
- Se(o, { minDesktopWidth: 1200, forceResize: !1 }), ie(() => (ve(), () => {
323
+ const o = J(null), c = J(null), [m, H] = a(null);
324
+ Fe(o, { minDesktopWidth: 1200, forceResize: !1 }), _(() => (Ee(), () => {
322
325
  document.body.style.overflow = "";
323
326
  }), []);
324
- const [v, M] = a(null), [w, z] = a([]), [S, d] = a(4), [h, T] = a(!1), [k, p] = a(1), [b, A] = a(null), [x, L] = a("ready"), [Z, _] = a(3), [$, E] = a(10), [G, K] = a(20), [le, N] = a(0), [j, D] = a(!1), [F, J] = a(""), [I, O] = a(!1), [q, Q] = a(!1), [X, W] = a(null), [ae, Y] = a([]), [de, ee] = a([]), [C, Te] = a(!1);
325
- se(() => {
326
- const e = m.current;
327
+ const [C, R] = a(null), [x, k] = a([]), [z, d] = a(4), [h, M] = a(!1), [v, f] = a(1), [y, B] = a(null), [b, E] = a("ready"), [Q, X] = a(3), [G, N] = a(10), [D, ee] = a(20), [he, O] = a(0), [j, q] = a(!1), [F, te] = a(""), [I, Y] = a(!1), [P, ne] = a(!1), [oe, A] = a(null), [fe, U] = a([]), [pe, ie] = a([]), [S, ye] = a(!1);
328
+ _(() => {
329
+ if (typeof window > "u") return;
330
+ const e = () => {
331
+ const i = s ?? window.innerWidth;
332
+ ye(i <= 768);
333
+ };
334
+ return e(), window.addEventListener("resize", e), window.addEventListener("orientationchange", e), () => {
335
+ window.removeEventListener("resize", e), window.removeEventListener("orientationchange", e);
336
+ };
337
+ }, [s]), ge(() => {
338
+ const e = c.current;
327
339
  if (!e || typeof window > "u") return;
328
- let r = null;
340
+ let i = null;
329
341
  try {
330
- r = new ResizeObserver((f) => {
331
- for (const c of f) {
332
- const y = c.contentRect;
333
- B(Math.round(Math.min(y.width, y.height)));
342
+ i = new ResizeObserver((p) => {
343
+ for (const g of p) {
344
+ const w = g.contentRect;
345
+ H(Math.round(Math.min(w.width, w.height)));
334
346
  }
335
- }), r.observe(e), B(Math.round(Math.min(e.clientWidth, e.clientHeight)));
347
+ }), i.observe(e), H(Math.round(Math.min(e.clientWidth, e.clientHeight)));
336
348
  } catch {
337
- B(Math.round(Math.min(e.clientWidth, e.clientHeight)));
349
+ H(Math.round(Math.min(e.clientWidth, e.clientHeight)));
338
350
  }
339
- return () => r?.disconnect();
340
- }, [C]);
341
- const te = (e) => [...e].sort(() => Math.random() - 0.5).slice(0, 6), ne = () => {
342
- if (!v) return;
343
- const e = te(re[v]);
344
- z(e), T(!0), D(!1), p(1), N(0), Y([]), ee([]), oe(e, [], !0);
345
- }, oe = (e = w, r = de, f = !1) => {
346
- const c = te(e.length ? e : re[v]);
347
- let y = Math.floor(Math.random() * c.length), H = c[y].name, R = 0;
348
- for (; r.includes(H) && R < 20; )
349
- y = Math.floor(Math.random() * c.length), H = c[y].name, R++;
350
- z(c), A(y), ee([...r, H]), O(!1), J(""), K(20), W(null), f ? (L("ready"), _(3), E(10)) : (L("memorize"), E(10));
351
+ return () => i?.disconnect();
352
+ }, [S]);
353
+ const re = (e) => [...e].sort(() => Math.random() - 0.5).slice(0, 6), se = () => {
354
+ if (!C) return;
355
+ const e = re(ue[C]);
356
+ k(e), M(!0), q(!1), f(1), O(0), U([]), ie([]), le(e, [], !0);
357
+ }, le = (e = x, i = pe, p = !1) => {
358
+ const g = re(e.length ? e : ue[C]);
359
+ let w = Math.floor(Math.random() * g.length), T = g[w].name, L = 0;
360
+ for (; i.includes(T) && L < 20; )
361
+ w = Math.floor(Math.random() * g.length), T = g[w].name, L++;
362
+ k(g), B(w), ie([...i, T]), Y(!1), te(""), ee(20), A(null), p ? (E("ready"), X(3), N(10)) : (E("memorize"), N(10));
351
363
  };
352
- ie(() => {
364
+ _(() => {
353
365
  if (!(!h || j || I)) {
354
- if (x === "ready")
355
- if (Z <= 0) L("memorize");
366
+ if (b === "ready")
367
+ if (Q <= 0) E("memorize");
356
368
  else {
357
- const e = setTimeout(() => _((r) => r - 1), 1e3);
369
+ const e = setTimeout(() => X((i) => i - 1), 1e3);
358
370
  return () => clearTimeout(e);
359
371
  }
360
- if (x === "memorize")
361
- if ($ <= 0) L("guess");
372
+ if (b === "memorize")
373
+ if (G <= 0) E("guess");
362
374
  else {
363
- const e = setTimeout(() => E((r) => r - 1), 1e3);
375
+ const e = setTimeout(() => N((i) => i - 1), 1e3);
364
376
  return () => clearTimeout(e);
365
377
  }
366
- if (x === "guess") {
367
- if (G <= 0) {
368
- O(!0), W("wrong");
369
- const r = w[b].name;
370
- Y((f) => [
371
- ...f,
372
- { round: k, answer: F, correct: r, result: "wrong" }
378
+ if (b === "guess") {
379
+ if (D <= 0) {
380
+ Y(!0), A("wrong");
381
+ const i = x[y].name;
382
+ U((p) => [
383
+ ...p,
384
+ { round: v, answer: F, correct: i, result: "wrong" }
373
385
  ]);
374
386
  return;
375
387
  }
376
- const e = setTimeout(() => K((r) => r - 1), 1e3);
388
+ const e = setTimeout(() => ee((i) => i - 1), 1e3);
377
389
  return () => clearTimeout(e);
378
390
  }
379
391
  }
380
- }, [x, Z, $, G, h, j, I, w, b, k, F]);
381
- const ce = () => {
382
- if (b === null || q) return;
383
- const e = w[b].name, r = F.toLowerCase().trim();
384
- let f = "wrong";
385
- r === e ? (N((c) => c + 1), W("correct"), f = "correct") : ze(r, e) === 1 ? (N((c) => c + 0.5), W("almost"), f = "almost") : (W("wrong"), f = "wrong"), Y((c) => [...c, { round: k, answer: r, correct: e, result: f }]), Q(!0), setTimeout(() => {
386
- Q(!1), O(!0);
392
+ }, [b, Q, G, D, h, j, I, x, y, v, F]);
393
+ const be = () => {
394
+ if (y === null || P) return;
395
+ const e = x[y].name, i = F.toLowerCase().trim();
396
+ let p = "wrong";
397
+ i === e ? (O((g) => g + 1), A("correct"), p = "correct") : je(i, e) === 1 ? (O((g) => g + 0.5), A("almost"), p = "almost") : (A("wrong"), p = "wrong"), U((g) => [...g, { round: v, answer: i, correct: e, result: p }]), ne(!0), setTimeout(() => {
398
+ ne(!1), Y(!0);
387
399
  }, 600);
388
- }, me = () => k < S ? (p((e) => e + 1), oe()) : D(!0), ge = () => {
389
- T(!1), D(!1), M(null);
390
- }, ue = fe(
400
+ }, we = () => v < z ? (f((e) => e + 1), le()) : q(!0), xe = () => {
401
+ M(!1), q(!1), R(null);
402
+ }, ve = Re(
391
403
  () => /* @__PURE__ */ n("div", { style: { ...t.gmLogoFixed, position: "absolute", top: 16, left: 16, zIndex: 30 }, children: /* @__PURE__ */ l("picture", { children: [
392
404
  /* @__PURE__ */ n(
393
405
  "source",
@@ -407,10 +419,7 @@ function Be({
407
419
  )
408
420
  ] }) }),
409
421
  []
410
- ), P = typeof window < "u" ? Math.min(
411
- (s ?? window.innerWidth) / 1440,
412
- (u ?? window.innerHeight) / 900
413
- ) : 1;
422
+ ), ae = 1440, de = 900, Ce = c.current?.clientWidth ?? s ?? (typeof window < "u" ? window.innerWidth : ae), ke = c.current?.clientHeight ?? u ?? (typeof window < "u" ? window.innerHeight : de), V = Math.min(Ce / ae, ke / de);
414
423
  return /* @__PURE__ */ n(
415
424
  "div",
416
425
  {
@@ -428,15 +437,15 @@ function Be({
428
437
  children: /* @__PURE__ */ n(
429
438
  "div",
430
439
  {
431
- ref: m,
440
+ ref: c,
432
441
  style: {
433
- width: C ? "100%" : `${Math.min(i ?? 1e3, s ?? 1e3, u ?? 1e3)}px`,
434
- height: C ? "100%" : `${Math.min(i ?? 1e3, s ?? 1e3, u ?? 1e3)}px`,
435
- borderRadius: C ? 0 : "20px",
442
+ width: S ? "100%" : `${Math.min(r ?? 1e3, s ?? 1e3, u ?? 1e3)}px`,
443
+ height: S ? "100%" : `${Math.min(r ?? 1e3, s ?? 1e3, u ?? 1e3)}px`,
444
+ borderRadius: S ? 0 : "20px",
436
445
  background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
437
446
  position: "relative",
438
447
  overflow: "hidden",
439
- boxShadow: C ? "none" : "0 0 40px rgba(0,0,0,0.08)",
448
+ boxShadow: S ? "none" : "0 0 40px rgba(0,0,0,0.08)",
440
449
  display: "flex",
441
450
  justifyContent: "center",
442
451
  alignItems: "center"
@@ -446,21 +455,21 @@ function Be({
446
455
  {
447
456
  id: "whats-missing-root",
448
457
  style: {
449
- transform: `scale(${P})`,
458
+ transform: `scale(${V})`,
450
459
  transformOrigin: "center center",
451
- width: `${100 / P}%`,
452
- height: `${100 / P}%`,
460
+ width: `${100 / V}%`,
461
+ height: `${100 / V}%`,
453
462
  display: "flex",
454
463
  flexDirection: "column",
455
464
  justifyContent: "center",
456
465
  alignItems: "center"
457
466
  },
458
467
  children: [
459
- !C && ue,
460
- !v && !h && /* @__PURE__ */ l("div", { style: t.gmCenterScreen, children: [
468
+ !S && ve,
469
+ !C && !h && /* @__PURE__ */ l("div", { style: t.gmCenterScreen, children: [
461
470
  /* @__PURE__ */ n("h1", { style: t.gmHeadline1, children: "WHAT'S MISSING?" }),
462
471
  /* @__PURE__ */ n("p", { style: t.gmBodyM, children: "Select a theme:" }),
463
- /* @__PURE__ */ n("div", { style: { display: "flex", gap: 16 }, children: ["animals", "food", "toys"].map((e) => /* @__PURE__ */ n("button", { style: t.gmButton, onClick: () => M(e), children: e === "animals" ? "🐶 Animals" : e === "food" ? "🍎 Food" : "🧸 Toys" }, e)) }),
472
+ /* @__PURE__ */ n("div", { style: { display: "flex", gap: 16 }, children: ["animals", "food", "toys"].map((e) => /* @__PURE__ */ n("button", { style: t.gmButton, onClick: () => R(e), children: e === "animals" ? "🐶 Animals" : e === "food" ? "🍎 Food" : "🧸 Toys" }, e)) }),
464
473
  /* @__PURE__ */ l("div", { style: { marginTop: 24 }, children: [
465
474
  /* @__PURE__ */ n("p", { style: t.gmBodyS, children: "Choose number of rounds:" }),
466
475
  /* @__PURE__ */ n("div", { style: { display: "flex", gap: 12, marginTop: 8 }, children: [3, 4, 5].map((e) => /* @__PURE__ */ n(
@@ -468,7 +477,7 @@ function Be({
468
477
  {
469
478
  style: {
470
479
  ...t.gmButton,
471
- ...S === e ? t.gmButtonActive : {}
480
+ ...z === e ? t.gmButtonActive : {}
472
481
  },
473
482
  onClick: () => d(e),
474
483
  children: e
@@ -477,24 +486,24 @@ function Be({
477
486
  )) })
478
487
  ] })
479
488
  ] }),
480
- v && !h && /* @__PURE__ */ l("div", { style: t.gmCenterScreen, children: [
489
+ C && !h && /* @__PURE__ */ l("div", { style: t.gmCenterScreen, children: [
481
490
  /* @__PURE__ */ l("h1", { style: t.gmHeadline1, children: [
482
491
  "Theme selected: ",
483
- v
492
+ C
484
493
  ] }),
485
494
  /* @__PURE__ */ l("p", { style: t.gmBodyM, children: [
486
495
  "Rounds: ",
487
- S
496
+ z
488
497
  ] }),
489
- /* @__PURE__ */ n("button", { style: t.gmButton, onClick: ne, children: "▶ Start game" })
498
+ /* @__PURE__ */ n("button", { style: t.gmButton, onClick: se, children: "▶ Start game" })
490
499
  ] }),
491
500
  j && /* @__PURE__ */ l("div", { style: t.gmCenterScreen, children: [
492
501
  /* @__PURE__ */ n("h1", { style: t.gmHeadline1, children: "Results" }),
493
502
  /* @__PURE__ */ l("h2", { style: t.gmHeadline3, children: [
494
503
  "Your score: ",
495
- le,
504
+ he,
496
505
  " / ",
497
- S
506
+ z
498
507
  ] }),
499
508
  /* @__PURE__ */ n("p", { style: { ...t.gmBodyM, color: "#10b981", marginTop: 12 }, children: "Yahoo! You did it! 🍬✨" }),
500
509
  /* @__PURE__ */ l("table", { style: t.gmTable, children: [
@@ -504,16 +513,16 @@ function Be({
504
513
  /* @__PURE__ */ n("th", { children: "Correct" }),
505
514
  /* @__PURE__ */ n("th", { children: "Result" })
506
515
  ] }) }),
507
- /* @__PURE__ */ n("tbody", { children: ae.map((e, r) => /* @__PURE__ */ l("tr", { children: [
516
+ /* @__PURE__ */ n("tbody", { children: fe.map((e, i) => /* @__PURE__ */ l("tr", { children: [
508
517
  /* @__PURE__ */ n("td", { style: t.gmTableCell, children: e.round }),
509
518
  /* @__PURE__ */ n("td", { style: t.gmTableCell, children: e.answer || "—" }),
510
519
  /* @__PURE__ */ n("td", { style: t.gmTableCell, children: e.correct }),
511
520
  /* @__PURE__ */ n("td", { style: t.gmTableCell, children: e.result === "correct" ? "✔ Correct" : e.result === "almost" ? "◐ Almost (0.5)" : "✘ Wrong" })
512
- ] }, r)) })
521
+ ] }, i)) })
513
522
  ] }),
514
523
  /* @__PURE__ */ l("div", { style: { display: "flex", gap: 12, marginTop: 24 }, children: [
515
- /* @__PURE__ */ n("button", { style: t.gmButton, onClick: ne, children: "🔁 Play again" }),
516
- /* @__PURE__ */ n("button", { style: t.gmButton, onClick: ge, children: "⬅️ Choose theme" })
524
+ /* @__PURE__ */ n("button", { style: t.gmButton, onClick: se, children: "🔁 Play again" }),
525
+ /* @__PURE__ */ n("button", { style: t.gmButton, onClick: xe, children: "⬅️ Choose theme" })
517
526
  ] })
518
527
  ] }),
519
528
  h && !j && /* @__PURE__ */ l("div", { style: t.gmGameLayout, children: [
@@ -528,64 +537,63 @@ function Be({
528
537
  alignItems: "center"
529
538
  },
530
539
  children: [
531
- x === "ready" && /* @__PURE__ */ l(pe, { children: [
540
+ b === "ready" && /* @__PURE__ */ l(Te, { children: [
532
541
  /* @__PURE__ */ n("h1", { style: { ...t.gmHeadline1, color: "#ec4c44" }, children: "GET READY" }),
533
542
  /* @__PURE__ */ n("div", { style: t.gmHourglass, children: "⏳" })
534
543
  ] }),
535
- x === "memorize" && /* @__PURE__ */ l("p", { style: { ...t.gmBodyM, color: "#10b981" }, children: [
544
+ b === "memorize" && /* @__PURE__ */ l("p", { style: { ...t.gmBodyM, color: "#10b981" }, children: [
536
545
  "MEMORIZE (",
537
- $,
546
+ G,
538
547
  ")"
539
548
  ] }),
540
- x === "guess" && !I && /* @__PURE__ */ l("p", { style: t.gmBodyM, children: [
549
+ b === "guess" && !I && /* @__PURE__ */ l("p", { style: t.gmBodyM, children: [
541
550
  "⏳ Time left: ",
542
- G,
551
+ D,
543
552
  "s"
544
553
  ] })
545
554
  ]
546
555
  }
547
556
  ),
548
- x !== "ready" && (() => {
549
- const e = C ? 12 : 20, r = 32, f = Math.min(
550
- m.current?.clientWidth ?? i ?? 1e3,
551
- m.current?.clientHeight ?? u ?? 1e3,
552
- s ?? 1e3
553
- ), c = C ? 2 : 3, y = Math.max(
554
- 90,
555
- Math.floor((f - r - e * (c - 1)) / c)
556
- );
557
+ b !== "ready" && (() => {
558
+ const e = S ? 12 : 20, i = 32, p = c.current?.clientWidth ?? Math.min(r ?? 1e3, s ?? 1e3), g = c.current?.clientHeight ?? Math.min(r ?? 1e3, u ?? 1e3), w = Math.min(p, g), T = typeof window < "u" ? window.innerWidth : s ?? 1e3, L = T < 600 ? 110 : T < 900 ? 130 : T < 1280 ? 150 : 180, ze = 4, ce = 2, Z = Math.max(
559
+ ce,
560
+ Math.min(
561
+ ze,
562
+ Math.floor((w - i + e) / (L + e)) || ce
563
+ )
564
+ ), Me = Math.floor((w - i - e * (Z - 1)) / Z), W = Math.max(L, Me);
557
565
  return /* @__PURE__ */ n(
558
566
  "div",
559
567
  {
560
568
  style: {
561
569
  ...t.gmGrid,
562
- gridTemplateColumns: `repeat(${c}, ${y}px)`,
563
- gridAutoRows: `${y}px`,
570
+ gridTemplateColumns: `repeat(${Z}, ${W}px)`,
571
+ gridAutoRows: `${W}px`,
564
572
  gap: `${e}px`,
565
573
  justifyContent: "center",
566
574
  justifyItems: "center",
567
575
  padding: 8,
568
576
  boxSizing: "border-box"
569
577
  },
570
- children: w.map((H, R) => {
571
- const he = b === R && x === "guess" && !I;
578
+ children: x.map((me, $) => {
579
+ const Se = y === $ && b === "guess" && !I;
572
580
  return /* @__PURE__ */ n(
573
581
  "div",
574
582
  {
575
583
  style: {
576
584
  ...t.gmCard,
577
- width: `${y}px`,
578
- height: `${y}px`,
579
- padding: C ? 8 : 10,
585
+ width: `${W}px`,
586
+ height: `${W}px`,
587
+ padding: W >= 160 ? 10 : 8,
580
588
  boxSizing: "border-box",
581
- ...X === "correct" && b === R ? t.gmCorrect : {},
582
- ...X === "wrong" && b === R ? t.gmWrong : {}
589
+ ...oe === "correct" && y === $ ? t.gmCorrect : {},
590
+ ...oe === "wrong" && y === $ ? t.gmWrong : {}
583
591
  },
584
- children: !he && /* @__PURE__ */ n(
592
+ children: !Se && /* @__PURE__ */ n(
585
593
  "img",
586
594
  {
587
- src: H.src,
588
- alt: H.name,
595
+ src: me.src,
596
+ alt: me.name,
589
597
  style: {
590
598
  width: "100%",
591
599
  height: "100%",
@@ -595,21 +603,21 @@ function Be({
595
603
  }
596
604
  )
597
605
  },
598
- R
606
+ $
599
607
  );
600
608
  })
601
609
  }
602
610
  );
603
611
  })(),
604
612
  /* @__PURE__ */ l("div", { style: { marginTop: 16, height: 80 }, children: [
605
- x === "guess" && !I && /* @__PURE__ */ l("div", { children: [
613
+ b === "guess" && !I && /* @__PURE__ */ l("div", { children: [
606
614
  /* @__PURE__ */ n(
607
615
  "input",
608
616
  {
609
617
  type: "text",
610
618
  placeholder: "Type the missing word",
611
619
  value: F,
612
- onChange: (e) => J(e.target.value),
620
+ onChange: (e) => te(e.target.value),
613
621
  style: t.gmInput
614
622
  }
615
623
  ),
@@ -617,13 +625,13 @@ function Be({
617
625
  "button",
618
626
  {
619
627
  style: { ...t.gmButton, marginLeft: 8 },
620
- onClick: ce,
621
- disabled: q,
622
- children: q ? "..." : "Check"
628
+ onClick: be,
629
+ disabled: P,
630
+ children: P ? "..." : "Check"
623
631
  }
624
632
  )
625
633
  ] }),
626
- I && /* @__PURE__ */ n("button", { type: "button", style: t.gmButton, onClick: me, children: "Next round" })
634
+ I && /* @__PURE__ */ n("button", { type: "button", style: t.gmButton, onClick: we, children: "Next round" })
627
635
  ] })
628
636
  ] })
629
637
  ]
@@ -635,6 +643,6 @@ function Be({
635
643
  );
636
644
  }
637
645
  export {
638
- Be as Game,
639
- re as themes
646
+ De as Game,
647
+ ue as themes
640
648
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblinkov/whats-missing",
3
- "version": "20.0.28",
3
+ "version": "20.0.30",
4
4
  "main": "dist/whats-missing.umd.js",
5
5
  "module": "dist/whats-missing.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -101,7 +101,8 @@ export const styles: Record<string, CSSProperties> = {
101
101
  gmGameLayout: {
102
102
  position: "relative",
103
103
  width: "100%",
104
- maxWidth: "600px",
104
+ // allow the layout to expand within the square container for tablets/laptops
105
+ maxWidth: "none",
105
106
  // should fill the parent square, not the full viewport
106
107
  minHeight: "100%",
107
108
  display: "flex",
@@ -116,9 +117,11 @@ export const styles: Record<string, CSSProperties> = {
116
117
 
117
118
  gmGrid: {
118
119
  width: "100%",
119
- maxWidth: "calc(210px * 3 + 20px * 2)",
120
+ // let the grid use all available space; sizing is controlled in Game.tsx
121
+ maxWidth: "none",
120
122
  margin: "0 auto",
121
123
  display: "grid",
124
+ // defaults; overridden responsively in Game.tsx
122
125
  gridTemplateColumns: "repeat(3, 210px)",
123
126
  gridAutoRows: "210px",
124
127
  gap: "20px",
package/src/Game.tsx CHANGED
@@ -186,6 +186,22 @@ export default function Game({
186
186
  const [usedHidden, setUsedHidden] = useState<string[]>([]);
187
187
  const [isMobile, setIsMobile] = useState(false);
188
188
 
189
+ // simple responsive flag; we still compute exact sizes from the measured square
190
+ useEffect(() => {
191
+ if (typeof window === "undefined") return;
192
+ const update = () => {
193
+ const w = screenWidth ?? window.innerWidth;
194
+ setIsMobile(w <= 768);
195
+ };
196
+ update();
197
+ window.addEventListener("resize", update);
198
+ window.addEventListener("orientationchange", update);
199
+ return () => {
200
+ window.removeEventListener("resize", update);
201
+ window.removeEventListener("orientationchange", update);
202
+ };
203
+ }, [screenWidth]);
204
+
189
205
  useLayoutEffect(() => {
190
206
  const el = squareRef.current;
191
207
  if (!el || typeof window === "undefined") return;
@@ -333,16 +349,13 @@ export default function Game({
333
349
  []
334
350
  );
335
351
 
336
- // ✅ масштаб интерфейса под реальное разрешение
352
+ // ✅ масштаб интерфейса под доступный размер контейнера (а не всей вкладки)
353
+ // это устраняет резкие перепады масштаба при открытии DevTools или панелей браузера
337
354
  const baseWidth = 1440;
338
355
  const baseHeight = 900;
339
- const scale =
340
- typeof window !== "undefined"
341
- ? Math.min(
342
- (screenWidth ?? window.innerWidth) / baseWidth,
343
- (screenHeight ?? window.innerHeight) / baseHeight
344
- )
345
- : 1;
356
+ const effectiveWidth = squareRef.current?.clientWidth ?? (screenWidth ?? (typeof window !== "undefined" ? window.innerWidth : baseWidth));
357
+ const effectiveHeight = squareRef.current?.clientHeight ?? (screenHeight ?? (typeof window !== "undefined" ? window.innerHeight : baseHeight));
358
+ const scale = Math.min(effectiveWidth / baseWidth, effectiveHeight / baseHeight);
346
359
 
347
360
  return (
348
361
  <div
@@ -513,18 +526,32 @@ export default function Game({
513
526
  {/* Сетка карточек */}
514
527
  {phase !== "ready" && (() => {
515
528
  const gap = isMobile ? 12 : 20;
516
- const innerGutters = 32;
517
- const effective = Math.min(
518
- squareRef.current?.clientWidth ?? gameCubeSize ?? 1000,
519
- squareRef.current?.clientHeight ?? screenHeight ?? 1000,
520
- screenWidth ?? 1000
521
- );
522
- const columns = isMobile ? 2 : 3;
523
- const cardSize = Math.max(
524
- 90,
525
- Math.floor((effective - innerGutters - gap * (columns - 1)) / columns)
529
+ const innerGutters = 32; // total padding inside the grid wrapper
530
+ const effW = squareRef.current?.clientWidth ?? Math.min(gameCubeSize ?? 1000, screenWidth ?? 1000);
531
+ const effH = squareRef.current?.clientHeight ?? Math.min(gameCubeSize ?? 1000, screenHeight ?? 1000);
532
+ const effective = Math.min(effW, effH);
533
+
534
+ // choose a comfortable minimum card size per breakpoint
535
+ const vw = typeof window !== "undefined" ? window.innerWidth : (screenWidth ?? 1000);
536
+ // Tune breakpoints so common laptop sizes (e.g., 1366×768) keep a 3×3 grid
537
+ // by allowing a slightly smaller minimum card size when width < 1280.
538
+ const minCardBase = vw < 600 ? 110 : vw < 900 ? 130 : vw < 1280 ? 150 : 180;
539
+
540
+ // compute columns to best fill available space without going below minCardBase
541
+ const maxColumns = 4;
542
+ const minColumns = 2;
543
+ const possibleColumns = Math.max(
544
+ minColumns,
545
+ Math.min(
546
+ maxColumns,
547
+ Math.floor((effective - innerGutters + gap) / (minCardBase + gap)) || minColumns
548
+ )
526
549
  );
527
550
 
551
+ const columns = possibleColumns;
552
+ const computed = Math.floor((effective - innerGutters - gap * (columns - 1)) / columns);
553
+ const cardSize = Math.max(minCardBase, computed);
554
+
528
555
  return (
529
556
  <div
530
557
  style={{
@@ -547,7 +574,7 @@ export default function Game({
547
574
  ...styles.gmCard,
548
575
  width: `${cardSize}px`,
549
576
  height: `${cardSize}px`,
550
- padding: isMobile ? 8 : 10,
577
+ padding: cardSize >= 160 ? 10 : 8,
551
578
  boxSizing: "border-box",
552
579
  ...(result === "correct" && hiddenIndex === i
553
580
  ? styles.gmCorrect