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