game-4-rhytm-chef / index.html
LukasBe's picture
Add 2 files
805fd19 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rhythm Chef: Beat Bites Deluxe</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@keyframes moveDown {
0% { transform: translateY(-100px); }
100% { transform: translateY(calc(100vh - 200px)); }
}
.cue {
animation: moveDown var(--duration) linear forwards;
transition: all 0.1s ease;
}
.hit-effect {
animation: scaleFade 0.5s ease-out forwards;
}
@keyframes scaleFade {
0% { transform: scale(0.8); opacity: 1; }
100% { transform: scale(1.5); opacity: 0; }
}
.feedback-text {
animation: fadeUp 1s ease-out forwards;
}
@keyframes fadeUp {
0% { transform: translateY(0) scale(1); opacity: 1; }
100% { transform: translateY(-50px) scale(1.2); opacity: 0; }
}
.combo-text {
animation: pulse 0.5s ease infinite alternate;
}
@keyframes pulse {
0% { transform: scale(1); }
100% { transform: scale(1.1); }
}
#gameArea {
touch-action: manipulation;
background: radial-gradient(circle at center, #1a202c 0%, #111827 100%);
overflow: hidden;
}
.kitchen-counter {
background: linear-gradient(to bottom, #8B5A2B 0%, #A67C52 100%);
box-shadow: 0 -10px 20px rgba(0,0,0,0.3);
}
.particle {
position: absolute;
pointer-events: none;
will-change: transform, opacity;
}
.glow {
filter: drop-shadow(0 0 8px currentColor);
}
.score-glow {
text-shadow: 0 0 10px rgba(255, 215, 0, 0.7);
}
.perfect-hit {
animation: perfectGlow 0.5s ease-out forwards;
}
@keyframes perfectGlow {
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
50% { box-shadow: 0 0 30px 15px rgba(255, 255, 255, 0.7); }
100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
/* New effects */
.ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: scale(0);
pointer-events: none;
animation: ripple 1s linear;
}
@keyframes ripple {
to {
transform: scale(4);
opacity: 0;
}
}
.floating-bg-element {
position: absolute;
opacity: 0.1;
animation: floatBg 30s linear infinite;
z-index: 0;
}
@keyframes floatBg {
0% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(50px, 50px) rotate(5deg); }
50% { transform: translate(100px, 0) rotate(0deg); }
75% { transform: translate(50px, -50px) rotate(-5deg); }
100% { transform: translate(0, 0) rotate(0deg); }
}
.combo-explosion {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 5;
}
.streak-light {
position: absolute;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent);
transform-origin: left center;
pointer-events: none;
z-index: 4;
}
.ingredient-trail {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
pointer-events: none;
z-index: 3;
}
.energy-wave {
position: absolute;
width: 300px;
height: 300px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, transparent 70%);
transform: scale(0);
opacity: 1;
pointer-events: none;
animation: energyWave 1.5s ease-out forwards;
}
@keyframes energyWave {
0% { transform: scale(0); opacity: 1; }
100% { transform: scale(3); opacity: 0; }
}
.floating-sparkle {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: white;
pointer-events: none;
animation: sparkleFloat 2s ease-out forwards;
}
@keyframes sparkleFloat {
0% { transform: translate(0, 0) scale(1); opacity: 1; }
100% { transform: translate(random(100) - 50px, -100px) scale(0); opacity: 0; }
}
.screen-flash {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: white;
opacity: 0;
pointer-events: none;
z-index: 10;
}
.combo-streak {
position: absolute;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
pointer-events: none;
z-index: 2;
}
.kitchen-light {
position: absolute;
width: 100%;
height: 100%;
background: radial-gradient(circle at 50% 30%, rgba(255,215,0,0.1) 0%, transparent 70%);
pointer-events: none;
z-index: 1;
animation: kitchenLightPulse 2s infinite alternate;
}
@keyframes kitchenLightPulse {
0% { opacity: 0.3; }
100% { opacity: 0.7; }
}
.perfect-ring {
position: absolute;
border-radius: 50%;
border: 3px solid rgba(255,255,255,0.8);
transform: scale(0);
pointer-events: none;
animation: perfectRing 0.8s ease-out forwards;
}
@keyframes perfectRing {
0% { transform: scale(0); opacity: 1; }
100% { transform: scale(3); opacity: 0; }
}
/* Settings panel */
.settings-panel {
transition: all 0.3s ease;
transform: translateY(100%);
}
.settings-panel.open {
transform: translateY(0);
}
.settings-overlay {
background-color: rgba(0, 0, 0, 0.7);
}
/* Range slider styling */
input[type="range"] {
-webkit-appearance: none;
height: 8px;
background: #4a5568;
border-radius: 4px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #f6ad55;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #f6ad55;
cursor: pointer;
}
/* Preset buttons */
.preset-btn {
transition: all 0.2s ease;
}
.preset-btn:hover {
transform: translateY(-2px);
}
.preset-btn.active {
background-color: #f6ad55;
color: #1a202c;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body class="bg-gray-900 text-white font-sans overflow-hidden">
<!-- Start Screen -->
<div id="startScreen" class="fixed inset-0 flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800 z-10 transition-opacity duration-500">
<div id="startScreenBg" class="absolute inset-0 overflow-hidden">
<!-- Floating background elements -->
<div class="floating-bg-element text-6xl" style="top:20%; left:10%;">🍳</div>
<div class="floating-bg-element text-6xl" style="top:30%; left:70%;">🥘</div>
<div class="floating-bg-element text-6xl" style="top:60%; left:20%;">🍕</div>
<div class="floating-bg-element text-6xl" style="top:70%; left:80%;">🍣</div>
</div>
<div class="text-center mb-12 relative z-10">
<h1 class="text-7xl font-bold mb-4 text-yellow-400 glow">🍳 RHYTHM CHEF</h1>
<h2 class="text-4xl mb-6 text-yellow-300 glow">Beat Bites Deluxe</h2>
<div class="text-xl text-gray-300 max-w-md mx-auto">
Tap the ingredients in rhythm to cook up perfect dishes!
</div>
</div>
<button id="startButton" class="bg-gradient-to-r from-yellow-500 to-yellow-600 hover:from-yellow-400 hover:to-yellow-500 text-4xl text-gray-900 font-bold py-5 px-16 rounded-full transition-all transform hover:scale-105 shadow-lg glow relative z-10">
START COOKING
</button>
<button id="settingsButton" class="absolute top-4 right-4 bg-gray-800 hover:bg-gray-700 text-yellow-400 text-2xl p-3 rounded-full transition-all transform hover:scale-110 shadow-lg">
⚙️
</button>
<div class="absolute bottom-8 text-gray-400 text-sm z-10">
Tap the beat when ingredients reach the counter
</div>
</div>
<!-- Settings Panel -->
<div id="settingsPanel" class="fixed inset-0 z-20 flex items-end settings-overlay hidden">
<div class="settings-panel bg-gray-800 w-full rounded-t-3xl p-6 max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-6">
<h3 class="text-3xl font-bold text-yellow-400">Game Settings</h3>
<button id="closeSettings" class="text-2xl p-2 rounded-full hover:bg-gray-700">
</button>
</div>
<div class="space-y-8">
<!-- Presets Section -->
<div>
<h4 class="text-xl font-semibold mb-4 text-gray-300">Presets</h4>
<div class="grid grid-cols-3 gap-3">
<button data-preset="easy" class="preset-btn bg-gray-700 hover:bg-gray-600 py-3 rounded-lg">
Easy
</button>
<button data-preset="medium" class="preset-btn bg-gray-700 hover:bg-gray-600 py-3 rounded-lg active">
Medium
</button>
<button data-preset="hard" class="preset-btn bg-gray-700 hover:bg-gray-600 py-3 rounded-lg">
Hard
</button>
</div>
</div>
<!-- Speed Settings -->
<div>
<div class="flex justify-between items-center mb-2">
<label for="speedRange" class="text-lg font-medium">Speed: <span id="speedValue">2.0</span>s</label>
</div>
<input type="range" id="speedRange" min="0.5" max="4" step="0.1" value="2" class="w-full">
</div>
<!-- BPM Settings -->
<div>
<div class="flex justify-between items-center mb-2">
<label for="bpmRange" class="text-lg font-medium">Tempo: <span id="bpmValue">128</span> BPM</label>
</div>
<input type="range" id="bpmRange" min="60" max="200" step="1" value="128" class="w-full">
</div>
<!-- Perfect Window -->
<div>
<div class="flex justify-between items-center mb-2">
<label for="perfectRange" class="text-lg font-medium">Perfect Window: <span id="perfectValue">80</span>ms</label>
</div>
<input type="range" id="perfectRange" min="20" max="150" step="5" value="80" class="w-full">
</div>
<!-- Good Window -->
<div>
<div class="flex justify-between items-center mb-2">
<label for="goodRange" class="text-lg font-medium">Good Window: <span id="goodValue">160</span>ms</label>
</div>
<input type="range" id="goodRange" min="50" max="300" step="5" value="160" class="w-full">
</div>
<!-- Number of Ingredients -->
<div>
<div class="flex justify-between items-center mb-2">
<label for="ingredientsRange" class="text-lg font-medium">Ingredients: <span id="ingredientsValue">10</span></label>
</div>
<input type="range" id="ingredientsRange" min="4" max="20" step="1" value="10" class="w-full">
</div>
<!-- Sequence Length -->
<div>
<div class="flex justify-between items-center mb-2">
<label for="sequenceRange" class="text-lg font-medium">Sequence Length: <span id="sequenceValue">16</span> beats</label>
</div>
<input type="range" id="sequenceRange" min="8" max="32" step="4" value="16" class="w-full">
</div>
<!-- Save/Load Section -->
<div class="pt-4 border-t border-gray-700">
<h4 class="text-xl font-semibold mb-4 text-gray-300">Save/Load</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="presetName" class="block text-sm font-medium mb-1">Preset Name</label>
<input type="text" id="presetName" placeholder="e.g. Level1" class="w-full bg-gray-700 rounded px-3 py-2 text-white">
</div>
<div class="flex items-end">
<button id="savePreset" class="bg-yellow-600 hover:bg-yellow-500 text-white px-4 py-2 rounded w-full">
Save
</button>
</div>
</div>
<div class="mt-4">
<label for="loadPreset" class="block text-sm font-medium mb-1">Load Preset</label>
<select id="loadPreset" class="w-full bg-gray-700 rounded px-3 py-2 text-white">
<option value="">Select a preset</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Game Screen -->
<div id="gameScreen" class="fixed inset-0 hidden flex flex-col">
<!-- Kitchen ambient light -->
<div class="kitchen-light"></div>
<!-- Score Display -->
<div class="flex justify-between px-8 pt-6 relative z-10">
<div class="text-4xl font-bold score-glow">
🏆 <span id="score">0</span>
</div>
<div id="comboContainer" class="text-4xl font-bold combo-text hidden">
🔥 <span id="combo">0</span>x
</div>
<button id="gameSettingsButton" class="bg-gray-800 hover:bg-gray-700 text-yellow-400 text-xl p-2 rounded-full transition-all transform hover:scale-110 shadow-lg">
⚙️
</button>
</div>
<!-- Game Area -->
<div id="gameArea" class="flex-1 relative overflow-hidden">
<!-- Floating particles background -->
<div id="particles"></div>
<!-- Combo streak effect -->
<div id="comboStreak" class="combo-streak hidden"></div>
<!-- Target Zone (Kitchen Counter) -->
<div id="targetZone" class="kitchen-counter absolute left-0 right-0 h-24 rounded-t-3xl flex items-center justify-center" style="bottom: 20%;">
<div class="w-full h-1 bg-yellow-300 bg-opacity-50 rounded-full mx-8"></div>
</div>
<!-- Feedback Text -->
<div id="feedbackText" class="absolute left-1/2 transform -translate-x-1/2 text-6xl font-bold text-center opacity-0" style="bottom: 30%;"></div>
<!-- Combo explosion container -->
<div id="comboExplosion" class="combo-explosion"></div>
<!-- Energy waves container -->
<div id="energyWaves"></div>
</div>
</div>
<!-- End Screen -->
<div id="endScreen" class="fixed inset-0 hidden flex flex-col items-center justify-center bg-gray-900 bg-opacity-95 z-20">
<div class="text-center relative">
<h2 class="text-6xl font-bold mb-8 text-yellow-400 glow">DISH COMPLETE! 🎉</h2>
<div class="text-5xl mb-8">Final Score: <span id="finalScore" class="text-yellow-300">0</span></div>
<div id="finalDish" class="text-9xl mb-12">🍲</div>
<div class="text-2xl mb-8">Max Combo: <span id="maxCombo" class="text-yellow-300">0</span>x</div>
<button id="restartButton" class="bg-gradient-to-r from-yellow-500 to-yellow-600 hover:from-yellow-400 hover:to-yellow-500 text-4xl text-gray-900 font-bold py-5 px-16 rounded-full transition-all transform hover:scale-105 shadow-lg glow">
COOK AGAIN
</button>
</div>
</div>
<!-- Screen flash effect -->
<div id="screenFlash" class="screen-flash"></div>
<script>
// Game state
const gameState = {
score: 0,
combo: 0,
maxCombo: 0,
isPlaying: false,
bpm: 128,
cueSpeed: 2, // seconds to reach target
perfectWindow: 80, // ms
goodWindow: 160, // ms,
ingredients: ['🍅', '🧀', '🍄', '🥩', '🥬', '🍞', '🥚', '🦐', '🌽', '🧅'],
dishes: ['🍕', '🍔', '🍣', '🥘', '🍛', '🍜', '🌮', '🥗', '🍲', '🍝'],
sequence: [
{ beat: 1, type: 'tap' },
{ beat: 2, type: 'tap' },
{ beat: 3, type: 'tap' },
{ beat: 4, type: 'tap' },
{ beat: 5.5, type: 'tap' },
{ beat: 6.5, type: 'tap' },
{ beat: 7, type: 'tap' },
{ beat: 8, type: 'tap' },
{ beat: 9, type: 'tap' },
{ beat: 10, type: 'tap' },
{ beat: 11.5, type: 'tap' },
{ beat: 12.5, type: 'tap' },
{ beat: 13, type: 'tap' },
{ beat: 14, type: 'tap' },
{ beat: 15, type: 'tap' },
{ beat: 16, type: 'tap' }
],
activeCues: [],
sequenceStartTime: 0,
currentSequenceIndex: 0,
audioContext: null,
metronomeInterval: null,
backgroundParticles: [],
lastTapPosition: { x: 0, y: 0 }
};
// DOM elements
const startScreen = document.getElementById('startScreen');
const gameScreen = document.getElementById('gameScreen');
const endScreen = document.getElementById('endScreen');
const startButton = document.getElementById('startButton');
const restartButton = document.getElementById('restartButton');
const gameArea = document.getElementById('gameArea');
const targetZone = document.getElementById('targetZone');
const feedbackText = document.getElementById('feedbackText');
const scoreDisplay = document.getElementById('score');
const comboDisplay = document.getElementById('combo');
const comboContainer = document.getElementById('comboContainer');
const finalScoreDisplay = document.getElementById('finalScore');
const maxComboDisplay = document.getElementById('maxCombo');
const finalDishDisplay = document.getElementById('finalDish');
const particlesContainer = document.getElementById('particles');
const comboExplosion = document.getElementById('comboExplosion');
const comboStreak = document.getElementById('comboStreak');
const energyWaves = document.getElementById('energyWaves');
const screenFlash = document.getElementById('screenFlash');
const startScreenBg = document.getElementById('startScreenBg');
const settingsButton = document.getElementById('settingsButton');
const gameSettingsButton = document.getElementById('gameSettingsButton');
const settingsPanel = document.getElementById('settingsPanel');
const closeSettings = document.getElementById('closeSettings');
// Settings elements
const speedRange = document.getElementById('speedRange');
const speedValue = document.getElementById('speedValue');
const bpmRange = document.getElementById('bpmRange');
const bpmValue = document.getElementById('bpmValue');
const perfectRange = document.getElementById('perfectRange');
const perfectValue = document.getElementById('perfectValue');
const goodRange = document.getElementById('goodRange');
const goodValue = document.getElementById('goodValue');
const ingredientsRange = document.getElementById('ingredientsRange');
const ingredientsValue = document.getElementById('ingredientsValue');
const sequenceRange = document.getElementById('sequenceRange');
const sequenceValue = document.getElementById('sequenceValue');
const presetName = document.getElementById('presetName');
const savePreset = document.getElementById('savePreset');
const loadPreset = document.getElementById('loadPreset');
const presetButtons = document.querySelectorAll('[data-preset]');
// Initialize audio context
function initAudio() {
try {
gameState.audioContext = new (window.AudioContext || window.webkitAudioContext)();
} catch (e) {
console.warn('Web Audio API not supported');
}
}
// Play a sound
function playSound(frequency, duration, type = 'sine') {
if (!gameState.audioContext) return;
const oscillator = gameState.audioContext.createOscillator();
const gainNode = gameState.audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(gameState.audioContext.destination);
oscillator.type = type;
oscillator.frequency.value = frequency;
gainNode.gain.value = 0.1;
oscillator.start();
oscillator.stop(gameState.audioContext.currentTime + duration);
}
// Create background particles
function createParticles() {
// Clear existing particles
particlesContainer.innerHTML = '';
gameState.backgroundParticles = [];
// Create new particles
for (let i = 0; i < 50; i++) {
createParticle(true);
}
}
function createParticle(isBackground = false) {
const particle = document.createElement('div');
particle.className = 'particle';
// Random properties
const size = Math.random() * 10 + 2;
const x = Math.random() * 100;
const y = Math.random() * 100;
const duration = Math.random() * 20 + 10;
const delay = Math.random() * 5;
const opacity = Math.random() * 0.5 + 0.1;
const color = `hsl(${Math.random() * 60 + 20}, 70%, 60%)`;
const shape = Math.random() > 0.7 ? 'text' : 'circle';
if (shape === 'text') {
const emojis = ['✨', '🌟', '⭐', '⚡', '💫', '🔥', '🍳', '🥘', '🍲'];
particle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
particle.style.fontSize = `${size}px`;
particle.style.width = 'auto';
particle.style.height = 'auto';
} else {
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.backgroundColor = color;
particle.style.borderRadius = '50%';
}
particle.style.left = `${x}%`;
particle.style.top = `${y}%`;
particle.style.opacity = opacity;
particle.style.animation = `float ${duration}s linear ${delay}s infinite`;
if (isBackground) {
particle.style.position = 'absolute';
particlesContainer.appendChild(particle);
gameState.backgroundParticles.push(particle);
}
return particle;
}
// Create floating animation
const style = document.createElement('style');
style.innerHTML = `
@keyframes float {
0% { transform: translate(0, 0) rotate(0deg); opacity: ${Math.random() * 0.3 + 0.1}; }
50% { transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 50 - 25}px) rotate(${Math.random() * 180 - 90}deg); opacity: ${Math.random() * 0.5 + 0.3}; }
100% { transform: translate(0, 0) rotate(0deg); opacity: ${Math.random() * 0.3 + 0.1}; }
}
`;
document.head.appendChild(style);
// Start metronome
function startMetronome() {
if (!gameState.audioContext) return;
const secondsPerBeat = 60 / gameState.bpm;
let nextBeatTime = gameState.audioContext.currentTime;
gameState.metronomeInterval = setInterval(() => {
playSound(880, 0.05, 'triangle');
nextBeatTime += secondsPerBeat;
// Visual metronome effect
createEnergyWave(targetZone.getBoundingClientRect().left + targetZone.offsetWidth / 2,
targetZone.getBoundingClientRect().top + targetZone.offsetHeight / 2);
}, secondsPerBeat * 1000);
}
// Stop metronome
function stopMetronome() {
if (gameState.metronomeInterval) {
clearInterval(gameState.metronomeInterval);
gameState.metronomeInterval = null;
}
}
// Create a cue element
function createCue() {
const cue = document.createElement('div');
const ingredient = gameState.ingredients[Math.floor(Math.random() * gameState.ingredients.length)];
cue.className = 'cue absolute w-20 h-20 rounded-full flex items-center justify-center text-4xl glow';
cue.style.setProperty('--duration', `${gameState.cueSpeed}s`);
cue.style.top = '-100px';
cue.style.left = `${Math.random() * 60 + 20}%`;
cue.textContent = ingredient;
// Add trail effect
createIngredientTrail(cue);
// Add to DOM and active cues array
gameArea.appendChild(cue);
const cueObj = {
element: cue,
targetTime: Date.now() + gameState.cueSpeed * 1000,
hit: false,
ingredient: ingredient
};
gameState.activeCues.push(cueObj);
// Remove cue after animation completes
setTimeout(() => {
if (!cueObj.hit) {
handleMiss(cueObj);
}
}, gameState.cueSpeed * 1000);
return cueObj;
}
// Create ingredient trail effect
function createIngredientTrail(cue) {
const trailInterval = setInterval(() => {
if (!cue.parentElement) {
clearInterval(trailInterval);
return;
}
const rect = cue.getBoundingClientRect();
const trail = document.createElement('div');
trail.className = 'ingredient-trail';
trail.style.left = `${rect.left + rect.width / 2}px`;
trail.style.top = `${rect.top + rect.height / 2}px`;
trail.style.backgroundColor = `hsl(${Math.random() * 60 + 20}, 80%, 60%)`;
trail.style.opacity = Math.random() * 0.6 + 0.2;
trail.style.transform = `scale(${Math.random() * 0.5 + 0.5})`;
document.body.appendChild(trail);
setTimeout(() => {
trail.style.transition = 'all 0.5s ease-out';
trail.style.opacity = '0';
trail.style.transform = 'scale(0)';
setTimeout(() => trail.remove(), 500);
}, 10);
}, 50);
// Clean up interval when cue is removed
cue.dataset.trailInterval = trailInterval;
}
// Show feedback
function showFeedback(text, isGood) {
feedbackText.textContent = text;
feedbackText.className = `absolute left-1/2 transform -translate-x-1/2 text-6xl font-bold text-center glow ${
isGood ? 'text-green-400' : 'text-red-400'
}`;
feedbackText.style.opacity = '1';
// Create hit effect
if (isGood) {
createHitEffect(feedbackText.getBoundingClientRect());
// Add ripple effect
createRippleEffect(gameState.lastTapPosition.x, gameState.lastTapPosition.y);
// Add perfect ring for perfect hits
if (text.includes('PERFECT')) {
createPerfectRing(gameState.lastTapPosition.x, gameState.lastTapPosition.y);
}
}
// Hide feedback after delay
setTimeout(() => {
feedbackText.style.opacity = '0';
}, 1000);
}
// Create hit effect
function createHitEffect(rect) {
const colors = ['#FFD700', '#FF6347', '#7FFFD4', '#FF69B4', '#9370DB'];
for (let i = 0; i < 25; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
const size = Math.random() * 16 + 4;
const color = colors[Math.floor(Math.random() * colors.length)];
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * 100 + 30;
const duration = Math.random() * 1 + 0.5;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.left = `${rect.left + rect.width / 2}px`;
particle.style.top = `${rect.top + rect.height / 2}px`;
particle.style.backgroundColor = color;
particle.style.borderRadius = '50%';
particle.style.opacity = '0.8';
particle.style.transform = `translate(${Math.cos(angle) * distance}px, ${Math.sin(angle) * distance}px)`;
particle.style.transition = `all ${duration}s ease-out`;
particle.style.boxShadow = `0 0 ${size/2}px ${color}`;
document.body.appendChild(article);
setTimeout(() => {
particle.style.opacity = '0';
particle.style.transform += ` scale(0.5)`;
setTimeout(() => particle.remove(), duration * 1000);
}, 10);
}
// Add floating sparkles
for (let i = 0; i < 10; i++) {
createFloatingSparkle(rect.left + rect.width / 2, rect.top + rect.height / 2);
}
}
// Create ripple effect
function createRippleEffect(x, y) {
const ripple = document.createElement('div');
ripple.className = 'ripple';
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
ripple.style.width = '50px';
ripple.style.height = '50px';
ripple.style.backgroundColor = `rgba(255, 255, 255, ${Math.random() * 0.2 + 0.1})`;
document.body.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 1000);
}
// Create perfect ring effect
function createPerfectRing(x, y) {
const ring = document.createElement('div');
ring.className = 'perfect-ring';
ring.style.left = `${x}px`;
ring.style.top = `${y}px`;
ring.style.width = '50px';
ring.style.height = '50px';
document.body.appendChild(ring);
setTimeout(() => {
ring.remove();
}, 800);
}
// Create floating sparkle
function createFloatingSparkle(x, y) {
const sparkle = document.createElement('div');
sparkle.className = 'floating-sparkle';
sparkle.style.left = `${x}px`;
sparkle.style.top = `${y}px`;
sparkle.style.backgroundColor = `hsl(${Math.random() * 60 + 20}, 100%, 80%)`;
document.body.appendChild(sparkle);
setTimeout(() => {
sparkle.remove();
}, 2000);
}
// Create energy wave
function createEnergyWave(x, y) {
const wave = document.createElement('div');
wave.className = 'energy-wave';
wave.style.left = `${x - 150}px`;
wave.style.top = `${y - 150}px`;
energyWaves.appendChild(wave);
setTimeout(() => {
wave.remove();
}, 1500);
}
// Create streak light effect
function createStreakLight(x, y, angle, length) {
const light = document.createElement('div');
light.className = 'streak-light';
light.style.left = `${x}px`;
light.style.top = `${y}px`;
light.style.width = `${length}px`;
light.style.transform = `rotate(${angle}rad)`;
document.body.appendChild(light);
setTimeout(() => {
light.style.opacity = '0';
light.style.transition = 'opacity 0.5s ease-out';
setTimeout(() => light.remove(), 500);
}, 10);
}
// Handle successful hit
function handleHit(cue, accuracy) {
if (cue.hit) return;
cue.hit = true;
// Visual feedback on the cue
cue.element.classList.add('perfect-hit');
cue.element.style.transform = 'scale(1.5)';
setTimeout(() => {
cue.element.remove();
}, 200);
// Clear trail interval
if (cue.element.dataset.trailInterval) {
clearInterval(parseInt(cue.element.dataset.trailInterval));
}
// Determine hit quality
let points = 0;
let feedback = '';
let soundFreq = 0;
if (Math.abs(accuracy) <= gameState.perfectWindow) {
points = 100;
feedback = 'PERFECT! ✨';
soundFreq = 1046.50; // C note
playSound(soundFreq, 0.2, 'sine');
playSound(soundFreq/2, 0.3, 'sine');
// Screen flash for perfect hits
screenFlash.style.opacity = '0.3';
screenFlash.style.transition = 'opacity 0.3s ease-out';
setTimeout(() => {
screenFlash.style.opacity = '0';
}, 300);
} else if (Math.abs(accuracy) <= gameState.goodWindow) {
points = 60;
feedback = 'GREAT! 👍';
soundFreq = 783.99; // G note
playSound(soundFreq, 0.15, 'square');
} else {
points = 30;
feedback = 'GOOD 👌';
soundFreq = 523.25; // C note lower
playSound(soundFreq, 0.1, 'sawtooth');
}
// Update score and combo
gameState.score += points;
gameState.combo += 1;
if (gameState.combo > gameState.maxCombo) {
gameState.maxCombo = gameState.combo;
}
scoreDisplay.textContent = gameState.score;
comboDisplay.textContent = gameState.combo;
// Show combo if > 1
if (gameState.combo > 1) {
comboContainer.classList.remove('hidden');
// Combo streak effect
if (gameState.combo % 5 === 0) {
comboStreak.classList.remove('hidden');
setTimeout(() => {
comboStreak.classList.add('hidden');
}, 300);
// Combo explosion for every 5 hits
if (gameState.combo % 10 === 0) {
createComboExplosion();
}
}
// Streak lights for high combos
if (gameState.combo > 3) {
for (let i = 0; i < 3; i++) {
const angle = Math.random() * Math.PI * 2;
const length = Math.random() * 200 + 100;
createStreakLight(gameState.lastTapPosition.x, gameState.lastTapPosition.y, angle, length);
}
}
}
// Show feedback
showFeedback(feedback, true);
// Create ingredient transformation effect
createIngredientEffect(cue);
}
// Create combo explosion effect
function createComboExplosion() {
comboExplosion.innerHTML = '';
for (let i = 0; i < 30; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
const size = Math.random() * 20 + 10;
const color = `hsl(${Math.random() * 60 + 20}, 100%, 70%)`;
const angle = Math.random() * Math.PI * 2;
const distance = Math.random() * 300 + 100;
const duration = Math.random() * 1 + 0.5;
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
particle.style.left = '50%';
particle.style.top = '50%';
particle.style.backgroundColor = color;
particle.style.borderRadius = '50%';
particle.style.opacity = '0.8';
particle.style.transform = `translate(-50%, -50%) translate(${Math.cos(angle) * distance}px, ${Math.sin(angle) * distance}px)`;
particle.style.transition = `all ${duration}s ease-out`;
particle.style.boxShadow = `0 0 ${size/2}px ${color}`;
comboExplosion.appendChild(particle);
setTimeout(() => {
particle.style.opacity = '0';
particle.style.transform += ` scale(0.5)`;
setTimeout(() => particle.remove(), duration * 1000);
}, 10);
}
}
// Create ingredient transformation effect
function createIngredientEffect(cue) {
const rect = cue.element.getBoundingClientRect();
const effect = document.createElement('div');
effect.className = 'absolute text-4xl glow';
effect.style.left = `${rect.left}px`;
effect.style.top = `${rect.top}px`;
effect.style.width = `${rect.width}px`;
effect.style.height = `${rect.height}px`;
effect.style.display = 'flex';
effect.style.alignItems = 'center';
effect.style.justifyContent = 'center';
effect.textContent = cue.ingredient;
document.body.appendChild(effect);
// Animate transformation
setTimeout(() => {
effect.style.transition = 'all 0.5s ease-out';
effect.style.transform = 'translateY(-50px) scale(2)';
effect.style.opacity = '0';
// Change to a cooked version
const cookedItems = {
'🍅': '🍅', // tomato stays tomato
'🧀': '🧀', // cheese stays cheese
'🍄': '🍄', // mushroom stays mushroom
'🥩': '🍖', // raw meat -> cooked meat
'🥬': '🥗', // lettuce -> salad
'🍞': '🍞', // bread stays bread
'🥚': '🍳', // egg -> fried egg
'🦐': '🍤', // shrimp -> fried shrimp
'🌽': '🍿', // corn -> popcorn
'🧅': '🧅' // onion stays onion
};
setTimeout(() => {
effect.textContent = cookedItems[cue.ingredient] || '🍽️';
}, 250);
setTimeout(() => {
effect.remove();
}, 1000);
}, 10);
}
// Handle miss
function handleMiss(cue) {
if (cue.hit) return;
cue.hit = true;
if (cue.element) {
cue.element.classList.add('opacity-50');
cue.element.style.transform = 'scale(0.8)';
setTimeout(() => {
cue.element.remove();
}, 500);
// Clear trail interval
if (cue.element.dataset.trailInterval) {
clearInterval(parseInt(cue.element.dataset.trailInterval));
}
}
// Reset combo
gameState.combo = 0;
comboDisplay.textContent = '0';
comboContainer.classList.add('hidden');
// Show feedback
showFeedback('MISS! 😩', false);
playSound(110, 0.3, 'sawtooth'); // Low buzz for miss
// Create break effect
if (cue.element) {
const rect = cue.element.getBoundingClientRect();
for (let i = 0; i < 8; i++) {
const piece = document.createElement('div');
piece.className = 'particle absolute text-xl';
piece.textContent = cue.ingredient;
piece.style.left = `${rect.left + rect.width/2}px`;
piece.style.top = `${rect.top + rect.height/2}px`;
piece.style.transform = `translate(${(Math.random() - 0.5) * 80}px, ${(Math.random() - 0.5) * 80}px) rotate(${Math.random() * 360}deg)`;
piece.style.opacity = '0.7';
piece.style.transition = 'all 0.8s ease-out';
document.body.appendChild(piece);
setTimeout(() => {
piece.style.opacity = '0';
piece.style.transform += ` translateY(50px)`;
setTimeout(() => piece.remove(), 800);
}, 10);
}
}
}
// Start game sequence
function startSequence() {
gameState.score = 0;
gameState.combo = 0;
gameState.maxCombo = 0;
gameState.isPlaying = true;
gameState.activeCues = [];
gameState.currentSequenceIndex = 0;
gameState.sequenceStartTime = Date.now();
scoreDisplay.textContent = '0';
comboDisplay.textContent = '0';
comboContainer.classList.add('hidden');
comboStreak.classList.add('hidden');
// Create background particles
createParticles();
// Start metronome
if (gameState.audioContext) {
startMetronome();
}
// Start spawning cues
spawnNextCue();
}
// Spawn next cue in sequence
function spawnNextCue() {
if (gameState.currentSequenceIndex >= gameState.sequence.length) {
endSequence();
return;
}
const currentBeat = gameState.sequence[gameState.currentSequenceIndex].beat;
const beatsPerSecond = gameState.bpm / 60;
const beatTime = currentBeat / beatsPerSecond * 1000;
// Calculate delay until this cue should spawn
const spawnDelay = beatTime - (Date.now() - gameState.sequenceStartTime) - (gameState.cueSpeed * 1000);
if (spawnDelay <= 0) {
// We're behind schedule, spawn immediately
createCue();
gameState.currentSequenceIndex++;
spawnNextCue();
} else {
// Schedule next cue
setTimeout(() => {
createCue();
gameState.currentSequenceIndex++;
spawnNextCue();
}, spawnDelay);
}
}
// End sequence
function endSequence() {
gameState.isPlaying = false;
stopMetronome();
// Clear background particles
particlesContainer.innerHTML = '';
// Determine final dish based on score
const dishIndex = Math.min(
Math.floor(gameState.score / 500),
gameState.dishes.length - 1
);
finalDishDisplay.textContent = gameState.dishes[dishIndex];
// Wait for last cues to finish
setTimeout(() => {
finalScoreDisplay.textContent = gameState.score;
maxComboDisplay.textContent = gameState.maxCombo;
endScreen.classList.remove('hidden');
// Celebration particles
for (let i = 0; i < 100; i++) {
setTimeout(() => {
const particle = createParticle();
particle.style.position = 'fixed';
particle.style.left = `${Math.random() * 100}%`;
particle.style.top = `${Math.random() * 100}%`;
particle.style.fontSize = `${Math.random() * 30 + 20}px`;
particle.style.opacity = '0.8';
particle.style.animation = `float ${Math.random() * 3 + 2}s ease-out forwards`;
const emojis = ['✨', '🌟', '⭐', '⚡', '💫', '🔥', '🎉', '🎊', '🥳'];
particle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
endScreen.appendChild(particle);
setTimeout(() => {
particle.remove();
}, 3000);
}, i * 50);
}
}, gameState.cueSpeed * 1000);
}
// Handle tap input
function handleTap(e) {
if (!gameState.isPlaying) return;
// Store tap position for effects
const rect = gameArea.getBoundingClientRect();
gameState.lastTapPosition = {
x: (e.clientX || e.touches[0].clientX) - rect.left,
y: (e.clientY || e.touches[0].clientY) - rect.top
};
const now = Date.now();
let closestCue = null;
let closestDiff = Infinity;
// Find the closest active cue to target time
for (const cue of gameState.activeCues) {
if (!cue.hit) {
const diff = now - cue.targetTime;
if (Math.abs(diff) < Math.abs(closestDiff)) {
closestDiff = diff;
closestCue = cue;
}
}
}
// Visual feedback on target zone
targetZone.classList.add('perfect-hit');
setTimeout(() => {
targetZone.classList.remove('perfect-hit');
}, 300);
// Create energy wave at tap position
createEnergyWave(gameState.lastTapPosition.x, gameState.lastTapPosition.y);
// Check if tap was close enough to any cue
if (closestCue && Math.abs(closestDiff) <= gameState.goodWindow * 2) {
handleHit(closestCue, closestDiff);
} else {
// Penalize random taps
gameState.combo = 0;
comboDisplay.textContent = '0';
comboContainer.classList.add('hidden');
showFeedback('TOO EARLY! 👎', false);
playSound(110, 0.3, 'square');
}
}
// Load presets from localStorage
function loadPresets() {
const presets = JSON.parse(localStorage.getItem('rhythmChefPresets')) || {};
const select = document.getElementById('loadPreset');
// Clear existing options except the first one
while (select.options.length > 1) {
select.remove(1);
}
// Add presets to dropdown
for (const [name, preset] of Object.entries(presets)) {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
}
}
// Save preset to localStorage
function savePresetToStorage(name) {
const presets = JSON.parse(localStorage.getItem('rhythmChefPresets')) || {};
presets[name] = {
bpm: gameState.bpm,
cueSpeed: gameState.cueSpeed,
perfectWindow: gameState.perfectWindow,
goodWindow: gameState.goodWindow,
ingredients: gameState.ingredients,
sequence: gameState.sequence
};
localStorage.setItem('rhythmChefPresets', JSON.stringify(presets));
loadPresets();
}
// Load preset from localStorage
function loadPresetFromStorage(name) {
const presets = JSON.parse(localStorage.getItem('rhythmChefPresets')) || {};
const preset = presets[name];
if (preset) {
gameState.bpm = preset.bpm;
gameState.cueSpeed = preset.cueSpeed;
gameState.perfectWindow = preset.perfectWindow;
gameState.goodWindow = preset.goodWindow;
gameState.ingredients = preset.ingredients;
gameState.sequence = preset.sequence;
// Update UI to match
bpmRange.value = gameState.bpm;
bpmValue.textContent = gameState.bpm;
speedRange.value = gameState.cueSpeed;
speedValue.textContent = gameState.cueSpeed.toFixed(1);
perfectRange.value = gameState.perfectWindow;
perfectValue.textContent = gameState.perfectWindow;
goodRange.value = gameState.goodWindow;
goodValue.textContent = gameState.goodWindow;
ingredientsRange.value = gameState.ingredients.length;
ingredientsValue.textContent = gameState.ingredients.length;
sequenceRange.value = gameState.sequence.length;
sequenceValue.textContent = gameState.sequence.length;
// Update preset name field
presetName.value = name;
return true;
}
return false;
}
// Apply preset settings
function applyPreset(presetName) {
switch(presetName) {
case 'easy':
gameState.bpm = 100;
gameState.cueSpeed = 2.5;
gameState.perfectWindow = 100;
gameState.goodWindow = 200;
gameState.ingredients = ['🍅', '🧀', '🍄', '🥩', '🥬', '🍞'];
gameState.sequence = Array(16).fill().map((_, i) => ({ beat: i + 1, type: 'tap' }));
break;
case 'medium':
gameState.bpm = 128;
gameState.cueSpeed = 2;
gameState.perfectWindow = 80;
gameState.goodWindow = 160;
gameState.ingredients = ['🍅', '🧀', '🍄', '🥩', '🥬', '🍞', '🥚', '🦐', '🌽', '🧅'];
gameState.sequence = [
{ beat: 1, type: 'tap' }, { beat: 2, type: 'tap' }, { beat: 3, type: 'tap' }, { beat: 4, type: 'tap' },
{ beat: 5.5, type: 'tap' }, { beat: 6.5, type: 'tap' }, { beat: 7, type: 'tap' }, { beat: 8, type: 'tap' },
{ beat: 9, type: 'tap' }, { beat: 10, type: 'tap' }, { beat: 11.5, type: 'tap' }, { beat: 12.5, type: 'tap' },
{ beat: 13, type: 'tap' }, { beat: 14, type: 'tap' }, { beat: 15, type: 'tap' }, { beat: 16, type: 'tap' }
];
break;
case 'hard':
gameState.bpm = 160;
gameState.cueSpeed = 1.5;
gameState.perfectWindow = 60;
gameState.goodWindow = 120;
gameState.ingredients = ['🍅', '🧀', '🍄', '🥩', '🥬', '🍞', '🥚', '🦐', '🌽', '🧅', '🥕', '🍠', '🥦', '🥒', '🍆'];
gameState.sequence = [
{ beat: 1, type: 'tap' }, { beat: 1.5, type: 'tap' }, { beat: 2, type: 'tap' }, { beat: 2.5, type: 'tap' },
{ beat: 3, type: 'tap' }, { beat: 3.5, type: 'tap' }, { beat: 4, type: 'tap' }, { beat: 4.5, type: 'tap' },
{ beat: 5, type: 'tap' }, { beat: 5.5, type: 'tap' }, { beat: 6, type: 'tap' }, { beat: 6.5, type: 'tap' },
{ beat: 7, type: 'tap' }, { beat: 7.5, type: 'tap' }, { beat: 8, type: 'tap' }, { beat: 8.5, type: 'tap' }
];
break;
}
// Update UI to match
bpmRange.value = gameState.bpm;
bpmValue.textContent = gameState.bpm;
speedRange.value = gameState.cueSpeed;
speedValue.textContent = gameState.cueSpeed.toFixed(1);
perfectRange.value = gameState.perfectWindow;
perfectValue.textContent = gameState.perfectWindow;
goodRange.value = gameState.goodWindow;
goodValue.textContent = gameState.goodWindow;
ingredientsRange.value = gameState.ingredients.length;
ingredientsValue.textContent = gameState.ingredients.length;
sequenceRange.value = gameState.sequence.length;
sequenceValue.textContent = gameState.sequence.length;
// Update active preset button
presetButtons.forEach(btn => {
btn.classList.toggle('active', btn.dataset.preset === presetName);
});
}
// Initialize game
function initGame() {
initAudio();
// Add floating elements to start screen
for (let i = 0; i < 10; i++) {
const element = document.createElement('div');
element.className = 'floating-bg-element text-4xl';
element.style.left = `${Math.random() * 100}%`;
element.style.top = `${Math.random() * 100}%`;
element.style.animationDuration = `${Math.random() * 30 + 20}s`;
element.style.animationDelay = `${Math.random() * 10}s`;
const emojis = ['🍳', '🥘', '🍕', '🍔', '🍟', '🌭', '🍿', '🧂', '🥗', '🍣'];
element.textContent = emojis[Math.floor(Math.random() * emojis.length)];
startScreenBg.appendChild(element);
}
// Load presets
loadPresets();
// Apply medium preset by default
applyPreset('medium');
// Start button
startButton.addEventListener('click', () => {
startScreen.classList.add('opacity-0');
setTimeout(() => {
startScreen.classList.add('hidden');
gameScreen.classList.remove('hidden');
startSequence();
}, 500);
});
// Restart button
restartButton.addEventListener('click', () => {
endScreen.classList.add('hidden');
startSequence();
});
// Settings button
settingsButton.addEventListener('click', () => {
settingsPanel.classList.remove('hidden');
setTimeout(() => {
settingsPanel.querySelector('.settings-panel').classList.add('open');
}, 10);
});
// Game settings button
gameSettingsButton.addEventListener('click', () => {
settingsPanel.classList.remove('hidden');
setTimeout(() => {
settingsPanel.querySelector('.settings-panel').classList.add('open');
}, 10);
});
// Close settings
closeSettings.addEventListener('click', () => {
settingsPanel.querySelector('.settings-panel').classList.remove('open');
setTimeout(() => {
settingsPanel.classList.add('hidden');
}, 300);
});
// Preset buttons
presetButtons.forEach(btn => {
btn.addEventListener('click', () => {
applyPreset(btn.dataset.preset);
});
});
// Range inputs
speedRange.addEventListener('input', () => {
gameState.cueSpeed = parseFloat(speedRange.value);
speedValue.textContent = gameState.cueSpeed.toFixed(1);
});
bpmRange.addEventListener('input', () => {
gameState.bpm = parseInt(bpmRange.value);
bpmValue.textContent = gameState.bpm;
});
perfectRange.addEventListener('input', () => {
gameState.perfectWindow = parseInt(perfectRange.value);
perfectValue.textContent = gameState.perfectWindow;
});
goodRange.addEventListener('input', () => {
gameState.goodWindow = parseInt(goodRange.value);
goodValue.textContent = gameState.goodWindow;
});
ingredientsRange.addEventListener('input', () => {
const count = parseInt(ingredientsRange.value);
ingredientsValue.textContent = count;
// Basic ingredients that are always available
const baseIngredients = ['🍅', '🧀', '🍄', '🥩', '🥬', '🍞', '🥚'];
// Additional ingredients that can be added
const extraIngredients = ['🦐', '🌽', '🧅', '🥕', '🍠', '🥦', '🥒', '🍆', '🍍', '🥑'];
// Create the ingredient list based on count
gameState.ingredients = [...baseIngredients];
if (count > baseIngredients.length) {
const needed = count - baseIngredients.length;
gameState.ingredients.push(...extraIngredients.slice(0, Math.min(needed, extraIngredients.length)));
} else {
gameState.ingredients = gameState.ingredients.slice(0, count);
}
});
sequenceRange.addEventListener('input', () => {
const length = parseInt(sequenceRange.value);
sequenceValue.textContent = length;
// Create a simple sequence with the specified length
gameState.sequence = Array(length).fill().map((_, i) => {
// Alternate between whole beats and half beats for variety
const beat = i % 2 === 0 ? i + 1 : i + 0.5;
return { beat, type: 'tap' };
});
});
// Save preset
savePreset.addEventListener('click', () => {
const name = presetName.value.trim();
if (name) {
savePresetToStorage(name);
presetName.value = '';
// Show confirmation
const feedback = document.createElement('div');
feedback.textContent = 'Preset saved!';
feedback.className = 'text-green-400 text-sm mt-2';
savePreset.parentNode.appendChild(feedback);
setTimeout(() => {
feedback.remove();
}, 2000);
}
});
// Load preset
loadPreset.addEventListener('change', () => {
if (loadPreset.value) {
loadPresetFromStorage(loadPreset.value);
presetName.value = loadPreset.value;
}
});
// Tap input
gameArea.addEventListener('click', handleTap);
// Touch support
gameArea.addEventListener('touchstart', (e) => {
e.preventDefault();
handleTap(e);
});
}
// Start the game when ready
window.addEventListener('DOMContentLoaded', 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/game-4-rhytm-chef" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>