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