@mblinkov/whats-missing 20.0.21 → 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 +230 -200
- package/package.json +1 -1
- package/src/Game.tsx +136 -94
|
@@ -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)},ye=(a,m)=>{const c=Array.from({length:a.length+1},()=>Array(m.length+1).fill(0));for(let s=0;s<=a.length;s++)c[s][0]=s;for(let s=0;s<=m.length;s++)c[0][s]=s;for(let s=1;s<=a.length;s++)for(let u=1;u<=m.length;u++)c[s][u]=a[s-1]===m[u-1]?c[s-1][u-1]:Math.min(c[s-1][u-1],c[s][u-1],c[s-1][u])+1;return c[a.length][m.length]};function xe(a,m={minDesktopWidth:1200,forceResize:!1}){const c=r.useRef(null),s=m.minDesktopWidth??1200,u=!!m.forceResize;r.useLayoutEffect(()=>{if(typeof window>"u"||!a||!a.current)return;const l=a.current,A=()=>{try{const i=document.body,d=getComputedStyle(i),p=parseFloat(i.style&&i.style.zoom||"");if(p&&!isNaN(p)&&p>0)return p;const v=parseFloat(d.zoom||"");if(v&&!isNaN(v)&&v>0)return v;const k=d.transform||d.webkitTransform||d.mozTransform;if(k&&k!=="none"){const z=k.match(/matrix\(([^)]+)\)/);if(z){const j=z[1].split(",").map(E=>parseFloat(E))[0];if(j&&!isNaN(j))return j}}}catch{}return 1},b=()=>{if(s&&window.innerWidth<s){S();return}const i=A();if(!i||i===1){S();return}c.current||(c.current={transform:l.style.transform||null,transformOrigin:l.style.transformOrigin||null,width:l.style.width||null,height:l.style.height||null,willChange:l.style.willChange||null});const d=1/i;u&&(l.style.width=`${i*100}%`,l.style.height=`${i*100}%`),l.style.transform=`scale(${d})`,l.style.transformOrigin="top left",l.style.willChange="transform",typeof process<"u"&&process.env&&process.env.NODE_ENV==="development"&&console.debug("[useNeutralizeBodyZoom] applied scale:",{scale:i,inv:d,forceResize:u})},S=()=>{if(!c.current)return;const i=c.current;i.transform===null?l.style.removeProperty("transform"):l.style.transform=i.transform,i.transformOrigin===null?l.style.removeProperty("transform-origin"):l.style.transformOrigin=i.transformOrigin,i.width===null?l.style.removeProperty("width"):l.style.width=i.width,i.height===null?l.style.removeProperty("height"):l.style.height=i.height,i.willChange===null?l.style.removeProperty("will-change"):l.style.willChange=i.willChange,c.current=null,process.env.NODE_ENV==="development"&&console.debug("[useNeutralizeBodyZoom] restored original styles")};let x=0;const w=()=>{cancelAnimationFrame(x),x=requestAnimationFrame(b)},f=new MutationObserver(i=>{for(const d of i)if(d.type==="attributes"&&(d.attributeName==="style"||d.attributeName==="class")){w();break}});return f.observe(document.body,{attributes:!0,attributeFilter:["style","class"]}),window.addEventListener("resize",w),w(),()=>{cancelAnimationFrame(x),f.disconnect(),window.removeEventListener("resize",w),S()}},[a,s,u])}function be(){const a=r.useRef(null);xe(a,{minDesktopWidth:1200,forceResize:!1}),r.useEffect(()=>(pe(),()=>{document.body.style.overflow=""}),[]);const[m,c]=r.useState(null),[s,u]=r.useState([]),[l,A]=r.useState(4),[b,S]=r.useState(!1),[x,w]=r.useState(1),[f,i]=r.useState(null),[d,p]=r.useState("ready"),[v,k]=r.useState(3),[z,M]=r.useState(10),[j,E]=r.useState(20),[Q,H]=r.useState(0),[L,F]=r.useState(!1),[B,q]=r.useState(""),[C,W]=r.useState(!1),[N,V]=r.useState(!1),[Y,I]=r.useState(null),[ee,G]=r.useState([]),[te,$]=r.useState([]),[y,ne]=r.useState(!1),[we,Z]=r.useState(1),[_,U]=r.useState(null);r.useEffect(()=>{const t=()=>{const g=window.visualViewport,h=g?Math.round(g.width):window.innerWidth,T=g?Math.round(g.height):window.innerHeight,R=h<768;if(ne(R),R)U(null),Z(1);else{const le=Math.min(h,T),ae=Math.max(360,Math.round(le*.97)),de=Math.min(ae,1e3);U(de),Z(1)}};t(),window.addEventListener("resize",t);const o=window.visualViewport;return o&&typeof o.addEventListener=="function"&&(o.addEventListener("resize",t),o.addEventListener("scroll",t)),()=>{window.removeEventListener("resize",t),o&&typeof o.removeEventListener=="function"&&(o.removeEventListener("resize",t),o.removeEventListener("scroll",t))}},[]);const K=t=>[...t].sort(()=>Math.random()-.5).slice(0,6),X=()=>{if(!m)return;const t=K(P[m]);u(t),S(!0),F(!1),w(1),H(0),G([]),$([]),J(t,[],!0)},J=(t=s,o=te,g=!1)=>{const h=K(t.length?t:P[m]);let T=Math.floor(Math.random()*h.length),R=h[T].name,O=0;for(;o.includes(R)&&O<20;)T=Math.floor(Math.random()*h.length),R=h[T].name,O++;u(h),i(T),$([...o,R]),W(!1),q(""),E(20),I(null),g?(p("ready"),k(3),M(10)):(p("memorize"),M(10))};r.useEffect(()=>{if(!(!b||L||C)){if(d==="ready")if(v<=0)p("memorize");else{const t=setTimeout(()=>k(o=>o-1),1e3);return()=>clearTimeout(t)}if(d==="memorize")if(z<=0)p("guess");else{const t=setTimeout(()=>M(o=>o-1),1e3);return()=>clearTimeout(t)}if(d==="guess"){if(j<=0){W(!0),I("wrong");const o=s[f].name;G(g=>[...g,{round:x,answer:B,correct:o,result:"wrong"}]);return}const t=setTimeout(()=>E(o=>o-1),1e3);return()=>clearTimeout(t)}}},[d,v,z,j,b,L,C,s,f,x,B]);const se=()=>{if(f===null||N)return;const t=s[f].name,o=B.toLowerCase().trim();let g="wrong";o===t?(H(h=>h+1),I("correct"),g="correct"):ye(o,t)===1?(H(h=>h+.5),I("almost"),g="almost"):(I("wrong"),g="wrong"),G(h=>[...h,{round:x,answer:o,correct:t,result:g}]),V(!0),setTimeout(()=>{V(!1),W(!0)},600)},oe=()=>x<l?(w(t=>t+1),J()):F(!0),re=()=>{S(!1),F(!1),c(null)},ie=r.useMemo(()=>e.jsx("div",{style:{...n.gmLogoFixed,position:"absolute",top:16,left:16,zIndex:30},children:e.jsxs("picture",{children:[e.jsx("source",{srcSet:window.origin+"/cloud/speakid/games/whatsmissing/logo.svg",type:"image/svg+xml"}),e.jsx("img",{src:window.origin+"/cloud/speakid/games/whatsmissing/logo.png",alt:"SPEAKID Logo",style:n.gmLogoImg,loading:"lazy"})]})}),[]);return e.jsx("div",{ref:a,style:{width:"100%",height:"100vh",display:"flex",justifyContent:"center",alignItems:"center",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",transition:"background 0.3s ease",overflowX:"clip"},children:e.jsx("div",{style:{width:y?"100%":_||1e3,height:y?"100%":_||1e3,display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",borderRadius:y?0:"20px",background:"linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",boxShadow:y?"none":"0 0 40px rgba(0,0,0,0.1)",margin:y?"0 auto":"unset",position:"relative"},children:e.jsx("div",{style:{transform:"none",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center"},children:e.jsxs("div",{id:"whats-missing-root",children:[!y&&ie,!m&&!b&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"WHAT'S MISSING?"}),e.jsx("p",{style:n.gmBodyM,children:"Select a theme:"}),e.jsx("div",{style:{display:"flex",gap:16},children:["animals","food","toys"].map(t=>e.jsx("button",{style:n.gmButton,onClick:()=>c(t),children:t==="animals"?"🐶 Animals":t==="food"?"🍎 Food":"🧸 Toys"},t))}),e.jsxs("div",{style:{marginTop:24},children:[e.jsx("p",{style:n.gmBodyS,children:"Choose number of rounds:"}),e.jsx("div",{style:{display:"flex",gap:12,marginTop:8},children:[3,4,5].map(t=>e.jsx("button",{style:{...n.gmButton,...l===t?n.gmButtonActive:{}},onClick:()=>A(t),children:t},t))})]})]}),m&&!b&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsxs("h1",{style:n.gmHeadline1,children:["Theme selected: ",m]}),e.jsxs("p",{style:n.gmBodyM,children:["Rounds: ",l]}),e.jsx("button",{style:n.gmButton,onClick:X,children:"▶ Start game"})]}),L&&e.jsxs("div",{style:n.gmCenterScreen,children:[e.jsx("h1",{style:n.gmHeadline1,children:"Results"}),e.jsxs("h2",{style:n.gmHeadline3,children:["Your score: ",Q," / ",l]}),e.jsx("p",{style:{...n.gmBodyM,color:"#10b981",marginTop:12},children:"Yahoo! You did it! 🍬✨"}),e.jsxs("table",{style:n.gmTable,children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Round"}),e.jsx("th",{children:"Your Answer"}),e.jsx("th",{children:"Correct"}),e.jsx("th",{children:"Result"})]})}),e.jsx("tbody",{children:ee.map((t,o)=>e.jsxs("tr",{children:[e.jsx("td",{style:n.gmTableCell,children:t.round}),e.jsx("td",{style:n.gmTableCell,children:t.answer||"—"}),e.jsx("td",{style:n.gmTableCell,children:t.correct}),e.jsx("td",{style:n.gmTableCell,children:t.result==="correct"?"✔ Correct":t.result==="almost"?"◐ Almost (0.5)":"✘ Wrong"})]},o))})]}),e.jsxs("div",{style:{display:"flex",gap:12,marginTop:24},children:[e.jsx("button",{style:n.gmButton,onClick:X,children:"🔁 Play again"}),e.jsx("button",{style:n.gmButton,onClick:re,children:"⬅️ Choose theme"})]})]}),b&&!L&&e.jsxs("div",{style:n.gmGameLayout,children:[e.jsxs("div",{style:{minHeight:160,display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center"},children:[d==="ready"&&e.jsxs(e.Fragment,{children:[e.jsx("h1",{style:{...n.gmHeadline1,color:"#ec4c44"},children:"GET READY"}),e.jsx("div",{style:n.gmHourglass,children:"⏳"})]}),d==="memorize"&&e.jsxs("p",{style:{...n.gmBodyM,color:"#10b981"},children:["MEMORIZE (",z,")"]}),d==="guess"&&!C&&e.jsxs("p",{style:n.gmBodyM,children:["⏳ Time left: ",j,"s"]})]}),d!=="ready"&&e.jsx("div",{style:{...n.gmGrid,gridTemplateColumns:y?"repeat(2, 1fr)":"repeat(3, 210px)",gridAutoRows:y?"150px":"210px",gap:y?"12px":"20px",justifyItems:"center"},children:s.map((t,o)=>{const g=f===o&&d==="guess"&&!C;return e.jsx("div",{style:{...n.gmCard,...Y==="correct"&&f===o?n.gmCorrect:{},...Y==="wrong"&&f===o?n.gmWrong:{}},children:!g&&e.jsx("img",{src:t.src,alt:t.name,style:{width:"100%",height:"100%",objectFit:"cover"}})},o)})}),e.jsxs("div",{style:{marginTop:16,height:80},children:[d==="guess"&&!C&&e.jsxs("div",{children:[e.jsx("input",{type:"text",placeholder:"Type the missing word",value:B,onChange:t=>q(t.target.value),style:n.gmInput}),e.jsx("button",{style:{...n.gmButton,marginLeft:8},onClick:se,disabled:N,children:N?"...":"Check"})]}),C&&e.jsx("button",{type:"button",style:n.gmButton,onClick:oe,children:"Next round"})]})]})]})})})})}exports.Game=be;exports.themes=P;
|
|
10
|
+
`,document.head.appendChild(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 pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (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 pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (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,21 +69,21 @@ const pe = window.origin + "/cloud/speakid/games/whatsmissing/", V = (l, g) => l
|
|
|
69
69
|
["teddy_bear", "teddy bear"],
|
|
70
70
|
["yo-yo", "yo-yo"],
|
|
71
71
|
["crayons", "crayons"]
|
|
72
|
-
],
|
|
73
|
-
food:
|
|
74
|
-
animals:
|
|
75
|
-
toys:
|
|
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: {
|
|
@@ -236,159 +236,175 @@ const ve = {
|
|
|
236
236
|
},
|
|
237
237
|
gmHourglass: {
|
|
238
238
|
fontSize: "42px",
|
|
239
|
-
...
|
|
239
|
+
...Te
|
|
240
240
|
}
|
|
241
|
-
},
|
|
242
|
-
const
|
|
243
|
-
|
|
241
|
+
}, Re = () => {
|
|
242
|
+
const a = document.createElement("style");
|
|
243
|
+
a.textContent = `
|
|
244
244
|
/* scope only inside game root */
|
|
245
245
|
#whats-missing-root, #whats-missing-root * { box-sizing: border-box; }
|
|
246
246
|
#whats-missing-root img { max-width:100%; height:auto; display:block; user-select:none; }
|
|
247
|
-
`, document.head.appendChild(
|
|
248
|
-
},
|
|
249
|
-
const
|
|
250
|
-
for (let
|
|
251
|
-
for (let
|
|
252
|
-
for (let
|
|
253
|
-
for (let u = 1; u <=
|
|
254
|
-
|
|
255
|
-
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];
|
|
256
256
|
};
|
|
257
|
-
function
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
if (typeof window > "u" || !
|
|
261
|
-
const s =
|
|
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 = () => {
|
|
262
262
|
try {
|
|
263
|
-
const
|
|
263
|
+
const r = document.body, b = getComputedStyle(r), y = parseFloat(r.style && r.style.zoom || "");
|
|
264
264
|
if (y && !isNaN(y) && y > 0) return y;
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
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;
|
|
274
273
|
}
|
|
275
274
|
} catch {
|
|
276
275
|
}
|
|
277
276
|
return 1;
|
|
278
|
-
},
|
|
279
|
-
if (
|
|
277
|
+
}, B = () => {
|
|
278
|
+
if (i && window.innerWidth < i) {
|
|
280
279
|
C();
|
|
281
280
|
return;
|
|
282
281
|
}
|
|
283
|
-
const
|
|
284
|
-
if (!
|
|
282
|
+
const r = S();
|
|
283
|
+
if (!r || r === 1) {
|
|
285
284
|
C();
|
|
286
285
|
return;
|
|
287
286
|
}
|
|
288
|
-
|
|
287
|
+
h.current || (h.current = {
|
|
289
288
|
transform: s.style.transform || null,
|
|
290
289
|
transformOrigin: s.style.transformOrigin || null,
|
|
291
290
|
width: s.style.width || null,
|
|
292
291
|
height: s.style.height || null,
|
|
293
292
|
willChange: s.style.willChange || null
|
|
294
293
|
});
|
|
295
|
-
const
|
|
296
|
-
u && (s.style.width = `${
|
|
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 });
|
|
297
296
|
}, C = () => {
|
|
298
|
-
if (!
|
|
299
|
-
const
|
|
300
|
-
|
|
297
|
+
if (!h.current) return;
|
|
298
|
+
const r = h.current;
|
|
299
|
+
r.transform === null ? s.style.removeProperty("transform") : s.style.transform = r.transform, r.transformOrigin === null ? s.style.removeProperty("transform-origin") : s.style.transformOrigin = r.transformOrigin, r.width === null ? s.style.removeProperty("width") : s.style.width = r.width, r.height === null ? s.style.removeProperty("height") : s.style.height = r.height, r.willChange === null ? s.style.removeProperty("will-change") : s.style.willChange = r.willChange, h.current = null, typeof process < "u" && process.env?.NODE_ENV === "development" && console.debug("[useNeutralizeBodyZoom] restored original styles");
|
|
301
300
|
};
|
|
302
|
-
let
|
|
303
|
-
const
|
|
304
|
-
cancelAnimationFrame(
|
|
305
|
-
},
|
|
306
|
-
for (const
|
|
307
|
-
if (
|
|
308
|
-
|
|
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();
|
|
309
308
|
break;
|
|
310
309
|
}
|
|
311
310
|
});
|
|
312
|
-
return
|
|
313
|
-
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();
|
|
314
313
|
};
|
|
315
|
-
}, [
|
|
314
|
+
}, [a, i, u]);
|
|
316
315
|
}
|
|
317
|
-
function
|
|
318
|
-
const
|
|
319
|
-
|
|
316
|
+
function Be() {
|
|
317
|
+
const a = Z(null), f = Z(null), [h, i] = c(null);
|
|
318
|
+
Ie(a, { minDesktopWidth: 1200, forceResize: !1 }), V(() => (Re(), () => {
|
|
320
319
|
document.body.style.overflow = "";
|
|
321
320
|
}), []);
|
|
322
|
-
const [
|
|
323
|
-
|
|
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(() => {
|
|
324
340
|
const e = () => {
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const ce = Math.min(f, I), me = Math.max(360, Math.round(ce * 0.97)), ge = Math.min(me, 1e3);
|
|
330
|
-
K(ge), _(1);
|
|
341
|
+
const g = window.visualViewport, 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;
|
|
331
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);
|
|
332
348
|
};
|
|
333
349
|
e(), window.addEventListener("resize", e);
|
|
334
|
-
const
|
|
335
|
-
return
|
|
336
|
-
window.removeEventListener("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));
|
|
337
353
|
};
|
|
338
354
|
}, []);
|
|
339
|
-
const
|
|
340
|
-
if (!
|
|
341
|
-
const e =
|
|
342
|
-
|
|
343
|
-
},
|
|
344
|
-
const
|
|
345
|
-
let
|
|
346
|
-
for (;
|
|
347
|
-
|
|
348
|
-
|
|
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));
|
|
349
365
|
};
|
|
350
|
-
|
|
366
|
+
V(() => {
|
|
351
367
|
if (!(!x || A || R)) {
|
|
352
|
-
if (
|
|
353
|
-
if (
|
|
368
|
+
if (d === "ready")
|
|
369
|
+
if (T <= 0) k("memorize");
|
|
354
370
|
else {
|
|
355
|
-
const e = setTimeout(() =>
|
|
371
|
+
const e = setTimeout(() => _((o) => o - 1), 1e3);
|
|
356
372
|
return () => clearTimeout(e);
|
|
357
373
|
}
|
|
358
|
-
if (
|
|
359
|
-
if (
|
|
374
|
+
if (d === "memorize")
|
|
375
|
+
if (j <= 0) k("guess");
|
|
360
376
|
else {
|
|
361
|
-
const e = setTimeout(() =>
|
|
377
|
+
const e = setTimeout(() => F((o) => o - 1), 1e3);
|
|
362
378
|
return () => clearTimeout(e);
|
|
363
379
|
}
|
|
364
|
-
if (
|
|
365
|
-
if (
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
...
|
|
370
|
-
{ 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" }
|
|
371
387
|
]);
|
|
372
388
|
return;
|
|
373
389
|
}
|
|
374
|
-
const e = setTimeout(() =>
|
|
390
|
+
const e = setTimeout(() => U((o) => o - 1), 1e3);
|
|
375
391
|
return () => clearTimeout(e);
|
|
376
392
|
}
|
|
377
393
|
}
|
|
378
|
-
}, [
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
381
|
-
const e =
|
|
382
|
-
let
|
|
383
|
-
|
|
384
|
-
|
|
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);
|
|
385
401
|
}, 600);
|
|
386
|
-
},
|
|
387
|
-
|
|
388
|
-
},
|
|
402
|
+
}, ue = () => r < C ? (b((e) => e + 1), oe()) : O(!0), ge = () => {
|
|
403
|
+
I(!1), O(!1), s(null);
|
|
404
|
+
}, he = ve(
|
|
389
405
|
() => (
|
|
390
406
|
// ensure logo is positioned inside the square container
|
|
391
|
-
/* @__PURE__ */ t("div", { style: { ...n.gmLogoFixed, position: "absolute", top: 16, left: 16, zIndex: 30 }, children: /* @__PURE__ */
|
|
407
|
+
/* @__PURE__ */ t("div", { style: { ...n.gmLogoFixed, position: "absolute", top: 16, left: 16, zIndex: 30 }, children: /* @__PURE__ */ m("picture", { children: [
|
|
392
408
|
/* @__PURE__ */ t("source", { srcSet: window.origin + "/cloud/speakid/games/whatsmissing/logo.svg", type: "image/svg+xml" }),
|
|
393
409
|
/* @__PURE__ */ t("img", { src: window.origin + "/cloud/speakid/games/whatsmissing/logo.png", alt: "SPEAKID Logo", style: n.gmLogoImg, loading: "lazy" })
|
|
394
410
|
] }) })
|
|
@@ -398,108 +414,113 @@ function Me() {
|
|
|
398
414
|
return /* @__PURE__ */ t(
|
|
399
415
|
"div",
|
|
400
416
|
{
|
|
401
|
-
ref:
|
|
417
|
+
ref: a,
|
|
402
418
|
style: {
|
|
403
|
-
|
|
404
|
-
|
|
419
|
+
// center relative to viewport so site layout doesn't push the game off-center
|
|
420
|
+
position: "fixed",
|
|
421
|
+
inset: 0,
|
|
405
422
|
display: "flex",
|
|
406
423
|
justifyContent: "center",
|
|
407
424
|
alignItems: "center",
|
|
408
425
|
background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
|
|
409
426
|
transition: "background 0.3s ease",
|
|
410
|
-
|
|
427
|
+
overflow: "hidden",
|
|
428
|
+
zIndex: 9999,
|
|
429
|
+
pointerEvents: "auto"
|
|
411
430
|
},
|
|
412
431
|
children: /* @__PURE__ */ t(
|
|
413
432
|
"div",
|
|
414
433
|
{
|
|
434
|
+
ref: f,
|
|
415
435
|
style: {
|
|
416
|
-
width:
|
|
417
|
-
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)",
|
|
418
440
|
display: "flex",
|
|
419
441
|
justifyContent: "center",
|
|
420
442
|
alignItems: "center",
|
|
421
443
|
overflow: "hidden",
|
|
422
|
-
borderRadius:
|
|
444
|
+
borderRadius: w ? 0 : "20px",
|
|
423
445
|
background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
|
|
424
|
-
boxShadow:
|
|
425
|
-
margin: b ? "0 auto" : "unset",
|
|
446
|
+
boxShadow: w ? "none" : "0 0 40px rgba(0,0,0,0.08)",
|
|
426
447
|
position: "relative"
|
|
427
|
-
// needed so absolute logo is inside the square
|
|
428
448
|
},
|
|
429
449
|
children: /* @__PURE__ */ t(
|
|
430
450
|
"div",
|
|
431
451
|
{
|
|
432
452
|
style: {
|
|
433
453
|
transform: "none",
|
|
454
|
+
transformOrigin: "50% 50%",
|
|
434
455
|
width: "100%",
|
|
435
456
|
height: "100%",
|
|
436
457
|
display: "flex",
|
|
437
458
|
justifyContent: "center",
|
|
438
459
|
alignItems: "center"
|
|
439
460
|
},
|
|
440
|
-
children: /* @__PURE__ */
|
|
441
|
-
!
|
|
442
|
-
!
|
|
461
|
+
children: /* @__PURE__ */ m("div", { id: "whats-missing-root", children: [
|
|
462
|
+
!w && he,
|
|
463
|
+
!u && !x && /* @__PURE__ */ m("div", { style: n.gmCenterScreen, children: [
|
|
443
464
|
/* @__PURE__ */ t("h1", { style: n.gmHeadline1, children: "WHAT'S MISSING?" }),
|
|
444
465
|
/* @__PURE__ */ t("p", { style: n.gmBodyM, children: "Select a theme:" }),
|
|
445
|
-
/* @__PURE__ */ t("div", { style: { display: "flex", gap: 16 }, children: ["animals", "food", "toys"].map((e) => /* @__PURE__ */ t("button", { style: n.gmButton, onClick: () =>
|
|
446
|
-
/* @__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: [
|
|
447
468
|
/* @__PURE__ */ t("p", { style: n.gmBodyS, children: "Choose number of rounds:" }),
|
|
448
469
|
/* @__PURE__ */ t("div", { style: { display: "flex", gap: 12, marginTop: 8 }, children: [3, 4, 5].map((e) => /* @__PURE__ */ t(
|
|
449
470
|
"button",
|
|
450
471
|
{
|
|
451
472
|
style: {
|
|
452
473
|
...n.gmButton,
|
|
453
|
-
...
|
|
474
|
+
...C === e ? n.gmButtonActive : {}
|
|
454
475
|
},
|
|
455
|
-
onClick: () =>
|
|
476
|
+
onClick: () => E(e),
|
|
456
477
|
children: e
|
|
457
478
|
},
|
|
458
479
|
e
|
|
459
480
|
)) })
|
|
460
481
|
] })
|
|
461
482
|
] }),
|
|
462
|
-
|
|
463
|
-
/* @__PURE__ */
|
|
483
|
+
u && !x && /* @__PURE__ */ m("div", { style: n.gmCenterScreen, children: [
|
|
484
|
+
/* @__PURE__ */ m("h1", { style: n.gmHeadline1, children: [
|
|
464
485
|
"Theme selected: ",
|
|
465
|
-
|
|
486
|
+
u
|
|
466
487
|
] }),
|
|
467
|
-
/* @__PURE__ */
|
|
488
|
+
/* @__PURE__ */ m("p", { style: n.gmBodyM, children: [
|
|
468
489
|
"Rounds: ",
|
|
469
|
-
|
|
490
|
+
C
|
|
470
491
|
] }),
|
|
471
|
-
/* @__PURE__ */ t("button", { style: n.gmButton, onClick:
|
|
492
|
+
/* @__PURE__ */ t("button", { style: n.gmButton, onClick: ne, children: "▶ Start game" })
|
|
472
493
|
] }),
|
|
473
|
-
A && /* @__PURE__ */
|
|
494
|
+
A && /* @__PURE__ */ m("div", { style: n.gmCenterScreen, children: [
|
|
474
495
|
/* @__PURE__ */ t("h1", { style: n.gmHeadline1, children: "Results" }),
|
|
475
|
-
/* @__PURE__ */
|
|
496
|
+
/* @__PURE__ */ m("h2", { style: n.gmHeadline3, children: [
|
|
476
497
|
"Your score: ",
|
|
477
|
-
|
|
498
|
+
le,
|
|
478
499
|
" / ",
|
|
479
|
-
|
|
500
|
+
C
|
|
480
501
|
] }),
|
|
481
502
|
/* @__PURE__ */ t("p", { style: { ...n.gmBodyM, color: "#10b981", marginTop: 12 }, children: "Yahoo! You did it! 🍬✨" }),
|
|
482
|
-
/* @__PURE__ */
|
|
483
|
-
/* @__PURE__ */ t("thead", { children: /* @__PURE__ */
|
|
503
|
+
/* @__PURE__ */ m("table", { style: n.gmTable, children: [
|
|
504
|
+
/* @__PURE__ */ t("thead", { children: /* @__PURE__ */ m("tr", { children: [
|
|
484
505
|
/* @__PURE__ */ t("th", { children: "Round" }),
|
|
485
506
|
/* @__PURE__ */ t("th", { children: "Your Answer" }),
|
|
486
507
|
/* @__PURE__ */ t("th", { children: "Correct" }),
|
|
487
508
|
/* @__PURE__ */ t("th", { children: "Result" })
|
|
488
509
|
] }) }),
|
|
489
|
-
/* @__PURE__ */ t("tbody", { children:
|
|
510
|
+
/* @__PURE__ */ t("tbody", { children: ae.map((e, o) => /* @__PURE__ */ m("tr", { children: [
|
|
490
511
|
/* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.round }),
|
|
491
512
|
/* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.answer || "—" }),
|
|
492
513
|
/* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.correct }),
|
|
493
514
|
/* @__PURE__ */ t("td", { style: n.gmTableCell, children: e.result === "correct" ? "✔ Correct" : e.result === "almost" ? "◐ Almost (0.5)" : "✘ Wrong" })
|
|
494
|
-
] },
|
|
515
|
+
] }, o)) })
|
|
495
516
|
] }),
|
|
496
|
-
/* @__PURE__ */
|
|
497
|
-
/* @__PURE__ */ t("button", { style: n.gmButton, onClick:
|
|
498
|
-
/* @__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" })
|
|
499
520
|
] })
|
|
500
521
|
] }),
|
|
501
|
-
x && !A && /* @__PURE__ */
|
|
502
|
-
/* @__PURE__ */
|
|
522
|
+
x && !A && /* @__PURE__ */ m("div", { style: n.gmGameLayout, children: [
|
|
523
|
+
/* @__PURE__ */ m(
|
|
503
524
|
"div",
|
|
504
525
|
{
|
|
505
526
|
style: {
|
|
@@ -510,70 +531,79 @@ function Me() {
|
|
|
510
531
|
alignItems: "center"
|
|
511
532
|
},
|
|
512
533
|
children: [
|
|
513
|
-
|
|
534
|
+
d === "ready" && /* @__PURE__ */ m(we, { children: [
|
|
514
535
|
/* @__PURE__ */ t("h1", { style: { ...n.gmHeadline1, color: "#ec4c44" }, children: "GET READY" }),
|
|
515
536
|
/* @__PURE__ */ t("div", { style: n.gmHourglass, children: "⏳" })
|
|
516
537
|
] }),
|
|
517
|
-
|
|
538
|
+
d === "memorize" && /* @__PURE__ */ m("p", { style: { ...n.gmBodyM, color: "#10b981" }, children: [
|
|
518
539
|
"MEMORIZE (",
|
|
519
|
-
|
|
540
|
+
j,
|
|
520
541
|
")"
|
|
521
542
|
] }),
|
|
522
|
-
|
|
543
|
+
d === "guess" && !R && /* @__PURE__ */ m("p", { style: n.gmBodyM, children: [
|
|
523
544
|
"⏳ Time left: ",
|
|
524
|
-
|
|
545
|
+
N,
|
|
525
546
|
"s"
|
|
526
547
|
] })
|
|
527
548
|
]
|
|
528
549
|
}
|
|
529
550
|
),
|
|
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
|
-
|
|
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" }
|
|
559
589
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
)
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
),
|
|
568
|
-
/* @__PURE__ */
|
|
569
|
-
|
|
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: [
|
|
570
600
|
/* @__PURE__ */ t(
|
|
571
601
|
"input",
|
|
572
602
|
{
|
|
573
603
|
type: "text",
|
|
574
604
|
placeholder: "Type the missing word",
|
|
575
|
-
value:
|
|
576
|
-
onChange: (e) =>
|
|
605
|
+
value: W,
|
|
606
|
+
onChange: (e) => K(e.target.value),
|
|
577
607
|
style: n.gmInput
|
|
578
608
|
}
|
|
579
609
|
),
|
|
@@ -581,13 +611,13 @@ function Me() {
|
|
|
581
611
|
"button",
|
|
582
612
|
{
|
|
583
613
|
style: { ...n.gmButton, marginLeft: 8 },
|
|
584
|
-
onClick:
|
|
614
|
+
onClick: me,
|
|
585
615
|
disabled: D,
|
|
586
616
|
children: D ? "..." : "Check"
|
|
587
617
|
}
|
|
588
618
|
)
|
|
589
619
|
] }),
|
|
590
|
-
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" })
|
|
591
621
|
] })
|
|
592
622
|
] })
|
|
593
623
|
] })
|
|
@@ -599,6 +629,6 @@ function Me() {
|
|
|
599
629
|
);
|
|
600
630
|
}
|
|
601
631
|
export {
|
|
602
|
-
|
|
603
|
-
|
|
632
|
+
Be as Game,
|
|
633
|
+
re as themes
|
|
604
634
|
};
|
package/package.json
CHANGED
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,39 +214,59 @@ export default function Game() {
|
|
|
207
214
|
const [resultsTable, setResultsTable] = useState<RoundResult[]>([]);
|
|
208
215
|
const [usedHidden, setUsedHidden] = useState<string[]>([]);
|
|
209
216
|
const [isMobile, setIsMobile] = useState(false);
|
|
210
|
-
const [scale, setScale] = useState(1);
|
|
211
217
|
const [containerSize, setContainerSize] = useState<number | null>(null);
|
|
212
218
|
|
|
219
|
+
// наблюдаем за реальным render-size квадрата — это даёт надежную базу для расчета карточек
|
|
220
|
+
useLayoutEffect(() => {
|
|
221
|
+
const el = squareRef.current;
|
|
222
|
+
if (!el || typeof window === "undefined") return;
|
|
223
|
+
let ro: ResizeObserver | null = null;
|
|
224
|
+
try {
|
|
225
|
+
ro = new ResizeObserver((entries) => {
|
|
226
|
+
for (const e of entries) {
|
|
227
|
+
const cr = e.contentRect;
|
|
228
|
+
setSquareSize(Math.round(Math.min(cr.width, cr.height)));
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
ro.observe(el);
|
|
232
|
+
// init
|
|
233
|
+
setSquareSize(Math.round(Math.min(el.clientWidth, el.clientHeight)));
|
|
234
|
+
} catch (err) {
|
|
235
|
+
// ResizeObserver may not be available — fallback to initial measurement
|
|
236
|
+
setSquareSize(Math.round(Math.min(el.clientWidth, el.clientHeight)));
|
|
237
|
+
}
|
|
238
|
+
return () => {
|
|
239
|
+
if (ro) ro.disconnect();
|
|
240
|
+
};
|
|
241
|
+
}, [isMobile]);
|
|
242
|
+
|
|
213
243
|
|
|
214
244
|
// ✅ адаптив под мобилки, планшеты и десктоп
|
|
215
245
|
useEffect(() => {
|
|
216
246
|
const resize = () => {
|
|
217
|
-
// Prefer visualViewport when available — it's more accurate under page zoom
|
|
218
247
|
const vv = (window as any).visualViewport;
|
|
219
248
|
const vw = vv ? Math.round(vv.width) : window.innerWidth;
|
|
220
249
|
const vh = vv ? Math.round(vv.height) : window.innerHeight;
|
|
221
250
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const tablet = vw >= 768 && vw < 1200;
|
|
225
|
-
|
|
226
|
-
// treat only phones as "mobile" so tablets use the desktop (square/centered) layout
|
|
251
|
+
// treat narrow screens as mobile; portrait tablets also considered mobile
|
|
252
|
+
const mobile = vw < 768 || (vw >= 768 && vw < 1200 && vh > vw);
|
|
227
253
|
setIsMobile(mobile);
|
|
228
254
|
|
|
229
255
|
if (mobile) {
|
|
230
|
-
// mobile: fluid full-width layout
|
|
231
256
|
setContainerSize(null);
|
|
232
|
-
|
|
233
|
-
} else {
|
|
234
|
-
// tablets and desktops: fit into a centered square (max 1000)
|
|
235
|
-
const safeFactor = 0.97;
|
|
236
|
-
const maxSquare = 1000;
|
|
237
|
-
const available = Math.min(vw, vh);
|
|
238
|
-
const sizePx = Math.max(360, Math.round(available * safeFactor)); // don't go too small
|
|
239
|
-
const finalSize = Math.min(sizePx, maxSquare);
|
|
240
|
-
setContainerSize(finalSize);
|
|
241
|
-
setScale(1);
|
|
257
|
+
return;
|
|
242
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);
|
|
243
270
|
};
|
|
244
271
|
|
|
245
272
|
// run once
|
|
@@ -397,34 +424,41 @@ export default function Game() {
|
|
|
397
424
|
<div
|
|
398
425
|
ref={containerRef}
|
|
399
426
|
style={{
|
|
400
|
-
|
|
401
|
-
|
|
427
|
+
// center relative to viewport so site layout doesn't push the game off-center
|
|
428
|
+
position: "fixed",
|
|
429
|
+
inset: 0,
|
|
402
430
|
display: "flex",
|
|
403
431
|
justifyContent: "center",
|
|
404
432
|
alignItems: "center",
|
|
405
433
|
background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
|
|
406
434
|
transition: "background 0.3s ease",
|
|
407
|
-
|
|
435
|
+
overflow: "hidden",
|
|
436
|
+
zIndex: 9999,
|
|
437
|
+
pointerEvents: "auto",
|
|
408
438
|
}}
|
|
409
439
|
>
|
|
410
440
|
<div
|
|
441
|
+
// visible square that should fit on tablets
|
|
442
|
+
ref={squareRef}
|
|
411
443
|
style={{
|
|
412
|
-
width: isMobile ? "100%" : containerSize
|
|
413
|
-
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)",
|
|
414
448
|
display: "flex",
|
|
415
449
|
justifyContent: "center",
|
|
416
450
|
alignItems: "center",
|
|
417
451
|
overflow: "hidden",
|
|
418
452
|
borderRadius: isMobile ? 0 : "20px",
|
|
419
453
|
background: "linear-gradient(to bottom, #fff8f8 0%, #f9fafb 100%)",
|
|
420
|
-
boxShadow: isMobile ? "none" : "0 0 40px rgba(0,0,0,0.
|
|
421
|
-
|
|
422
|
-
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",
|
|
423
456
|
}}
|
|
424
457
|
>
|
|
425
458
|
<div
|
|
426
459
|
style={{
|
|
427
460
|
transform: "none",
|
|
461
|
+
transformOrigin: "50% 50%",
|
|
428
462
|
width: "100%",
|
|
429
463
|
height: "100%",
|
|
430
464
|
display: "flex",
|
|
@@ -543,50 +577,58 @@ export default function Game() {
|
|
|
543
577
|
<p style={styles.gmBodyM}>⏳ Time left: {timeLeft}s</p>
|
|
544
578
|
)}
|
|
545
579
|
</div>
|
|
546
|
-
{phase !== "ready" && (
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
+
})()}
|
|
590
632
|
<div style={{ marginTop: 16, height: 80 }}>
|
|
591
633
|
{phase === "guess" && !answered && (
|
|
592
634
|
<div>
|