Skip to content

Instantly share code, notes, and snippets.

@f-gueguen
Created October 4, 2025 01:32
Show Gist options
  • Select an option

  • Save f-gueguen/254fa57fcf5608d73a92082ba6651d42 to your computer and use it in GitHub Desktop.

Select an option

Save f-gueguen/254fa57fcf5608d73a92082ba6651d42 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Game</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@keyframes shake{0%,100%{transform:translateX(0) rotate(0deg)}25%{transform:translateX(-10px) rotate(-5deg)}75%{transform:translateX(10px) rotate(5deg)}}@keyframes sparkle{0%,100%{transform:scale(1);filter:brightness(1)}50%{transform:scale(1.15);filter:brightness(1.3)}}@keyframes bounce-in{0%{transform:scale(0)}50%{transform:scale(1.2)}100%{transform:scale(1)}}@keyframes fade-in{from{opacity:0}to{opacity:1}}@keyframes particle-burst{0%{opacity:1;transform:translate(0,0) rotate(0deg) scale(1)}100%{opacity:0;transform:translate(calc(var(--vx) * 50),calc(var(--vy) * 50)) rotate(var(--rotation)) scale(0.3)}}.animate-shake{animation:shake .5s ease-in-out}.animate-sparkle{animation:sparkle .6s ease-in-out}.animate-bounce-in{animation:bounce-in .4s ease-out}.animate-fade-in{animation:fade-in .3s ease-out}input[type=range]::-webkit-slider-thumb{appearance:none;width:24px;height:24px;background:#a855f7;border-radius:50%;cursor:pointer}input[type=range]::-moz-range-thumb{width:24px;height:24px;background:#a855f7;border-radius:50%;cursor:pointer;border:none}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const{useState,useEffect,useRef}=React;const SettingsIcon=()=>React.createElement("svg",{width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2},React.createElement("circle",{cx:12,cy:12,r:3}),React.createElement("path",{d:"M12 1v6m0 6v6m8.66-13.66l-4.24 4.24m-4.24 4.24l-4.24 4.24m13.66-4.24l-4.24-4.24m-4.24-4.24L3.34 3.34"}));const MemoryGame=()=>{const contentSets={uppercase:{items:['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'],color:'bg-blue-300'},lowercase:{items:['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'],color:'bg-green-300'},numbers:{items:['0','1','2','3','4','5','6','7','8','9'],color:'bg-yellow-300'},hiragana:{items:['あ','い','う','え','お','か','き','く','け','こ','さ','し','す','せ','そ','た','ち','つ','て','と','な','に','ぬ','ね','の','は','ひ','ふ','へ','ほ','ま','み','む','め','も','や','ゆ','よ','ら','り','る','れ','ろ','わ','を','ん'],color:'bg-pink-300'},katakana:{items:['ア','イ','ウ','エ','オ','カ','キ','ク','ケ','コ','サ','シ','ス','セ','ソ','タ','チ','ツ','テ','ト','ナ','ニ','ヌ','ネ','ノ','ハ','ヒ','フ','ヘ','ホ','マ','ミ','ム','メ','モ','ヤ','ユ','ヨ','ラ','リ','ル','レ','ロ','ワ','ヲ','ン'],color:'bg-purple-300'},emoji:{items:['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐨','🦁','🐯','🐸','🐵','🐔','🐧','🐦','🐤','🦆','🦅','🦉'],color:'bg-orange-300'}};const[activeToggles,setActiveToggles]=useState({emoji:!0});const[isRandomMode,setIsRandomMode]=useState(!1);const[difficulty,setDifficulty]=useState('normal');const[cards,setCards]=useState([]);const[flipped,setFlipped]=useState([]);const[matched,setMatched]=useState([]);const[shaking,setShaking]=useState([]);const[sparkling,setSparkling]=useState([]);const[showSettings,setShowSettings]=useState(!1);const[particles,setParticles]=useState([]);const[viewTime,setViewTime]=useState(1500);const[dragInfo,setDragInfo]=useState(null);const dragStartPos=useRef(null);const getDifficultyPairs=()=>{if(difficulty==='small')return 4;if(difficulty==='large')return 12;return 8};useEffect(()=>{initializeGame()},[activeToggles,isRandomMode,difficulty]);const initializeGame=()=>{const allContent=[];Object.entries(activeToggles).forEach(([key,value])=>{if(value){contentSets[key].items.forEach(item=>{allContent.push({content:item,type:key,color:contentSets[key].color})})}});if(allContent.length===0){setCards([]);return}const shuffled=allContent.sort(()=>Math.random()-.5);const numPairs=getDifficultyPairs();const selectedContent=shuffled.slice(0,Math.min(numPairs,Math.floor(allContent.length/2)));const gameCards=[...selectedContent,...selectedContent].sort(()=>Math.random()-.5).map((item,index)=>({id:index,content:item.content,type:item.type,color:item.color,position:isRandomMode?{x:Math.random()*70+15,y:Math.random()*70+15,rotation:Math.random()*360}:null}));setCards(gameCards);setFlipped([]);setMatched([]);setParticles([]);setDragInfo(null)};const createParticles=(index,content)=>{const card=document.getElementById(`card-${index}`);if(!card)return;const rect=card.getBoundingClientRect();const centerX=rect.left+rect.width/2;const centerY=rect.top+rect.height/2;const newParticles=Array.from({length:25},(_,i)=>{const angle=(i/25)*Math.PI*2;const speed=3+Math.random()*2;return{id:Date.now()+i+Math.random(),content,x:centerX,y:centerY,vx:Math.cos(angle)*speed,vy:Math.sin(angle)*speed,rotation:Math.random()*360,delay:Math.random()*100}});setParticles(prev=>[...prev,...newParticles]);setTimeout(()=>{setParticles(prev=>prev.filter(p=>!newParticles.some(np=>np.id===p.id)))},1200)};const handleCardClick=index=>{if(flipped.length===2||flipped.includes(index)||matched.includes(index))return;const newFlipped=[...flipped,index];setFlipped(newFlipped);if(newFlipped.length===2){const[first,second]=newFlipped;if(cards[first].content===cards[second].content){setSparkling([first,second]);setTimeout(()=>{createParticles(first,cards[first].content);createParticles(second,cards[second].content);setMatched([...matched,first,second]);setFlipped([]);setSparkling([])},viewTime)}else{setShaking([first,second]);setTimeout(()=>{setFlipped([]);setShaking([])},800)}}};const handlePointerDown=(e,index)=>{if(!isRandomMode||flipped.includes(index)||matched.includes(index))return;e.preventDefault();e.stopPropagation();const clientX=e.clientX||(e.touches&&e.touches[0].clientX);const clientY=e.clientY||(e.touches&&e.touches[0].clientY);dragStartPos.current={x:clientX,y:clientY,time:Date.now()};setDragInfo({index,startX:clientX,startY:clientY})};const handlePointerMove=e=>{if(!isRandomMode||!dragInfo)return;e.preventDefault();const clientX=e.clientX||(e.touches&&e.touches[0].clientX);const clientY=e.clientY||(e.touches&&e.touches[0].clientY);const container=document.getElementById('game-container');if(!container)return;const rect=container.getBoundingClientRect();const x=((clientX-rect.left)/rect.width)*100;const y=((clientY-rect.top)/rect.height)*100;if(x>=5&&x<=95&&y>=5&&y<=95){setCards(prev=>prev.map((card,i)=>i===dragInfo.index?{...card,position:{...card.position,x,y}}:card))}};const handlePointerUp=(e,index)=>{if(!isRandomMode)return;const clientX=e.clientX||(e.changedTouches&&e.changedTouches[0].clientX);const clientY=e.clientY||(e.changedTouches&&e.changedTouches[0].clientY);if(dragStartPos.current&&dragInfo){const deltaX=Math.abs(clientX-dragStartPos.current.x);const deltaY=Math.abs(clientY-dragStartPos.current.y);const deltaTime=Date.now()-dragStartPos.current.time;const wasClick=deltaX<10&&deltaY<10&&deltaTime<300;if(wasClick){handleCardClick(index)}}setDragInfo(null);dragStartPos.current=null};useEffect(()=>{if(!isRandomMode||!dragInfo)return;const handleGlobalMove=e=>handlePointerMove(e);const handleGlobalUp=()=>{setDragInfo(null);dragStartPos.current=null};window.addEventListener('mousemove',handleGlobalMove);window.addEventListener('mouseup',handleGlobalUp);window.addEventListener('touchmove',handleGlobalMove,{passive:!1});window.addEventListener('touchend',handleGlobalUp);return()=>{window.removeEventListener('mousemove',handleGlobalMove);window.removeEventListener('mouseup',handleGlobalUp);window.removeEventListener('touchmove',handleGlobalMove);window.removeEventListener('touchend',handleGlobalUp)}},[dragInfo,isRandomMode]);const toggleContent=key=>{setActiveToggles(prev=>({...prev,[key]:!prev[key]}))};const getFontSize=content=>{if(/[\u3040-\u309F\u30A0-\u30FF]/.test(content))return'text-4xl';if(/[0-9]/.test(content))return'text-5xl font-bold';if(/[a-z]/.test(content))return'text-5xl font-bold';if(/[A-Z]/.test(content))return'text-5xl font-bold';return'text-5xl'};return React.createElement("div",{className:"min-h-screen bg-teal-100 p-4 relative overflow-hidden"},React.createElement("button",{onClick:()=>setShowSettings(!0),className:"fixed top-4 right-4 bg-white bg-opacity-80 p-3 rounded-full shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 z-40"},React.createElement(SettingsIcon)),React.createElement("div",{id:"game-container",className:`${isRandomMode?'absolute inset-0 p-4':'flex items-center justify-center min-h-screen'}`},!isRandomMode?React.createElement("div",{className:"grid grid-cols-4 sm:grid-cols-6 gap-4"},cards.map((card,index)=>{const isFlipped=flipped.includes(index)||matched.includes(index);const isShaking=shaking.includes(index);const isSparkling=sparkling.includes(index);const isMatched=matched.includes(index);return React.createElement("div",{key:card.id,id:`card-${index}`,onClick:()=>handleCardClick(index),className:`w-20 h-20 sm:w-24 sm:h-24 rounded-2xl cursor-pointer flex items-center justify-center ${getFontSize(card.content)} transition-all duration-300 transform ${isFlipped?card.color:'bg-indigo-400'} ${isFlipped?'scale-105':'hover:scale-110'} ${isShaking?'animate-shake':''} ${isSparkling?'animate-sparkle':''} ${isMatched?'opacity-0 pointer-events-none':''} shadow-lg hover:shadow-xl`},isFlipped?React.createElement("span",{className:"animate-bounce-in"},card.content):React.createElement("span",{className:"text-4xl"},"❓"))})):cards.map((card,index)=>{const isFlipped=flipped.includes(index)||matched.includes(index);const isShaking=shaking.includes(index);const isSparkling=sparkling.includes(index);const isMatched=matched.includes(index);const isDragging=dragInfo?.index===index;return React.createElement("div",{key:card.id,id:`card-${index}`,onMouseDown:e=>handlePointerDown(e,index),onMouseUp:e=>handlePointerUp(e,index),onTouchStart:e=>handlePointerDown(e,index),onTouchEnd:e=>handlePointerUp(e,index),style:{left:`${card.position?.x||50}%`,top:`${card.position?.y||50}%`,transform:`translate(-50%, -50%) rotate(${card.position?.rotation||0}deg)`,touchAction:'none'},className:`absolute w-20 h-20 sm:w-24 sm:h-24 rounded-2xl select-none flex items-center justify-center ${getFontSize(card.content)} transition-opacity duration-300 ${isFlipped?card.color:'bg-indigo-400'} ${isShaking?'animate-shake':''} ${isSparkling?'animate-sparkle':''} ${isMatched?'opacity-0 pointer-events-none':''} shadow-lg ${isDragging?'cursor-grabbing scale-110 z-50':'cursor-grab hover:scale-105'}`},isFlipped?React.createElement("span",{className:"animate-bounce-in pointer-events-none"},card.content):React.createElement("span",{className:"text-4xl pointer-events-none"},"❓"))})),particles.map(particle=>React.createElement("div",{key:particle.id,className:"fixed pointer-events-none text-2xl z-50",style:{left:particle.x,top:particle.y,animation:`particle-burst 1.2s ease-out forwards ${particle.delay}ms`,'--vx':`${particle.vx}px`,'--vy':`${particle.vy}px`,'--rotation':`${particle.rotation}deg`}},particle.content)),showSettings&&React.createElement("div",{className:"fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50 animate-fade-in p-4"},React.createElement("div",{className:"bg-white rounded-3xl p-8 max-w-md w-full max-h-[90vh] overflow-y-auto animate-bounce-in"},React.createElement("h2",{className:"text-3xl font-bold text-purple-600 mb-6 text-center"},"⚙️"),React.createElement("div",{className:"grid grid-cols-3 gap-3 mb-6"},[{key:'uppercase',icon:'A',color:'bg-blue-300'},{key:'lowercase',icon:'a',color:'bg-green-300'},{key:'numbers',icon:'1',color:'bg-yellow-300'},{key:'hiragana',icon:'あ',color:'bg-pink-300'},{key:'katakana',icon:'ア',color:'bg-purple-300'},{key:'emoji',icon:'🐶',color:'bg-orange-300'}].map(({key,icon,color})=>React.createElement("button",{key:key,onClick:()=>toggleContent(key),className:`aspect-square py-3 px-2 rounded-xl text-3xl font-semibold transition-all duration-200 transform hover:scale-105 flex items-center justify-center ${activeToggles[key]?color:'bg-gray-300'}`},icon))),React.createElement("div",{className:"space-y-4 pt-6 border-t border-gray-200"},React.createElement("button",{onClick:()=>setIsRandomMode(!isRandomMode),className:`w-full py-3 px-4 rounded-xl text-lg font-semibold transition-all duration-200 transform hover:scale-105 flex items-center justify-center gap-2 ${isRandomMode?'bg-teal-400':'bg-gray-300'}`},React.createElement("span",{className:"text-2xl"},"🎲")),React.createElement("div",{className:"grid grid-cols-3 gap-3"},[{key:'small',label:'4',pairs:4},{key:'normal',label:'8',pairs:8},{key:'large',label:'12',pairs:12}].map(({key,label})=>React.createElement("button",{key:key,onClick:()=>setDifficulty(key),className:`py-3 px-2 rounded-xl text-lg font-semibold transition-all duration-200 transform hover:scale-105 ${difficulty===key?'bg-blue-400':'bg-gray-300'}`},label))),React.createElement("div",null,React.createElement("div",{className:"flex items-center justify-between mb-2"},React.createElement("span",{className:"text-lg font-semibold text-gray-700"},"⏱️"),React.createElement("span",{className:"text-sm text-gray-600"},viewTime+"ms")),React.createElement("input",{type:"range",min:"500",max:"3000",step:"100",value:viewTime,onChange:e=>setViewTime(parseInt(e.target.value)),className:"w-full h-3 bg-purple-200 rounded-lg appearance-none cursor-pointer"}))),React.createElement("button",{onClick:()=>{setShowSettings(!1);initializeGame()},className:"w-full bg-purple-500 text-white py-3 px-6 mt-6 rounded-xl text-xl font-bold shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200"},"✓"))))};ReactDOM.render(React.createElement(MemoryGame),document.getElementById('root'));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment