// data.jsx — fictional apps + procedural icon generator
// All app names, developers, and icon visuals are original.

// ─── Supabase client ──────────────────────────────────────────────────────
const SUPABASE_URL = 'https://ordurpiskylnhbbdpdkk.supabase.co';
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9yZHVycGlza3lsbmhiYmRwZGtrIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzgyOTU5OTYsImV4cCI6MjA5Mzg3MTk5Nn0.9-uCsw6QC9dx1C-gWkaCWItnltXc7IdQvBGbk_I9JLM';
const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

async function fetchApps() {
  const { data, error } = await supabase.from('apps').select('*').order('rating', { ascending: false });
  return error ? null : data;
}

async function fetchCollections() {
  const { data, error } = await supabase.from('collections').select('*');
  return error ? null : data.map(c => ({ ...c, appIds: c.app_ids }));
}

async function toggleFavorite(appId, isFavorited) {
  if (isFavorited) {
    const { error } = await supabase.from('favorites').delete().eq('app_id', appId);
    return !error;
  } else {
    const { error } = await supabase.from('favorites').insert({ app_id: appId });
    return !error;
  }
}

async function fetchFavorites() {
  const { data, error } = await supabase.from('favorites').select('app_id');
  return error ? [] : data.map(f => f.app_id);
}

// ─── Icon palettes (curated, oklch-tuned) ─────────────────────────────────
const ICON_PALETTES = {
  midnight: ['#0b0d12', '#1a1d26', '#2b3040'],
  paper:    ['#f5f1ea', '#e8e2d6', '#cdc4b1'],
  ember:    ['#ff5b3b', '#c73a1f', '#ffd1c2'],
  amber:    ['#f5a623', '#c97f10', '#fde7be'],
  citrus:   ['#d4e34a', '#7a9a1f', '#f3f7c8'],
  moss:     ['#2f6b3d', '#173d23', '#9ec9a3'],
  jade:     ['#22b07a', '#0d6a48', '#a3e6cd'],
  sky:      ['#5ab1ff', '#1d63c2', '#cfe4ff'],
  ocean:    ['#1e3a8a', '#0a1b4a', '#5d7bd6'],
  ultra:    ['#5b3df5', '#2a1abf', '#c5b6fb'],
  orchid:   ['#a955d6', '#5d2080', '#e7c8f3'],
  rose:     ['#ff5d8f', '#b3214d', '#ffd1de'],
  graphite: ['#3a3a3a', '#1a1a1a', '#8a8a8a'],
  cream:    ['#fff8e8', '#e7d9b5', '#3a2e16'],
  ink:      ['#0f1115', '#fafafa', '#7a8190'],
};

const PALETTE_KEYS = Object.keys(ICON_PALETTES);

// ─── Procedural icon renderer ─────────────────────────────────────────────
// Each app gets a `style` token that picks a deterministic SVG composition.
// Visuals are abstract / geometric — no real-app logos.

function IconArt({ app, size = 120, radius = 'continuous' }) {
  const r = radius === 'circle' ? size / 2 : radius === 'square' ? 0 : size * 0.225;

  // Real App Store icon — render as image
  if (app.icon_url) {
    return (
      <img src={app.icon_url} width={size} height={size} alt={app.app_name}
        style={{ display: 'block', borderRadius: r, objectFit: 'cover' }} />
    );
  }

  const [c1, c2, c3] = ICON_PALETTES[app.palette] || ICON_PALETTES.paper;
  const id = `g_${app.id}`;
  const clipId = `clip_${app.id}`;

  const renderInside = () => {
    switch (app.style) {
      case 'glyph':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <text x="50%" y="50%" textAnchor="middle" dominantBaseline="central"
              fontFamily="Geist, ui-sans-serif" fontWeight="700"
              fontSize={size * 0.52} fill={c2} letterSpacing="-0.04em">
              {app.glyph || app.app_name[0]}
            </text>
          </React.Fragment>
        );
      case 'mono-glyph':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <text x="50%" y="50%" textAnchor="middle" dominantBaseline="central"
              fontFamily="Geist Mono, ui-monospace" fontWeight="500"
              fontSize={size * 0.46} fill={c3}>
              {app.glyph || app.app_name[0].toLowerCase()}
            </text>
          </React.Fragment>
        );
      case 'gradient-disc':
        return (
          <React.Fragment>
            <defs>
              <radialGradient id={id} cx="30%" cy="25%" r="90%">
                <stop offset="0%" stopColor={c3} />
                <stop offset="60%" stopColor={c1} />
                <stop offset="100%" stopColor={c2} />
              </radialGradient>
            </defs>
            <rect width={size} height={size} fill={`url(#${id})`} />
            <circle cx={size * 0.5} cy={size * 0.5} r={size * 0.22} fill={c3} opacity="0.92" />
          </React.Fragment>
        );
      case 'gradient-flat':
        return (
          <React.Fragment>
            <defs>
              <linearGradient id={id} x1="0" y1="0" x2="1" y2="1">
                <stop offset="0%" stopColor={c1} />
                <stop offset="100%" stopColor={c2} />
              </linearGradient>
            </defs>
            <rect width={size} height={size} fill={`url(#${id})`} />
          </React.Fragment>
        );
      case 'orb':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c2} />
            <defs>
              <radialGradient id={id} cx="35%" cy="30%" r="70%">
                <stop offset="0%" stopColor={c3} stopOpacity="0.95" />
                <stop offset="100%" stopColor={c1} stopOpacity="0.95" />
              </radialGradient>
            </defs>
            <circle cx={size * 0.5} cy={size * 0.5} r={size * 0.36} fill={`url(#${id})`} />
          </React.Fragment>
        );
      case 'rings':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            {[0.42, 0.32, 0.22, 0.12].map((rr, i) => (
              <circle key={i} cx={size * 0.5} cy={size * 0.5} r={size * rr}
                fill="none" stroke={i % 2 === 0 ? c3 : c2}
                strokeWidth={size * 0.018} />
            ))}
          </React.Fragment>
        );
      case 'stripes':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            {[0, 1, 2, 3, 4].map((i) => (
              <rect key={i} x={i * size * 0.2} y="0" width={size * 0.1} height={size}
                fill={i % 2 === 0 ? c3 : c2} opacity="0.85" />
            ))}
          </React.Fragment>
        );
      case 'split':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <polygon points={`0,0 ${size},0 0,${size}`} fill={c2} />
            <circle cx={size * 0.7} cy={size * 0.7} r={size * 0.18} fill={c3} />
          </React.Fragment>
        );
      case 'grid':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            {[0, 1, 2].map((y) =>
              [0, 1, 2].map((x) => (
                <rect key={`${x}-${y}`}
                  x={size * 0.2 + x * size * 0.22} y={size * 0.2 + y * size * 0.22}
                  width={size * 0.16} height={size * 0.16}
                  rx={size * 0.03} fill={(x + y) % 2 === 0 ? c2 : c3} />
              ))
            )}
          </React.Fragment>
        );
      case 'wave':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <path d={`M0,${size * 0.7} Q${size * 0.25},${size * 0.4} ${size * 0.5},${size * 0.7} T${size},${size * 0.7} L${size},${size} L0,${size} Z`}
              fill={c2} />
            <path d={`M0,${size * 0.85} Q${size * 0.25},${size * 0.6} ${size * 0.5},${size * 0.85} T${size},${size * 0.85} L${size},${size} L0,${size} Z`}
              fill={c3} opacity="0.75" />
          </React.Fragment>
        );
      case 'arc':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <path d={`M ${size * 0.15} ${size * 0.85} A ${size * 0.7} ${size * 0.7} 0 0 1 ${size * 0.85} ${size * 0.85}`}
              fill="none" stroke={c3} strokeWidth={size * 0.08} strokeLinecap="round" />
            <circle cx={size * 0.5} cy={size * 0.85} r={size * 0.06} fill={c2} />
          </React.Fragment>
        );
      case 'mono-blocks':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <rect x={size * 0.2} y={size * 0.2} width={size * 0.25} height={size * 0.6}
              rx={size * 0.04} fill={c2} />
            <rect x={size * 0.55} y={size * 0.2} width={size * 0.25} height={size * 0.25}
              rx={size * 0.04} fill={c3} />
            <rect x={size * 0.55} y={size * 0.55} width={size * 0.25} height={size * 0.25}
              rx={size * 0.04} fill={c3} opacity="0.55" />
          </React.Fragment>
        );
      case 'spark':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <path d={`M ${size * 0.5} ${size * 0.18} L ${size * 0.62} ${size * 0.45} L ${size * 0.85} ${size * 0.5} L ${size * 0.62} ${size * 0.55} L ${size * 0.5} ${size * 0.82} L ${size * 0.38} ${size * 0.55} L ${size * 0.15} ${size * 0.5} L ${size * 0.38} ${size * 0.45} Z`}
              fill={c3} />
          </React.Fragment>
        );
      case 'leaf':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <path d={`M ${size * 0.2} ${size * 0.8} Q ${size * 0.2} ${size * 0.2} ${size * 0.8} ${size * 0.2} Q ${size * 0.8} ${size * 0.8} ${size * 0.2} ${size * 0.8} Z`}
              fill={c2} />
            <path d={`M ${size * 0.2} ${size * 0.8} L ${size * 0.8} ${size * 0.2}`}
              stroke={c3} strokeWidth={size * 0.015} />
          </React.Fragment>
        );
      case 'eye':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <ellipse cx={size * 0.5} cy={size * 0.5} rx={size * 0.36} ry={size * 0.22} fill={c3} />
            <circle cx={size * 0.5} cy={size * 0.5} r={size * 0.13} fill={c2} />
          </React.Fragment>
        );
      case 'cube':
        return (
          <React.Fragment>
            <rect width={size} height={size} fill={c1} />
            <polygon points={`${size*0.5},${size*0.2} ${size*0.78},${size*0.36} ${size*0.78},${size*0.68} ${size*0.5},${size*0.84} ${size*0.22},${size*0.68} ${size*0.22},${size*0.36}`} fill={c2} />
            <polygon points={`${size*0.5},${size*0.2} ${size*0.78},${size*0.36} ${size*0.5},${size*0.52} ${size*0.22},${size*0.36}`} fill={c3} opacity="0.9" />
            <polygon points={`${size*0.5},${size*0.52} ${size*0.78},${size*0.36} ${size*0.78},${size*0.68} ${size*0.5},${size*0.84}`} fill={c2} />
          </React.Fragment>
        );
      case 'noise-field':
        return (
          <React.Fragment>
            <defs>
              <radialGradient id={id} cx="50%" cy="50%" r="65%">
                <stop offset="0%" stopColor={c3} />
                <stop offset="100%" stopColor={c1} />
              </radialGradient>
            </defs>
            <rect width={size} height={size} fill={`url(#${id})`} />
            {Array.from({ length: 12 }).map((_, i) => {
              const cx = ((i * 73) % 100) / 100 * size;
              const cy = ((i * 41 + 17) % 100) / 100 * size;
              return <circle key={i} cx={cx} cy={cy} r={size * 0.014} fill={c2} opacity="0.4" />;
            })}
          </React.Fragment>
        );
      default:
        return <rect width={size} height={size} fill={c1} />;
    }
  };

  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ display: 'block', borderRadius: r, overflow: 'hidden' }}>
      <defs>
        <clipPath id={clipId}>
          <rect width={size} height={size} rx={r} ry={r} />
        </clipPath>
      </defs>
      <g clipPath={`url(#${clipId})`}>{renderInside()}</g>
    </svg>
  );
}

// ─── Categories + collections ────────────────────────────────────────────
const CATEGORIES = [
  'Productivity', 'Health & Fitness', 'Finance', 'Social', 'Photo & Video',
  'Music', 'Games', 'Education', 'Travel', 'Utilities', 'Lifestyle', 'Developer'
];

const STYLES = ['Minimal', 'Gradient', 'Monochrome', 'Glyph', 'Illustrative', 'Geometric', 'Skeuomorphic'];

const COLOR_TAGS = [
  { name: 'Black',   hex: '#0b0d12' },
  { name: 'White',   hex: '#f5f1ea' },
  { name: 'Red',     hex: '#ff5b3b' },
  { name: 'Orange',  hex: '#f5a623' },
  { name: 'Yellow',  hex: '#d4e34a' },
  { name: 'Green',   hex: '#22b07a' },
  { name: 'Blue',    hex: '#5ab1ff' },
  { name: 'Indigo',  hex: '#5b3df5' },
  { name: 'Purple',  hex: '#a955d6' },
  { name: 'Pink',    hex: '#ff5d8f' },
  { name: 'Beige',   hex: '#e8e2d6' },
  { name: 'Gray',    hex: '#7a8190' },
];

// ─── App seed (fictional) ────────────────────────────────────────────────
// id, app_name, developer, category, palette key, style key, glyph, tags, rating, year, paid
const APPS = [
  { id: 'a01', app_name: 'Threadline', developer: 'Folio Labs', category: 'Productivity', palette: 'paper', style: 'glyph', glyph: 'T', colors: ['Beige','Black'], styles: ['Minimal','Glyph'], rating: 4.8, year: 2025, paid: true },
  { id: 'a02', app_name: 'Halcyon', developer: 'Drift Studio', category: 'Health & Fitness', palette: 'ocean', style: 'gradient-disc', colors: ['Blue','Indigo'], styles: ['Gradient'], rating: 4.6, year: 2024, paid: false },
  { id: 'a03', app_name: 'Ledger Note', developer: 'Hund & Co', category: 'Finance', palette: 'midnight', style: 'mono-blocks', colors: ['Black','Gray'], styles: ['Monochrome','Geometric'], rating: 4.9, year: 2025, paid: true },
  { id: 'a04', app_name: 'Mira', developer: 'North Bureau', category: 'Photo & Video', palette: 'rose', style: 'eye', colors: ['Pink'], styles: ['Illustrative'], rating: 4.5, year: 2024, paid: false },
  { id: 'a05', app_name: 'Pebble', developer: 'Tuesday Inc', category: 'Lifestyle', palette: 'cream', style: 'orb', colors: ['Beige','Yellow'], styles: ['Minimal'], rating: 4.7, year: 2025, paid: false },
  { id: 'a06', app_name: 'Folio', developer: 'Atlas & Sons', category: 'Productivity', palette: 'graphite', style: 'mono-glyph', glyph: 'fo', colors: ['Black','Gray'], styles: ['Monochrome','Glyph'], rating: 4.4, year: 2023, paid: true },
  { id: 'a07', app_name: 'Lumen AI', developer: 'Vector Foundry', category: 'Utilities', palette: 'ultra', style: 'spark', colors: ['Indigo','Purple'], styles: ['Gradient'], rating: 4.6, year: 2025, paid: true },
  { id: 'a08', app_name: 'Cassette', developer: 'Echo House', category: 'Music', palette: 'amber', style: 'rings', colors: ['Orange','Yellow'], styles: ['Geometric'], rating: 4.3, year: 2024, paid: false },
  { id: 'a09', app_name: 'Verdant', developer: 'Field Notes', category: 'Health & Fitness', palette: 'moss', style: 'leaf', colors: ['Green'], styles: ['Illustrative'], rating: 4.8, year: 2025, paid: false },
  { id: 'a10', app_name: 'Trove', developer: 'Hund & Co', category: 'Finance', palette: 'jade', style: 'arc', colors: ['Green'], styles: ['Minimal','Geometric'], rating: 4.7, year: 2024, paid: true },
  { id: 'a11', app_name: 'Pico', developer: 'Tiny Robot', category: 'Developer', palette: 'ink', style: 'mono-glyph', glyph: '$_', colors: ['Black','White'], styles: ['Monochrome'], rating: 4.9, year: 2025, paid: true },
  { id: 'a12', app_name: 'Aperture', developer: 'North Bureau', category: 'Photo & Video', palette: 'graphite', style: 'orb', colors: ['Black'], styles: ['Minimal'], rating: 4.5, year: 2024, paid: true },
  { id: 'a13', app_name: 'Stride', developer: 'Northbound', category: 'Health & Fitness', palette: 'ember', style: 'arc', colors: ['Red','Orange'], styles: ['Geometric'], rating: 4.4, year: 2024, paid: false },
  { id: 'a14', app_name: 'Quill', developer: 'Folio Labs', category: 'Productivity', palette: 'cream', style: 'glyph', glyph: 'Q', colors: ['Beige'], styles: ['Glyph','Minimal'], rating: 4.6, year: 2025, paid: true },
  { id: 'a15', app_name: 'Nimbus', developer: 'Vector Foundry', category: 'Utilities', palette: 'sky', style: 'wave', colors: ['Blue'], styles: ['Gradient','Illustrative'], rating: 4.3, year: 2024, paid: false },
  { id: 'a16', app_name: 'Mango', developer: 'Tropic & Co', category: 'Games', palette: 'amber', style: 'split', colors: ['Orange','Yellow'], styles: ['Illustrative'], rating: 4.2, year: 2023, paid: false },
  { id: 'a17', app_name: 'Mosaic', developer: 'Studio Olm', category: 'Education', palette: 'orchid', style: 'grid', colors: ['Purple'], styles: ['Geometric'], rating: 4.7, year: 2025, paid: true },
  { id: 'a18', app_name: 'Brick', developer: 'Hund & Co', category: 'Finance', palette: 'midnight', style: 'cube', colors: ['Black'], styles: ['Geometric','Monochrome'], rating: 4.5, year: 2024, paid: true },
  { id: 'a19', app_name: 'Pulse', developer: 'Drift Studio', category: 'Health & Fitness', palette: 'rose', style: 'rings', colors: ['Pink','Red'], styles: ['Geometric'], rating: 4.6, year: 2025, paid: false },
  { id: 'a20', app_name: 'Atlas', developer: 'Atlas & Sons', category: 'Travel', palette: 'paper', style: 'arc', colors: ['Beige'], styles: ['Minimal'], rating: 4.8, year: 2025, paid: true },
  { id: 'a21', app_name: 'Coda', developer: 'Echo House', category: 'Music', palette: 'ink', style: 'wave', colors: ['Black','White'], styles: ['Monochrome'], rating: 4.4, year: 2024, paid: true },
  { id: 'a22', app_name: 'Bloom', developer: 'Field Notes', category: 'Lifestyle', palette: 'rose', style: 'noise-field', colors: ['Pink'], styles: ['Gradient'], rating: 4.7, year: 2025, paid: false },
  { id: 'a23', app_name: 'Decimal', developer: 'Tuesday Inc', category: 'Finance', palette: 'citrus', style: 'mono-glyph', glyph: '0.', colors: ['Yellow','Green'], styles: ['Monochrome','Glyph'], rating: 4.5, year: 2024, paid: false },
  { id: 'a24', app_name: 'Forge', developer: 'Tiny Robot', category: 'Developer', palette: 'ember', style: 'spark', colors: ['Red','Orange'], styles: ['Gradient'], rating: 4.6, year: 2025, paid: true },
  { id: 'a25', app_name: 'Linen', developer: 'Studio Olm', category: 'Lifestyle', palette: 'paper', style: 'stripes', colors: ['Beige'], styles: ['Minimal'], rating: 4.3, year: 2023, paid: false },
  { id: 'a26', app_name: 'Vesper', developer: 'Drift Studio', category: 'Productivity', palette: 'midnight', style: 'gradient-flat', colors: ['Black','Indigo'], styles: ['Gradient','Monochrome'], rating: 4.9, year: 2025, paid: true },
  { id: 'a27', app_name: 'Tumbler', developer: 'Tropic & Co', category: 'Games', palette: 'jade', style: 'cube', colors: ['Green'], styles: ['Geometric'], rating: 4.1, year: 2023, paid: false },
  { id: 'a28', app_name: 'Index', developer: 'Folio Labs', category: 'Education', palette: 'graphite', style: 'grid', colors: ['Gray','Black'], styles: ['Monochrome','Geometric'], rating: 4.6, year: 2024, paid: true },
  { id: 'a29', app_name: 'Citrine', developer: 'Tuesday Inc', category: 'Photo & Video', palette: 'amber', style: 'gradient-disc', colors: ['Yellow','Orange'], styles: ['Gradient'], rating: 4.5, year: 2024, paid: false },
  { id: 'a30', app_name: 'Marble', developer: 'North Bureau', category: 'Lifestyle', palette: 'cream', style: 'noise-field', colors: ['Beige'], styles: ['Gradient','Minimal'], rating: 4.7, year: 2025, paid: true },
  { id: 'a31', app_name: 'Beacon', developer: 'Northbound', category: 'Travel', palette: 'sky', style: 'gradient-disc', colors: ['Blue'], styles: ['Gradient'], rating: 4.4, year: 2024, paid: false },
  { id: 'a32', app_name: 'Tundra', developer: 'Atlas & Sons', category: 'Travel', palette: 'ink', style: 'glyph', glyph: 'T', colors: ['Black','White'], styles: ['Glyph','Monochrome'], rating: 4.5, year: 2024, paid: true },
  { id: 'a33', app_name: 'Glow', developer: 'Vector Foundry', category: 'Utilities', palette: 'orchid', style: 'orb', colors: ['Purple','Pink'], styles: ['Gradient'], rating: 4.6, year: 2025, paid: false },
  { id: 'a34', app_name: 'Murmur', developer: 'Echo House', category: 'Social', palette: 'rose', style: 'mono-glyph', glyph: 'm', colors: ['Pink'], styles: ['Glyph'], rating: 4.2, year: 2024, paid: false },
  { id: 'a35', app_name: 'Pivot', developer: 'Hund & Co', category: 'Finance', palette: 'sky', style: 'split', colors: ['Blue'], styles: ['Geometric'], rating: 4.5, year: 2025, paid: true },
  { id: 'a36', app_name: 'Habit', developer: 'Tuesday Inc', category: 'Health & Fitness', palette: 'citrus', style: 'rings', colors: ['Green','Yellow'], styles: ['Geometric'], rating: 4.7, year: 2024, paid: false },
  { id: 'a37', app_name: 'Slate', developer: 'Folio Labs', category: 'Productivity', palette: 'graphite', style: 'gradient-flat', colors: ['Gray','Black'], styles: ['Minimal','Gradient'], rating: 4.8, year: 2025, paid: true },
  { id: 'a38', app_name: 'Postcard', developer: 'North Bureau', category: 'Social', palette: 'amber', style: 'arc', colors: ['Orange'], styles: ['Illustrative'], rating: 4.3, year: 2024, paid: false },
  { id: 'a39', app_name: 'Ember', developer: 'Drift Studio', category: 'Music', palette: 'ember', style: 'gradient-disc', colors: ['Red','Orange'], styles: ['Gradient'], rating: 4.6, year: 2025, paid: true },
  { id: 'a40', app_name: 'Margin', developer: 'Studio Olm', category: 'Education', palette: 'paper', style: 'mono-glyph', glyph: 'M', colors: ['Beige','Black'], styles: ['Monochrome','Glyph'], rating: 4.5, year: 2024, paid: false },
  { id: 'a41', app_name: 'Lattice', developer: 'Vector Foundry', category: 'Developer', palette: 'ocean', style: 'grid', colors: ['Blue','Indigo'], styles: ['Geometric'], rating: 4.8, year: 2025, paid: true },
  { id: 'a42', app_name: 'Saunter', developer: 'Northbound', category: 'Travel', palette: 'moss', style: 'leaf', colors: ['Green'], styles: ['Illustrative'], rating: 4.4, year: 2024, paid: false },
  { id: 'a43', app_name: 'Static', developer: 'Tiny Robot', category: 'Developer', palette: 'ink', style: 'stripes', colors: ['Black','White'], styles: ['Monochrome'], rating: 4.5, year: 2024, paid: true },
  { id: 'a44', app_name: 'Sigil', developer: 'Atlas & Sons', category: 'Utilities', palette: 'midnight', style: 'spark', colors: ['Black','Indigo'], styles: ['Geometric'], rating: 4.7, year: 2025, paid: true },
  { id: 'a45', app_name: 'Plum', developer: 'Tropic & Co', category: 'Games', palette: 'orchid', style: 'gradient-disc', colors: ['Purple'], styles: ['Gradient'], rating: 4.2, year: 2023, paid: false },
  { id: 'a46', app_name: 'Lull', developer: 'Field Notes', category: 'Health & Fitness', palette: 'sky', style: 'wave', colors: ['Blue'], styles: ['Gradient'], rating: 4.6, year: 2025, paid: false },
  { id: 'a47', app_name: 'Folio Pro', developer: 'Folio Labs', category: 'Productivity', palette: 'cream', style: 'mono-glyph', glyph: 'F', colors: ['Beige'], styles: ['Minimal','Glyph'], rating: 4.7, year: 2025, paid: true },
  { id: 'a48', app_name: 'Halftone', developer: 'North Bureau', category: 'Photo & Video', palette: 'graphite', style: 'noise-field', colors: ['Gray'], styles: ['Monochrome'], rating: 4.4, year: 2024, paid: true },
];

// ─── Collections ────────────────────────────────────────────────────────
const COLLECTIONS = [
  { id: 'c1', title: 'Minimal', description: '24 quiet, restrained icons.', palette: 'paper', appIds: ['a01','a05','a14','a20','a25','a30','a37','a40','a47'] },
  { id: 'c2', title: 'AI Apps', description: 'Vectors, gradients, sparks.', palette: 'ultra', appIds: ['a07','a24','a44','a26'] },
  { id: 'c3', title: 'Fintech', description: 'Money, but tasteful.', palette: 'jade', appIds: ['a03','a10','a18','a23','a35'] },
  { id: 'c4', title: 'Wellness', description: 'Calm palettes, soft shapes.', palette: 'moss', appIds: ['a02','a09','a13','a19','a36','a42','a46'] },
  { id: 'c5', title: 'Gradient', description: 'Color over form.', palette: 'rose', appIds: ['a02','a07','a15','a22','a26','a29','a39','a45'] },
  { id: 'c6', title: 'Monochrome', description: 'Black, white, restraint.', palette: 'ink', appIds: ['a06','a11','a21','a32','a40','a43','a48'] },
  { id: 'c7', title: 'Productivity', description: 'Tools that earn the dock.', palette: 'graphite', appIds: ['a01','a06','a14','a26','a37','a47'] },
  { id: 'c8', title: 'Glyphs', description: 'A single letter says a lot.', palette: 'amber', appIds: ['a01','a06','a11','a14','a23','a32','a34','a40','a47'] },
];

// ─── Helpers ────────────────────────────────────────────────────────────
function getApp(id) { return APPS.find(a => a.id === id); }
function getCollection(id) { return COLLECTIONS.find(c => c.id === id); }
function similarApps(app, n = 8) {
  return APPS
    .filter(a => a.id !== app.id)
    .map(a => {
      let score = 0;
      if (a.palette === app.palette) score += 3;
      if (a.style === app.style) score += 2;
      if (a.category === app.category) score += 1;
      a.styles.forEach(s => app.styles.includes(s) && (score += 1));
      return { a, score };
    })
    .sort((x, y) => y.score - x.score)
    .slice(0, n)
    .map(({ a }) => a);
}

// ─── Export to window ───────────────────────────────────────────────────
Object.assign(window, {
  supabase,
  IconArt, ICON_PALETTES, PALETTE_KEYS,
  APPS, COLLECTIONS, CATEGORIES, STYLES, COLOR_TAGS,
  getApp, getCollection, similarApps,
  fetchApps, fetchCollections, fetchFavorites, toggleFavorite,
});
