// ui.jsx — Shared UI primitives for ImmoBot
// Icons, buttons, cards, badges, score visualization, bottom nav, top bar

// ─── Icons (line, 24px viewbox, 1.7 stroke) ──────────────────────────────
const I = {
  home: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 11l9-8 9 8"/><path d="M5 10v10a1 1 0 001 1h12a1 1 0 001-1V10"/><path d="M9 21v-6h6v6"/></svg>,
  search: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>,
  key: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="8" cy="15" r="4"/><path d="M10.85 12.15L19 4"/><path d="M18 5l3 3"/><path d="M15 8l3 3"/></svg>,
  bookmark: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2v16z"/></svg>,
  bookmarkFill: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M19 21l-7-5-7 5V5a2 2 0 012-2h10a2 2 0 012 2v16z"/></svg>,
  bell: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M18 8a6 6 0 10-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 01-3.4 0"/></svg>,
  user: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-7 8-7s8 3 8 7"/></svg>,
  heart: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>,
  heartFill: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" {...p}><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>,
  archive: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="2" y="4" width="20" height="5" rx="1"/><path d="M4 9v11a1 1 0 001 1h14a1 1 0 001-1V9"/><path d="M10 13h4"/></svg>,
  share: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 16V4"/><path d="M7 9l5-5 5 5"/><path d="M5 20h14"/></svg>,
  filter: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 6h18M6 12h12M10 18h4"/></svg>,
  sort: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 6h12M3 12h8M3 18h4"/><path d="M17 4v16M14 17l3 3 3-3"/></svg>,
  chevR: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9 6l6 6-6 6"/></svg>,
  chevL: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M15 6l-6 6 6 6"/></svg>,
  chevD: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M6 9l6 6 6-6"/></svg>,
  x: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M18 6L6 18M6 6l12 12"/></svg>,
  plus: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 5v14M5 12h14"/></svg>,
  check: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M5 13l4 4L19 7"/></svg>,
  spark: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1"/></svg>,
  brain: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 5a3 3 0 00-3-3 3 3 0 00-3 3 3 3 0 00-3 3 3 3 0 001 2.2A3 3 0 003 13a3 3 0 003 3 3 3 0 003 3 3 3 0 003-3V5z"/><path d="M12 5a3 3 0 013-3 3 3 0 013 3 3 3 0 013 3 3 3 0 01-1 2.2A3 3 0 0121 13a3 3 0 01-3 3 3 3 0 01-3 3 3 3 0 01-3-3V5z"/></svg>,
  pin: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 22s-7-7-7-12a7 7 0 0114 0c0 5-7 12-7 12z"/><circle cx="12" cy="10" r="2.5"/></svg>,
  bed: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 18v-7a2 2 0 012-2h14a2 2 0 012 2v7"/><path d="M3 14h18"/><path d="M3 21v-3M21 21v-3"/><circle cx="8" cy="11.5" r="1.5"/></svg>,
  ruler: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M2 8l14 14 6-6L8 2 2 8z"/><path d="M7.5 7.5l2 2M11.5 5.5l1 1M5.5 11.5l1 1M9.5 13.5l2 2M13.5 11.5l2 2"/></svg>,
  calendar: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="5" width="18" height="16" rx="2"/><path d="M3 10h18M8 3v4M16 3v4"/></svg>,
  euro: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M19 5a8 8 0 100 14"/><path d="M3 10h10M3 14h10"/></svg>,
  refresh: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M21 12a9 9 0 11-3-6.7"/><path d="M21 4v5h-5"/></svg>,
  download: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 4v12"/><path d="M7 11l5 5 5-5"/><path d="M5 20h14"/></svg>,
  trash: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M4 7h16M9 7V4h6v3M6 7v13a1 1 0 001 1h10a1 1 0 001-1V7M10 11v6M14 11v6"/></svg>,
  more: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" {...p}><circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/></svg>,
  grid: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>,
  list: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 6h18M3 12h18M3 18h18"/></svg>,
  swipe: (p={}) => <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9 18l4-12 4 12"/><path d="M11 14h4"/><path d="M3 18l3-3M21 18l-3-3"/></svg>,
  warn: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 2L2 21h20L12 2z"/><path d="M12 9v5M12 18v.5"/></svg>,
  trend: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 17l6-6 4 4 8-8"/><path d="M14 7h7v7"/></svg>,
  zap: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M13 2L4 14h7l-1 8 9-12h-7l1-8z"/></svg>,
  link: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M10 14a5 5 0 007 0l3-3a5 5 0 00-7-7l-1 1"/><path d="M14 10a5 5 0 00-7 0l-3 3a5 5 0 007 7l1-1"/></svg>,
  message: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M21 12a8 8 0 01-8 8c-1.6 0-3.1-.5-4.4-1.3L3 21l1.5-5.6A8 8 0 1121 12z"/></svg>,
  globe: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 010 18M12 3a14 14 0 000 18"/></svg>,
  flame: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 3s4 4 4 9a4 4 0 11-8 0c0-2 2-3 2-5s-2-3 2-4z"/></svg>,
  shield: (p={}) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M12 2l9 4v6c0 5-4 9-9 10C7 21 3 17 3 12V6l9-4z"/></svg>,
  settings: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...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 01-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 01-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 012.8-2.8l.1.1a1.7 1.7 0 001.8.3h.1A1.7 1.7 0 0010 3V3a2 2 0 014 0v.1a1.7 1.7 0 001 1.5 1.7 1.7 0 001.8-.3l.1-.1a2 2 0 012.8 2.8l-.1.1a1.7 1.7 0 00-.3 1.8v.1a1.7 1.7 0 001.5 1H21a2 2 0 010 4h-.1a1.7 1.7 0 00-1.5 1z"/></svg>,
  logout: (p={}) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4"/><path d="M16 17l5-5-5-5"/><path d="M21 12H9"/></svg>,
};

// ─── Platform badges ────────────────────────────────────────────────────
function PlatformBadge({ platform, size = 'sm' }) {
  // Stylized — we draw our own monograms; not the actual brand logos
  const fontSize = size === 'lg' ? 11 : 10;
  const pad = size === 'lg' ? '5px 10px' : '3px 8px';
  if (platform === 'immoscout24') {
    return (
      <span style={{
        display: 'inline-flex', alignItems: 'center', gap: 5,
        background: '#E8F4EA', color: '#1F6B3A',
        padding: pad, borderRadius: 999, fontSize, fontWeight: 700, letterSpacing: 0.2,
      }}>
        <span style={{ width: 6, height: 6, borderRadius: 999, background: '#1F6B3A' }} />
        Scout24
      </span>
    );
  }
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 5,
      background: '#FAF1DD', color: '#8A6418',
      padding: pad, borderRadius: 999, fontSize, fontWeight: 700, letterSpacing: 0.2,
    }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, background: '#C28428' }} />
      Kleinanz.
    </span>
  );
}

// ─── Score Ring (semi-circular with KPIs) ───────────────────────────────
function ScoreRing({ score, size = 96, stroke = 8, showGrade = true }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const pct = score / 100;
  const color = score >= 80 ? '#2F8F5A' : score >= 60 ? '#C28428' : '#C8331C';
  const grade = score >= 90 ? 'A+' : score >= 80 ? 'A' : score >= 70 ? 'B' : score >= 60 ? 'C' : score >= 50 ? 'D' : 'F';
  return (
    <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="#ECE6DD" strokeWidth={stroke} />
        <circle
          cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth={stroke}
          strokeDasharray={c} strokeDashoffset={c * (1 - pct)} strokeLinecap="round"
          style={{ transition: 'stroke-dashoffset .8s cubic-bezier(.4,.0,.2,1)' }}
        />
      </svg>
      <div style={{
        position: 'absolute', inset: 0,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        lineHeight: 1,
      }}>
        <div style={{ fontSize: size * 0.32, fontWeight: 700, color: 'var(--ink)', letterSpacing: -0.02 }}>{score}</div>
        {showGrade && (
          <div style={{ fontSize: size * 0.11, fontWeight: 700, color, marginTop: 4, letterSpacing: 0.05 }}>
            {grade} · /100
          </div>
        )}
      </div>
    </div>
  );
}

// ─── Score Bar (horizontal, for compact spots) ──────────────────────────
function ScoreBar({ score, label, note }) {
  const color = score >= 80 ? '#2F8F5A' : score >= 60 ? '#C28428' : '#C8331C';
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <span style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>{label}</span>
        <span style={{ fontSize: 13, fontWeight: 700, color, fontVariantNumeric: 'tabular-nums' }}>{score}</span>
      </div>
      <div style={{ height: 6, background: '#ECE6DD', borderRadius: 999, overflow: 'hidden' }}>
        <div style={{ width: `${score}%`, height: '100%', background: color, borderRadius: 999, transition: 'width .8s ease' }} />
      </div>
      {note && <div style={{ fontSize: 12, color: 'var(--ink-muted)', marginTop: -2 }}>{note}</div>}
    </div>
  );
}

// ─── Photo Slider (horizontal swipeable image gallery) ─────────────────
function PhotoSlider({ photos, height = 240, rounded = 0, onPhotoTap }) {
  const [idx, setIdx] = React.useState(0);
  const startX = React.useRef(null);
  const handleStart = (e) => { startX.current = (e.touches ? e.touches[0].clientX : e.clientX); };
  const handleEnd = (e) => {
    if (startX.current == null) return;
    const endX = (e.changedTouches ? e.changedTouches[0].clientX : e.clientX);
    const d = endX - startX.current;
    if (Math.abs(d) > 40) {
      if (d < 0 && idx < photos.length - 1) setIdx(idx + 1);
      if (d > 0 && idx > 0) setIdx(idx - 1);
    }
    startX.current = null;
  };
  return (
    <div
      style={{ position: 'relative', width: '100%', height, overflow: 'hidden', borderRadius: rounded, background: '#E8E0D2' }}
      onTouchStart={handleStart} onTouchEnd={handleEnd}
      onMouseDown={handleStart} onMouseUp={handleEnd}
      onClick={onPhotoTap}
    >
      <div style={{ display: 'flex', width: `${photos.length * 100}%`, height: '100%', transform: `translateX(-${idx * (100/photos.length)}%)`, transition: 'transform .3s cubic-bezier(.4,.0,.2,1)' }}>
        {photos.map((src, i) => (
          <div key={i} className="photo" style={{ width: `${100/photos.length}%`, height: '100%', backgroundImage: `url(${src})` }} />
        ))}
      </div>
      {/* dots */}
      <div style={{
        position: 'absolute', bottom: 10, left: 0, right: 0, zIndex: 2,
        display: 'flex', justifyContent: 'center', gap: 5,
      }}>
        {photos.map((_, i) => (
          <div key={i} style={{
            width: idx === i ? 18 : 6, height: 6, borderRadius: 999,
            background: idx === i ? '#fff' : 'rgba(255,255,255,.55)',
            transition: 'width .2s ease',
          }} />
        ))}
      </div>
      {/* photo count */}
      <div style={{
        position: 'absolute', top: 12, right: 12, zIndex: 2,
        background: 'rgba(0,0,0,.55)', color: '#fff',
        fontSize: 11, fontWeight: 600, padding: '4px 8px', borderRadius: 999,
        backdropFilter: 'blur(8px)',
      }}>
        {idx + 1} / {photos.length}
      </div>
    </div>
  );
}

// ─── Listing Card (used in lists, dashboard, results) ──────────────────
function ListingCard({ listing, onTap, isFav, onFav, compact = false }) {
  const isKauf = listing.type === 'kauf';
  const priceStr = isKauf ? fmtPrice(listing.price) : fmtPrice(listing.price) + ' /Mt.';
  const aiScore = listing.aiScore;
  return (
    <div
      className="card fade-in" style={{ overflow: 'hidden', cursor: 'pointer' }}
      onClick={onTap}
    >
      <div style={{ position: 'relative' }}>
        <PhotoSlider photos={listing.photos} height={compact ? 168 : 208} />
        <div style={{
          position: 'absolute', top: 12, left: 12, zIndex: 3,
          display: 'flex', gap: 6, alignItems: 'center',
        }}>
          <PlatformBadge platform={listing.platform} />
          {listing.provisionsfrei && (
            <span style={{
              background: 'rgba(255,255,255,.92)', color: 'var(--ink)',
              padding: '3px 8px', borderRadius: 999, fontSize: 10, fontWeight: 700,
              backdropFilter: 'blur(6px)',
            }}>PROVISIONSFREI</span>
          )}
        </div>
        {aiScore && (
          <div style={{
            position: 'absolute', bottom: 12, left: 12, zIndex: 3,
            background: '#fff', borderRadius: 999, padding: '4px 4px 4px 4px',
            display: 'flex', alignItems: 'center', gap: 8,
            boxShadow: '0 4px 12px rgba(0,0,0,.15)',
          }}>
            <div style={{
              width: 28, height: 28, borderRadius: 999,
              background: aiScore >= 80 ? '#2F8F5A' : aiScore >= 60 ? '#C28428' : '#C8331C',
              color: '#fff', fontSize: 12, fontWeight: 800,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>{aiScore}</div>
            <span style={{ fontSize: 11, fontWeight: 700, color: 'var(--ink)', paddingRight: 10, letterSpacing: 0.02 }}>KI-Score</span>
          </div>
        )}
        <button
          onClick={(e) => { e.stopPropagation(); onFav && onFav(listing.id); }}
          style={{
            position: 'absolute', top: 10, right: 10, zIndex: 3,
            width: 38, height: 38, borderRadius: 999, border: 0,
            background: 'rgba(255,255,255,.92)',
            backdropFilter: 'blur(8px)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            cursor: 'pointer', color: isFav ? '#C8331C' : 'var(--ink)',
          }}
        >
          {isFav ? <I.heartFill /> : <I.heart />}
        </button>
      </div>
      <div style={{ padding: '14px 16px 16px' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'baseline' }}>
          <div style={{ fontFamily: 'var(--font-display)', fontSize: 22, fontWeight: 400, color: 'var(--ink)', lineHeight: 1.1, letterSpacing: -0.01 }}>
            {priceStr}
          </div>
          {isKauf && (
            <div style={{ fontSize: 11, color: 'var(--ink-muted)', fontWeight: 600 }}>
              {Math.round(listing.price / listing.area).toLocaleString('de-DE')} €/m²
            </div>
          )}
        </div>
        <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--ink)', marginTop: 6, lineHeight: 1.3, letterSpacing: -0.01 }}>
          {listing.title}
        </div>
        <div style={{ fontSize: 13, color: 'var(--ink-muted)', marginTop: 4, display: 'flex', alignItems: 'center', gap: 4 }}>
          <I.pin style={{ width: 13, height: 13, opacity: .7 }} /> {listing.district}, {listing.city}
        </div>
        <div style={{ display: 'flex', gap: 14, marginTop: 10, color: 'var(--ink-2)', fontSize: 13, fontWeight: 500 }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 4 }}><I.ruler style={{ width: 14, height: 14, opacity: .7 }} /> {fmtArea(listing.area)}</span>
          <span style={{ display: 'flex', alignItems: 'center', gap: 4 }}><I.bed style={{ width: 14, height: 14, opacity: .7 }} /> {listing.rooms} Zi.</span>
          <span style={{ display: 'flex', alignItems: 'center', gap: 4 }}><I.calendar style={{ width: 14, height: 14, opacity: .7 }} /> {listing.baujahr}</span>
        </div>
      </div>
    </div>
  );
}

// ─── Compact Listing Row (for dashboard "best deals") ──────────────────
function ListingRow({ listing, onTap, rank }) {
  return (
    <div
      onClick={onTap}
      style={{
        display: 'flex', gap: 12, padding: '10px',
        background: 'var(--surface)', borderRadius: 'var(--r-md)',
        cursor: 'pointer', alignItems: 'center',
      }}
    >
      {rank != null && (
        <div style={{
          fontFamily: 'var(--font-display)', fontSize: 22, fontWeight: 400,
          width: 26, textAlign: 'center', color: 'var(--ink-faint)', flexShrink: 0,
        }}>{rank}</div>
      )}
      <div className="photo" style={{
        width: 72, height: 72, borderRadius: 12, flexShrink: 0,
        backgroundImage: `url(${listing.photos[0]})`,
      }} />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
          {listing.title}
        </div>
        <div style={{ fontSize: 12, color: 'var(--ink-muted)', marginTop: 2 }}>
          {listing.district}, {listing.city} · {fmtArea(listing.area)} · {listing.rooms} Zi.
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 6, gap: 8 }}>
          <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--ink)', whiteSpace: 'nowrap' }}>{fmtPriceShort(listing.price)}</div>
          {listing.aiScore && (
            <div style={{
              fontSize: 11, fontWeight: 800, color: '#fff',
              background: listing.aiScore >= 80 ? '#2F8F5A' : listing.aiScore >= 60 ? '#C28428' : '#C8331C',
              padding: '2px 7px', borderRadius: 999,
            }}>{listing.aiScore}</div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Bottom Nav ─────────────────────────────────────────────────────────
function BottomNav({ active, onTab, notifBadge = 0 }) {
  const items = [
    { id: 'immobilien',    label: 'Kaufen',   icon: <I.home /> },
    { id: 'mieten',        label: 'Mieten',   icon: <I.key /> },
    { id: 'portfolio',     label: 'Merkliste',icon: <I.bookmark /> },
    { id: 'notifications', label: 'Mitteil.', icon: <I.bell />, badge: notifBadge },
    { id: 'profile',       label: 'Profil',   icon: <I.user /> },
  ];
  return (
    <div style={{
      position: 'absolute', bottom: 0, left: 0, right: 0, zIndex: 40,
      paddingBottom: 28, paddingTop: 8,
      background: 'rgba(255,255,255,.92)',
      backdropFilter: 'blur(20px) saturate(180%)',
      WebkitBackdropFilter: 'blur(20px) saturate(180%)',
      borderTop: '0.5px solid var(--border)',
      display: 'flex', justifyContent: 'space-around', alignItems: 'flex-start',
    }}>
      {items.map(it => (
        <button
          key={it.id}
          onClick={() => onTab(it.id)}
          style={{
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
            background: 'transparent', border: 0, cursor: 'pointer', padding: '6px 10px',
            color: active === it.id ? 'var(--ink)' : 'var(--ink-faint)',
            position: 'relative',
          }}
        >
          <div style={{ position: 'relative' }}>
            {it.icon}
            {it.badge > 0 && (
              <div style={{
                position: 'absolute', top: -4, right: -6,
                background: 'var(--danger)', color: '#fff',
                fontSize: 9, fontWeight: 800,
                minWidth: 16, height: 16, padding: '0 4px', borderRadius: 999,
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                boxShadow: '0 0 0 2px var(--surface)',
              }}>{it.badge}</div>
            )}
          </div>
          <span style={{ fontSize: 10, fontWeight: 600, letterSpacing: -0.01 }}>{it.label}</span>
        </button>
      ))}
    </div>
  );
}

// ─── Top Bar / Header ───────────────────────────────────────────────────
function TopBar({ title, onBack, trailing, large = false, transparent = false, dark = false }) {
  return (
    <div style={{
      position: 'sticky', top: 0, zIndex: 20,
      background: transparent ? 'transparent' : 'rgba(250,247,242,.92)',
      backdropFilter: transparent ? 'none' : 'blur(20px) saturate(180%)',
      WebkitBackdropFilter: transparent ? 'none' : 'blur(20px) saturate(180%)',
      borderBottom: transparent ? 'none' : '0.5px solid var(--border)',
    }}>
      {/* Status bar safe area (handled by iOS frame) */}
      <div style={{ height: 52 }} />
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '8px 16px 12px', minHeight: 44,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 4, minWidth: 44 }}>
          {onBack && (
            <button onClick={onBack} style={{
              width: 36, height: 36, borderRadius: 999, border: 0,
              background: transparent ? 'rgba(255,255,255,.85)' : 'var(--surface)',
              boxShadow: '0 1px 4px rgba(0,0,0,.06)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              cursor: 'pointer', color: dark ? '#fff' : 'var(--ink)',
            }}>
              <I.chevL />
            </button>
          )}
        </div>
        {!large && (
          <div style={{ fontSize: 17, fontWeight: 700, color: dark ? '#fff' : 'var(--ink)', letterSpacing: -0.01 }}>
            {title}
          </div>
        )}
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 44, justifyContent: 'flex-end' }}>
          {trailing}
        </div>
      </div>
      {large && (
        <div style={{ padding: '0 20px 14px' }}>
          <h1 style={{
            margin: 0, fontFamily: 'var(--font-display)', fontWeight: 400,
            fontSize: 38, lineHeight: 1.0, letterSpacing: -0.02, color: dark ? '#fff' : 'var(--ink)',
          }}>{title}</h1>
        </div>
      )}
    </div>
  );
}

// ─── Empty State ────────────────────────────────────────────────────────
function EmptyState({ icon, title, subtitle, cta }) {
  return (
    <div style={{ padding: '60px 32px', textAlign: 'center' }}>
      <div style={{
        width: 64, height: 64, borderRadius: 999,
        background: 'var(--primary-soft)', color: 'var(--primary)',
        margin: '0 auto 16px', display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>{icon}</div>
      <h3 style={{ margin: 0, fontFamily: 'var(--font-display)', fontWeight: 400, fontSize: 24, letterSpacing: -0.01, color: 'var(--ink)' }}>
        {title}
      </h3>
      {subtitle && (
        <p style={{ margin: '8px 0 16px', fontSize: 14, color: 'var(--ink-muted)', lineHeight: 1.5 }}>
          {subtitle}
        </p>
      )}
      {cta}
    </div>
  );
}

// ─── Sheet (modal sheet from bottom) ───────────────────────────────────
function Sheet({ open, onClose, children, title, height = '78%' }) {
  if (!open) return null;
  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 100,
      display: 'flex', flexDirection: 'column', justifyContent: 'flex-end',
    }}>
      <div
        onClick={onClose}
        style={{
          position: 'absolute', inset: 0, background: 'rgba(20,16,11,.5)',
          animation: 'fadeIn .2s ease both',
        }}
      />
      <div style={{
        position: 'relative', zIndex: 2,
        background: 'var(--bg)', borderRadius: '24px 24px 0 0',
        height, display: 'flex', flexDirection: 'column',
        animation: 'sheetUp .3s cubic-bezier(.4,.0,.2,1) both',
        boxShadow: '0 -8px 32px rgba(0,0,0,.2)',
      }}>
        <div style={{ display: 'flex', justifyContent: 'center', padding: '10px 0 6px' }}>
          <div style={{ width: 40, height: 5, borderRadius: 999, background: 'var(--border-strong)' }} />
        </div>
        {title && (
          <div style={{
            display: 'flex', justifyContent: 'space-between', alignItems: 'center',
            padding: '6px 20px 12px',
          }}>
            <h3 style={{ margin: 0, fontSize: 18, fontWeight: 700, color: 'var(--ink)', letterSpacing: -0.01 }}>{title}</h3>
            <button onClick={onClose} style={{
              width: 32, height: 32, borderRadius: 999, border: 0,
              background: 'var(--surface-2)', cursor: 'pointer',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              color: 'var(--ink-muted)',
            }}><I.x /></button>
          </div>
        )}
        <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>{children}</div>
      </div>
      <style>{`@keyframes sheetUp { from { transform: translateY(100%) } to { transform: none } }`}</style>
    </div>
  );
}

// ─── Stat Tile ──────────────────────────────────────────────────────────
function StatTile({ value, label, delta, accent = false, icon }) {
  return (
    <div style={{
      background: accent ? 'var(--ink)' : 'var(--surface)',
      color: accent ? '#fff' : 'var(--ink)',
      padding: '14px 14px 16px', borderRadius: 'var(--r-md)',
      flex: 1, minWidth: 0,
    }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
        <div style={{ fontSize: 11, fontWeight: 600, color: accent ? 'rgba(255,255,255,.65)' : 'var(--ink-muted)', textTransform: 'uppercase', letterSpacing: 0.05 }}>
          {label}
        </div>
        {icon && <div style={{ opacity: .55 }}>{icon}</div>}
      </div>
      <div style={{ fontFamily: 'var(--font-display)', fontSize: 30, fontWeight: 400, letterSpacing: -0.02, lineHeight: 1, marginTop: 8 }}>
        {value}
      </div>
      {delta && (
        <div style={{ fontSize: 11, marginTop: 6, color: accent ? 'rgba(255,255,255,.7)' : 'var(--ink-muted)', fontWeight: 500 }}>
          {delta}
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  I, PlatformBadge, ScoreRing, ScoreBar, PhotoSlider,
  ListingCard, ListingRow, BottomNav, TopBar, EmptyState, Sheet, StatTile,
});
