// components.jsx — shared UI primitives + icon set const { useState, useEffect, useRef, useCallback, useMemo, createContext, useContext } = React; // ============================================================ // Icons (compact SVG set, currentColor) // ============================================================ const Icon = {}; const _icon = (name, body, viewBox) => Icon[name] = ({size=18, ...rest}) => ( {body} ); _icon('home', <>>); _icon('create', <>>); _icon('fortune', <>>); _icon('credits', <>>); _icon('history', <>>); _icon('star', <>>); _icon('sparkles', <>>); _icon('photo', <>>); _icon('video', <>>); _icon('face', <>>); _icon('multiface', <>>); _icon('wand', <>>); _icon('lock', <>>); _icon('upload', <>>); _icon('download', <>>); _icon('check', <>>); _icon('checkCircle', <>>); _icon('x', <>>); _icon('chevronRight', <>>); _icon('chevronLeft', <>>); _icon('chevronUp', <>>); _icon('chevronDown', <>>); _icon('arrowRight', <>>); _icon('plus', <>>); _icon('minus', <>>); _icon('refresh', <>>); _icon('settings', <>>); _icon('share', <>>); _icon('copy', <>>); _icon('heart', <>>); _icon('heartBroken', <>>); _icon('user', <>>); _icon('users', <>>); _icon('crown', <>>); _icon('coins', <>>); _icon('clock', <>>); _icon('info', <>>); _icon('warning', <>>); _icon('mail', <>>); _icon('paypal', <>>); _icon('telegramStar', <>>); _icon('gift', <>>); _icon('flame', <>>); _icon('zap', <>>); _icon('filter', <>>); _icon('search', <>>); _icon('trash', <>>); _icon('externalLink', <>>); _icon('eye', <>>); _icon('grid', <>>); _icon('language', <>>); _icon('helpCircle', <>>); _icon('shield', <>>); _icon('moon', <>>); _icon('book', <>>); _icon('palette', <>>); _icon('image', <>>); _icon('film', <>>); _icon('wallet', <>>); _icon('trophy', <>>); window.Icon = Icon; // ============================================================ // Visual helpers // ============================================================ function StarField({count=40}){ const stars = useMemo(()=>{ const arr = []; for (let i=0; i {stars.map((s, i) => ( ))} ); } window.StarField = StarField; // AI banner that uses one of the cropped reference images function AIBanner({ kind='campaign-upsell', children, height=140 }){ const assetBase = (window.RavanaMiniApp && window.RavanaMiniApp.assetBase) || '/miniapp-assets'; return ( {children} ); } window.AIBanner = AIBanner; // Generic placeholder media (no AI-image embedded) function PlaceholderMedia({ label='media', aspect='4/5', kind='photo' }){ return ( {label} ); } window.PlaceholderMedia = PlaceholderMedia; // Step indicator (for flow wizards) function Stepper({ steps, current }){ return ( {steps.map((s, i) => ( ))} ); } window.Stepper = Stepper; // Reusable header for nested screens function ScreenHeader({ title, subtitle, onBack, right }){ return ( {onBack && ( )} {title} {subtitle && {subtitle}} {right} ); } window.ScreenHeader = ScreenHeader; // Sheet (bottom modal) function Sheet({ open, onClose, children, dialog=false }){ if (!open) return null; return ( e.stopPropagation()}> {children} ); } window.Sheet = Sheet; // Active job card function ActiveJobCard({ job, lang, onClick }){ const status = job.status; return ( {job.type === 'multiface' ? : job.type === 'image_gen' ? : } {job.name} ● {t('status_'+status, lang)} {job.eta && · ETA {job.eta}} ); } window.ActiveJobCard = ActiveJobCard; // Cost preview box function CostPanel({ items }){ return ( {items.map((row, i) => ( {row.k} {row.v} ))} ); } window.CostPanel = CostPanel; // Premium chip with crown function PremiumChip({ kind='free', lang }){ if (kind === 'premium') return Premium; if (kind === 'ref_premium') return Ref Premium; if (kind === 'returning') return {t('old_payer', lang)}; return {t('free', lang)}; } window.PremiumChip = PremiumChip;