+ 9 Linhas alteradas: 9 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,9 @@ # GEMINI_API_KEY: Required for Gemini AI API calls. # AI Studio automatically injects this at runtime from user secrets. # Users configure this via the Secrets panel in the AI Studio UI. GEMINI_API_KEY="MY_GEMINI_API_KEY" # APP_URL: The URL where this applet is hosted. # AI Studio automatically injects this at runtime with the Cloud Run service URL. # Used for self-referential links, OAuth callbacks, and API endpoints. APP_URL="MY_APP_URL" ‎.gitignore‎ + 8 Linhas alteradas: 8 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,8 @@ node_modules/ build/ dist/ coverage/ .DS_Store *.log .env* !.env.example ‎README.md‎ + 14 - 5 Linhas alteradas: 14 adições e 5 exclusões. Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -1,11 +1,20 @@
GHBanner

Built with AI Studio

# Run and deploy your AI Studio app

The fastest path from prompt to production with Gemini.

This contains everything you need to run your app locally. Start building View your app in AI Studio: https://ai.studio/apps/acdd4626-9cbe-4478-a4aa-e2088eb9d4d2 ## Run Locally **Prerequisites:** Node.js 1. Install dependencies: `npm install` 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key 3. Run the app: `npm run dev` ‎firebase-applet-config.json‎ + 10 Linhas alteradas: 10 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,10 @@ { "projectId": "gen-lang-client-0982718841", "appId": "1:653624667754:web:82ac310ef27c543f0ba413", "apiKey": "AIzaSyAU5dM3t8M8S3PL875heLgvtIZheFVz2uo", "authDomain": "gen-lang-client-0982718841.firebaseapp.com", "firestoreDatabaseId": "ai-studio-acdd4626-9cbe-4478-a4aa-e2088eb9d4d2", "storageBucket": "gen-lang-client-0982718841.firebasestorage.app", "messagingSenderId": "653624667754", "measurementId": "" } ‎firebase-blueprint.json‎ + 76 Linhas alteradas: 76 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,76 @@ { "entities": { "Product": { "title": "Product", "description": "A box/product available for sale", "type": "object", "properties": { "name": { "type": "string", "description": "Name of the box" }, "description": { "type": "string", "description": "Short description" }, "price": { "type": "number", "description": "Price in BRL" }, "stock": { "type": "integer", "description": "Available quantity" }, "imageUrl": { "type": "string", "description": "URL of the product image" }, "createdAt": { "type": "string", "format": "date-time" } }, "required": ["name", "price", "stock", "imageUrl"] }, "Order": { "title": "Order", "description": "A customer order", "type": "object", "properties": { "userId": { "type": "string" }, "items": { "type": "array" }, "total": { "type": "number" }, "status": { "type": "string", "enum": ["pending", "paid", "shipped", "cancelled"] }, "paymentMethod": { "type": "string" }, "customerInfo": { "type": "object" }, "createdAt": { "type": "string", "format": "date-time" } }, "required": ["userId", "items", "total", "status"] }, "User": { "title": "User", "description": "User profile and role", "type": "object", "properties": { "email": { "type": "string" }, "role": { "type": "string", "enum": ["admin", "customer"] }, "createdAt": { "type": "string", "format": "date-time" } }, "required": ["email", "role"] }, "FeaturedItem": { "title": "Featured Item", "description": "An image/item to be displayed in the featured section", "type": "object", "properties": { "title": { "type": "string", "description": "Title of the highlight" }, "imageUrl": { "type": "string", "description": "URL of the image" }, "category": { "type": "string", "enum": ["enel", "sabesp"], "description": "Category of the highlight" }, "createdAt": { "type": "string", "format": "date-time" } }, "required": ["imageUrl", "category"] }, "ProjectPrice": { "title": "Project Price", "description": "Pricing for electrical projects", "type": "object", "properties": { "name": { "type": "string", "description": "Name of the project type" }, "basePrice": { "type": "number", "description": "Price per unit" }, "unitName": { "type": "string", "description": "Unit (e.g., m², point)" }, "description": { "type": "string", "description": "Details about the project" }, "createdAt": { "type": "string", "format": "date-time" } }, "required": ["name", "basePrice", "unitName"] } }, "firestore": { "products": { "schema": "Product", "description": "List of boxes for sale" }, "orders": { "schema": "Order", "description": "Customer purchase history" }, "users": { "schema": "User", "description": "User roles and metadata" }, "featured": { "schema": "FeaturedItem", "description": "Highlights for the home page" }, "project_prices": { "schema": "ProjectPrice", "description": "Pricing table for electrical projects" } } } ‎firestore.rules‎ + 67 Linhas alteradas: 67 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,67 @@ rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // =============================================================== // Assumed Data Model // =============================================================== // Collection: products // Fields: name (string), description (string), price (number), stock (number), imageUrl (string), createdAt (timestamp) // // Collection: orders // Fields: userId (string), items (array), total (number), status (string), paymentMethod (string), customerInfo (map), createdAt (timestamp) // // Collection: users // Fields: email (string), role (string), createdAt (timestamp) // // Collection: project_prices // Fields: name (string), basePrice (number), unitName (string), description (string), createdAt (timestamp) // =============================================================== // Helper Functions function isAuthenticated() { return request.auth != null; } function isAdmin() { return isAuthenticated() && request.auth.token.email == "amewengenhariaeletrica@gmail.com" && request.auth.token.email_verified == true; } function isOwner(userId) { return isAuthenticated() && request.auth.uid == userId; } // Products: Everyone can read, only admins can write match /products/{productId} { allow read: if true; allow write: if isAdmin(); } // Orders: Users can read/write their own, admins can read/write all match /orders/{orderId} { allow read: if isAdmin() || (isAuthenticated() && resource.data.userId == request.auth.uid); allow create: if isAuthenticated(); allow update, delete: if isAdmin(); } // Users: Users can read/write their own, admins can read/write all match /users/{userId} { allow read: if isAdmin() || isOwner(userId); allow create: if isAuthenticated() && isOwner(userId); allow update: if isAdmin() || (isOwner(userId) && request.resource.data.role == resource.data.role); } // Featured Items: Everyone can read, only admins can write match /featured/{itemId} { allow read: if true; allow write: if isAdmin(); } // Project Prices: Everyone can read, only admins can write match /project_prices/{priceId} { allow read: if true; allow write: if isAdmin(); } } } ‎index.html‎ + 13 Linhas alteradas: 13 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,13 @@ My Google AI Studio App
‎metadata.json‎ + 5 Linhas alteradas: 5 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,5 @@ { "name": "A.M.E.W. Luz - Caixas Padrão", "description": "Venda de caixas de luz padrão Enel e caixas Sabesp com qualidade e segurança.", "requestFramePermissions": [] } ‎package-lock.json‎ + 5,833 Linhas alteradas: 5833 adições e 0 exclusões Alguns arquivos gerados não são renderizados por padrão. Saiba mais sobre como personalizar a aparência dos arquivos alterados no GitHub. ‎package.json‎ + 40 Linhas alteradas: 40 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,40 @@ { "name": "react-example", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite --port=3000 --host=0.0.0.0", "build": "vite build", "preview": "vite preview", "clean": "rm -rf dist", "lint": "tsc --noEmit" }, "dependencies": { "@google/genai": "^1.29.0", "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", "clsx": "^2.1.1", "dotenv": "^17.2.3", "express": "^4.21.2", "firebase": "^12.10.0", "lucide-react": "^0.546.0", "motion": "^12.36.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-firebase-hooks": "^5.1.1", "react-hot-toast": "^2.6.0", "react-router-dom": "^7.13.1", "tailwind-merge": "^3.5.0", "vite": "^6.2.0" }, "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^22.14.0", "autoprefixer": "^10.4.21", "tailwindcss": "^4.1.14", "tsx": "^4.21.0", "typescript": "~5.8.2", "vite": "^6.2.0" } } ‎src/App.tsx‎ + 73 Linhas alteradas: 73 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,73 @@ import React from 'react'; import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom'; import { Toaster } from 'react-hot-toast'; import { CartProvider } from './context/CartContext'; import { Navbar } from './components/Navbar'; import { WhatsAppWidget } from './components/WhatsAppWidget'; import { Home } from './pages/Home'; import { Products } from './pages/Products'; import { Cart } from './pages/Cart'; import { Checkout } from './pages/Checkout'; import { Contact } from './pages/Contact'; import { Admin } from './pages/Admin'; import { Login } from './pages/Login'; export default function App() { return (
} /> } /> } /> } /> } /> } /> } />
AMEW
A.M.E.W. LUZ

Especialistas em soluções elétricas e hidráulicas. Caixas de luz, hidrômetros e projetos de engenharia com conformidade técnica total.

Navegação

Contato

  • São Paulo - SP
  • (11) 97187-8021
  • amewengenhariaeletrica@gmail.com

© 2026 AMEW Engenharia. Todos os direitos reservados.

); } ‎src/components/Navbar.tsx‎ + 70 Linhas alteradas: 70 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,70 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { ShoppingCart, Package, Phone, User, Home } from 'lucide-react'; import { useCart } from '../context/CartContext'; import { auth } from '../firebase'; import { useAuthState } from 'react-firebase-hooks/auth'; export const Navbar: React.FC = () => { const { cart } = useCart(); const [user] = useAuthState(auth); const cartCount = cart.reduce((sum, item) => sum + item.quantity, 0); return ( ); }; ‎src/components/ProductCard.tsx‎ + 53 Linhas alteradas: 53 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,53 @@ import React from 'react'; import { motion } from 'motion/react'; import { ShoppingCart, Plus, Minus } from 'lucide-react'; import { Product } from '../types'; import { useCart } from '../context/CartContext'; interface ProductCardProps { product: Product; } export const ProductCard: React.FC = ({ product }) => { const { addToCart } = useCart(); return (
{product.name} {product.stock <= 0 && (
Esgotado
)}

{product.name}

R$ {product.price.toFixed(2)}

{product.description}

); }; ‎src/components/ProjectCalculator.tsx‎ + 154 Linhas alteradas: 154 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,154 @@ import React, { useState, useEffect } from 'react'; import { collection, getDocs, query, orderBy } from 'firebase/firestore'; import { db } from '../firebase'; import { ProjectPrice } from '../types'; import { Calculator, ArrowRight, MessageCircle, Sparkles } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; export const ProjectCalculator: React.FC = () => { const [prices, setPrices] = useState([]); const [selectedProject, setSelectedProject] = useState(null); const [quantity, setQuantity] = useState(1); const [loading, setLoading] = useState(true); useEffect(() => { const fetchPrices = async () => { try { const q = query(collection(db, 'project_prices'), orderBy('createdAt', 'desc')); const snapshot = await getDocs(q); const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as ProjectPrice)); setPrices(data); if (data.length > 0) setSelectedProject(data[0]); } catch (error) { console.error('Error fetching project prices:', error); } finally { setLoading(false); } }; fetchPrices(); }, []); const total = selectedProject ? selectedProject.basePrice * quantity : 0; const handleWhatsApp = () => { if (!selectedProject) return; const message = `Olá! Gostaria de um orçamento para: Projeto: ${selectedProject.name} Quantidade: ${quantity} ${selectedProject.unitName} Valor Estimado: R$ ${total.toFixed(2)}`; window.open(`https://wa.me/5511971878021?text=${encodeURIComponent(message)}`, '_blank'); }; if (loading) return null; if (prices.length === 0) return null; return (
Orçamento Instantâneo

Calcule seu Projeto Elétrico

Selecione o tipo de projeto e informe a metragem ou quantidade de pontos para receber uma estimativa automática de valores.

Transparência Total

Valores baseados em nossa tabela atualizada.

Suporte via WhatsApp

Tire suas dúvidas técnicas em tempo real.

{prices.map((p) => ( ))}
{selectedProject && (
setQuantity(Math.max(1, parseInt(e.target.value) || 0))} className="w-full px-6 py-4 rounded-2xl bg-zinc-50 border-none focus:ring-2 focus:ring-emerald-500 outline-none font-bold text-xl" />

Total Estimado

R$ {total.toFixed(2)}

)}
); }; ‎src/components/WhatsAppWidget.tsx‎ + 70 Linhas alteradas: 70 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,70 @@ import React, { useState } from 'react'; import { MessageCircle, Send, X } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; export const WhatsAppWidget: React.FC = () => { const [isOpen, setIsOpen] = useState(false); const [message, setMessage] = useState(''); const phoneNumber = '5511971878021'; const handleSendMessage = (e: React.FormEvent) => { e.preventDefault(); const encodedMessage = encodeURIComponent(message || 'Olá! Gostaria de saber mais sobre os produtos da AMEW.'); window.open(`https://wa.me/${phoneNumber}?text=${encodedMessage}`, '_blank'); setIsOpen(false); setMessage(''); }; return (
{isOpen && (

Atendimento AMEW

Online agora

Olá! Como podemos ajudar você hoje?

); }; ‎src/pages/Home.tsx‎ + 296 Linhas alteradas: 296 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,296 @@ import React, { useEffect, useState } from 'react'; import { collection, query, limit, getDocs } from 'firebase/firestore'; import { db } from '../firebase'; import { Product, FeaturedItem } from '../types'; import { ProductCard } from '../components/ProductCard'; import { ProjectCalculator } from '../components/ProjectCalculator'; import { motion, AnimatePresence } from 'motion/react'; import { ArrowRight, Package, ShieldCheck, Truck, ChevronLeft, ChevronRight, Calculator, Sparkles } from 'lucide-react'; import { Link } from 'react-router-dom'; export const Home: React.FC = () => { const [featuredProducts, setFeaturedProducts] = useState([]); const [featuredItems, setFeaturedItems] = useState([]); const [loading, setLoading] = useState(true); const [currentSlide, setCurrentSlide] = useState(0); useEffect(() => { const fetchData = async () => { try { // Fetch Featured Products const qProducts = query(collection(db, 'products'), limit(8)); const pSnapshot = await getDocs(qProducts); let products = pSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as Product)); if (products.length === 0) { products = [ { id: 'enel-8', name: 'Caixa de Luz 8 Medidores', description: 'Visor lateral completa, policarbonato resistente. Entregamos ligado!', price: 2850.00, stock: 5, imageUrl: 'https://images.unsplash.com/photo-1558444479-c84829091c22?auto=format&fit=crop&q=80&w=800', createdAt: new Date() }, { id: 'sabesp-std', name: 'Caixa Sabesp Padrão', description: 'Economia e segurança para sua conta de água. Padrão homologado.', price: 195.00, stock: 25, imageUrl: 'https://images.unsplash.com/photo-1584622650111-993a426fbf0a?auto=format&fit=crop&q=80&w=800', createdAt: new Date() } ]; } setFeaturedProducts(products); // Fetch Featured Items (Highlights) const qFeatured = query(collection(db, 'featured'), limit(10)); const fSnapshot = await getDocs(qFeatured); const items = fSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as FeaturedItem)); setFeaturedItems(items); } catch (error) { console.error('Error fetching data:', error); } finally { setLoading(false); } }; fetchData(); }, []); const nextSlide = () => { setCurrentSlide((prev) => (prev + 1) % featuredItems.length); }; const prevSlide = () => { setCurrentSlide((prev) => (prev - 1 + featuredItems.length) % featuredItems.length); }; const scrollToCalculator = () => { const element = document.getElementById('orcamento'); element?.scrollIntoView({ behavior: 'smooth' }); }; return (
{/* Hero Section */}
Caixa de Luz Padrão

AMEWLUZ

Especialistas em Caixas de Luz e Hidrômetros. Qualidade, segurança e conformidade técnica para sua instalação.

Ver Coleção
{/* Features */}
{[ { icon: Truck, title: "Entrega Rápida", desc: "Enviamos nossos produtos para toda SP com agilidade e segurança." }, { icon: ShieldCheck, title: "Padrão Homologado", desc: "Produtos que atendem 100% às normas técnicas exigidas pelas concessionárias." }, { icon: Package, title: "Pronta Entrega", desc: "Amplo estoque de caixas de luz e hidrômetros para sua obra." } ].map((f, i) => (

{f.title}

{f.desc}

))}
{/* Quick Access to Projects */}
{/* Project Calculator Section */} {/* Consultancy Section */}
Consultoria Técnica
Serviço Especializado

Consultoria de Notas Técnicas

Nossa equipe técnica analisa seu projeto e garante que sua instalação esteja em total conformidade com as normas da Enel e Sabesp, evitando reprovações.

Falar com Consultor
{[ { label: "Análise de Projetos", icon: ShieldCheck }, { label: "Normas Enel/Sabesp", icon: Package }, { label: "Evite Reprovações", icon: ShieldCheck }, { label: "Suporte Especializado", icon: Truck } ].map((item, idx) => (

{item.label}

))}
{/* Featured Items Showcase */} {featuredItems.length > 0 && (
{featuredItems[currentSlide].title}

{featuredItems[currentSlide].title || 'Destaques AMEW'}

Ver Produtos
{featuredItems.length > 1 && ( <>
{featuredItems.map((_, i) => (
)}
)} {/* Featured Products */}

Destaques da Semana

As caixas mais desejadas pelos nossos clientes.

Ver todos
{loading ? (
{[1, 2, 3, 4].map(i => (
))}
) : (
{featuredProducts.map(product => ( ))}
)}
); }; ‎src/pages/Login.tsx‎ + 47 Linhas alteradas: 47 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,47 @@ import React from 'react'; import { auth } from '../firebase'; import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth'; import { useNavigate } from 'react-router-dom'; import { motion } from 'motion/react'; import { LogIn } from 'lucide-react'; import toast from 'react-hot-toast'; export const Login: React.FC = () => { const navigate = useNavigate(); const handleGoogleLogin = async () => { const provider = new GoogleAuthProvider(); try { await signInWithPopup(auth, provider); toast.success('Login realizado com sucesso!'); navigate('/'); } catch (error) { console.error('Login error:', error); toast.error('Erro ao realizar login.'); } }; return (

Bem-vindo de volta

Acesse sua conta para gerenciar seus pedidos e preferências.

); }; ‎src/pages/Products.tsx‎ + 90 Linhas alteradas: 90 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,90 @@ import React, { useEffect, useState } from 'react'; import { collection, getDocs, query, orderBy } from 'firebase/firestore'; import { db } from '../firebase'; import { Product } from '../types'; import { ProductCard } from '../components/ProductCard'; import { Search, SlidersHorizontal, X } from 'lucide-react'; export const Products: React.FC = () => { const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); useEffect(() => { const fetchProducts = async () => { try { const q = query(collection(db, 'products'), orderBy('createdAt', 'desc')); const snapshot = await getDocs(q); const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as Product)); setProducts(data); } catch (error) { console.error('Error fetching products:', error); } finally { setLoading(false); } }; fetchProducts(); }, []); const filteredProducts = products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) || p.description.toLowerCase().includes(searchTerm.toLowerCase()) ); return (

Nossas Caixas

{searchTerm ? `${filteredProducts.length} ${filteredProducts.length === 1 ? 'produto encontrado' : 'produtos encontrados'}` : 'Explore nossa coleção completa de embalagens e presentes.'}

setSearchTerm(e.target.value)} className="w-full pl-10 pr-10 py-3 bg-white border border-black/5 rounded-xl focus:outline-none focus:ring-2 focus:ring-emerald-500 transition-all" /> {searchTerm && ( )}
{loading ? (
{[1, 2, 3, 4, 5, 6, 7, 8].map(i => (
))}
) : filteredProducts.length > 0 ? (
{filteredProducts.map(product => ( ))}
) : (

Nenhum produto encontrado para sua busca.

)}
); }; ‎src/types.ts‎ + 55 Linhas alteradas: 55 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,55 @@ export interface Product { id: string; name: string; description: string; price: number; stock: number; imageUrl: string; createdAt: any; } export interface CartItem extends Product { quantity: number; } export interface Order { id?: string; userId: string; items: CartItem[]; total: number; status: 'pending' | 'paid' | 'shipped' | 'cancelled'; paymentMethod: string; customerInfo: { name: string; email: string; phone: string; address: string; city: string; zip: string; }; createdAt: any; } export interface UserProfile { uid: string; email: string; role: 'admin' | 'customer'; createdAt: any; } export interface FeaturedItem { id: string; title?: string; imageUrl: string; category: 'enel' | 'sabesp'; createdAt: any; } export interface ProjectPrice { id: string; name: string; basePrice: number; unitName: string; description: string; createdAt: any; } ‎src/utils/imageOptimizer.ts‎ + 64 Linhas alteradas: 64 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,64 @@ /** * Optimizes an image by resizing it and compressing it using the Canvas API. * @param source - The image source (File, Blob, or Base64 string/URL) * @param maxWidth - The maximum width for the optimized image (default: 1200) * @param quality - The compression quality (0.0 to 1.0, default: 0.7) * @returns A promise that resolves to the optimized Base64 string */ export const optimizeImage = async ( source: File | Blob | string, maxWidth: number = 1200, quality: number = 0.7 ): Promise => { return new Promise((resolve, reject) => { const img = new Image(); // Handle cross-origin for URLs if (typeof source === 'string' && source.startsWith('http')) { img.crossOrigin = 'anonymous'; } img.onload = () => { const canvas = document.createElement('canvas'); let width = img.width; let height = img.height; // Calculate new dimensions while maintaining aspect ratio if (width > maxWidth) { height = Math.round((height * maxWidth) / width); width = maxWidth; } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (!ctx) { reject(new Error('Could not get canvas context')); return; } // Draw and compress ctx.drawImage(img, 0, 0, width, height); // Convert to WebP if supported, otherwise JPEG const optimizedBase64 = canvas.toDataURL('image/jpeg', quality); resolve(optimizedBase64); }; img.onerror = () => { reject(new Error('Failed to load image for optimization')); }; if (source instanceof File || source instanceof Blob) { const reader = new FileReader(); reader.onload = (e) => { img.src = e.target?.result as string; }; reader.onerror = () => reject(new Error('Failed to read file')); reader.readAsDataURL(source); } else { img.src = source; } }); }; ‎tsconfig.json‎ + 26 Linhas alteradas: 26 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,26 @@ { "compilerOptions": { "target": "ES2022", "experimentalDecorators": true, "useDefineForClassFields": false, "module": "ESNext", "lib": [ "ES2022", "DOM", "DOM.Iterable" ], "skipLibCheck": true, "moduleResolution": "bundler", "isolatedModules": true, "moduleDetection": "force", "allowJs": true, "jsx": "react-jsx", "paths": { "@/*": [ "./*" ] }, "allowImportingTsExtensions": true, "noEmit": true } } ‎vite.config.ts‎ + 24 Linhas alteradas: 24 adições e 0 exclusões Número da linha do arquivo original Número da linha diferente Mudança de linha diferencial @@ -0,0 +1,24 @@ import tailwindcss from '@tailwindcss/vite'; import react from '@vitejs/plugin-react'; import path from 'path'; import {defineConfig, loadEnv} from 'vite'; export default defineConfig(({mode}) => { const env = loadEnv(mode, '.', ''); return { plugins: [react(), tailwindcss()], define: { 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY), }, resolve: { alias: { '@': path.resolve(__dirname, '.'), }, }, server: { // HMR is disabled in AI Studio via DISABLE_HMR env var. // Do not modify—file watching is disabled to prevent flickering during agent edits. hmr: process.env.DISABLE_HMR !== 'true', }, }; });