brincando2 / index.html
adriano2005's picture
Update index.html
2e23536 verified
Raw
History Blame Contribute Delete
14.5 kB
<!DOCTYPE html>
<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>