Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Sparkle Tower</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap'); | |
body { | |
font-family: 'Poppins', sans-serif; | |
background: linear-gradient(135deg, #f5f7fa 0%, #ffeef8 100%); | |
} | |
.letter-tile { | |
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
background: linear-gradient(135deg, #ffffff 0%, #f9f0ff 100%); | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); | |
border-radius: 12px; | |
position: relative; | |
overflow: hidden; | |
} | |
.letter-tile::before { | |
content: ''; | |
position: absolute; | |
top: -50%; | |
left: -50%; | |
width: 200%; | |
height: 200%; | |
background: linear-gradient( | |
to bottom right, | |
rgba(255, 255, 255, 0.3) 0%, | |
rgba(255, 255, 255, 0) 60% | |
); | |
transform: rotate(30deg); | |
transition: all 0.3s ease; | |
} | |
.letter-tile:hover::before { | |
left: 100%; | |
} | |
.letter-tile.selected { | |
transform: translateY(-5px) scale(1.05); | |
box-shadow: 0 10px 25px rgba(168, 85, 247, 0.3); | |
background: linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 100%); | |
color: #7e22ce; | |
} | |
.feedback-message { | |
animation: fadeInOut 2.5s ease-out forwards; | |
} | |
@keyframes fadeInOut { | |
0% { opacity: 0; transform: translateY(10px); } | |
20% { opacity: 1; transform: translateY(0); } | |
80% { opacity: 1; transform: translateY(0); } | |
100% { opacity: 0; transform: translateY(-10px); } | |
} | |
.timer-pulse { | |
animation: pulse 1.5s infinite; | |
} | |
@keyframes pulse { | |
0%, 100% { transform: scale(1); } | |
50% { transform: scale(1.1); } | |
} | |
#tower-container { | |
position: relative; | |
height: 250px; | |
overflow: hidden; | |
background: linear-gradient(to bottom, #faf5ff, #f3e8ff); | |
border-radius: 16px; | |
margin: 16px; | |
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.05); | |
} | |
#tower-canvas { | |
position: absolute; | |
bottom: 0; | |
left: 50%; | |
transform-origin: center bottom; | |
transform: translateX(-50%); | |
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
} | |
@keyframes towerSway { | |
0%, 100% { transform: translateX(-50%) rotate(0.5deg); } | |
50% { transform: translateX(-50%) rotate(-0.5deg); } | |
} | |
.word-badge { | |
position: relative; | |
overflow: hidden; | |
transition: all 0.3s ease; | |
} | |
.word-badge::after { | |
content: ''; | |
position: absolute; | |
top: -50%; | |
left: -50%; | |
width: 200%; | |
height: 200%; | |
background: linear-gradient( | |
to bottom right, | |
rgba(255, 255, 255, 0.4) 0%, | |
rgba(255, 255, 255, 0) 60% | |
); | |
transform: rotate(30deg); | |
transition: all 0.5s ease; | |
} | |
.word-badge:hover::after { | |
left: 100%; | |
} | |
.sparkle { | |
position: absolute; | |
width: 4px; | |
height: 4px; | |
background: white; | |
border-radius: 50%; | |
pointer-events: none; | |
opacity: 0; | |
animation: sparkle 1s ease-out forwards; | |
} | |
@keyframes sparkle { | |
0% { transform: scale(0); opacity: 0; } | |
50% { transform: scale(1); opacity: 1; } | |
100% { transform: scale(0); opacity: 0; } | |
} | |
.floating { | |
animation: float 3s ease-in-out infinite; | |
} | |
@keyframes float { | |
0%, 100% { transform: translateY(0); } | |
50% { transform: translateY(-10px); } | |
} | |
.btn-primary { | |
background: linear-gradient(135deg, #a855f7 0%, #8b5cf6 100%); | |
box-shadow: 0 4px 15px rgba(168, 85, 247, 0.3); | |
transition: all 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
} | |
.btn-primary:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 8px 20px rgba(168, 85, 247, 0.4); | |
} | |
.btn-primary::after { | |
content: ''; | |
position: absolute; | |
top: -50%; | |
left: -50%; | |
width: 200%; | |
height: 200%; | |
background: linear-gradient( | |
to bottom right, | |
rgba(255, 255, 255, 0.3) 0%, | |
rgba(255, 255, 255, 0) 60% | |
); | |
transform: rotate(30deg); | |
transition: all 0.5s ease; | |
} | |
.btn-primary:hover::after { | |
left: 100%; | |
} | |
.btn-secondary { | |
background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%); | |
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); | |
transition: all 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
} | |
.btn-secondary:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); | |
} | |
.btn-secondary::after { | |
content: ''; | |
position: absolute; | |
top: -50%; | |
left: -50%; | |
width: 200%; | |
height: 200%; | |
background: linear-gradient( | |
to bottom right, | |
rgba(255, 255, 255, 0.4) 0%, | |
rgba(255, 255, 255, 0) 60% | |
); | |
transform: rotate(30deg); | |
transition: all 0.5s ease; | |
} | |
.btn-secondary:hover::after { | |
left: 100%; | |
} | |
.crown { | |
position: absolute; | |
top: -15px; | |
left: 50%; | |
transform: translateX(-50%); | |
font-size: 24px; | |
color: #f59e0b; | |
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | |
z-index: 10; | |
} | |
</style> | |
</head> | |
<body class="min-h-screen flex items-center justify-center p-4"> | |
<div class="max-w-md w-full bg-white rounded-3xl shadow-2xl overflow-hidden"> | |
<!-- Game Header --> | |
<div class="relative bg-gradient-to-r from-purple-500 to-pink-500 p-6 text-white"> | |
<div class="absolute top-0 left-0 w-full h-full opacity-10"> | |
<div class="absolute top-10 left-10 w-20 h-20 rounded-full bg-white"></div> | |
<div class="absolute top-20 right-20 w-32 h-32 rounded-full bg-white"></div> | |
<div class="absolute bottom-10 left-1/4 w-24 h-24 rounded-full bg-white"></div> | |
</div> | |
<h1 class="text-3xl font-bold text-center relative z-10 floating">Sparkle Tower</h1> | |
<p class="text-center text-purple-100 relative z-10">Build your dream tower with words!</p> | |
<div class="absolute -bottom-6 left-1/2 transform -translate-x-1/2 w-12 h-12 bg-white rounded-full flex items-center justify-center shadow-lg"> | |
<div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full flex items-center justify-center text-white"> | |
<i class="fas fa-star"></i> | |
</div> | |
</div> | |
</div> | |
<!-- Tower Visualization --> | |
<div id="tower-container" class="relative mt-8"> | |
<div class="absolute inset-0 flex items-center justify-center" id="tower-placeholder"> | |
<div class="text-center text-purple-300"> | |
<i class="fas fa-cloud text-5xl mb-2"></i> | |
<p class="text-lg">Your tower will appear here!</p> | |
</div> | |
</div> | |
<canvas id="tower-canvas" width="300" height="800"></canvas> | |
</div> | |
<!-- Game Info --> | |
<div class="p-6 bg-gradient-to-r from-purple-50 to-pink-50 flex justify-between items-center relative"> | |
<div class="text-center"> | |
<p class="text-xs text-purple-500 font-semibold">SCORE</p> | |
<p id="score" class="text-3xl font-bold text-purple-800">0</p> | |
</div> | |
<div class="text-center"> | |
<p class="text-xs text-purple-500 font-semibold">HEIGHT</p> | |
<p id="height" class="text-3xl font-bold text-purple-800">0</p> | |
</div> | |
<div class="text-center relative"> | |
<p class="text-xs text-purple-500 font-semibold">TIME</p> | |
<p id="timer" class="text-3xl font-bold text-pink-600">60</p> | |
<div class="absolute -top-2 -right-2 w-6 h-6 bg-pink-500 rounded-full flex items-center justify-center text-white text-xs"> | |
<i class="fas fa-clock"></i> | |
</div> | |
</div> | |
</div> | |
<!-- Feedback Message --> | |
<div id="feedback" class="h-16 flex items-center justify-center bg-gradient-to-r from-purple-50 to-pink-50"> | |
<p id="feedback-message" class="text-lg font-semibold opacity-0"></p> | |
</div> | |
<!-- Letter Tiles --> | |
<div id="letter-area" class="p-6 grid grid-cols-6 gap-3"> | |
<!-- Letter tiles will be generated here --> | |
</div> | |
<!-- Current Word --> | |
<div class="px-6 py-4 bg-gradient-to-r from-purple-50 to-pink-50"> | |
<div class="bg-white rounded-xl p-4 shadow-inner relative"> | |
<p class="text-xs text-purple-400 mb-1">Current Word:</p> | |
<p id="current-word" class="text-3xl font-mono text-center min-h-10 text-purple-800">-</p> | |
<div class="absolute top-0 right-0 mt-2 mr-3 text-purple-300"> | |
<i class="fas fa-pencil-alt"></i> | |
</div> | |
</div> | |
</div> | |
<!-- Controls --> | |
<div class="p-6 flex gap-4"> | |
<button id="submit-btn" class="flex-1 btn-primary text-white font-bold py-4 px-6 rounded-xl transition disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<i class="fas fa-check mr-2"></i> Submit | |
</button> | |
<button id="clear-btn" class="flex-1 btn-secondary text-purple-800 font-bold py-4 px-6 rounded-xl transition"> | |
<i class="fas fa-eraser mr-2"></i> Clear | |
</button> | |
</div> | |
<!-- Found Words --> | |
<div class="p-6 bg-gradient-to-r from-purple-50 to-pink-50 border-t border-purple-100"> | |
<div class="flex justify-between items-center mb-2"> | |
<p class="text-xs text-purple-400">Found Words (<span id="found-count">0</span>):</p> | |
<div class="text-purple-300"> | |
<i class="fas fa-trophy"></i> | |
</div> | |
</div> | |
<div id="found-words" class="flex flex-wrap gap-2"> | |
<!-- Found words will appear here --> | |
</div> | |
</div> | |
<!-- Start Button --> | |
<div class="p-6 bg-white"> | |
<button id="start-btn" class="w-full btn-primary text-white font-bold py-4 px-6 rounded-xl transition"> | |
<i class="fas fa-play mr-2"></i> Start Game | |
</button> | |
</div> | |
</div> | |
<script> | |
// Game configuration | |
const config = { | |
roundTime: 60, | |
availableLetters: ['A', 'E', 'S', 'T', 'R', 'N', 'L', 'O', 'I', 'D'], | |
validWords: [ | |
"ART", "EAT", "NET", "RAT", "RENT", "STAR", "START", "TAN", "TEA", "TEN", | |
"NEST", "RATE", "REST", "SAT", "SEA", "SENT", "SET", "TERN", "EARN", "EAST", | |
"EATS", "NEAT", "RANT", "SEAT", "STAR", "TEAR", "TENS", "ANTS", "ARTS", "ERAS", | |
"NATS", "NEAR", "NEST", "RATS", "SANE", "TARE", "TARN", "TARS", "TEAS", "TENS", | |
"LOVE", "DREAM", "STAR", "ROSE", "PEARL", "LOTUS", "DIARY", "STORY", "TALES", "SONG", | |
"DANCE", "MELODY", "HEART", "SWEET", "CANDY", "SUNNY", "LIGHT", "ANGEL", "FAIRY", "MAGIC" | |
], | |
blockColors: [ | |
'#FF9FF3', '#FECA57', '#FF6B6B', '#48DBFB', '#1DD1A1', | |
'#F368E0', '#FF9FF3', '#00D2D3', '#54A0FF', '#5F27CD', | |
'#C56CF0', '#FFB8B8', '#FF9F43', '#EE5253', '#0ABDE3', | |
'#10AC84', '#2E86DE', '#341F97', '#B33771', '#6D214F' | |
] | |
}; | |
// Game state | |
const state = { | |
gameActive: false, | |
timer: config.roundTime, | |
score: 0, | |
height: 0, | |
selectedLetters: [], | |
foundWords: [], | |
timerInterval: null, | |
towerBlocks: [], | |
swayAnimation: null | |
}; | |
// DOM elements | |
const elements = { | |
letterArea: document.getElementById('letter-area'), | |
currentWord: document.getElementById('current-word'), | |
score: document.getElementById('score'), | |
height: document.getElementById('height'), | |
timer: document.getElementById('timer'), | |
feedback: document.getElementById('feedback-message'), | |
submitBtn: document.getElementById('submit-btn'), | |
clearBtn: document.getElementById('clear-btn'), | |
startBtn: document.getElementById('start-btn'), | |
foundWords: document.getElementById('found-words'), | |
foundCount: document.getElementById('found-count'), | |
towerCanvas: document.getElementById('tower-canvas'), | |
towerCtx: document.getElementById('tower-canvas').getContext('2d'), | |
towerContainer: document.getElementById('tower-container'), | |
towerPlaceholder: document.getElementById('tower-placeholder') | |
}; | |
// Initialize the game | |
function initGame() { | |
// Create letter tiles | |
elements.letterArea.innerHTML = ''; | |
config.availableLetters.forEach(letter => { | |
const tile = document.createElement('div'); | |
tile.className = 'letter-tile flex items-center justify-center text-3xl font-bold cursor-pointer h-16'; | |
tile.textContent = letter; | |
tile.dataset.letter = letter; | |
// Add sparkle effect on click | |
tile.addEventListener('click', (e) => { | |
toggleLetter(letter, tile); | |
createSparkle(e); | |
}); | |
elements.letterArea.appendChild(tile); | |
}); | |
// Reset game state | |
state.gameActive = false; | |
state.timer = config.roundTime; | |
state.score = 0; | |
state.height = 0; | |
state.selectedLetters = []; | |
state.foundWords = []; | |
state.towerBlocks = []; | |
// Clear found words | |
elements.foundWords.innerHTML = ''; | |
// Stop any existing sway animation | |
if (state.swayAnimation) { | |
cancelAnimationFrame(state.swayAnimation); | |
} | |
// Reset tower canvas | |
elements.towerCanvas.style.transform = 'translateX(-50%) scale(1)'; | |
elements.towerCanvas.style.animation = 'none'; | |
elements.towerCtx.clearRect(0, 0, elements.towerCanvas.width, elements.towerCanvas.height); | |
// Show placeholder | |
elements.towerPlaceholder.style.display = 'flex'; | |
// Update UI | |
updateUI(); | |
} | |
// Create sparkle effect | |
function createSparkle(event) { | |
const sparkle = document.createElement('div'); | |
sparkle.className = 'sparkle'; | |
// Position the sparkle at the click location | |
const rect = event.currentTarget.getBoundingClientRect(); | |
const x = event.clientX - rect.left; | |
const y = event.clientY - rect.top; | |
sparkle.style.left = `${x}px`; | |
sparkle.style.top = `${y}px`; | |
event.currentTarget.appendChild(sparkle); | |
// Remove after animation | |
setTimeout(() => { | |
sparkle.remove(); | |
}, 1000); | |
} | |
// Calculate the optimal scale for the tower | |
function calculateTowerScale() { | |
const containerHeight = elements.towerContainer.clientHeight; | |
const towerHeight = state.towerBlocks.length * 25 + 40; // 25px per block + some margin | |
// Calculate scale to fit the tower in the container | |
const scale = Math.min(1, containerHeight / towerHeight * 0.85); | |
return scale; | |
} | |
// Draw the tower with current scale | |
function drawTower() { | |
const scale = calculateTowerScale(); | |
// Apply the scale transform | |
elements.towerCanvas.style.transform = `translateX(-50%) scale(${scale})`; | |
// Clear canvas | |
elements.towerCtx.clearRect(0, 0, elements.towerCanvas.width, elements.towerCanvas.height); | |
// Draw sky gradient | |
const skyGradient = elements.towerCtx.createLinearGradient(0, 0, 0, elements.towerCanvas.height); | |
skyGradient.addColorStop(0, '#f3e8ff'); | |
skyGradient.addColorStop(1, '#fae8ff'); | |
elements.towerCtx.fillStyle = skyGradient; | |
elements.towerCtx.fillRect(0, 0, elements.towerCanvas.width, elements.towerCanvas.height); | |
// Draw ground | |
elements.towerCtx.fillStyle = '#e9d5ff'; | |
elements.towerCtx.fillRect(0, elements.towerCanvas.height - 15, elements.towerCanvas.width, 15); | |
// Draw grass details | |
elements.towerCtx.fillStyle = '#a855f7'; | |
for (let i = 0; i < 20; i++) { | |
const x = Math.random() * elements.towerCanvas.width; | |
const height = 5 + Math.random() * 10; | |
elements.towerCtx.fillRect(x, elements.towerCanvas.height - 15, 2, -height); | |
} | |
// Draw each block in the tower | |
state.towerBlocks.forEach((block, index) => { | |
const yPos = elements.towerCanvas.height - 25 - (index * 25); | |
// Add slight horizontal offset for "wavy" effect | |
const waveOffset = Math.sin(Date.now() / 500 + index * 0.3) * 4; | |
// Block | |
elements.towerCtx.fillStyle = block.color; | |
elements.towerCtx.beginPath(); | |
elements.towerCtx.roundRect(block.x + waveOffset, yPos, block.width, 20, [0, 0, 8, 8]); | |
elements.towerCtx.fill(); | |
// Block pattern | |
elements.towerCtx.fillStyle = 'rgba(255, 255, 255, 0.3)'; | |
for (let i = 0; i < block.width / 15; i++) { | |
const x = block.x + waveOffset + 5 + i * 15; | |
elements.towerCtx.beginPath(); | |
elements.towerCtx.arc(x, yPos + 10, 2, 0, Math.PI * 2); | |
elements.towerCtx.fill(); | |
} | |
// Block border | |
elements.towerCtx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; | |
elements.towerCtx.lineWidth = 1; | |
elements.towerCtx.beginPath(); | |
elements.towerCtx.roundRect(block.x + waveOffset, yPos, block.width, 20, [0, 0, 8, 8]); | |
elements.towerCtx.stroke(); | |
}); | |
// Draw tower top if there are blocks | |
if (state.towerBlocks.length > 0) { | |
const topBlock = state.towerBlocks[state.towerBlocks.length - 1]; | |
const topY = elements.towerCanvas.height - 25 - (state.towerBlocks.length * 25); | |
const waveOffset = Math.sin(Date.now() / 500 + state.towerBlocks.length * 0.3) * 4; | |
// Flag | |
elements.towerCtx.fillStyle = '#FF9FF3'; | |
elements.troyCtx.beginPath(); | |
elements.towerCtx.moveTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 15); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + 20 + waveOffset, topY - 5); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY + 5); | |
elements.towerCtx.closePath(); | |
elements.towerCtx.fill(); | |
// Flag details | |
elements.towerCtx.fillStyle = '#FF6B6B'; | |
elements.towerCtx.beginPath(); | |
elements.towerCtx.arc(topBlock.x + topBlock.width/2 + 10 + waveOffset, topY - 10, 3, 0, Math.PI * 2); | |
elements.towerCtx.fill(); | |
// Flag pole | |
elements.towerCtx.strokeStyle = '#FFFFFF'; | |
elements.towerCtx.lineWidth = 2; | |
elements.towerCtx.beginPath(); | |
elements.towerCtx.moveTo(topBlock.x + topBlock.width/2 + waveOffset, topY); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 15); | |
elements.towerCtx.stroke(); | |
// Add crown to the tower if it's tall enough | |
if (state.towerBlocks.length > 10) { | |
elements.towerCtx.fillStyle = '#FECA57'; | |
elements.towerCtx.beginPath(); | |
elements.towerCtx.moveTo(topBlock.x + topBlock.width/2 + waveOffset - 15, topY - 25); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 40); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset + 15, topY - 25); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset + 10, topY - 25); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 35); | |
elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset - 10, topY - 25); | |
elements.towerCtx.closePath(); | |
elements.towerCtx.fill(); | |
elements.towerCtx.strokeStyle = '#FF9F43'; | |
elements.towerCtx.lineWidth = 1; | |
elements.towerCtx.stroke(); | |
} | |
} | |
// Continue the animation loop for the waving effect | |
if (state.gameActive) { | |
state.swayAnimation = requestAnimationFrame(drawTower); | |
} | |
} | |
// Add a block to the tower | |
function addTowerBlock(wordLength) { | |
// Hide placeholder when first block is added | |
if (state.towerBlocks.length === 0) { | |
elements.towerPlaceholder.style.display = 'none'; | |
} | |
// Determine block properties | |
const color = config.blockColors[Math.floor(Math.random() * config.blockColors.length)]; | |
const width = 50 + (wordLength * 6); // Bigger blocks for longer words | |
const x = (elements.towerCanvas.width - width) / 2; // Center the block | |
// Add slight random offset for visual interest | |
const xOffset = Math.random() * 10 - 5; | |
// Add the block to our tower | |
state.towerBlocks.push({ | |
x: x + xOffset, | |
width: width, | |
color: color, | |
wordLength: wordLength | |
}); | |
// Start the waving animation if not already running | |
if (!state.swayAnimation) { | |
drawTower(); | |
} | |
// Add a little growth animation | |
animateTowerGrowth(); | |
// Add floating hearts occasionally | |
if (Math.random() > 0.7) { | |
createFloatingHeart(); | |
} | |
} | |
// Create floating heart effect | |
function createFloatingHeart() { | |
const heart = document.createElement('div'); | |
heart.className = 'absolute text-pink-400 text-xl'; | |
heart.innerHTML = '<i class="fas fa-heart"></i>'; | |
// Position at random location in tower container | |
const containerRect = elements.towerContainer.getBoundingClientRect(); | |
const x = Math.random() * containerRect.width; | |
heart.style.left = `${x}px`; | |
heart.style.bottom = '0'; | |
heart.style.opacity = '0'; | |
heart.style.transform = 'translateY(0)'; | |
elements.towerContainer.appendChild(heart); | |
// Animate heart floating up | |
setTimeout(() => { | |
heart.style.transition = 'all 3s ease-out'; | |
heart.style.opacity = '1'; | |
heart.style.transform = `translateY(-${containerRect.height}px)`; | |
// Remove after animation | |
setTimeout(() => { | |
heart.remove(); | |
}, 3000); | |
}, 0); | |
} | |
// Animate tower growth | |
function animateTowerGrowth() { | |
let scale = calculateTowerScale(); | |
let tempScale = scale * 1.1; // Start slightly larger | |
const animate = () => { | |
if (tempScale <= scale) return; | |
tempScale -= 0.005; | |
elements.towerCanvas.style.transform = `translateX(-50%) scale(${tempScale})`; | |
requestAnimationFrame(animate); | |
}; | |
animate(); | |
} | |
// Start a new round | |
function startRound() { | |
if (state.timerInterval) clearInterval(state.timerInterval); | |
initGame(); | |
state.gameActive = true; | |
// Start timer | |
state.timerInterval = setInterval(() => { | |
state.timer--; | |
updateUI(); | |
if (state.timer <= 0) { | |
endRound(); | |
} | |
// Add timer urgency effect | |
if (state.timer <= 10 && !elements.timer.classList.contains('timer-pulse')) { | |
elements.timer.classList.add('timer-pulse'); | |
} | |
}, 1000); | |
showFeedback("β¨ Build your dream tower with words! β¨", 2000); | |
} | |
// End the current round | |
function endRound() { | |
state.gameActive = false; | |
clearInterval(state.timerInterval); | |
// Special message based on performance | |
let message; | |
if (state.score >= 500) { | |
message = `Amazing! You scored ${state.score} points! π`; | |
} else if (state.score >= 300) { | |
message = `Great job! You scored ${state.score} points! π`; | |
} else { | |
message = `You scored ${state.score} points! Try again! β¨`; | |
} | |
showFeedback(message, 3000); | |
// Stop the sway animation | |
if (state.swayAnimation) { | |
cancelAnimationFrame(state.swayAnimation); | |
state.swayAnimation = null; | |
} | |
} | |
// Toggle letter selection | |
function toggleLetter(letter, tile) { | |
if (!state.gameActive) return; | |
const index = state.selectedLetters.indexOf(letter); | |
if (index === -1) { | |
// Select the letter | |
state.selectedLetters.push(letter); | |
tile.classList.add('selected'); | |
} else { | |
// Deselect the letter (remove last occurrence) | |
const lastIndex = state.selectedLetters.lastIndexOf(letter); | |
if (lastIndex !== -1) { | |
state.selectedLetters.splice(lastIndex, 1); | |
} | |
tile.classList.remove('selected'); | |
} | |
updateUI(); | |
} | |
// Clear selected letters | |
function clearSelection() { | |
if (!state.gameActive) return; | |
state.selectedLetters = []; | |
document.querySelectorAll('.letter-tile').forEach(tile => { | |
tile.classList.remove('selected'); | |
}); | |
updateUI(); | |
} | |
// Submit current word | |
function submitWord() { | |
if (!state.gameActive || state.selectedLetters.length < 3) { | |
showFeedback(state.selectedLetters.length < 3 ? "Word too short! (min 3 letters)" : "Game not active", 1500); | |
return; | |
} | |
const word = state.selectedLetters.join('').toUpperCase(); | |
if (state.foundWords.includes(word)) { | |
showFeedback("Already found! Try another word", 1500); | |
} else if (config.validWords.includes(word)) { | |
// Valid word | |
state.foundWords.push(word); | |
state.score += word.length * 10; | |
state.height += word.length; | |
// Add a block to the tower for this word | |
addTowerBlock(word.length); | |
// Add to found words display | |
const wordBadge = document.createElement('span'); | |
wordBadge.className = 'word-badge bg-gradient-to-r from-purple-100 to-pink-100 text-purple-800 text-sm font-medium px-3 py-1 rounded-full'; | |
wordBadge.textContent = word; | |
elements.foundWords.appendChild(wordBadge); | |
// Add sparkle effect to the badge | |
wordBadge.addEventListener('mouseenter', (e) => { | |
for (let i = 0; i < 3; i++) { | |
setTimeout(() => { | |
createSparkle(e); | |
}, i * 200); | |
} | |
}); | |
showFeedback(`+${word.length * 10} points! ${getRandomEmoji()}`, 1500); | |
// Clear the selection after successful submission | |
clearSelection(); | |
} else { | |
showFeedback("Not in our dictionary", 1500); | |
} | |
updateUI(); | |
} | |
// Get random celebratory emoji | |
function getRandomEmoji() { | |
const emojis = ['β¨', 'πΈ', 'π', 'π', 'π', 'π«', 'π', 'π']; | |
return emojis[Math.floor(Math.random() * emojis.length)]; | |
} | |
// Show feedback message | |
function showFeedback(message, duration) { | |
elements.feedback.textContent = message; | |
elements.feedback.className = 'text-lg font-semibold feedback-message'; | |
// Set color based on message type | |
if (message.includes("Not") || message.includes("Already") || message.includes("short")) { | |
elements.feedback.classList.add('text-pink-500'); | |
} else if (message.includes("points")) { | |
elements.feedback.classList.add('text-purple-600'); | |
} else { | |
elements.feedback.classList.add('text-pink-400'); | |
} | |
} | |
// Update UI elements | |
function updateUI() { | |
// Update current word display | |
elements.currentWord.textContent = state.selectedLetters.length > 0 | |
? state.selectedLetters.join('') | |
: '-'; | |
// Update score and height | |
elements.score.textContent = state.score; | |
elements.height.textContent = state.height; | |
elements.timer.textContent = state.timer; | |
elements.foundCount.textContent = state.foundWords.length; | |
// Update timer color when low | |
if (state.timer <= 10) { | |
elements.timer.classList.add('text-pink-600'); | |
} else { | |
elements.timer.classList.remove('text-pink-600'); | |
} | |
// Enable/disable submit button | |
elements.submitBtn.disabled = !state.gameActive || state.selectedLetters.length < 3; | |
} | |
// Event listeners | |
elements.startBtn.addEventListener('click', startRound); | |
elements.clearBtn.addEventListener('click', clearSelection); | |
elements.submitBtn.addEventListener('click', submitWord); | |
// Add sparkle effect to buttons on hover | |
[elements.startBtn, elements.submitBtn, elements.clearBtn].forEach(btn => { | |
btn.addEventListener('mouseenter', (e) => { | |
for (let i = 0; i < 3; i++) { | |
setTimeout(() => { | |
createSparkle(e); | |
}, i * 200); | |
} | |
}); | |
}); | |
// Initialize the game on load | |
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=LukasBe/tower-text-twist" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |