/* IslamPlay — theme tokens, icons, and shared primitives */

const IP_THEME = {
  bg: '#0f0d0a',
  bgElev: '#1a160f',
  bgCard: '#221c12',
  gold: '#d4af6a',
  goldLight: '#e9c97a',
  goldDark: '#9a7a3e',
  cream: '#f3e9d2',
  creamMute: 'rgba(243, 233, 210, 0.65)',
  creamDim: 'rgba(243, 233, 210, 0.4)',
  line: 'rgba(212, 175, 106, 0.18)',
  lineStrong: 'rgba(212, 175, 106, 0.32)',
  danger: '#c96f4a',
};

// ── Islamic geometric background SVG (subtle pattern)
function IPPattern({ opacity = 0.06, color = '#d4af6a' }) {
  return (
    <svg width="100%" height="100%" style={{ position: 'absolute', inset: 0, opacity, pointerEvents: 'none' }} aria-hidden>
      <defs>
        <pattern id="ip-geo" x="0" y="0" width="60" height="60" patternUnits="userSpaceOnUse">
          <g fill="none" stroke={color} strokeWidth="0.6">
            <path d="M30 0 L60 15 L60 45 L30 60 L0 45 L0 15 Z"/>
            <path d="M30 10 L50 20 L50 40 L30 50 L10 40 L10 20 Z"/>
            <circle cx="30" cy="30" r="4"/>
            <path d="M30 15 L30 45 M15 22.5 L45 37.5 M15 37.5 L45 22.5"/>
          </g>
        </pattern>
      </defs>
      <rect width="100%" height="100%" fill="url(#ip-geo)"/>
    </svg>
  );
}

// ── Ornamental arch/mihrab frame used for hero artwork
function IPMihrab({ size = 180, children, glow = true }) {
  return (
    <div style={{
      position: 'relative', width: size, height: size * 1.25,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>
      <svg viewBox="0 0 100 125" width={size} height={size * 1.25} style={{ position: 'absolute', inset: 0, filter: glow ? 'drop-shadow(0 0 24px rgba(212, 175, 106, 0.35))' : 'none' }}>
        <defs>
          <linearGradient id="ip-arch" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="#e9c97a"/>
            <stop offset="50%" stopColor="#d4af6a"/>
            <stop offset="100%" stopColor="#8a6a2e"/>
          </linearGradient>
          <linearGradient id="ip-fill" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor="#3a2e1a"/>
            <stop offset="100%" stopColor="#1a1208"/>
          </linearGradient>
        </defs>
        {/* Mihrab arch path — pointed with onion dome */}
        <path d="M50 4
                 C 52 4, 54 6, 54 9
                 C 58 10, 60 14, 58 17
                 C 62 20, 62 26, 58 28
                 C 62 32, 60 38, 54 38
                 L 54 42
                 C 70 42, 78 55, 78 72
                 L 78 120
                 L 22 120
                 L 22 72
                 C 22 55, 30 42, 46 42
                 L 46 38
                 C 40 38, 38 32, 42 28
                 C 38 26, 38 20, 42 17
                 C 40 14, 42 10, 46 9
                 C 46 6, 48 4, 50 4 Z"
              fill="url(#ip-fill)" stroke="url(#ip-arch)" strokeWidth="1.2"/>
        {/* Inner trim */}
        <path d="M50 10
                 C 64 44, 72 56, 72 74
                 L 72 114
                 L 28 114
                 L 28 74
                 C 28 56, 36 44, 50 10 Z"
              fill="none" stroke="url(#ip-arch)" strokeWidth="0.4" opacity="0.6"/>
        {/* Star-crescent finial */}
        <circle cx="50" cy="2" r="1.2" fill="#e9c97a"/>
      </svg>
      <div style={{ position: 'relative', zIndex: 2, marginTop: size * 0.25 }}>{children}</div>
    </div>
  );
}

// ── Icon set (stroke = gold by default)
function IPIcon({ name, size = 22, color = IP_THEME.cream, stroke = 1.6 }) {
  const P = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth: stroke, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (name) {
    case 'home': return <svg {...P}><path d="M3 11l9-8 9 8"/><path d="M5 10v10h14V10"/></svg>;
    case 'search': return <svg {...P}><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>;
    case 'library': return <svg {...P}><path d="M4 4v16M9 4v16M14 4l5 1-3 15-5-1z"/></svg>;
    case 'profile': return <svg {...P}><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/></svg>;
    case 'play': return <svg {...P} fill={color} stroke="none"><path d="M7 4l14 8-14 8V4z"/></svg>;
    case 'pause': return <svg {...P} fill={color} stroke="none"><rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/></svg>;
    case 'next': return <svg {...P} fill={color} stroke="none"><path d="M5 4l10 8-10 8V4z"/><rect x="17" y="4" width="3" height="16" rx="1"/></svg>;
    case 'prev': return <svg {...P} fill={color} stroke="none"><path d="M19 4L9 12l10 8V4z"/><rect x="4" y="4" width="3" height="16" rx="1"/></svg>;
    case 'heart': return <svg {...P}><path d="M12 21s-7-4.35-7-10a4 4 0 017-2.65A4 4 0 0119 11c0 5.65-7 10-7 10z"/></svg>;
    case 'heart-fill': return <svg {...P} fill={color} stroke={color}><path d="M12 21s-7-4.35-7-10a4 4 0 017-2.65A4 4 0 0119 11c0 5.65-7 10-7 10z"/></svg>;
    case 'shuffle': return <svg {...P}><path d="M16 3h5v5M4 20L21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/></svg>;
    case 'repeat': return <svg {...P}><path d="M17 2l4 4-4 4M3 11V9a4 4 0 014-4h14M7 22l-4-4 4-4M21 13v2a4 4 0 01-4 4H3"/></svg>;
    case 'download': return <svg {...P}><path d="M12 3v13M6 12l6 6 6-6M4 21h16"/></svg>;
    case 'share': return <svg {...P}><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.6 13.5l6.8 4M15.4 6.5l-6.8 4"/></svg>;
    case 'more': return <svg {...P}><circle cx="5" cy="12" r="1.5" fill={color}/><circle cx="12" cy="12" r="1.5" fill={color}/><circle cx="19" cy="12" r="1.5" fill={color}/></svg>;
    case 'menu': return <svg {...P}><path d="M3 6h18M3 12h18M3 18h18"/></svg>;
    case 'back': return <svg {...P}><path d="M15 18l-6-6 6-6"/></svg>;
    case 'plus': return <svg {...P}><path d="M12 5v14M5 12h14"/></svg>;
    case 'mail': return <svg {...P}><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>;
    case 'lock': return <svg {...P}><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V7a4 4 0 018 0v4"/></svg>;
    case 'user': return <svg {...P}><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4.4 3.6-8 8-8s8 3.6 8 8"/></svg>;
    case 'eye': return <svg {...P}><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg>;
    case 'check': return <svg {...P}><path d="M4 12l5 5L20 6"/></svg>;
    case 'moon': return <svg {...P}><path d="M21 12.8A9 9 0 1111.2 3a7 7 0 009.8 9.8z"/></svg>;
    case 'queue': return <svg {...P}><path d="M3 6h13M3 12h13M3 18h9M17 14l5 3-5 3v-6z" fill={color} stroke={color}/></svg>;
    case 'settings': return <svg {...P}><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.7 1.7 0 00-1.8-.3 1.7 1.7 0 00-1 1.5V21a2 2 0 11-4 0v-.1a1.7 1.7 0 00-1.1-1.5 1.7 1.7 0 00-1.8.3l-.1.1a2 2 0 11-2.8-2.8l.1-.1a1.7 1.7 0 00.3-1.8 1.7 1.7 0 00-1.5-1H3a2 2 0 110-4h.1a1.7 1.7 0 001.5-1.1 1.7 1.7 0 00-.3-1.8l-.1-.1a2 2 0 112.8-2.8l.1.1a1.7 1.7 0 001.8.3h0a1.7 1.7 0 001-1.5V3a2 2 0 114 0v.1a1.7 1.7 0 001 1.5 1.7 1.7 0 001.8-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.7 1.7 0 00-.3 1.8V9a1.7 1.7 0 001.5 1H21a2 2 0 110 4h-.1a1.7 1.7 0 00-1.5 1z"/></svg>;
    case 'bell': return <svg {...P}><path d="M6 8a6 6 0 0112 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 004 0"/></svg>;
    case 'book': return <svg {...P}><path d="M4 4v16a2 2 0 002 2h14V6a2 2 0 00-2-2H6a2 2 0 00-2 2z"/><path d="M4 4a2 2 0 002 2h14"/></svg>;
    case 'clock': return <svg {...P}><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></svg>;
    case 'logout': return <svg {...P}><path d="M15 3h4a2 2 0 012 2v14a2 2 0 01-2 2h-4M10 17l-5-5 5-5M5 12h12"/></svg>;
    case 'mic': return <svg {...P}><rect x="9" y="3" width="6" height="12" rx="3"/><path d="M5 11a7 7 0 0014 0M12 18v3"/></svg>;
    case 'globe': return <svg {...P}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18"/></svg>;
    default: return null;
  }
}

// ── Gold button
function IPButton({ children, onClick, variant = 'primary', block = true, icon, style = {} }) {
  const base = {
    height: 52, borderRadius: 14, border: 'none',
    fontFamily: 'Inter, sans-serif', fontSize: 15, fontWeight: 600,
    letterSpacing: 0.3, cursor: 'pointer',
    display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
    width: block ? '100%' : 'auto', padding: '0 20px',
    transition: 'transform 0.1s, filter 0.2s',
  };
  const variants = {
    primary: {
      background: 'linear-gradient(180deg, #e9c97a 0%, #d4af6a 50%, #b8904a 100%)',
      color: '#17130d',
      boxShadow: '0 4px 20px rgba(212, 175, 106, 0.35), inset 0 1px 0 rgba(255,255,255,0.4)',
    },
    outline: {
      background: 'transparent',
      color: IP_THEME.cream,
      border: `1px solid ${IP_THEME.lineStrong}`,
    },
    ghost: {
      background: 'rgba(212, 175, 106, 0.08)',
      color: IP_THEME.goldLight,
    },
    danger: {
      background: 'rgba(201, 111, 74, 0.12)',
      color: IP_THEME.danger,
      border: `1px solid rgba(201, 111, 74, 0.3)`,
    },
  };
  return (
    <button onClick={onClick} style={{ ...base, ...variants[variant], ...style }}>
      {icon && <IPIcon name={icon} size={18} color={variant === 'primary' ? '#17130d' : IP_THEME.goldLight}/>}
      {children}
    </button>
  );
}

// ── Text field with icon
function IPField({ icon, type = 'text', placeholder, value, onChange, trailing }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center',
      background: 'rgba(212, 175, 106, 0.06)',
      border: `1px solid ${IP_THEME.line}`,
      borderRadius: 14, padding: '0 14px', height: 52,
    }}>
      {icon && <IPIcon name={icon} size={18} color={IP_THEME.goldLight}/>}
      <input
        type={type} placeholder={placeholder} value={value}
        onChange={e => onChange && onChange(e.target.value)}
        style={{
          flex: 1, background: 'transparent', border: 'none', outline: 'none',
          color: IP_THEME.cream, fontSize: 15, padding: '0 12px', fontFamily: 'Inter',
        }}
      />
      {trailing}
    </div>
  );
}

// ── Cover art (generative mihrab-styled tile for each surah/playlist)
function IPCover({ title = '', size = 56, radius = 12, hue = 0, reciter = '', cover }) {
  // Stable hue from title string
  const seed = [...title].reduce((a, c) => a + c.charCodeAt(0), 0) + hue;
  const h1 = (seed * 13) % 60;     // warm band
  // Image cover — render the real image
  if (cover) {
    return (
      <div style={{
        width: size, height: size, borderRadius: radius,
        background: `#1a160f url(${cover}) center/cover no-repeat`,
        position: 'relative', overflow: 'hidden', flexShrink: 0,
        border: `1px solid ${IP_THEME.line}`,
      }}/>
    );
  }
  return (
    <div style={{
      width: size, height: size, borderRadius: radius,
      background: `linear-gradient(135deg, hsl(${30 + h1 * 0.3} 40% 22%) 0%, hsl(${18 + h1 * 0.2} 50% 10%) 100%)`,
      position: 'relative', overflow: 'hidden', flexShrink: 0,
      border: `1px solid ${IP_THEME.line}`,
    }}>
      {/* Mini pattern */}
      <svg width="100%" height="100%" viewBox="0 0 60 60" style={{ position: 'absolute', inset: 0, opacity: 0.5 }}>
        <defs>
          <pattern id={`cov-${seed}`} width="20" height="20" patternUnits="userSpaceOnUse">
            <path d="M10 2l8 8-8 8-8-8z" fill="none" stroke="#d4af6a" strokeWidth="0.4"/>
            <circle cx="10" cy="10" r="1" fill="#d4af6a" opacity="0.6"/>
          </pattern>
        </defs>
        <rect width="60" height="60" fill={`url(#cov-${seed})`}/>
      </svg>
      {/* Center monogram */}
      <div style={{
        position: 'absolute', inset: 0, display: 'flex',
        alignItems: 'center', justifyContent: 'center',
        fontFamily: 'Amiri, serif', color: IP_THEME.goldLight,
        fontSize: size * 0.36, textShadow: '0 2px 6px rgba(0,0,0,0.6)',
        fontWeight: 700,
      }}>
        {title.charAt(0) || '﷽'}
      </div>
    </div>
  );
}

// ── Screen container — dark gradient with pattern
function IPScreen({ children, noPattern = false, style = {} }) {
  return (
    <div className="ip-scroll" style={{
      width: '100%', height: '100%', position: 'relative',
      background: `radial-gradient(ellipse at top, #2a2010 0%, ${IP_THEME.bg} 55%, #0b0906 100%)`,
      color: IP_THEME.cream,
      fontFamily: 'Inter, sans-serif',
      overflow: 'auto',
      ...style,
    }}>
      {!noPattern && <IPPattern opacity={0.05}/>}
      <div style={{ position: 'relative', zIndex: 1, width: '100%', minHeight: '100%' }}>
        {children}
      </div>
    </div>
  );
}

// ── Bottom tab bar
function IPTabBar({ active, onChange }) {
  const tabs = [
    { id: 'home', label: 'Главная', icon: 'home' },
    { id: 'search', label: 'Поиск', icon: 'search' },
    { id: 'library', label: 'Библиотека', icon: 'library' },
    { id: 'profile', label: 'Профиль', icon: 'profile' },
  ];
  return (
    <div style={{
      position: 'absolute', bottom: 0, left: 0, right: 0,
      height: `calc(72px + env(safe-area-inset-bottom, 0px))`,
      padding: `10px 16px env(safe-area-inset-bottom, 0px)`,
      background: 'linear-gradient(180deg, rgba(15,13,10,0) 0%, rgba(15,13,10,0.92) 40%, rgba(15,13,10,0.98) 100%)',
      backdropFilter: 'blur(14px)',
      borderTop: `1px solid ${IP_THEME.line}`,
      display: 'flex', alignItems: 'flex-start', justifyContent: 'space-around',
      zIndex: 20,
    }}>
      {tabs.map(t => (
        <button key={t.id} onClick={() => onChange(t.id)} style={{
          background: 'transparent', border: 'none', cursor: 'pointer',
          display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
          padding: '6px 8px',
          color: active === t.id ? IP_THEME.goldLight : IP_THEME.creamDim,
          fontFamily: 'Inter', fontSize: 11, letterSpacing: 0.3, fontWeight: 500,
        }}>
          <IPIcon name={t.icon} size={22} color={active === t.id ? IP_THEME.goldLight : IP_THEME.creamDim}/>
          {t.label}
        </button>
      ))}
    </div>
  );
}

// ── Mini player pinned above tab bar
function IPMiniPlayer({ track, playing, onPlay, onOpen, progress }) {
  if (!track) return null;
  return (
    <div onClick={onOpen} style={{
      position: 'absolute', left: 8, right: 8,
      bottom: `calc(72px + 4px + env(safe-area-inset-bottom, 0px))`,
      height: 58, borderRadius: 12,
      background: 'linear-gradient(180deg, rgba(58, 46, 26, 0.85) 0%, rgba(34, 28, 18, 0.95) 100%)',
      backdropFilter: 'blur(14px)',
      border: `1px solid ${IP_THEME.line}`,
      display: 'flex', alignItems: 'center', padding: '0 10px',
      cursor: 'pointer', zIndex: 15,
      boxShadow: '0 10px 30px rgba(0,0,0,0.35)',
    }}>
      <IPCover title={track.title} size={42} radius={8}/>
      <div style={{ flex: 1, marginLeft: 10, minWidth: 0 }}>
        <div style={{
          fontSize: 13, fontWeight: 600, color: IP_THEME.cream,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{track.title}</div>
        <div style={{
          fontSize: 11, color: IP_THEME.creamDim,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{track.reciter}</div>
      </div>
      <button onClick={e => { e.stopPropagation(); onPlay(); }} style={{
        background: 'transparent', border: 'none', cursor: 'pointer', padding: 8,
      }}>
        <IPIcon name={playing ? 'pause' : 'play'} size={22} color={IP_THEME.goldLight}/>
      </button>
      {/* progress line */}
      <div style={{
        position: 'absolute', left: 10, right: 10, bottom: 4, height: 2,
        background: 'rgba(212, 175, 106, 0.15)', borderRadius: 2,
      }}>
        <div style={{
          width: `${progress * 100}%`, height: '100%',
          background: IP_THEME.gold, borderRadius: 2, transition: 'width 0.2s',
        }}/>
      </div>
    </div>
  );
}

// ── Top bar with greeting + actions
function IPTopBar({ title, subtitle, right, backAction }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: `calc(20px + env(safe-area-inset-top, 0px)) 20px 16px`, gap: 12,
    }}>
      {backAction && (
        <button onClick={backAction} style={{
          width: 36, height: 36, borderRadius: 12,
          background: 'rgba(212, 175, 106, 0.08)',
          border: `1px solid ${IP_THEME.line}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', flexShrink: 0,
        }}>
          <IPIcon name="back" size={18} color={IP_THEME.goldLight}/>
        </button>
      )}
      <div style={{ flex: 1, minWidth: 0 }}>
        {subtitle && <div style={{
          fontSize: 11, color: IP_THEME.creamDim,
          letterSpacing: 0.14 + 'em', textTransform: 'uppercase', fontWeight: 500,
        }}>{subtitle}</div>}
        <div style={{
          fontFamily: 'Cormorant Garamond, serif', fontWeight: 600,
          fontSize: 26, color: IP_THEME.cream, marginTop: 2,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{title}</div>
      </div>
      {right}
    </div>
  );
}

// ── Bottom sheet (modal) — used for prompts & lists.
// Rendered via portal to #ip-sheet-root (sits on top of everything inside the app canvas).
function IPSheet({ open, onClose, title, children }) {
  if (!open) return null;
  const root = (typeof document !== 'undefined') ? document.getElementById('ip-sheet-root') : null;
  const content = (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 9999,
      background: 'rgba(0,0,0,0.65)',
      display: 'flex', alignItems: 'flex-end',
      animation: 'ipFade 0.18s ease-out',
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: '100%',
        background: '#1a160f',
        borderTop: `1px solid ${IP_THEME.lineStrong}`,
        borderRadius: '20px 20px 0 0',
        padding: `20px 20px calc(20px + env(safe-area-inset-bottom, 0px))`,
        maxHeight: '85%', overflowY: 'auto',
        animation: 'ipSlideUp 0.22s ease-out',
        boxShadow: '0 -20px 60px rgba(0,0,0,0.5)',
      }}>
        <div style={{
          width: 36, height: 4, background: IP_THEME.lineStrong,
          borderRadius: 2, margin: '0 auto 14px',
        }}/>
        {title && (
          <div style={{
            fontFamily: 'Cormorant Garamond, serif', fontSize: 20, fontWeight: 600,
            color: IP_THEME.cream, marginBottom: 14, textAlign: 'center',
          }}>{title}</div>
        )}
        {children}
      </div>
      <style>{`@keyframes ipFade { from { opacity: 0; } to { opacity: 1; } }`}</style>
    </div>
  );
  return root ? ReactDOM.createPortal(content, root) : content;
}

// Prompt sheet — text input + Cancel/OK
function IPPrompt({ open, title, placeholder, defaultValue, onCancel, onSubmit, submitLabel = 'OK' }) {
  const [val, setVal] = React.useState(defaultValue || '');
  React.useEffect(() => { if (open) setVal(defaultValue || ''); }, [open]);
  return (
    <IPSheet open={open} onClose={onCancel} title={title}>
      <input autoFocus value={val} onChange={e => setVal(e.target.value)} placeholder={placeholder}
        style={{
          width: '100%', height: 48, borderRadius: 12,
          background: 'rgba(212, 175, 106, 0.06)',
          border: `1px solid ${IP_THEME.lineStrong}`,
          color: IP_THEME.cream, fontSize: 15,
          padding: '0 14px', outline: 'none',
          fontFamily: 'Inter, sans-serif',
          marginBottom: 14,
        }}/>
      <div style={{ display: 'flex', gap: 10 }}>
        <IPButton variant="outline" onClick={onCancel}>Отмена</IPButton>
        <IPButton onClick={() => onSubmit(val.trim())} style={{ opacity: val.trim() ? 1 : 0.5 }}>{submitLabel}</IPButton>
      </div>
    </IPSheet>
  );
}

// Confirm sheet — two buttons
function IPConfirm({ open, title, message, onCancel, onConfirm, confirmLabel = 'Да', danger }) {
  return (
    <IPSheet open={open} onClose={onCancel} title={title}>
      {message && <div style={{ fontSize: 14, color: IP_THEME.creamMute, textAlign: 'center', marginBottom: 14 }}>{message}</div>}
      <div style={{ display: 'flex', gap: 10 }}>
        <IPButton variant="outline" onClick={onCancel}>Отмена</IPButton>
        <IPButton variant={danger ? 'danger' : 'primary'} onClick={onConfirm}>{confirmLabel}</IPButton>
      </div>
    </IPSheet>
  );
}

Object.assign(window, {
  IP_THEME, IPPattern, IPMihrab, IPIcon, IPButton, IPField, IPCover,
  IPScreen, IPTabBar, IPMiniPlayer, IPTopBar, IPSheet, IPPrompt, IPConfirm,
});
