# If you used Create React App npm run build # If you used Vite npm run build import React, { useState, useEffect, useRef } from "react"; import { HashRouter, Routes, Route, Link, useLocation, Outlet, useNavigate } from "react-router-dom"; import { motion, AnimatePresence } from "framer-motion"; import { Camera, Leaf, Utensils, Home as HomeIcon, Scan as ScanIcon, Flower2, User, MessageSquare, X, Send, Mic, ChefHat, Play, Clock, Volume2, VolumeX, Sun, Info, Plus, Calendar, Flame, Droplets, Wind, Settings, Bell, Heart, LogOut, ChevronRight, Upload, Check, Loader2, Zap } from "lucide-react"; import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; import * as mobilenet from '@tensorflow-models/mobilenet'; import '@tensorflow/tfjs'; // ========================================== // UTILS // ========================================== function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } // ========================================== // AI & DATA SERVICES // ========================================== // AI Model Service let model: mobilenet.MobileNet | null = null; async function loadModel() { if (!model) { console.log('Loading MobileNet model...'); model = await mobilenet.load(); console.log('Model loaded.'); } return model; } async function classifyImage( imageElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement ): Promise<{ className: string; probability: number }[]> { const net = await loadModel(); const predictions = await net.classify(imageElement); return predictions; } // Data Types export type ScanType = 'food' | 'plant' | 'unknown'; export interface ScanResult { id: string; type: ScanType; name: string; confidence: number; safety: 'safe' | 'caution' | 'unsafe'; image: string; description: string; details: Record; timestamp: number; } // Mock Data export const MEAL_SUGGESTIONS = [ { id: 'm1', type: 'Breakfast', name: 'Green Smoothie Bowl', calories: 320, image: 'https://images.unsplash.com/photo-1610970881699-44a5587cabec?auto=format&fit=crop&q=80&w=1200' }, { id: 'm2', type: 'Lunch', name: 'Quinoa Salad', calories: 450, image: 'https://images.unsplash.com/photo-1505253716362-afaea1d3d1af?auto=format&fit=crop&q=80&w=1200' }, { id: 'm3', type: 'Dinner', name: 'Grilled Salmon & Asparagus', calories: 580, image: 'https://images.unsplash.com/photo-1467003909585-2f8a7270028d?auto=format&fit=crop&q=80&w=1200' } ]; export const RECIPES = [ { id: 'r1', name: 'Avocado Toast with Egg', image: 'https://images.unsplash.com/photo-1525351484163-7529414395d8?auto=format&fit=crop&q=80&w=1200', time: '10 min', calories: 350, ingredients: ['2 slices whole grain bread', '1 ripe avocado', '2 eggs', 'Salt & Pepper', 'Chili flakes'], steps: [ 'Toast the bread until golden brown.', 'Mash the avocado with salt and pepper.', 'Fry or poach the eggs to your liking.', 'Spread avocado on toast and top with egg.', 'Sprinkle with chili flakes and serve.' ] }, { id: 'r2', name: 'Mediterranean Salad', image: 'https://images.unsplash.com/photo-1540189549336-e6e99c3679fe?auto=format&fit=crop&q=80&w=1200', time: '15 min', calories: 280, ingredients: ['Cucumber', 'Tomatoes', 'Red Onion', 'Feta Cheese', 'Olives', 'Olive Oil', 'Lemon Juice'], steps: [ 'Chop cucumber, tomatoes, and onion.', 'Combine in a large bowl.', 'Add olives and crumbled feta cheese.', 'Drizzle with olive oil and lemon juice.', 'Toss gently and serve fresh.' ] }, { id: 'r3', name: 'Grilled Chicken & Veggies', image: 'https://images.unsplash.com/photo-1532550907401-a500c9a57435?auto=format&fit=crop&q=80&w=1200', time: '30 min', calories: 450, ingredients: ['Chicken Breast', 'Bell Peppers', 'Zucchini', 'Olive Oil', 'Herbs'], steps: [ 'Marinate chicken with herbs and olive oil.', 'Chop vegetables into chunks.', 'Grill chicken for 6-8 mins per side.', 'Grill vegetables until tender.', 'Serve hot with a side of rice or salad.' ] } ]; export const RECOGNITION_DATABASE = [ // Plants { name: 'Snake Plant', keywords: ['snake', 'plant', 'sansevieria', 'dracaena'], type: 'plant', safety: 'caution', image: 'https://images.unsplash.com/photo-1593482886870-930272168db4?auto=format&fit=crop&q=80&w=1200', description: 'A hardy indoor plant with upright, sword-like leaves.', details: { scientificName: 'Sansevieria trifasciata', care: { water: 'Low', light: 'Low to Bright', toxicity: 'Toxic to pets' } } }, { name: 'Monstera', keywords: ['monstera', 'leaf', 'cheese', 'pot', 'flowerpot'], type: 'plant', safety: 'caution', image: 'https://images.unsplash.com/photo-1614594975525-e45190c55d0b?auto=format&fit=crop&q=80&w=1200', description: 'Tropical plant with distinctive holey leaves.', details: { scientificName: 'Monstera deliciosa', care: { water: 'Medium', light: 'Indirect', toxicity: 'Toxic to pets' } } }, { name: 'Basil', keywords: ['basil', 'herb', 'green'], type: 'plant', safety: 'safe', image: 'https://images.unsplash.com/photo-1618375531912-867984bdfdfc?auto=format&fit=crop&q=80&w=1200', description: 'A fragrant herb often used in cooking.', details: { scientificName: 'Ocimum basilicum', care: { water: 'Regular', light: 'Full Sun', toxicity: 'Safe' } } }, { name: 'Daisy', keywords: ['daisy', 'flower', 'white', 'yellow'], type: 'plant', safety: 'safe', image: 'https://images.unsplash.com/photo-1560717789-0ac7c58ac90a?auto=format&fit=crop&q=80&w=1200', description: 'A classic flower with white petals and a yellow center.', details: { scientificName: 'Bellis perennis', care: { water: 'Regular', light: 'Full Sun', toxicity: 'Mildly toxic if ingested in large quantities' } } }, { name: 'Cactus', keywords: ['cactus', 'succulent', 'thorn', 'spine'], type: 'plant', safety: 'caution', image: 'https://images.unsplash.com/photo-1459411552884-841db9b3cc2a?auto=format&fit=crop&q=80&w=1200', description: 'A succulent plant with spines, adapted to dry environments.', details: { scientificName: 'Cactaceae', care: { water: 'Very Low', light: 'Full Sun', toxicity: 'Physical injury risk' } } }, // Fruits/Veg { name: 'Apple', keywords: ['apple', 'red', 'fruit', 'granny smith', 'delicious'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?auto=format&fit=crop&q=80&w=1200', description: 'A crisp, sweet, red fruit.', details: { calories: 95, nutrients: { carbs: '25g', fiber: '4g', protein: '0.5g' } } }, { name: 'Tomato', keywords: ['tomato', 'vegetable', 'red', 'cherry tomato'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1592924357228-91a4daadcfea?auto=format&fit=crop&q=80&w=1200', description: 'A juicy red fruit often used as a vegetable.', details: { calories: 22, nutrients: { carbs: '4.8g', fiber: '1.5g', protein: '1.1g' } } }, { name: 'Grapes', keywords: ['grape', 'fruit', 'purple', 'green', 'vine'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1537640538965-1756deb9920c?auto=format&fit=crop&q=80&w=1200', description: 'Small, sweet fruit that grows in clusters.', details: { calories: 69, nutrients: { carbs: '18g', fiber: '0.9g', protein: '0.7g' } } }, { name: 'Banana', keywords: ['banana', 'yellow', 'fruit', 'plantain'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1571771896395-d97159529e38?auto=format&fit=crop&q=80&w=1200', description: 'A curved, yellow fruit with a thick skin and soft sweet flesh.', details: { calories: 105, nutrients: { carbs: '27g', fiber: '3.1g', protein: '1.3g' } } }, { name: 'Orange', keywords: ['orange', 'citrus', 'fruit', 'tangerine', 'mandarin'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1611080626919-7cf5a9dbab5b?auto=format&fit=crop&q=80&w=1200', description: 'A citrus fruit known for its high vitamin C content.', details: { calories: 62, nutrients: { carbs: '15g', fiber: '3g', protein: '1.2g' } } }, { name: 'Broccoli', keywords: ['broccoli', 'vegetable', 'green', 'cauliflower'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1584270354949-c26b0d5b4a0c?auto=format&fit=crop&q=80&w=1200', description: 'A green vegetable in the cabbage family.', details: { calories: 55, nutrients: { carbs: '11g', fiber: '5g', protein: '3.7g' } } }, { name: 'Strawberry', keywords: ['strawberry', 'berry', 'red', 'fruit'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1464965911861-746a04b4bca6?auto=format&fit=crop&q=80&w=1200', description: 'A sweet, red heart-shaped fruit.', details: { calories: 53, nutrients: { carbs: '12g', fiber: '3g', protein: '1g' } } }, { name: 'Lemon', keywords: ['lemon', 'citrus', 'yellow'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1590502593747-df8f31bdce85?auto=format&fit=crop&q=80&w=1200', description: 'A sour yellow citrus fruit used for flavoring.', details: { calories: 29, nutrients: { carbs: '9g', fiber: '2.8g', protein: '1.1g' } } }, { name: 'Pineapple', keywords: ['pineapple', 'ananas'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1550258987-190a2d41a8ba?auto=format&fit=crop&q=80&w=1200', description: 'A tropical fruit with spiky skin and sweet yellow flesh.', details: { calories: 50, nutrients: { carbs: '13g', fiber: '1.4g', protein: '0.5g' } } }, { name: 'Pepper', keywords: ['bell pepper', 'pepper', 'capsicum', 'green pepper', 'red pepper'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1563565375-f3fdf5ea2e6d?auto=format&fit=crop&q=80&w=1200', description: 'A crisp vegetable available in various colors.', details: { calories: 20, nutrients: { carbs: '4.6g', fiber: '1.7g', protein: '0.9g' } } }, { name: 'Cucumber', keywords: ['cucumber', 'pickle', 'zucchini'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1449300079323-02e209d9d3a6?auto=format&fit=crop&q=80&w=1200', description: 'A long green vegetable with high water content.', details: { calories: 15, nutrients: { carbs: '3.6g', fiber: '0.5g', protein: '0.7g' } } }, { name: 'Corn', keywords: ['corn', 'maize', 'cob'], type: 'food', safety: 'safe', image: 'https://images.unsplash.com/photo-1551754655-cd27e38d2076?auto=format&fit=crop&q=80&w=1200', description: 'A cereal grain often eaten as a vegetable.', details: { calories: 86, nutrients: { carbs: '19g', fiber: '2.7g', protein: '3.2g' } } } ]; async function matchImageToDatabase(predictions: { className: string; probability: number }[]): Promise { let bestMatch = null; let highestConfidence = 0; for (const prediction of predictions) { const pName = prediction.className.toLowerCase(); const match = RECOGNITION_DATABASE.find(item => item.keywords.some(keyword => pName.includes(keyword)) ); if (match && prediction.probability > highestConfidence) { bestMatch = match; highestConfidence = prediction.probability; } } if (bestMatch) { return { id: Math.random().toString(36).substr(2, 9), type: bestMatch.type as ScanType, name: bestMatch.name, confidence: highestConfidence, safety: bestMatch.safety as any, image: bestMatch.image, description: bestMatch.description, details: bestMatch.details, timestamp: Date.now() }; } return null; } // ========================================== // COMPONENTS // ========================================== const Splash = ({ onComplete }: { onComplete: () => void }) => { const [step, setStep] = useState(0); useEffect(() => { const times = [0, 600, 1200, 1800, 2500]; const t1 = setTimeout(() => setStep(1), times[1]); const t2 = setTimeout(() => setStep(2), times[2]); const t3 = setTimeout(() => setStep(3), times[3]); const t4 = setTimeout(() => onComplete(), times[4]); return () => { clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); clearTimeout(t4); }; }, [onComplete]); return (
{step === 0 && ( )} {step === 1 && ( )} {step === 2 && ( )} {step === 3 && (
)}
= 3 ? 1 : 0, y: step >= 3 ? 0 : 20 }} transition={{ duration: 0.5 }} className="mt-8 text-center" >

GREENLENS

See Nature Clearly

); }; interface Message { id: string; text: string; sender: 'user' | 'ai'; type?: 'text' | 'recipe-card' | 'cooking-step'; data?: any; } const AIChat = () => { const [isOpen, setIsOpen] = useState(false); const [messages, setMessages] = useState([ { id: '1', text: "Hi! I'm GreenLens AI. Ask me about plants, food, or recipes!", sender: 'ai' } ]); const [inputText, setInputText] = useState(""); const [isListening, setIsListening] = useState(false); const [cookingMode, setCookingMode] = useState<{ recipe: any, step: number } | null>(null); const [isMuted, setIsMuted] = useState(false); const [timer, setTimer] = useState(null); const messagesEndRef = useRef(null); const recognitionRef = useRef(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, isOpen]); // Timer effect useEffect(() => { let interval: NodeJS.Timeout | undefined; if (timer !== null && timer > 0) { interval = setInterval(() => setTimer(t => (t ? t - 1 : 0)), 1000); } else if (timer === 0) { speak("Timer finished!"); setTimer(null); } return () => { if (interval) clearInterval(interval); }; }, [timer]); // eslint-disable-line react-hooks/exhaustive-deps // speak omitted intentionally to avoid re-triggering on mute changes; but if mute changes, timer finished message may be spoken incorrectly. // For production, consider using a ref for isMuted. const speak = (text: string) => { if (isMuted) return; if ('speechSynthesis' in window) { window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); utterance.rate = 1; utterance.pitch = 1; window.speechSynthesis.speak(utterance); } }; const handleSend = () => { if (!inputText.trim()) return; const userMsg: Message = { id: Date.now().toString(), text: inputText, sender: 'user' }; setMessages(prev => [...prev, userMsg]); setInputText(""); setTimeout(() => { let aiResponse: Message = { id: (Date.now() + 1).toString(), text: "I'm not sure about that yet.", sender: 'ai' }; const lowerInput = userMsg.text.toLowerCase(); if (lowerInput.includes("recipe") || lowerInput.includes("cook") || lowerInput.includes("dinner") || lowerInput.includes("hungry")) { const matchedRecipe = RECIPES.find(r => lowerInput.includes(r.name.toLowerCase().split(' ')[0])); const recipeToUse = matchedRecipe || RECIPES[Math.floor(Math.random() * RECIPES.length)]; aiResponse = { id: (Date.now() + 1).toString(), text: `Here's a delicious idea: ${recipeToUse.name}. It takes about ${recipeToUse.time}. Would you like to start cooking?`, sender: 'ai', type: 'recipe-card', data: recipeToUse }; } else { const dbMatch = RECOGNITION_DATABASE.find(item => item.keywords.some(k => lowerInput.includes(k)) || lowerInput.includes(item.name.toLowerCase()) ); if (dbMatch) { if (dbMatch.type === 'plant') { aiResponse.text = `${dbMatch.name} is a ${dbMatch.description.toLowerCase()} Care tip: ${dbMatch.details.care?.light} and water ${dbMatch.details.care?.water.toLowerCase()}.`; } else { aiResponse.text = `${dbMatch.name} contains about ${dbMatch.details.calories} calories. It's ${dbMatch.description.toLowerCase()}`; } } else if (lowerInput.includes("plant") || lowerInput.includes("water")) { aiResponse.text = "For most indoor plants, check the top inch of soil. If it's dry, it's time to water! Ensure good drainage to prevent root rot."; } else if (lowerInput.includes("hello") || lowerInput.includes("hi")) { aiResponse.text = "Hello there! Ready to explore nature or cook something delicious?"; } else if (lowerInput.includes("thank")) { aiResponse.text = "You're welcome! Happy to help."; } else { aiResponse.text = "I can help you identify plants, find recipes, or check nutrition facts. Try asking 'How many calories in an apple?' or 'Suggest a dinner recipe'."; } } setMessages(prev => [...prev, aiResponse]); speak(aiResponse.text); }, 800); }; const toggleListening = () => { if (isListening) { if (recognitionRef.current) recognitionRef.current.stop(); setIsListening(false); return; } const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition; if (SpeechRecognition) { const recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = false; recognition.lang = 'en-US'; recognition.onstart = () => setIsListening(true); recognition.onend = () => setIsListening(false); recognition.onresult = (event: any) => { const transcript = event.results[0][0].transcript; setInputText(transcript); }; recognitionRef.current = recognition; recognition.start(); } else { alert("Voice input is not supported in this browser. Please use Chrome or Safari."); } }; const parseTimeFromStep = (step: string) => { const match = step.match(/(\d+)\s*(min|minute|sec|second)/i); if (match) { const val = parseInt(match[1]); const unit = match[2].toLowerCase(); return unit.startsWith('min') ? val * 60 : val; } return null; }; return ( <> setIsOpen(true)} className="fixed bottom-20 right-4 z-40 flex h-14 w-14 items-center justify-center rounded-full bg-emerald-600 text-white shadow-lg shadow-emerald-200" > {isOpen && (

GreenLens Assistant

Online • AI Powered

{messages.map((msg) => (

{msg.text}

{msg.type === 'recipe-card' && msg.data && (
{msg.data.name}

{msg.data.name}

{msg.data.calories} kcal {msg.data.time}
)}
))} {cookingMode && (

{cookingMode.recipe.name}

Step {cookingMode.step + 1} of {cookingMode.recipe.steps.length}

{cookingMode.recipe.steps[cookingMode.step]}

{(() => { const time = parseTimeFromStep(cookingMode.recipe.steps[cookingMode.step]); if (time && timer === null) { return ( ); } else if (timer !== null) { return (
{Math.floor(timer / 60)}:{(timer % 60).toString().padStart(2, '0')}
); } return null; })()}
{cookingMode.step < cookingMode.recipe.steps.length - 1 ? ( ) : ( )}
)}
setInputText(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSend()} placeholder="Ask anything..." className="flex-1 bg-transparent text-sm outline-none placeholder:text-slate-400" />
)} ); }; const Layout = () => { const location = useLocation(); const navItems = [ { name: "Home", icon: HomeIcon, path: "/" }, { name: "Scan", icon: ScanIcon, path: "/scan" }, { name: "Meals", icon: Utensils, path: "/meals" }, { name: "Plants", icon: Flower2, path: "/plants" }, { name: "Profile", icon: User, path: "/profile" }, ]; return (
); }; // ========================================== // PAGES // ========================================== const Home = () => { const navigate = useNavigate(); return (

Hello, Explorer

Ready to discover nature?

navigate("/scan")} className="relative mb-8 flex h-40 w-full cursor-pointer flex-col items-center justify-center overflow-hidden rounded-3xl bg-gradient-to-br from-emerald-600 to-teal-700 shadow-xl shadow-emerald-200" >
Scan Anything
{[ { label: "Food", icon: Utensils, color: "bg-orange-100 text-orange-600", path: "/scan?mode=food" }, { label: "Plant", icon: Leaf, color: "bg-emerald-100 text-emerald-600", path: "/scan?mode=plant" }, { label: "Upload", icon: Plus, color: "bg-blue-100 text-blue-600", path: "/scan?mode=upload" }, { label: "Plan", icon: Calendar, color: "bg-purple-100 text-purple-600", path: "/meals" }, ].map((item) => ( navigate(item.path)} className="flex flex-col items-center space-y-2" >
{item.label}
))}

Tip of the Day

"Did you know? Adding lemon to your spinach helps your body absorb more iron due to the Vitamin C boost!"

Today's Suggestions

{MEAL_SUGGESTIONS.map((meal) => (
{meal.name}
{meal.type}

{meal.name}

{meal.calories} kcal

))}
); }; const Scan = () => { const navigate = useNavigate(); const videoRef = useRef(null); const [image, setImage] = useState(null); const [isAnalyzing, setIsAnalyzing] = useState(false); const [result, setResult] = useState(null); const [cameraActive, setCameraActive] = useState(true); useEffect(() => { if (cameraActive && !image) { startCamera(); } else { stopCamera(); } return () => stopCamera(); }, [cameraActive, image]); const startCamera = async () => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }); if (videoRef.current) { videoRef.current.srcObject = stream; } } catch (err) { console.error("Camera error:", err); setCameraActive(false); } }; const stopCamera = () => { if (videoRef.current && videoRef.current.srcObject) { const stream = videoRef.current.srcObject as MediaStream; stream.getTracks().forEach(track => track.stop()); videoRef.current.srcObject = null; } }; const capturePhoto = () => { if (videoRef.current) { const canvas = document.createElement("canvas"); canvas.width = videoRef.current.videoWidth; canvas.height = videoRef.current.videoHeight; canvas.getContext("2d")?.drawImage(videoRef.current, 0, 0); const dataUrl = canvas.toDataURL("image/jpeg"); setImage(dataUrl); handleAnalyze(videoRef.current); setCameraActive(false); } }; const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { const url = URL.createObjectURL(file); setImage(url); setCameraActive(false); const img = new Image(); img.crossOrigin = "anonymous"; img.src = url; img.onload = () => { handleAnalyze(img); }; } }; const handleAnalyze = async (source: HTMLImageElement | HTMLVideoElement) => { setIsAnalyzing(true); try { const predictions = await classifyImage(source); console.log("AI Predictions:", predictions); let scanResult = await matchImageToDatabase(predictions); if (!scanResult && predictions.length > 0) { const top = predictions[0]; scanResult = { id: Math.random().toString(36).substr(2, 9), type: 'unknown', name: top.className.split(',')[0], confidence: top.probability, safety: 'caution', image: image || '', description: `Identified as ${top.className}. Not found in our curated database.`, details: { rawPredictions: predictions }, timestamp: Date.now() }; } else if (!scanResult) { throw new Error("Could not identify object"); } setResult(scanResult); } catch (error) { console.error("Analysis failed:", error); setResult({ id: "err", type: 'unknown', name: "Unknown Object", confidence: 0, safety: 'caution', image: image || '', description: "We couldn't identify this object clearly. Please try again.", details: {}, timestamp: Date.now() }); } finally { setIsAnalyzing(false); } }; const reset = () => { setImage(null); setResult(null); setCameraActive(true); }; if (result) { return (
Scanned

{result.name}

{result.safety.toUpperCase()} {(result.confidence * 100).toFixed(0)}% Confidence

Description

{result.description}

{result.type === 'food' && (

Nutrition Facts

{result.details.calories || '?'} kcal
{result.details.nutrients?.protein || '?'} Protein
{result.details.nutrients?.carbs || '?'} Carbs
)} {result.type === 'plant' && (

Care Guide

Light {result.details.care?.light || 'Moderate'}
Water {result.details.care?.water || 'Regularly'}
{result.details.care?.toxicity && (
Warning: {result.details.care.toxicity}
)}
)} {result.type === 'unknown' && result.details.rawPredictions && (

AI Results

    {result.details.rawPredictions.slice(0, 3).map((p: any) => (
  • {p.className} ({(p.probability * 100).toFixed(0)}%)
  • ))}
)}
); } return (
{isAnalyzing ? 'Analyzing...' : 'Point at Food or Plant'}
{!image ? (
Tips
); }; const Meals = () => { const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; const currentDayIndex = 2; return (

Meal Planner

{days.map((day, i) => (
{}} className={`flex h-14 w-10 flex-col items-center justify-center rounded-xl text-xs font-medium transition-colors ${ i === currentDayIndex ? "bg-emerald-600 text-white shadow-lg shadow-emerald-200" : "text-slate-400 hover:bg-slate-50" }`} > {day} {12 + i}
))}
Calories
1,250 / 2,000 kcal
Water
1.2 / 2.5 L
{["Breakfast", "Lunch", "Dinner", "Snack"].map((slot, index) => { const meal = MEAL_SUGGESTIONS[index % MEAL_SUGGESTIONS.length]; return (

{slot}

Recommended
{meal.name}

{meal.name}

{meal.calories} kcal
20 min
); })}
); }; const Plants = () => { const navigate = useNavigate(); const myPlants = [ { id: 1, name: "Monstera Deliciosa", nickname: "Monty", image: "https://images.unsplash.com/photo-1614594975525-e45190c55d0b?auto=format&fit=crop&q=80&w=1200", nextWater: "Today", waterStatus: "urgent" }, { id: 2, name: "Snake Plant", nickname: "Snape", image: "https://images.unsplash.com/photo-1593482886870-930272168db4?auto=format&fit=crop&q=80&w=1200", nextWater: "in 5 days", waterStatus: "ok" }, { id: 3, name: "Fiddle Leaf Fig", nickname: "Figgy", image: "https://images.unsplash.com/photo-1612437118748-0c3098f98c8c?auto=format&fit=crop&q=80&w=1200", nextWater: "Tomorrow", waterStatus: "soon" } ]; return (

My Plants

Care Dashboard

1 Needs Water
Good Light
Humidity OK
{myPlants.map((plant) => (
{plant.name}

{plant.nickname}

{plant.name}

{plant.nextWater}
))}
); }; const Profile = () => { return (

Alex Green

Nature Enthusiast

Account

{[ { icon: User, label: "Personal Details" }, { icon: Heart, label: "Favorites" }, { icon: Bell, label: "Notifications" }, ].map((item) => (
{item.label}
))}

Preferences

{[ { icon: Settings, label: "App Settings" }, { icon: LogOut, label: "Log Out", color: "text-red-500" }, ].map((item) => (
{item.label}
))}

© 2024 GreenLens Inc. All rights reserved.

Terms of Service • Privacy Policy

v1.0.0

); }; // ========================================== // MAIN APP ENTRY // ========================================== export function App() { const [splashComplete, setSplashComplete] = useState(false); if (!splashComplete) { return setSplashComplete(true)} />; } return ( }> } /> } /> } /> } /> } /> ); }