|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Card Trick Game</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
.card { |
|
width: 80px; |
|
height: 120px; |
|
perspective: 1000px; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.card-inner { |
|
position: relative; |
|
width: 100%; |
|
height: 100%; |
|
transform-style: preserve-3d; |
|
transition: transform 0.6s; |
|
} |
|
|
|
.card .card-inner.flipped { |
|
transform: rotateY(180deg); |
|
} |
|
|
|
.card-front, .card-back { |
|
position: absolute; |
|
width: 100%; |
|
height: 100%; |
|
backface-visibility: hidden; |
|
border-radius: 8px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.card-front { |
|
background: white; |
|
transform: rotateY(180deg); |
|
font-weight: bold; |
|
font-size: 1.2rem; |
|
} |
|
|
|
.card-back { |
|
background: linear-gradient(135deg, #4f46e5, #8b5cf6); |
|
color: white; |
|
} |
|
|
|
.highlight-card { |
|
box-shadow: 0 0 15px 5px rgba(255, 215, 0, 0.7); |
|
transform: scale(1.05); |
|
} |
|
|
|
.pile-button { |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.pile-button:hover { |
|
transform: translateY(-3px); |
|
} |
|
|
|
.pile-button:active { |
|
transform: translateY(1px); |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.card { |
|
width: 60px; |
|
height: 90px; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-100 min-h-screen flex flex-col items-center py-8"> |
|
<div class="container mx-auto px-4"> |
|
<h1 class="text-4xl font-bold text-center text-indigo-800 mb-2">Magical Card Trick</h1> |
|
<p class="text-center text-gray-600 mb-8">I'll read your mind through your favorite number!</p> |
|
|
|
<div id="game-container" class="flex flex-col items-center"> |
|
|
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const gameState = { |
|
phase: 'number-input', |
|
favoriteNumber: null, |
|
deck: [], |
|
currentRound: 0, |
|
ternaryDigits: [], |
|
selectedPiles: [], |
|
memorizedCard: null, |
|
memorizedCardPosition: null |
|
}; |
|
|
|
|
|
const suits = ['♥', '♦', '♠', '♣']; |
|
const values = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']; |
|
|
|
|
|
function initGame() { |
|
renderGame(); |
|
} |
|
|
|
|
|
function renderGame() { |
|
const container = document.getElementById('game-container'); |
|
container.innerHTML = ''; |
|
|
|
switch (gameState.phase) { |
|
case 'number-input': |
|
renderNumberInput(container); |
|
break; |
|
case 'pile-selection': |
|
renderPileSelection(container); |
|
break; |
|
case 'result': |
|
renderResult(container); |
|
break; |
|
} |
|
} |
|
|
|
|
|
function renderNumberInput(container) { |
|
const div = document.createElement('div'); |
|
div.className = 'bg-white p-8 rounded-lg shadow-lg max-w-md w-full'; |
|
|
|
div.innerHTML = ` |
|
<h2 class="text-2xl font-semibold text-indigo-700 mb-4">Enter Your Favorite Number</h2> |
|
<p class="text-gray-600 mb-6">Choose a number between 1 and 27. This will help me find your card later!</p> |
|
|
|
<div class="flex flex-col space-y-4"> |
|
<input |
|
type="number" |
|
id="favorite-number" |
|
min="1" |
|
max="27" |
|
placeholder="1-27" |
|
class="border-2 border-indigo-200 rounded-lg px-4 py-2 focus:outline-none focus:border-indigo-500 text-center text-xl" |
|
> |
|
|
|
<button |
|
id="submit-number" |
|
class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200" |
|
> |
|
Start the Magic! |
|
</button> |
|
</div> |
|
`; |
|
|
|
container.appendChild(div); |
|
|
|
document.getElementById('submit-number').addEventListener('click', () => { |
|
const numberInput = document.getElementById('favorite-number'); |
|
const number = parseInt(numberInput.value); |
|
|
|
if (isNaN(number) || number < 1 || number > 27) { |
|
alert('Please enter a number between 1 and 27'); |
|
return; |
|
} |
|
|
|
gameState.favoriteNumber = number; |
|
gameState.phase = 'pile-selection'; |
|
gameState.currentRound = 0; |
|
|
|
|
|
const num = number - 1; |
|
gameState.ternaryDigits = num.toString(3).padStart(3, '0').split('').reverse(); |
|
|
|
|
|
gameState.deck = createDeck(); |
|
shuffleDeck(gameState.deck); |
|
|
|
renderGame(); |
|
}); |
|
} |
|
|
|
|
|
function renderPileSelection(container) { |
|
const div = document.createElement('div'); |
|
div.className = 'bg-white p-6 rounded-lg shadow-lg w-full'; |
|
|
|
div.innerHTML = ` |
|
<h2 class="text-2xl font-semibold text-indigo-700 mb-4">Round ${gameState.currentRound + 1} of 3</h2> |
|
<p class="text-gray-600 mb-6">Look at the cards below and memorize one in your mind. Then select the pile where your card is located:</p> |
|
|
|
<div id="pile-grid" class="grid grid-cols-3 gap-4 mb-8"></div> |
|
|
|
<div class="flex justify-center space-x-4"> |
|
<button |
|
id="pile-0" |
|
class="pile-button bg-red-500 hover:bg-red-600 text-white font-bold py-3 px-6 rounded-lg transition duration-200" |
|
> |
|
Pile 1 |
|
</button> |
|
<button |
|
id="pile-1" |
|
class="pile-button bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg transition duration-200" |
|
> |
|
Pile 2 |
|
</button> |
|
<button |
|
id="pile-2" |
|
class="pile-button bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg transition duration-200" |
|
> |
|
Pile 3 |
|
</button> |
|
</div> |
|
`; |
|
|
|
container.appendChild(div); |
|
|
|
|
|
const pileGrid = document.getElementById('pile-grid'); |
|
const piles = splitIntoPiles(gameState.deck); |
|
|
|
for (let row = 0; row < 9; row++) { |
|
for (let col = 0; col < 3; col++) { |
|
const card = piles[col][row]; |
|
const cardElement = createCardElement(card, row * 3 + col, true); |
|
pileGrid.appendChild(cardElement); |
|
} |
|
} |
|
|
|
|
|
document.getElementById('pile-0').addEventListener('click', () => handlePileSelection(0)); |
|
document.getElementById('pile-1').addEventListener('click', () => handlePileSelection(1)); |
|
document.getElementById('pile-2').addEventListener('click', () => handlePileSelection(2)); |
|
} |
|
|
|
|
|
function renderResult(container) { |
|
const position = gameState.memorizedCardPosition + 1; |
|
|
|
const div = document.createElement('div'); |
|
div.className = 'bg-white p-8 rounded-lg shadow-lg max-w-3xl w-full'; |
|
|
|
div.innerHTML = ` |
|
<h2 class="text-3xl font-bold text-indigo-700 mb-4 text-center">✨ The Magic Revealed! ✨</h2> |
|
|
|
<div class="mb-8 text-center"> |
|
<p class="text-xl text-gray-700 mb-2">Your favorite number was <span class="font-bold text-indigo-600">${gameState.favoriteNumber}</span></p> |
|
<p class="text-xl text-gray-700">Your card is at position <span class="font-bold text-indigo-600">${position}</span> in the deck!</p> |
|
</div> |
|
|
|
<div id="result-deck" class="grid grid-cols-9 gap-1 mb-8"> |
|
<!-- Cards will be inserted here face up --> |
|
</div> |
|
|
|
<div class="text-center"> |
|
<p class="text-gray-600 mb-6">See? I knew your card would be at position ${position} all along!</p> |
|
|
|
<button |
|
id="play-again" |
|
class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-8 rounded-lg transition duration-200" |
|
> |
|
Play Again |
|
</button> |
|
</div> |
|
`; |
|
|
|
container.appendChild(div); |
|
|
|
|
|
const resultDeck = document.getElementById('result-deck'); |
|
|
|
gameState.deck.forEach((card, index) => { |
|
const cardElement = document.createElement('div'); |
|
cardElement.className = `flex flex-col items-center p-2 rounded ${index === gameState.memorizedCardPosition ? 'bg-yellow-100 highlight-card' : 'bg-gray-50'}`; |
|
|
|
cardElement.innerHTML = ` |
|
<div class="text-xs font-semibold text-gray-500 mb-1">${index + 1}</div> |
|
<div class="card"> |
|
<div class="card-inner flipped"> |
|
<div class="card-front">${card.value}${card.suit}</div> |
|
<div class="card-back"></div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
resultDeck.appendChild(cardElement); |
|
}); |
|
|
|
document.getElementById('play-again').addEventListener('click', resetGame); |
|
} |
|
|
|
|
|
function createCardElement(card, index, clickable = true) { |
|
const cardElement = document.createElement('div'); |
|
cardElement.className = 'card'; |
|
cardElement.dataset.index = index; |
|
|
|
|
|
const showFace = true; |
|
|
|
cardElement.innerHTML = ` |
|
<div class="card-inner ${showFace ? 'flipped' : ''}"> |
|
<div class="card-front">${card.value}${card.suit}</div> |
|
<div class="card-back"></div> |
|
</div> |
|
`; |
|
|
|
if (clickable) { |
|
cardElement.addEventListener('click', () => { |
|
|
|
document.querySelectorAll('.card').forEach(c => { |
|
c.classList.remove('highlight-card'); |
|
}); |
|
|
|
|
|
cardElement.classList.add('highlight-card'); |
|
|
|
|
|
gameState.memorizedCard = card; |
|
gameState.memorizedCardPosition = index; |
|
}); |
|
} |
|
|
|
return cardElement; |
|
} |
|
|
|
|
|
function handlePileSelection(pileIndex) { |
|
gameState.selectedPiles[gameState.currentRound] = pileIndex; |
|
|
|
|
|
const ternaryDigit = parseInt(gameState.ternaryDigits[gameState.currentRound]); |
|
const piles = splitIntoPiles(gameState.deck); |
|
|
|
let gatheredPiles = []; |
|
|
|
switch (ternaryDigit) { |
|
case 0: |
|
|
|
gatheredPiles = [...piles[pileIndex], ...piles[(pileIndex + 1) % 3], ...piles[(pileIndex + 2) % 3]]; |
|
break; |
|
case 1: |
|
|
|
gatheredPiles = [...piles[(pileIndex + 1) % 3], ...piles[pileIndex], ...piles[(pileIndex + 2) % 3]]; |
|
break; |
|
case 2: |
|
|
|
gatheredPiles = [...piles[(pileIndex + 1) % 3], ...piles[(pileIndex + 2) % 3], ...piles[pileIndex]]; |
|
break; |
|
} |
|
|
|
gameState.deck = gatheredPiles; |
|
gameState.currentRound++; |
|
|
|
if (gameState.currentRound < 3) { |
|
renderGame(); |
|
} else { |
|
|
|
const ternaryStr = (gameState.favoriteNumber - 1).toString(3).padStart(3, '0'); |
|
const digits = ternaryStr.split('').reverse().map(Number); |
|
|
|
|
|
gameState.memorizedCardPosition = |
|
9 * digits[2] + 3 * digits[1] + digits[0]; |
|
|
|
gameState.phase = 'result'; |
|
renderGame(); |
|
} |
|
} |
|
|
|
|
|
function createDeck() { |
|
const deck = []; |
|
for (const suit of suits) { |
|
for (const value of values) { |
|
deck.push({ suit, value }); |
|
} |
|
} |
|
return deck; |
|
} |
|
|
|
|
|
function shuffleDeck(deck) { |
|
for (let i = deck.length - 1; i > 0; i--) { |
|
const j = Math.floor(Math.random() * (i + 1)); |
|
[deck[i], deck[j]] = [deck[j], deck[i]]; |
|
} |
|
} |
|
|
|
|
|
function splitIntoPiles(deck) { |
|
const piles = [[], [], []]; |
|
for (let i = 0; i < 27; i++) { |
|
const pileIndex = i % 3; |
|
piles[pileIndex].push(deck[i]); |
|
} |
|
return piles; |
|
} |
|
|
|
|
|
function resetGame() { |
|
gameState.phase = 'number-input'; |
|
gameState.favoriteNumber = null; |
|
gameState.deck = []; |
|
gameState.currentRound = 0; |
|
gameState.ternaryDigits = []; |
|
gameState.selectedPiles = []; |
|
gameState.memorizedCard = null; |
|
gameState.memorizedCardPosition = null; |
|
|
|
renderGame(); |
|
} |
|
|
|
|
|
window.onload = initGame; |
|
</script> |
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=manyone/manyone-cards27" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |