<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name='robots' content='noindex, nofollow'/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<meta name="format-detection" content="telephone=no">
<link rel="apple-touch-icon" sizes="180x180" href="icon.png">
<meta name="apple-mobile-web-app-title" content="Cat">
<link rel="icon" type="image/png" sizes="192x192" href="icon.png">
<link rel="icon" type="image/png" sizes="512x512" href="icon.png">
<title>Cat</title>
<style>
:root {
--tile-size: min(12vh, 25vw);
--gap: 8px;
}
* {
box-sizing: border-box;
touch-action: none;
-webkit-user-select: none;
user-select: none;
}
body {
margin: 0;
padding: 0;
height: 100dvh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
background-color: #f0f9ff;
font-family: 'Comic Sans MS', sans-serif;
overflow: hidden;
}
.grid {
display: grid;
grid-template-columns: repeat(3, var(--tile-size));
grid-template-rows: repeat(3, var(--tile-size));
gap: var(--gap);
padding: var(--gap);
background: #e2e8f0;
border-radius: 15px;
box-shadow: inset 0 4px 10px rgba(0,0,0,0.1);
}
.full { width: 80%; }
.tray {
display: grid;
grid-template-columns: repeat(3, var(--tile-size));
grid-template-rows: repeat(3, var(--tile-size));
gap: var(--gap);
padding: var(--gap);
background: rgba(255,255,255,0.5);
border: 3px dashed #94a3b8;
border-radius: 15px;
}
.slot {
width: var(--tile-size);
height: var(--tile-size);
background: #cbd5e1;
border-radius: 8px;
position: relative;
}
.piece {
width: var(--tile-size);
height: var(--tile-size);
background-size: cover;
background-position: center;
border-radius: 8px;
position: absolute;
cursor: grab;
z-index: 2;
transition: transform 0.1s;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.piece.dragging {
z-index: 100;
transform: scale(1.1);
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
opacity: 0.9;
}
#win-overlay {
position: fixed;
inset: 0;
background: rgba(255, 255, 255, 0.9);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 1000;
}
.btn {
padding: 15px 30px;
background: #0ea5e9;
color: white;
border: none;
border-radius: 50px;
font-size: 1.2rem;
font-weight: bold;
box-shadow: 0 4px 0 #0369a1;
}
</style>
</head>
<body>
<div class="grid" id="main-grid">
<div class="slot" data-index="1"></div>
<div class="slot" data-index="2"></div>
<div class="slot" data-index="3"></div>
<div class="slot" data-index="4"></div>
<div class="slot" data-index="5"></div>
<div class="slot" data-index="6"></div>
<div class="slot" data-index="7"></div>
<div class="slot" data-index="8"></div>
<div class="slot" data-index="9"></div>
</div>
<div class="tray" id="tray">
<div class="slot"></div><div class="slot"></div><div class="slot"></div>
<div class="slot"></div><div class="slot"></div><div class="slot"></div>
<div class="slot"></div><div class="slot"></div><div class="slot"></div>
</div>
<div id="win-overlay">
<img class="full" src="full.jpg">
<h2 style="color: #0369a1; font-size: 2rem;">Meow! You found cat!</h2>
<button class="btn" onclick="location.reload()">Play Again</button>
</div>
<script>
// --- SOUND ENGINE ---
const AudioContext = window.AudioContext || window.webkitAudioContext;
const audioCtx = new AudioContext();
const unlockAudio = () => {
if (audioCtx.state === 'suspended') audioCtx.resume();
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
gain.gain.value = 0;
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(0); osc.stop(0.1);
window.removeEventListener('pointerdown', unlockAudio);
window.removeEventListener('touchstart', unlockAudio);
};
window.addEventListener('pointerdown', unlockAudio);
window.addEventListener('touchstart', unlockAudio);
function playTone(freq, type, duration, vol = 0.1) {
try {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
} catch(e) {}
}
// --- WEB SPEECH FUNCTION ---
function speakWinMessage() {
if ('speechSynthesis' in window) {
const msg = new SpeechSynthesisUtterance();
msg.text = "Meow! You found cat!";
msg.rate = 1;
msg.pitch = 1.3; // Higher pitch for cat theme
window.speechSynthesis.speak(msg);
}
}
const SFX = {
pickup: () => playTone(600, 'sine', 0.1),
drop: () => playTone(400, 'triangle', 0.15),
tap: () => playTone(800, 'sine', 0.05, 0.05),
win: () => {
const notes = [523, 659, 783, 1046];
notes.forEach((f, i) => setTimeout(() => playTone(f, 'sine', 0.4, 0.05), i * 120));
}
};
// --- GAME LOGIC ---
let draggedElement = null;
let startX, startY, initialX, initialY;
const traySlots = document.querySelectorAll('#tray .slot');
const gridSlots = document.querySelectorAll('#main-grid .slot');
function init() {
const ids = [1, 2, 3, 4, 5, 6, 7, 8, 9].sort(() => Math.random() - 0.5);
ids.forEach((id, i) => {
const piece = document.createElement('div');
piece.className = 'piece';
piece.style.backgroundImage = `url('${id}.jpg')`;
piece.dataset.id = id;
piece.addEventListener('pointerdown', onPointerDown);
piece.addEventListener('click', onClickPiece);
traySlots[i].appendChild(piece);
});
}
function onPointerDown(e) {
SFX.pickup();
draggedElement = e.target;
draggedElement.classList.add('dragging');
const rect = draggedElement.getBoundingClientRect();
startX = e.clientX; startY = e.clientY;
initialX = rect.left; initialY = rect.top;
draggedElement.style.position = 'fixed';
draggedElement.style.left = initialX + 'px';
draggedElement.style.top = initialY + 'px';
document.addEventListener('pointermove', onPointerMove);
document.addEventListener('pointerup', onPointerUp);
}
function onPointerMove(e) {
if (!draggedElement) return;
draggedElement.style.left = (initialX + (e.clientX - startX)) + 'px';
draggedElement.style.top = (initialY + (e.clientY - startY)) + 'px';
}
function onPointerUp(e) {
if (!draggedElement) return;
SFX.drop();
draggedElement.hidden = true;
const target = document.elementFromPoint(e.clientX, e.clientY);
draggedElement.hidden = false;
const slot = target ? target.closest('.slot') : null;
if (slot) {
if (slot.hasChildNodes()) {
const existingPiece = slot.querySelector('.piece');
draggedElement.parentElement.appendChild(existingPiece);
existingPiece.style.position = 'static';
}
slot.appendChild(draggedElement);
}
draggedElement.style.position = 'static';
draggedElement.classList.remove('dragging');
draggedElement = null;
document.removeEventListener('pointermove', onPointerMove);
document.removeEventListener('pointerup', onPointerUp);
checkWin();
}
function onClickPiece(e) {
const piece = e.target;
if (piece.closest('#main-grid')) {
SFX.tap();
for (let slot of traySlots) {
if (!slot.hasChildNodes()) {
slot.appendChild(piece);
break;
}
}
}
}
function checkWin() {
let correct = 0;
gridSlots.forEach((slot, i) => {
const piece = slot.querySelector('.piece');
if (piece && piece.dataset.id == (i + 1)) correct++;
});
if (correct === 9) {
SFX.win();
document.getElementById('win-overlay').style.display = 'flex';
// Trigger Web Speech
speakWinMessage();
}
}
init();
</script>
</body>
</html>