import React, { useState, useRef, useEffect } from 'react'; import { Coins, ShoppingBag, Utensils, Bike, ChefHat, BellRing, Play, Zap, Grid, Hammer, Eraser, TrendingUp, CheckCircle2, Box, RotateCcw, Trash2, Move, Hand, MousePointer2 } from 'lucide-react'; // --- CONFIG --- const UPGRADES = [ { id: 'neon', name: 'Neon Sign', cost: 250, icon: '✨', desc: 'x1.5 Tip Multiplier', effect: { type: 'mult', value: 1.5 } }, { id: 'scooter_red', name: 'Turbo Scooter', cost: 350, icon: '🛵', desc: 'Speed Bonus (+20)', effect: { type: 'flat', value: 20 } }, { id: 'jukebox', name: 'Jukebox', cost: 300, icon: '🎵', desc: 'Party Bonus (+10)', effect: { type: 'flat', value: 10 } }, ]; const BLOCKS = [ { id: 'brick', name: 'Red Brick', cost: 10, color: '#ef4444', side: '#b91c1c', top: '#f87171' }, { id: 'wood', name: 'Oak Wood', cost: 10, color: '#d97706', side: '#92400e', top: '#fbbf24' }, { id: 'stone', name: 'Stone', cost: 10, color: '#94a3b8', side: '#64748b', top: '#cbd5e1' }, { id: 'grass', name: 'Grass', cost: 10, color: '#22c55e', side: '#15803d', top: '#4ade80' }, { id: 'water', name: 'Water', cost: 10, color: '#3b82f6', side: '#1d4ed8', top: '#60a5fa' }, { id: 'gold', name: 'Gold', cost: 50, color: '#eab308', side: '#a16207', top: '#facc15' }, { id: 'glass', name: 'Ice/Glass', cost: 15, color: 'rgba(56, 189, 248, 0.4)', side: 'rgba(2, 132, 199, 0.4)', top: 'rgba(125, 211, 252, 0.4)' }, { id: 'leaves', name: 'Leaves', cost: 10, color: '#16a34a', side: '#14532d', top: '#22c55e' }, ]; const INGREDIENTS = [ { id: 'dough', icon: '🍘', name: 'Dough' }, { id: 'sauce', icon: '🍅', name: 'Sauce' }, { id: 'cheese', icon: '🧀', name: 'Cheese' }, { id: 'pep', icon: '🍕', name: 'Pepperoni' }, { id: 'mush', icon: '🍄', name: 'Mushroom' }, { id: 'basil', icon: '🌿', name: 'Basil' }, ]; const RECIPES = [ { name: 'Cheese Pizza', req: ['dough', 'sauce', 'cheese'] }, { name: 'Pepperoni', req: ['dough', 'sauce', 'cheese', 'pep'] }, { name: 'Veggie', req: ['dough', 'sauce', 'cheese', 'mush', 'basil'] }, ]; const GRID_SIZE = 10; const BLOCK_SIZE = 40; const DRAG_THRESHOLD = 5; // Pixels mouse must move to count as "Rotation" export default function PizzaTimeDash() { // --- STATE --- const [coins, setCoins] = useState(0); const [mode, setMode] = useState('kitchen'); const [phase, setPhase] = useState('IDLE'); // Inventory const [upgrades, setUpgrades] = useState([]); const [blockInventory, setBlockInventory] = useState({ brick: 20, wood: 20, grass: 20 }); // 3D Building Data const [voxels, setVoxels] = useState({}); const [selectedBlockId, setSelectedBlockId] = useState('brick'); const [isEraser, setIsEraser] = useState(false); // Camera & Interaction State const [viewRot, setViewRot] = useState({ x: 60, z: 45 }); // Drag vs Click Tracking const dragStartPos = useRef({ x: 0, y: 0 }); const isRotating = useRef(false); // Game Data const [currentOrder, setCurrentOrder] = useState(null); const [cookedIngredients, setCookedIngredients] = useState([]); const [targetTime, setTargetTime] = useState({ h: 12, m: 0 }); const [playerTime, setPlayerTime] = useState({ h: 12, m: 0 }); const [message, setMessage] = useState("Welcome! Serve pizzas to earn blocks."); const [streak, setStreak] = useState(0); const [lastRewardDetails, setLastRewardDetails] = useState(null); // --- ACTIONS --- const startOrder = () => { const randomRecipe = RECIPES[Math.floor(Math.random() * RECIPES.length)]; const h = Math.floor(Math.random() * 12) + 1; const m = Math.floor(Math.random() * 12) * 5; setCurrentOrder({ recipe: randomRecipe, due: { h, m } }); setTargetTime({ h, m }); setPlayerTime({ h: 12, m: 0 }); setCookedIngredients([]); setPhase('NOTIFICATION'); setMessage("New Online Order Received!"); setMode('kitchen'); }; const acceptOrder = () => { setPhase('COOKING'); setMessage(`Cook a ${currentOrder.recipe.name}!`); }; const addIngredient = (ingId) => { const neededIngredient = currentOrder.recipe.req[cookedIngredients.length]; if (ingId === neededIngredient) { const newList = [...cookedIngredients, ingId]; setCookedIngredients(newList); if (newList.length === currentOrder.recipe.req.length) { setTimeout(() => { setPhase('CLOCK'); setMessage(`Set delivery time to ${formatTime(targetTime)}!`); }, 500); } } else { setMessage("Wrong ingredient!"); setTimeout(() => setMessage(`Cook a ${currentOrder.recipe.name}!`), 1000); } }; const attemptDelivery = () => { if (playerTime.h === targetTime.h && playerTime.m === targetTime.m) { setPhase('DELIVERING'); setMessage("Order on the way!"); setTimeout(() => { let base = 20 + (streak * 2); let bonus = 0; let multiplier = 1; let bonusBreakdown = []; UPGRADES.forEach(item => { if (upgrades.includes(item.id)) { if (item.effect.type === 'flat') { bonus += item.effect.value; bonusBreakdown.push({ name: item.name, val: `+${item.effect.value}` }); } if (item.effect.type === 'mult') { multiplier *= item.effect.value; bonusBreakdown.push({ name: item.name, val: `x${item.effect.value}` }); } } }); const total = Math.floor((base + bonus) * multiplier); setLastRewardDetails({ base, bonusBreakdown, total, multiplier }); setCoins(prev => prev + total); setStreak(prev => prev + 1); setPhase('SUMMARY'); setMessage(`Delivered! Earned ${total} Coins!`); }, 2500); } else { setStreak(0); setMessage("Time is wrong!"); } }; const buyBlock = (blockId, amount = 5) => { const block = BLOCKS.find(b => b.id === blockId); const totalCost = block.cost * amount; if (coins >= totalCost) { setCoins(prev => prev - totalCost); setBlockInventory(prev => ({ ...prev, [blockId]: (prev[blockId] || 0) + amount })); } }; const buyUpgrade = (item) => { if (coins >= item.cost && !upgrades.includes(item.id)) { setCoins(prev => prev - item.cost); setUpgrades(prev => [...prev, item.id]); } }; // --- 3D INTERACTION LOGIC --- const handlePointerDown = (e) => { if (mode !== 'build') return; // Start tracking drag dragStartPos.current = { x: e.clientX, y: e.clientY }; isRotating.current = false; }; const handlePointerMove = (e) => { if (mode !== 'build') return; // Calculate distance moved const deltaX = e.clientX - dragStartPos.current.x; const deltaY = e.clientY - dragStartPos.current.y; const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // If we are holding down the button (e.buttons === 1 means left click is held) if (e.buttons === 1) { if (distance > DRAG_THRESHOLD) { isRotating.current = true; // We moved enough to count as a drag/rotate } if (isRotating.current) { // Apply rotation setViewRot(prev => ({ x: Math.max(10, Math.min(85, prev.x - (e.movementY * 0.5))), z: prev.z - (e.movementX * 0.5) })); } } }; const handlePointerUp = () => { // Reset drag state handled by the individual block clicks // but we can clean up here if needed }; const placeVoxel = (x, y, z) => { // CRITICAL: If we were just rotating the camera, DO NOT PLACE A BLOCK if (isRotating.current) return; const key = `${x},${y},${z}`; // Bounds check if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE || z < 0 || z > 12) return; if (isEraser) { if (voxels[key]) { const blockId = voxels[key].id; setBlockInventory(prev => ({...prev, [blockId]: (prev[blockId] || 0) + 1})); const newVoxels = { ...voxels }; delete newVoxels[key]; setVoxels(newVoxels); } } else { if (voxels[key]) return; // Already a block there if ((blockInventory[selectedBlockId] || 0) > 0) { setVoxels(prev => ({ ...prev, [key]: { id: selectedBlockId } })); setBlockInventory(prev => ({ ...prev, [selectedBlockId]: prev[selectedBlockId] - 1 })); } } }; const clearAll = () => { if (window.confirm("Clear all blocks?")) setVoxels({}); }; const closeSummary = () => { setPhase('IDLE'); setLastRewardDetails(null); }; const adjustMinutes = (amount) => { let newM = playerTime.m + amount; let newH = playerTime.h; if (newM >= 60) { newM = 0; newH = newH === 12 ? 1 : newH + 1; } else if (newM < 0) { newM = 55; newH = newH === 1 ? 12 : newH - 1; } setPlayerTime({ h: newH, m: newM }); }; const adjustHours = (amount) => { let newH = playerTime.h + amount; if (newH > 12) newH = 1; if (newH < 1) newH = 12; setPlayerTime({ ...playerTime, h: newH }); }; const formatTime = (t) => { const m = t.m < 10 ? `0${t.m}` : t.m; return `${t.h}:${m}`; }; const hourRotation = (playerTime.h % 12) * 30 + (playerTime.m * 0.5); const minuteRotation = playerTime.m * 6; // --- RENDER --- return (
{currentOrder.recipe.name}
Due at {formatTime(targetTime)}
1. Click grid to place block.
2. Click block side to stack.
3. Drag background to rotate.
Check the left screen to start!