// Common UI components for the Duyuf app // ─── Icons ──────────────────────────────────────────────────────────── function Icon({ name, size = 22, color = 'currentColor', strokeWidth = 1.7 }) { const s = { width: size, height: size, fill: 'none', stroke: color, strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round' }; switch (name) { case 'home': return (); case 'explore': return (); case 'briefcase': return (); case 'wallet': return (); case 'user': return (); case 'plane': return (); case 'bus': return (); case 'bed': return (); case 'car': return (); case 'kaaba': return (); case 'search': return (); case 'bell': return (); case 'heart': return (); case 'star': return (); case 'star-fill': return (); case 'arrow-l': return (); case 'arrow-r': return (); case 'arrow-down': return (); case 'pin': return (); case 'calendar': return (); case 'plus': return (); case 'minus': return (); case 'check': return (); case 'close': return (); case 'filter': return (); case 'qr': return (); case 'gift': return (); case 'shield': return (); case 'wifi': return (); case 'coffee': return (); case 'settings': return (); case 'globe': return (); case 'moon': return (); case 'sun': return (); case 'chevron-r': return (); case 'chevron-l': return (); case 'phone': return (); case 'percent': return (); case 'mail': return (); case 'lock': return (); case 'logout': return (); case 'card': return (); case 'compass': return (); case 'dots': return (); case 'trend-up': return (); case 'scan': return (); default: return null; } } // ─── Button ─────────────────────────────────────────────────────────── function Button({ children, kind = 'primary', size = 'md', icon, full, onClick, theme, style }) { const T = theme; const sizes = { sm: { h: 36, px: 14, fs: 13, r: 10 }, md: { h: 48, px: 18, fs: 14.5, r: 14 }, lg: { h: 56, px: 22, fs: 16, r: 16 }, }; const sz = sizes[size]; const kinds = { primary: { bg: T.primary, fg: '#fff', border: 'none', shadow: '0 6px 16px rgba(193,152,114,0.35)' }, secondary: { bg: T.surface, fg: T.text, border: `1px solid ${T.hairline}`, shadow: 'none' }, ghost: { bg: 'transparent', fg: T.primary, border: 'none', shadow: 'none' }, dark: { bg: T.ink, fg: T.cream, border: 'none', shadow: '0 6px 16px rgba(31,27,23,0.3)' }, soft: { bg: T.camelSoft, fg: T.camelDeep, border: 'none', shadow: 'none' }, }; const k = kinds[kind]; return ( ); } // ─── Image placeholder ──────────────────────────────────────────────── function ImgSlot({ label, height = 160, radius = 16, tone = 'camel', children, theme }) { const T = theme; // Use brand tones for placeholders, with subtle pattern for hospitality feel const tones = { camel: { bg: T.camelSoft, fg: T.camelDeep, accent: T.camel }, brown: { bg: '#D8CFC4', fg: T.pastelBrown, accent: T.pastelBrown }, dark: { bg: T.inkSoft, fg: T.cream, accent: T.camel }, cream: { bg: T.creamDeep, fg: T.inkSoft, accent: T.camel }, }; const t = tones[tone] || tones.camel; return (
{label && (
{label}
)} {children}
); } // ─── Bottom tab bar (3 tabs: Home / Bookings / Profile) ──────────────── function BottomTabs({ active = 'home', theme, lang, onSelect }) { const T = theme; const l = L[lang]; const tabs = [ { id: 'me', icon: 'user', label: l.profile }, { id: 'home', icon: 'home', label: l.home }, { id: 'trips', icon: 'briefcase', label: l.mybookings }, ]; return (
{tabs.map(t => { const isActive = t.id === active; return ( ); })}
); } // ─── Status bar (iOS-style, themed) ─────────────────────────────────── function PhoneStatusBar({ theme, time = '9:41', overlay = false, color }) { const T = theme; const c = color || (T.isDark || overlay ? '#fff' : T.ink); return (
{time}
{/* signal */} {/* wifi */} {/* battery */}
); } // ─── Home indicator ─────────────────────────────────────────────────── function HomeIndicator({ color = '#000' }) { return (
); } // ─── Frame wrapper — replaces IOSDevice with our own to hold RTL etc. ─ function PhoneFrame({ width = 360, height = 760, theme, dir = 'ltr', children, statusOverlay = false, hideStatus = false }) { const T = theme; return (
{/* Dynamic island */}
{!hideStatus && }
{children}
); } // ─── Card ───────────────────────────────────────────────────────────── function Card({ children, theme, padding = 16, radius = 18, style }) { const T = theme; return (
{children}
); } // ─── Section header ─────────────────────────────────────────────────── function SectionHead({ title, action, theme, style }) { const T = theme; return (

{title}

{action && ( {action} )}
); } // ─── Star rating ────────────────────────────────────────────────────── function Stars({ value = 4, color = '#C9882F', size = 12 }) { return (
{[1,2,3,4,5].map(i => ( ))}
); } // ─── Chip ───────────────────────────────────────────────────────────── function Chip({ children, active, theme, icon, style, onClick }) { const T = theme; return ( ); } // ─── Badge ──────────────────────────────────────────────────────────── function Badge({ children, tone = 'camel', theme, style }) { const T = theme; const tones = { camel: { bg: T.camelSoft, fg: T.camelDeep }, green: { bg: 'rgba(63,142,92,0.14)', fg: '#2E6A45' }, warn: { bg: 'rgba(201,136,47,0.16)', fg: '#8C5D14' }, red: { bg: 'rgba(180,81,59,0.14)', fg: '#8A3A29' }, ink: { bg: T.ink, fg: T.cream }, }; const t = tones[tone] || tones.camel; return ( {children} ); } // ─── Provider badge (marketplace — show vendor on every listing) ───── function ProviderBadge({ theme, name, color, size = 'sm', verified = true, style }) { const T = theme; // Derive a brand chip from the provider name (deterministic by hash) const palette = ['#0E5C3F', '#7F6D5F', '#1F8A5B', '#8C5D14', '#3C342D', '#5B7A8A', '#A87F5A']; const hash = [...(name || 'X')].reduce((a, c) => a + c.charCodeAt(0), 0); const bg = color || palette[hash % palette.length]; const initials = (name || 'P').split(' ').map(w => w[0]).slice(0, 2).join(''); const sizes = { xs: { logo: 16, fs: 8, gap: 5, ts: 10 }, sm: { logo: 20, fs: 9, gap: 6, ts: 11 }, md: { logo: 26, fs: 10, gap: 8, ts: 12.5 }, }; const s = sizes[size]; return (
{initials}
{name} {verified && ( )}
); } Object.assign(window, { Icon, Button, ImgSlot, BottomTabs, PhoneStatusBar, HomeIndicator, PhoneFrame, Card, SectionHead, Stars, Chip, Badge, ProviderBadge, });