Spaces:
Running
Running
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ACME Store - Elite SPA</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap" rel="stylesheet"> | |
| <style> | |
| body { font-family: 'Inter', sans-serif; } | |
| /* Ocultar scrollbar para o side-cart mantendo funcionalidade */ | |
| .no-scrollbar::-webkit-scrollbar { display: none; } | |
| .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } | |
| </style> | |
| <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> | |
| <script crossorigin 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> | |
| </head> | |
| <body class="bg-gray-50 text-gray-900 antialiased overflow-x-hidden"> | |
| <div id="root"></div> | |
| <script type="text/babel" data-presets="react"> | |
| const { useState, useMemo } = React; | |
| // 1. DATA MOCK: Banco de Dados Simulado | |
| const mockProducts = [ | |
| { id: "1", name: "Tênis Nike Pro 2026", price: 599.90, category: "Calçados", description: "Design aerodinâmico com amortecimento de grafeno. Feito para a performance extrema.", imageUrl: "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=800&q=80" }, | |
| { id: "2", name: "Jaqueta Techwear Elite", price: 899.00, category: "Vestuário", description: "Tecido impermeável inteligente com controle térmico integrado. O futuro do streetwear.", imageUrl: "https://images.unsplash.com/photo-1551028719-00167b16eac5?w=800&q=80" }, | |
| { id: "3", name: "Mochila Anti-Roubo v2", price: 349.50, category: "Acessórios", description: "Proteção RFID, zíperes ocultos e porta de carregamento USB-C ultra-rápida.", imageUrl: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=800&q=80" }, | |
| { id: "4", name: "Smartwatch Ultra AI", price: 1299.99, category: "Eletrônicos", description: "Monitoramento biométrico com IA preditiva. Bateria de 14 dias em titanium aeroespacial.", imageUrl: "https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&q=80" } | |
| ]; | |
| // Ícones SVG reutilizáveis | |
| const Icons = { | |
| Cart: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>, | |
| Support: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>, | |
| Close: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>, | |
| ArrowLeft: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> | |
| }; | |
| // 2. COMPONENTE PRINCIPAL (Engine da SPA) | |
| function App() { | |
| // 🎯 [DECISÃO ARQUITETURAL]: Estado Centralizado | |
| const [cartItems, setCartItems] = useState([]); | |
| const [currentView, setCurrentView] = useState('home'); // 'home' | 'product' | |
| const [selectedProduct, setSelectedProduct] = useState(null); | |
| const [isCartOpen, setIsCartOpen] = useState(false); | |
| const [isSupportOpen, setIsSupportOpen] = useState(false); | |
| // Funções de Carrinho | |
| const addToCart = (product) => { | |
| setCartItems(prev => { | |
| const exists = prev.find(i => i.id === product.id); | |
| if (exists) return prev.map(i => i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i); | |
| return [...prev, { ...product, quantity: 1 }]; | |
| }); | |
| setIsCartOpen(true); // Abre o carrinho automaticamente para dar feedback visual | |
| }; | |
| const removeFromCart = (productId) => { | |
| setCartItems(prev => prev.filter(i => i.id !== productId)); | |
| }; | |
| const totalCartValue = useMemo(() => cartItems.reduce((acc, item) => acc + (item.price * item.quantity), 0), [cartItems]); | |
| const totalItemsCount = useMemo(() => cartItems.reduce((acc, item) => acc + item.quantity, 0), [cartItems]); | |
| // Navegação | |
| const openProduct = (product) => { | |
| setSelectedProduct(product); | |
| setCurrentView('product'); | |
| window.scrollTo(0, 0); | |
| }; | |
| const goHome = () => { | |
| setCurrentView('home'); | |
| setSelectedProduct(null); | |
| }; | |
| return ( | |
| <div className="min-h-screen flex flex-col"> | |
| {/* HEADER FIXO */} | |
| <header className="sticky top-0 z-40 w-full backdrop-blur-lg bg-white/80 border-b border-gray-200"> | |
| <div className="max-w-7xl mx-auto px-4 md:px-8 py-4 flex justify-between items-center"> | |
| <h1 onClick={goHome} className="text-2xl font-black tracking-tighter cursor-pointer hover:opacity-70 transition"> | |
| ACME. | |
| </h1> | |
| <div className="flex items-center gap-4"> | |
| <button onClick={() => setIsSupportOpen(true)} className="p-2 text-gray-600 hover:text-black hover:bg-gray-100 rounded-full transition"> | |
| <Icons.Support /> | |
| </button> | |
| <button onClick={() => setIsCartOpen(true)} className="flex items-center gap-2 bg-black text-white px-5 py-2 rounded-full font-medium hover:scale-105 transition-transform"> | |
| <Icons.Cart /> | |
| <span>{totalItemsCount}</span> | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| {/* ÁREA DE CONTEÚDO DINÂMICO (ROTEAMENTO) */} | |
| <main className="flex-grow w-full max-w-7xl mx-auto px-4 md:px-8 py-8"> | |
| {currentView === 'home' && ( | |
| <HomeView products={mockProducts} onSelectProduct={openProduct} /> | |
| )} | |
| {currentView === 'product' && selectedProduct && ( | |
| <ProductView product={selectedProduct} onBack={goHome} onAdd={() => addToCart(selectedProduct)} /> | |
| )} | |
| </main> | |
| {/* MODAL DO CARRINHO (OFFCANVAS DIREITO) */} | |
| {isCartOpen && ( | |
| <div className="fixed inset-0 z-50 flex justify-end"> | |
| <div className="fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity" onClick={() => setIsCartOpen(false)}></div> | |
| <div className="relative w-full max-w-md bg-white h-full shadow-2xl flex flex-col animate-[slideIn_0.3s_ease-out]"> | |
| <div className="flex items-center justify-between p-6 border-b"> | |
| <h2 className="text-xl font-bold">Seu Carrinho</h2> | |
| <button onClick={() => setIsCartOpen(false)} className="p-2 hover:bg-gray-100 rounded-full"><Icons.Close /></button> | |
| </div> | |
| <div className="flex-1 overflow-y-auto p-6 no-scrollbar"> | |
| {cartItems.length === 0 ? ( | |
| <div className="flex flex-col items-center justify-center h-full text-gray-500"> | |
| <Icons.Cart /> | |
| <p className="mt-4 font-medium">Seu carrinho está vazio.</p> | |
| </div> | |
| ) : ( | |
| <ul className="space-y-6"> | |
| {cartItems.map(item => ( | |
| <li key={item.id} className="flex items-center gap-4"> | |
| <img src={item.imageUrl} alt={item.name} className="w-20 h-20 object-cover rounded-lg" /> | |
| <div className="flex-1"> | |
| <h4 className="font-semibold text-sm">{item.name}</h4> | |
| <p className="text-gray-500 text-sm">Qtd: {item.quantity}</p> | |
| <p className="font-bold mt-1">R$ {(item.price * item.quantity).toFixed(2)}</p> | |
| </div> | |
| <button onClick={() => removeFromCart(item.id)} className="text-red-500 text-sm font-medium hover:underline">Remover</button> | |
| </li> | |
| ))} | |
| </ul> | |
| )} | |
| </div> | |
| <div className="p-6 border-t bg-gray-50"> | |
| <div className="flex justify-between font-bold text-xl mb-4"> | |
| <span>Total</span> | |
| <span>R$ {totalCartValue.toFixed(2)}</span> | |
| </div> | |
| <button disabled={cartItems.length === 0} className="w-full bg-black text-white py-4 rounded-xl font-bold hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition"> | |
| Finalizar Compra | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| {/* MODAL DE SUPORTE */} | |
| {isSupportOpen && ( | |
| <div className="fixed inset-0 z-50 flex items-center justify-center p-4"> | |
| <div className="fixed inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setIsSupportOpen(false)}></div> | |
| <div className="relative w-full max-w-lg bg-white rounded-2xl shadow-2xl p-8 animate-[fadeIn_0.2s_ease-out]"> | |
| <button onClick={() => setIsSupportOpen(false)} className="absolute top-4 right-4 p-2 hover:bg-gray-100 rounded-full"><Icons.Close /></button> | |
| <h2 className="text-2xl font-bold mb-2">Central de Suporte</h2> | |
| <p className="text-gray-500 mb-6">Como podemos ajudar você hoje?</p> | |
| <textarea className="w-full border border-gray-300 rounded-xl p-4 min-h-[150px] focus:ring-2 focus:ring-black focus:outline-none mb-4" placeholder="Descreva seu problema..."></textarea> | |
| <button className="w-full bg-black text-white py-3 rounded-xl font-bold hover:bg-gray-800 transition" onClick={() => { alert('Mensagem enviada com sucesso!'); setIsSupportOpen(false); }}> | |
| Enviar Mensagem | |
| </button> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| // --- SUB-COMPONENTES --- | |
| // VIEW: HOME (Vitrine) | |
| function HomeView({ products, onSelectProduct }) { | |
| return ( | |
| <div> | |
| <div className="mb-12"> | |
| <span className="text-sm font-bold tracking-widest uppercase text-gray-500">Coleção 2026</span> | |
| <h2 className="text-4xl md:text-5xl font-black mt-2">Destaques da Semana.</h2> | |
| </div> | |
| <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8"> | |
| {products.map(product => ( | |
| <article key={product.id} onClick={() => onSelectProduct(product)} className="group cursor-pointer flex flex-col"> | |
| <div className="relative aspect-[4/5] overflow-hidden rounded-2xl bg-gray-100 mb-4"> | |
| <img src={product.imageUrl} alt={product.name} className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-500" /> | |
| </div> | |
| <span className="text-xs text-gray-500 font-medium mb-1">{product.category}</span> | |
| <h3 className="font-bold text-lg leading-tight group-hover:underline">{product.name}</h3> | |
| <p className="mt-1 text-gray-900 font-medium">R$ {product.price.toFixed(2)}</p> | |
| </article> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // VIEW: DETALHES DO PRODUTO | |
| function ProductView({ product, onBack, onAdd }) { | |
| return ( | |
| <div className="animate-[fadeIn_0.3s_ease-out]"> | |
| <button onClick={onBack} className="flex items-center gap-2 text-gray-600 hover:text-black mb-8 font-medium transition"> | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> | |
| Voltar para vitrine | |
| </button> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-12 lg:gap-24 items-start"> | |
| <div className="relative aspect-square md:aspect-[4/5] rounded-3xl overflow-hidden bg-gray-100"> | |
| <img src={product.imageUrl} alt={product.name} className="object-cover w-full h-full" /> | |
| </div> | |
| <div className="flex flex-col pt-4 md:pt-12"> | |
| <span className="text-sm font-bold tracking-widest uppercase text-gray-500 mb-2">{product.category}</span> | |
| <h1 className="text-4xl md:text-5xl font-black tracking-tight mb-4">{product.name}</h1> | |
| <p className="text-3xl font-light text-gray-900 mb-8">R$ {product.price.toFixed(2)}</p> | |
| <p className="text-gray-600 text-lg leading-relaxed mb-12"> | |
| {product.description} | |
| </p> | |
| <button onClick={onAdd} className="w-full md:w-auto bg-black text-white px-8 py-5 rounded-full font-bold text-lg hover:scale-[1.02] hover:bg-gray-900 active:scale-95 transition-all shadow-2xl shadow-black/20"> | |
| Adicionar ao Carrinho | |
| </button> | |
| <div className="mt-12 pt-8 border-t border-gray-200 grid grid-cols-2 gap-6 text-sm"> | |
| <div> | |
| <span className="block font-bold mb-1">Garantia</span> | |
| <span className="text-gray-600">12 meses de cobertura total.</span> | |
| </div> | |
| <div> | |
| <span className="block font-bold mb-1">Entrega</span> | |
| <span className="text-gray-600">Frete expresso em até 2 dias úteis.</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // Custom Animations | |
| const style = document.createElement('style'); | |
| style.innerHTML = ` | |
| @keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } | |
| `; | |
| document.head.appendChild(style); | |
| // INICIALIZAÇÃO | |
| const root = ReactDOM.createRoot(document.getElementById('root')); | |
| root.render(<App />); | |
| </script> | |
| </body> | |
| </html> |