Ir al contenido
Yesuars Tåblero Digitål™ - Edición Navidad
❅
❆
❅
❆
❅
❆
❅
❆
❅
❆
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Yesuars Tåblero Digitål™ - Edición Navidad</title> <script src="https://cdn.tailwindcss.com"></script> <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <style> /* Fondo Noche Navideña */ body { margin: 0; background-color: #050a14; height: 100vh; overflow: hidden; } #root { height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; position: relative; z-index: 2; } ::-webkit-scrollbar { width: 0px; background: transparent; } * { scrollbar-width: none; -ms-overflow-style: none; } /* --- EFECTO NIEVE (CSS PURO) --- */ .snowflake { position: fixed; top: -10px; color: #fff; font-size: 1em; font-family: Arial; text-shadow: 0 0 1px #000; user-select: none; z-index: 1; pointer-events: none; animation-name: snowflakes-fall, snowflakes-shake; animation-duration: 10s, 3s; animation-timing-function: linear, ease-in-out; animation-iteration-count: infinite, infinite; animation-play-state: running, running; opacity: 0.6; } @keyframes snowflakes-fall { 0% { top: -10%; } 100% { top: 100%; } } @keyframes snowflakes-shake { 0% { transform: translateX(0px); } 50% { transform: translateX(80px); } 100% { transform: translateX(0px); } } /* Posiciones aleatorias para la nieve */ .snowflake:nth-of-type(1) { left: 1%; animation-delay: 0s, 0s; } .snowflake:nth-of-type(2) { left: 10%; animation-delay: 1s, 1s; } .snowflake:nth-of-type(3) { left: 20%; animation-delay: 6s, .5s; } .snowflake:nth-of-type(4) { left: 30%; animation-delay: 4s, 2s; } .snowflake:nth-of-type(5) { left: 40%; animation-delay: 2s, 2s; } .snowflake:nth-of-type(6) { left: 50%; animation-delay: 8s, 3s; } .snowflake:nth-of-type(7) { left: 60%; animation-delay: 6s, 2s; } .snowflake:nth-of-type(8) { left: 70%; animation-delay: 2.5s, 1s; } .snowflake:nth-of-type(9) { left: 80%; animation-delay: 1s, 0s; } .snowflake:nth-of-type(10) { left: 90%; animation-delay: 3s, 1.5s; } /* --- 1. ANIMACIÓN DE BORDE GIRATORIO (NAVIDAD: ROJO/VERDE/DORADO) --- */ @keyframes spin-slow { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .card-wrapper-gradient { position: relative; border-radius: 1.5rem; padding: 4px; overflow: hidden; box-shadow: 0 0 50px rgba(220, 38, 38, 0.4); /* Glow Rojo */ z-index: 50; } .card-wrapper-gradient::before { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; /* Gradiente Navideño */ background: conic-gradient(from 0deg, #ef4444, #22c55e, #eab308, #ef4444); animation: spin-slow 4s linear infinite; z-index: 0; } .card-content-inner { position: relative; z-index: 1; background: #0f131a; border-radius: 1.3rem; height: 100%; width: 100%; overflow: hidden; display: flex; flex-direction: column; } /* --- 2. TARJETA PRINCIPAL: FLASH AL PRESIONAR --- */ @keyframes rgb-flash { 0% { border-color: #ef4444; box-shadow: 0 0 20px rgba(239, 68, 68, 0.8); } 33% { border-color: #22c55e; box-shadow: 0 0 20px rgba(34, 197, 94, 0.8); } 66% { border-color: #eab308; box-shadow: 0 0 20px rgba(234, 179, 8, 0.8); } 100% { border-color: #ef4444; box-shadow: 0 0 20px rgba(239, 68, 68, 0.8); } } .card-premium { transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); border: 1px solid rgba(255, 255, 255, 0.08); position: relative; } .card-premium:hover { transform: translateY(-3px) scale(1.01); border-color: rgba(255, 215, 0, 0.3); /* Borde dorado al hover */ } .card-premium:active { transform: scale(0.96); border-width: 2px !important; border-style: solid !important; animation: rgb-flash 0.5s linear infinite; z-index: 20; } /* Clase para tarjeta seleccionada (Borde Persistente) */ .card-selected { border-color: rgba(239, 68, 68, 0.5) !important; /* Rojo Navidad */ box-shadow: 0 0 20px rgba(239, 68, 68, 0.2); background: rgba(239, 68, 68, 0.05) !important; } /* Animaciones Generales */ @keyframes aurora-shift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } /* Texto Aurora Navideño */ .text-aurora { background: linear-gradient(-45deg, #ef4444, #22c55e, #eab308, #ef4444); background-size: 300%; -webkit-background-clip: text; background-clip: text; color: transparent; animation: aurora-shift 6s ease infinite; text-shadow: 0 0 30px rgba(220, 38, 38, 0.3); } .animate-scale-up { animation: scale-up 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; } @keyframes scale-up { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } } .mask-gradient-right { mask-image: linear-gradient(to right, black 85%, transparent 100%); -webkit-mask-image: linear-gradient(to right, black 85%, transparent 100%); } @keyframes float-slow { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } } .animate-float { animation: float-slow 6s ease-in-out infinite; } .animate-bounce-subtle { animation: bounce-subtle 2s infinite ease-in-out; } @keyframes bounce-subtle { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } } /* Animación para el cambio de números */ @keyframes number-pop { 0% { transform: scale(1); } 50% { transform: scale(1.3) rotate(-5deg); color: #eab308; } /* Pop Dorado */ 100% { transform: scale(1); } } .animate-number-pop { animation: number-pop 0.3s ease-out; } /* Animación Badge */ @keyframes badge-pop { 0% { transform: scale(0); } 60% { transform: scale(1.2); } 100% { transform: scale(1); } } .animate-badge-pop { animation: badge-pop 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; } </style> </head> <body> <!-- Efecto Nieve --> <div class="snowflake">❅</div> <div class="snowflake">❆</div> <div class="snowflake">❅</div> <div class="snowflake">❆</div> <div class="snowflake">❅</div> <div class="snowflake">❆</div> <div class="snowflake">❅</div> <div class="snowflake">❆</div> <div class="snowflake">❅</div> <div class="snowflake">❆</div> <div id="root"></div> <script type="text/babel"> const { useState, useEffect } = React; const Icons = { Trash: ({ className, onClick }) => <svg onClick={onClick} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>, X: ({ className, onClick }) => <svg onClick={onClick} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} style={{cursor: 'pointer'}}><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>, Plus: ({ size, strokeWidth }) => <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>, Minus: ({ size, strokeWidth }) => <svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/></svg>, DollarSign: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><line x1="12" x2="12" y1="2" y2="22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>, Info: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="8"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>, Check: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" className={className}><polyline points="20 6 9 17 4 12"/></svg>, MapPin: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>, Phone: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>, Mail: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>, Instagram: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><rect x="2" y="2" width="20" height="20" rx="5" ry="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"/></svg>, Facebook: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg>, Bike: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><circle cx="5.5" cy="17.5" r="3.5"/><circle cx="18.5" cy="17.5" r="3.5"/><path d="M15 6h-5a1 1 0 0 0-1 1v3.5"/><path d="M5.5 17.5h13"/><path d="M9 17.5V11a2 2 0 0 1 2-2h2.5"/><path d="M8 10h4"/><path d="M18.5 14h-3.5L12 7"/></svg>, Store: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"/><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"/><path d="M2 7h20"/><path d="M22 7v3a2 2 0 0 1-2 2v0a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12v0a2 2 0 0 1-2-2V7"/></svg>, ArrowLeft: ({ className }) => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> }; const BUSINESSES = { 'gourart': { id: 'gourart', name: 'Gourart', description: 'Cocina Gourmet & Experiencias de Autor', style: 'text-amber-400', badgeBg: 'bg-amber-500/10', badgeBorder: 'border-amber-500/20', headerGradient: 'from-amber-900/80', icon: 'G' }, 'pizzapop': { id: 'pizzapop', name: 'Pizza Pop', description: 'La mejor pizza americana de la zona', style: 'text-orange-500', badgeBg: 'bg-orange-500/10', badgeBorder: 'border-orange-500/20', headerGradient: 'from-orange-900/80', icon: 'P' } }; const MENU_ITEMS = [ { id: 1, businessId: 'gourart', name: "Burger 'Yesuar✦s' Royal", description: "Carne Angus 200g, queso brie fundido, cebolla caramelizada al vino tinto.", price: 850, category: "Hamburguesas", image: "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?auto=format&fit=crop&w=800&q=80", popular: true, spicy: false }, { id: 2, businessId: 'gourart', name: "Tacos Gobernador 2025", description: "Camarones salteados, costra de queso manchego, aguacate y salsa serrano.", price: 450, category: "Tacos", image: "https://images.unsplash.com/photo-1613514785940-daed07799d9b?auto=format&fit=crop&w=800&q=80", popular: true, spicy: true }, { id: 3, businessId: 'pizzapop', name: "Pizza Trufa Negra", description: "Base blanca, mozzarella fior di latte, pasta de trufa negra.", price: 1200, category: "Pizzas", image: "https://images.unsplash.com/photo-1513104890138-7c749659a591?auto=format&fit=crop&w=800&q=80", popular: false, spicy: false }, { id: 4, businessId: 'gourart', name: "Mojito Frutos Rojos", description: "Ron blanco añejo, mix de frutos del bosque, menta fresca.", price: 350, category: "Bebidas", image: "https://images.unsplash.com/photo-1514362545857-3bc16549766b?auto=format&fit=crop&w=800&q=80", popular: false, spicy: false }, { id: 5, businessId: 'gourart', name: "Cheesecake Maracuyá", description: "Base de galleta crujiente con suave crema de queso y maracuyá.", price: 290, category: "Postres", image: "https://images.unsplash.com/photo-1533134242443-d4fd215305ad?auto=format&fit=crop&w=800&q=80", popular: true, spicy: false }, { id: 6, businessId: 'pizzapop', name: "Nachos Volcánicos", description: "Totopos, cheddar caliente, jalapeños, chili con carne.", price: 550, category: "Entradas", image: "https://images.unsplash.com/photo-1513456852971-30c0b8199d4d?auto=format&fit=crop&w=800&q=80", popular: false, spicy: true }, { id: 7, businessId: 'pizzapop', name: "Pepperoni Lovers", description: "Doble pepperoni americano, extra queso mozzarella.", price: 750, category: "Pizzas", image: "https://images.unsplash.com/photo-1628840042765-356cda07504e?auto=format&fit=crop&w=800&q=80", popular: true, spicy: false }, { id: 8, businessId: 'pizzapop', name: "Hawaiian Sunset", description: "Jamón ahumado, piña fresca, mozzarella.", price: 650, category: "Pizzas", image: "https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?auto=format&fit=crop&w=800&q=80", popular: false, spicy: false } ]; const CATEGORIES = ["Todos", "Hamburguesas", "Tacos", "Pizzas", "Entradas", "Bebidas", "Postres"]; const shuffleArray = (array) => { let currentIndex = array.length, randomIndex; while (currentIndex != 0) { randomIndex = Math.floor(Math.random() * currentIndex); currentIndex--; [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; } return array; }; function App() { // ESTADO DE NAVEGACIÓN (SPA) const [currentView, setCurrentView] = useState('home'); // 'home' | 'business' const [selectedBusinessId, setSelectedBusinessId] = useState(null); const [activeCategory, setActiveCategory] = useState("Todos"); const [cart, setCart] = useState([]); const [animateHeader, setAnimateHeader] = useState(false); const [showItemModal, setShowItemModal] = useState(null); const [showSummaryModal, setShowSummaryModal] = useState(false); const [showInfoModal, setShowInfoModal] = useState(false); const [popAnimation, setPopAnimation] = useState(false); const [shuffledItems, setShuffledItems] = useState([]); // Helper para obtener el hash de forma segura (Parent o Local) const getHash = () => { try { // Intentamos obtener el hash del padre (browser URL) return window.parent.location.hash.replace('#', ''); } catch (e) { // Fallback si falla (ej. bloqueado por cross-origin, aunque en Odoo suele ser same-origin) return window.location.hash.replace('#', ''); } }; // --- 1. MANEJO DE RUTAS (HASH ROUTING FIX) --- useEffect(() => { const handleHashChange = () => { const hash = getHash(); if (BUSINESSES[hash]) { setSelectedBusinessId(hash); setCurrentView('business'); setActiveCategory("Todos"); const root = document.getElementById('root'); if(root) root.scrollTo(0,0); } else { setSelectedBusinessId(null); setCurrentView('home'); setActiveCategory("Todos"); } }; // Mezcla inicial y check de hash setShuffledItems(shuffleArray([...MENU_ITEMS])); handleHashChange(); // Listeners const handleScroll = (e) => setAnimateHeader(e.target.scrollTop > 50); const root = document.getElementById('root'); if(root) root.addEventListener('scroll', handleScroll); // Escuchamos en window.parent para cambios manuales en la URL try { window.parent.addEventListener('hashchange', handleHashChange); } catch(e) { window.addEventListener('hashchange', handleHashChange); } return () => { if(root) root.removeEventListener('scroll', handleScroll); try { window.parent.removeEventListener('hashchange', handleHashChange); } catch(e) { window.removeEventListener('hashchange', handleHashChange); } }; }, []); useEffect(() => { if(cart.length > 0) { setPopAnimation(true); const timer = setTimeout(() => setPopAnimation(false), 300); return () => clearTimeout(timer); } }, [cart]); // --- 2. FUNCIONES DE NAVEGACIÓN ACTUALIZADAS (FIX) --- const goToBusiness = (bizId) => { try { window.parent.location.hash = bizId; } catch (e) { window.location.hash = bizId; } }; const goHome = () => { try { window.parent.location.hash = ''; // Forzamos un poco la limpieza si queda el hash vacío '#' if(window.parent.location.href.endsWith('#')) { history.pushState("", document.title, window.parent.location.pathname + window.parent.location.search); } } catch (e) { window.location.hash = ''; } }; // -------------------------------- const addToCart = (item, e) => { if(e) e.stopPropagation(); setCart(prev => [...prev, item]); }; const removeOneFromCart = (itemId, e) => { if(e) e.stopPropagation(); setCart(prev => { const index = prev.findIndex(i => i.id === itemId); if (index > -1) { const newCart = [...prev]; newCart.splice(index, 1); return newCart; } return prev; }); }; const clearCart = (e) => { if(e) e.stopPropagation(); setCart([]); setShowSummaryModal(false); }; const openSummary = () => { if (cart.length > 0) setShowSummaryModal(true); }; const cartTotal = cart.reduce((acc, item) => acc + item.price, 0); const cartCount = cart.length; // LÓGICA DE VISUALIZACIÓN DE ÍTEMS let displayItems = []; if (currentView === 'home') { // En Home mostramos mezcla (Discovery) displayItems = activeCategory === "Todos" ? shuffledItems : shuffledItems.filter(item => item.category === activeCategory); } else { // En Vista de Negocio mostramos solo sus items const businessItems = MENU_ITEMS.filter(item => item.businessId === selectedBusinessId); displayItems = activeCategory === "Todos" ? businessItems : businessItems.filter(item => item.category === activeCategory); } const currentBusinessData = selectedBusinessId ? BUSINESSES[selectedBusinessId] : null; const glassStyle = { background: 'rgba(255,255,255,0.03)', backdropFilter: 'blur(10px)' }; return ( <div className="text-white font-sans pb-32 relative min-h-full selection:bg-rose-500 selection:text-white" style={{ fontFamily: 'system-ui, sans-serif' }}> {/* Header Dinámico */} <header className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${animateHeader ? 'bg-[#0f0f12]/90 backdrop-blur-xl border-b border-white/5 py-3 shadow-2xl' : 'bg-transparent py-4'}`}> <div className="max-w-5xl mx-auto px-4 flex justify-between items-center"> {/* Logo / Botón Volver */} <div className="flex items-center gap-3"> {currentView === 'business' && ( <button onClick={goHome} className="bg-white/10 p-2 rounded-full hover:bg-white/20 transition-all"> <Icons.ArrowLeft className="w-5 h-5 text-white" /> </button> )} <div> <h1 className="text-xl font-black tracking-tighter text-aurora cursor-pointer" onClick={goHome}> Yesuar✦s<span className="text-[0.5em] align-top text-white opacity-50">®</span> </h1> <p className="text-[10px] text-gray-400 font-medium tracking-widest uppercase opacity-80">Tåblero Digitål™</p> </div> </div> <div className="flex items-center gap-3"> <button onClick={() => setShowInfoModal(true)} className="bg-white/5 p-2 md:p-2.5 rounded-full border border-white/10 text-white hover:bg-white/20 transition-all active:scale-95 shadow-lg" title="Información" > <Icons.Info className="w-5 h-5" /> </button> <div onClick={openSummary} className={`flex items-center gap-2 md:gap-4 bg-white/5 px-3 md:px-4 py-1.5 md:py-2 rounded-full border border-white/10 shadow-lg backdrop-blur-md transition-all active:scale-95 ${cartCount > 0 ? 'cursor-pointer hover:bg-white/10 hover:border-white/30' : 'opacity-50 cursor-default'}`} > <span className="hidden md:block text-gray-400 text-xs font-bold uppercase tracking-widest">Total Estimado</span> <div className="flex items-center gap-1"> <span className="md:hidden text-gray-400 text-[10px] font-bold uppercase mr-1">Total</span> <span className={`text-sm md:text-xl font-black text-white ${popAnimation ? 'animate-number-pop' : ''}`}> RD${cartTotal.toLocaleString()} </span> </div> {cartCount > 0 && ( <button onClick={clearCart} className="bg-white/10 p-1.5 rounded-full hover:bg-red-500/20 hover:text-red-400 transition-colors ml-1 z-20" title="Limpiar"> <Icons.Trash className="w-3 h-3 md:w-4 md:h-4" /> </button> )} </div> </div> </div> </header> {/* VISTA HOME */} {currentView === 'home' && ( <> {/* Hero Home */} <section className="relative pt-24 pb-4 px-4 z-10 text-center md:text-left"> <div className="max-w-5xl mx-auto"> <div className="animate-float"> <h2 className="text-4xl md:text-6xl font-bold leading-tight mb-2 text-white"> Recomendaciones <br/> <span className="text-aurora">Puerto Plata</span> </h2> </div> <p className="text-gray-400 text-sm md:text-base max-w-md mx-auto md:mx-0 mt-4 mb-6"> Descubre los mejores productos de nuestros negocios asociados. </p> {/* Selector de Negocios Rápido */} <div className="flex gap-4 overflow-x-auto pb-4 no-scrollbar"> {Object.values(BUSINESSES).map(biz => ( <div key={biz.id} onClick={() => goToBusiness(biz.id)} className="flex-shrink-0 w-32 bg-white/5 border border-white/10 rounded-2xl p-4 flex flex-col items-center justify-center gap-2 cursor-pointer hover:bg-white/10 hover:border-white/20 transition-all active:scale-95 group"> <div className={`w-12 h-12 rounded-full flex items-center justify-center text-xl font-black ${biz.style} bg-white/10 group-hover:scale-110 transition-transform`}> {biz.icon} </div> <span className="text-xs font-bold text-center">{biz.name}</span> </div> ))} </div> </div> </section> </> )} {/* VISTA PERFIL NEGOCIO */} {currentView === 'business' && currentBusinessData && ( <section className="relative pt-24 pb-8 px-4 z-10"> <div className="max-w-5xl mx-auto"> <div className={`w-full rounded-3xl p-8 mb-6 relative overflow-hidden border border-white/10 bg-gradient-to-br ${currentBusinessData.headerGradient} to-[#0f0f12]`}> <div className="relative z-10 flex flex-col md:flex-row items-center md:items-start gap-6 text-center md:text-left"> <div className={`w-20 h-20 rounded-full flex items-center justify-center text-4xl font-black bg-white/10 backdrop-blur border border-white/20 shadow-xl ${currentBusinessData.style}`}> {currentBusinessData.icon} </div> <div> <div className="flex items-center justify-center md:justify-start gap-2 mb-2"> <h2 className="text-4xl font-black text-white">{currentBusinessData.name}</h2> <Icons.Check className="w-6 h-6 text-blue-400" /> </div> <p className="text-white/80 max-w-lg font-medium">{currentBusinessData.description}</p> </div> </div> </div> </div> </section> )} {/* Nav Categorías (Común) */} <nav className={`sticky z-40 bg-[#0f0f12]/95 backdrop-blur-lg border-b border-white/5 py-4 pl-4 overflow-x-auto no-scrollbar mask-gradient-right ${currentView === 'home' ? 'top-[68px]' : 'top-[68px]'}`}> <div className="flex space-x-3 w-max pr-4"> {CATEGORIES.map((cat) => ( <button key={cat} onClick={() => setActiveCategory(cat)} className={`px-5 py-2 rounded-full text-sm font-bold transition-all duration-300 transform active:scale-95 border ${activeCategory === cat ? 'bg-white text-black border-white shadow-[0_0_15px_rgba(255,255,255,0.4)]' : 'bg-white/5 text-gray-400 border-transparent hover:bg-white/10 hover:text-white'}`}> {cat} </button> ))} </div> </nav> {/* Grid Productos */} <main className="max-w-5xl mx-auto px-4 py-8 z-10 relative"> {displayItems.length === 0 ? ( <div className="text-center py-20 text-gray-500"> <p>No hay productos en esta categoría para este negocio.</p> </div> ) : ( <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {displayItems.map((item, index) => { const qtyInCart = cart.filter(c => c.id === item.id).length; const isSelected = qtyInCart > 0; const business = BUSINESSES[item.businessId]; return ( <div key={item.id} onClick={() => setShowItemModal(item)} className={`group relative rounded-3xl overflow-hidden cursor-pointer animate-fade-in-up card-premium ${isSelected ? 'card-selected' : ''}`} style={{ ...glassStyle, animationDelay: `${index * 50}ms` }} > <div className="h-48 overflow-hidden relative"> <img src={item.image} alt={item.name} className="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700" /> <div className="absolute inset-0 bg-gradient-to-t from-[#0f0f12] via-transparent to-transparent opacity-90"></div> <div className="absolute top-3 left-3 flex gap-2"> {item.popular && <span className="bg-yellow-400/90 text-black text-xs font-bold px-2 py-1 rounded-lg flex items-center gap-1 backdrop-blur-md shadow-lg shadow-yellow-500/20">Top</span>} {item.spicy && <span className="bg-rose-500/90 text-white text-xs font-bold px-2 py-1 rounded-lg flex items-center gap-1 backdrop-blur-md shadow-lg shadow-rose-500/20">Hot</span>} </div> {isSelected && ( <div className="absolute top-3 right-3 bg-gradient-to-r from-rose-500 to-purple-600 text-white font-black w-8 h-8 flex items-center justify-center rounded-full shadow-lg z-20 animate-badge-pop border-2 border-white/20"> {qtyInCart} </div> )} </div> <div className="p-5 relative -mt-10"> {/* HEADER DEL NEGOCIO (Clickable para ir al perfil) */} <div onClick={(e) => { e.stopPropagation(); goToBusiness(business.id); }} className={`absolute top-[-2.5rem] right-4 px-3 py-1 rounded-full border backdrop-blur-md text-[10px] font-bold uppercase tracking-wider flex items-center gap-1.5 shadow-lg cursor-pointer hover:scale-105 transition-transform ${business.badgeBg} ${business.badgeBorder} ${business.style}`} > <Icons.Store className="w-3 h-3" /> {business.name} </div> <h3 className={`text-xl font-bold mb-2 leading-tight transition-colors ${isSelected ? 'text-rose-300' : 'text-white group-hover:text-purple-300'}`}>{item.name}</h3> <div className="flex items-center justify-between mt-auto"> <span className="text-2xl font-black text-white">RD${item.price}</span> <div className="flex items-center gap-2"> {isSelected && ( <button onClick={(e) => removeOneFromCart(item.id, e)} className="bg-[#1a1a1e] border border-white/20 text-white p-3 rounded-xl hover:bg-red-500/20 hover:border-red-500/50 hover:text-red-400 transition-all active:scale-90 shadow-lg" > <Icons.Minus size={20} strokeWidth={3} /> </button> )} <button onClick={(e) => addToCart(item, e)} className={`p-3 rounded-xl transition-all active:scale-90 shadow-lg ${isSelected ? 'bg-rose-500 text-white shadow-rose-500/40' : 'bg-white text-black hover:bg-gradient-to-r hover:from-rose-500 hover:to-purple-500 hover:text-white'}`}> <Icons.Plus size={20} strokeWidth={3} /> </button> </div> </div> </div> </div> ); })} </div> )} </main> {/* MODAL 1: DETALLE DE PRODUCTO */} {showItemModal && ( <div className="fixed inset-0 z-[100] flex items-center justify-center px-4"> <div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={() => setShowItemModal(null)}></div> <div className="relative w-full max-w-md animate-scale-up z-10 card-wrapper-gradient"> <div className="card-content-inner"> <button onClick={() => setShowItemModal(null)} className="absolute top-4 right-4 z-20 bg-black/50 p-2 rounded-full text-white hover:bg-white hover:text-black"><Icons.X className="w-5 h-5" /></button> <div className="h-64 relative"> <img src={showItemModal.image} alt={showItemModal.name} className="w-full h-full object-cover" /> <div className="absolute inset-0 bg-gradient-to-t from-[#1a1a1e] to-transparent"></div> {cart.filter(c => c.id === showItemModal.id).length > 0 && ( <div className="absolute bottom-4 left-6 bg-rose-500/90 backdrop-blur text-white px-3 py-1 rounded-full text-xs font-bold border border-white/20 shadow-xl flex items-center gap-2"> <span className="w-2 h-2 bg-white rounded-full animate-pulse"></span> Ya tienes {cart.filter(c => c.id === showItemModal.id).length} en tu orden </div> )} </div> <div className="p-6 -mt-12 relative flex-1"> {/* HEADER NEGOCIO EN MODAL (Clickable) */} <div onClick={() => { setShowItemModal(null); goToBusiness(showItemModal.businessId); }} className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md text-[10px] font-bold uppercase mb-2 cursor-pointer hover:opacity-80 transition-opacity ${BUSINESSES[showItemModal.businessId].style} ${BUSINESSES[showItemModal.businessId].badgeBg} border ${BUSINESSES[showItemModal.businessId].badgeBorder}`} > <Icons.Store className="w-3 h-3" /> {BUSINESSES[showItemModal.businessId].name} <span className="ml-1 opacity-50">| Visitar Tienda →</span> </div> <h2 className="text-3xl font-bold mb-2 text-white">{showItemModal.name}</h2> <p className="text-gray-400 mb-6 leading-relaxed">{showItemModal.description}</p> <div className="flex items-center justify-between mt-auto"> <div className="text-3xl font-black text-white">RD${showItemModal.price}</div> {cart.filter(c => c.id === showItemModal.id).length > 0 ? ( <div className="flex items-center bg-white/10 rounded-2xl p-1 border border-white/10"> <button onClick={() => removeOneFromCart(showItemModal.id)} className="p-3 hover:bg-white/10 rounded-xl transition-colors text-white"><Icons.Minus size={20} strokeWidth={3}/></button> <span className="font-black text-xl w-12 text-center text-white">{cart.filter(c => c.id === showItemModal.id).length}</span> <button onClick={() => addToCart(showItemModal)} className="p-3 bg-white text-black rounded-xl hover:bg-rose-400 transition-colors"><Icons.Plus size={20} strokeWidth={3}/></button> </div> ) : ( <button onClick={() => { addToCart(showItemModal); }} className="text-white px-8 py-3 rounded-2xl font-bold hover:shadow-lg transition-all active:scale-95 bg-gradient-to-r from-rose-500 to-purple-600 hover:shadow-purple-500/50">Agregar al Total</button> )} </div> </div> </div> </div> </div> )} {/* MODAL 2: RESUMEN DE CUENTA */} {showSummaryModal && ( <div className="fixed inset-0 z-[200] flex items-center justify-center px-4"> <div className="absolute inset-0 bg-black/90 backdrop-blur-md transition-opacity" onClick={() => setShowSummaryModal(false)}></div> <div className="relative w-full max-w-md animate-scale-up z-10 card-wrapper-gradient max-h-[85vh] flex flex-col"> <div className="card-content-inner p-6 h-full"> <div className="flex justify-between items-center mb-4 border-b border-white/10 pb-4 shrink-0"> <h2 className="text-2xl font-bold text-aurora">Resumen de Consumo</h2> <button onClick={() => setShowSummaryModal(false)} className="bg-white/10 p-2 rounded-full hover:bg-white/20"><Icons.X className="w-5 h-5" /></button> </div> <div className="flex-1 overflow-y-auto min-h-0 space-y-3 mb-4 pr-2 custom-scrollbar"> {cart.map((item, idx) => ( <div key={idx} className="flex justify-between items-center bg-white/5 p-3 rounded-xl border border-white/5 shrink-0"> <div className="flex items-center gap-3"> <div className="w-2 h-2 rounded-full bg-rose-500"></div> <div> <span className="font-medium text-sm block">{item.name}</span> <span className="text-[10px] text-gray-400">{BUSINESSES[item.businessId].name}</span> </div> </div> <span className="font-bold text-rose-400">RD${item.price}</span> </div> ))} </div> <div className="shrink-0 space-y-4 pt-2 border-t border-white/10"> <div className="space-y-1"> <div className="flex justify-between text-gray-400 text-sm"> <span>Subtotal</span> <span>RD${cartTotal.toLocaleString()}</span> </div> <div className="flex justify-between text-xl font-black text-white"> <span>Total Estimado</span> <span>RD${cartTotal.toLocaleString()}</span> </div> </div> <div className="bg-yellow-500/10 border border-yellow-500/20 p-3 rounded-xl flex gap-3 items-start"> <Icons.Info className="w-4 h-4 text-yellow-500 flex-shrink-0 mt-0.5" /> <div className="space-y-1"> <p className="text-[11px] text-yellow-200/90 font-medium leading-relaxed"> <strong className="text-yellow-400 block mb-0.5">Nota Importante:</strong> Los precios mostrados incluyen ITBIS (18%). La Propina Legal (10%) será agregada en su factura final según la ley de RD. </p> <p className="text-[9px] text-gray-400 uppercase tracking-widest font-bold pt-1"> Tiempo espera aprox: 15-20 min </p> </div> </div> <button onClick={() => setShowSummaryModal(false)} className="w-full bg-white/10 hover:bg-white/20 py-3 rounded-xl font-bold text-sm transition-colors">Seguir Viendo</button> </div> </div> </div> </div> )} {/* MODAL 3: INFORMACIÓN CORPORATIVA */} {showInfoModal && ( <div className="fixed inset-0 z-[200] flex items-center justify-center px-4"> <div className="absolute inset-0 bg-black/90 backdrop-blur-md transition-opacity" onClick={() => setShowInfoModal(false)}></div> <div className="relative w-full max-w-md animate-scale-up z-10 card-wrapper-gradient max-h-[85vh] flex flex-col"> <div className="card-content-inner p-6 h-full overflow-y-auto custom-scrollbar"> <div className="flex justify-between items-start mb-6"> <div> <h2 className="text-2xl font-black text-aurora mb-1">Yesuars S.R.L.</h2> <p className="text-xs text-gray-400 font-medium tracking-wide">Innovación Digital • Est. 2025</p> </div> <button onClick={() => setShowInfoModal(false)} className="bg-white/10 p-2 rounded-full hover:bg-white/20"><Icons.X className="w-5 h-5" /></button> </div> <div className="space-y-6"> <div className="text-sm text-gray-300 leading-relaxed space-y-2"> <p>Fundada por <strong>Yesuar Torres</strong> y <strong>Heidy Ruiz</strong>, líderes en soluciones tecnológicas premium en el Caribe.</p> <div className="flex flex-wrap gap-2 pt-1"> {['Tåblero Digitål™', 'Validåte RD℠', 'Magåzine™'].map(tag => ( <span key={tag} className="bg-white/5 border border-white/10 px-2 py-1 rounded-md text-[10px] uppercase font-bold text-gray-400">{tag}</span> ))} </div> </div> <div className="relative rounded-2xl overflow-hidden border border-white/10 group h-40"> <img src="https://images.unsplash.com/photo-1524661135-423995f22d0b?auto=format&fit=crop&w=800&q=80" className="w-full h-full object-cover opacity-60 group-hover:opacity-80 transition-opacity" alt="Mapa" /> <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent flex flex-col justify-end p-4"> <div className="flex items-center gap-2 text-white mb-1"> <Icons.MapPin className="w-4 h-4 text-rose-400" /> <span className="font-bold text-sm">Puerto Plata, Rep. Dom.</span> </div> <button className="bg-white text-black text-xs font-bold py-2 px-4 rounded-lg w-full mt-2 hover:bg-rose-100 transition-colors flex items-center justify-center gap-2"> Abrir en Google Maps <Icons.Check className="w-3 h-3"/> </button> </div> </div> <div className="grid grid-cols-2 gap-3"> <a href="#" className="bg-white/5 border border-white/10 p-3 rounded-xl hover:bg-white/10 transition-colors flex flex-col items-center gap-2 text-center"> <div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center text-green-400"><Icons.Phone className="w-4 h-4" /></div> <span className="text-xs font-bold text-gray-300">Llamar Ahora</span> </a> <a href="#" className="bg-white/5 border border-white/10 p-3 rounded-xl hover:bg-white/10 transition-colors flex flex-col items-center gap-2 text-center"> <div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400"><Icons.Mail className="w-4 h-4" /></div> <span className="text-xs font-bold text-gray-300">Email</span> </a> </div> <div className="pt-2"> <p className="text-[10px] text-gray-500 uppercase font-bold tracking-widest text-center mb-3">Pide por Delivery</p> <div className="grid grid-cols-3 gap-3"> <button className="bg-[#ea0029]/10 border border-[#ea0029]/30 p-2 rounded-xl hover:bg-[#ea0029]/20 transition-all active:scale-95 flex flex-col items-center justify-center gap-1 group"> <div className="text-[#ea0029] group-hover:scale-110 transition-transform"><Icons.Bike className="w-6 h-6" /></div> <span className="text-[10px] font-bold text-[#ea0029]">PedidosYa</span> </button> <button className="bg-[#00a650]/10 border border-[#00a650]/30 p-2 rounded-xl hover:bg-[#00a650]/20 transition-all active:scale-95 flex flex-col items-center justify-center gap-1 group"> <div className="text-[#00a650] group-hover:scale-110 transition-transform"><Icons.Bike className="w-6 h-6" /></div> <span className="text-[10px] font-bold text-[#00a650]">Komida</span> </button> <button className="bg-[#1877f2]/10 border border-[#1877f2]/30 p-2 rounded-xl hover:bg-[#1877f2]/20 transition-all active:scale-95 flex flex-col items-center justify-center gap-1 group"> <div className="text-[#1877f2] group-hover:scale-110 transition-transform"><Icons.Facebook className="w-6 h-6" /></div> <span className="text-[10px] font-bold text-[#1877f2]">Facebook</span> </button> </div> </div> <div className="pt-2 border-t border-white/10"> <p className="text-[10px] text-gray-500 uppercase font-bold tracking-widest text-center mb-4">Síguenos en Redes</p> <div className="flex justify-center gap-4"> <button className="w-10 h-10 rounded-full bg-gradient-to-tr from-yellow-500 via-red-500 to-purple-600 p-[1px]"> <div className="w-full h-full bg-[#1a1a1e] rounded-full flex items-center justify-center hover:bg-transparent transition-colors group"> <Icons.Instagram className="w-5 h-5 text-white group-hover:scale-110 transition-transform" /> </div> </button> </div> </div> </div> </div> </div> </div> )} </div> ); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />); </script> </body> </html>