[
  {
    "path": ".gitignore",
    "content": "node_modules/\ndata.json\n.env\n*.log\n.DS_Store\n"
  },
  {
    "path": "dashboard.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=yes\">\n  <title>UNIPARTS · NEURAL SCAN</title>\n  <link href=\"https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600;14..32,700;14..32,800&display=swap\" rel=\"stylesheet\">\n  <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css\">\n  <style>\n    * {\n      margin: 0;\n      padding: 0;\n      box-sizing: border-box;\n    }\n\n    :root {\n      --bg-deep: #070b12;\n      --glass-bg: rgba(15, 25, 35, 0.65);\n      --glass-edge: rgba(255, 255, 255, 0.1);\n      --glass-edge-glow: rgba(0, 255, 255, 0.25);\n      --neon-cyan: #00f3ff;\n      --neon-pink: #ff2d75;\n      --neon-gold: #ffd966;\n      --neon-purple: #b77eff;\n      --text-primary: #f5f7fc;\n      --text-secondary: #a0b3c9;\n      --card-hover: rgba(0, 243, 255, 0.05);\n    }\n\n    body {\n      font-family: 'Inter', sans-serif;\n      background: var(--bg-deep);\n      color: var(--text-primary);\n      min-height: 100vh;\n      overflow-x: hidden;\n      position: relative;\n    }\n\n    /* Animated Background */\n    .bg-aura {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      z-index: -3;\n      background: radial-gradient(circle at 20% 30%, #0a1a2a, #02060c);\n    }\n    .bg-aura::before {\n      content: '';\n      position: absolute;\n      width: 200%;\n      height: 200%;\n      top: -50%;\n      left: -50%;\n      background: radial-gradient(circle, rgba(0, 243, 255, 0.08) 2px, transparent 2px);\n      background-size: 50px 50px;\n      animation: drift 80s linear infinite;\n      opacity: 0.5;\n    }\n    @keyframes drift {\n      0% { transform: translate(0, 0) rotate(0deg); }\n      100% { transform: translate(-15%, -15%) rotate(2deg); }\n    }\n\n    /* Floating Orbs */\n    .orb {\n      position: fixed;\n      border-radius: 50%;\n      filter: blur(100px);\n      z-index: -2;\n      pointer-events: none;\n    }\n    .orb-1 {\n      width: 600px;\n      height: 600px;\n      background: radial-gradient(circle, rgba(0, 243, 255, 0.2), transparent 70%);\n      top: -200px;\n      left: -200px;\n      animation: floatOrb 22s ease-in-out infinite;\n    }\n    .orb-2 {\n      width: 700px;\n      height: 700px;\n      background: radial-gradient(circle, rgba(255, 45, 117, 0.15), transparent 70%);\n      bottom: -250px;\n      right: -250px;\n      animation: floatOrb 28s ease-in-out infinite reverse;\n    }\n    @keyframes floatOrb {\n      0%, 100% { transform: translate(0, 0) scale(1); }\n      50% { transform: translate(40px, -30px) scale(1.1); }\n    }\n\n    /* Scan line */\n    .scan-line {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 3px;\n      background: linear-gradient(90deg, transparent, var(--neon-cyan), var(--neon-pink), transparent);\n      animation: scanMove 10s linear infinite;\n      z-index: 10;\n      pointer-events: none;\n      opacity: 0.7;\n    }\n    @keyframes scanMove {\n      0% { transform: translateY(-100vh); }\n      100% { transform: translateY(100vh); }\n    }\n\n    /* Main container */\n    .app {\n      max-width: 1400px;\n      margin: 0 auto;\n      padding: 28px 24px 48px;\n      position: relative;\n      z-index: 10;\n    }\n\n    /* Glass card */\n    .glass-card {\n      background: var(--glass-bg);\n      backdrop-filter: blur(16px);\n      -webkit-backdrop-filter: blur(16px);\n      border: 1px solid var(--glass-edge);\n      border-radius: 36px;\n      transition: all 0.4s cubic-bezier(0.2, 0.9, 0.4, 1.1);\n      transform-style: preserve-3d;\n      perspective: 1200px;\n    }\n    .glass-card:hover {\n      border-color: var(--glass-edge-glow);\n      box-shadow: 0 0 25px rgba(0, 243, 255, 0.2);\n      transform: translateY(-5px);\n    }\n\n    /* Header */\n    .header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 24px 36px;\n      margin-bottom: 48px;\n      flex-wrap: wrap;\n      gap: 20px;\n    }\n    .logo h1 {\n      font-size: 2.5rem;\n      font-weight: 800;\n      background: linear-gradient(135deg, #fff, var(--neon-cyan), var(--neon-pink));\n      -webkit-background-clip: text;\n      background-clip: text;\n      color: transparent;\n      letter-spacing: -1px;\n    }\n    .logo p {\n      font-size: 0.75rem;\n      letter-spacing: 3px;\n      color: var(--text-secondary);\n      margin-top: 4px;\n    }\n    .header-stats {\n      display: flex;\n      gap: 24px;\n    }\n    .stat-pill {\n      background: rgba(0, 0, 0, 0.4);\n      border-radius: 60px;\n      padding: 8px 24px;\n      border: 1px solid var(--glass-edge);\n      font-family: monospace;\n      font-weight: 500;\n    }\n    .stat-pill i {\n      color: var(--neon-cyan);\n      margin-right: 6px;\n    }\n\n    /* Stats row */\n    .stats-row {\n      display: grid;\n      grid-template-columns: repeat(3, 1fr);\n      gap: 28px;\n      margin-bottom: 56px;\n    }\n    .stat-item {\n      padding: 32px 20px;\n      text-align: center;\n      position: relative;\n      overflow: hidden;\n    }\n    .stat-item::before {\n      content: '';\n      position: absolute;\n      top: 0;\n      left: -100%;\n      width: 100%;\n      height: 100%;\n      background: linear-gradient(90deg, transparent, rgba(0, 243, 255, 0.08), transparent);\n      transition: left 0.6s;\n    }\n    .stat-item:hover::before {\n      left: 100%;\n    }\n    .stat-number {\n      font-size: 3.2rem;\n      font-weight: 800;\n      background: linear-gradient(135deg, #fff, var(--neon-cyan));\n      -webkit-background-clip: text;\n      background-clip: text;\n      color: transparent;\n      line-height: 1;\n    }\n    .stat-label {\n      font-size: 0.8rem;\n      text-transform: uppercase;\n      letter-spacing: 3px;\n      color: var(--text-secondary);\n      margin-top: 12px;\n    }\n\n    /* Mode toggle */\n    .mode-toggle {\n      display: flex;\n      justify-content: center;\n      margin-bottom: 48px;\n    }\n    .mode-pill {\n      display: flex;\n      background: rgba(0, 0, 0, 0.5);\n      padding: 6px;\n      border-radius: 80px;\n      border: 1px solid var(--glass-edge);\n      backdrop-filter: blur(12px);\n    }\n    .mode-btn {\n      padding: 14px 42px;\n      border-radius: 60px;\n      font-weight: 700;\n      font-size: 1rem;\n      cursor: pointer;\n      transition: 0.25s;\n      background: transparent;\n      color: var(--text-secondary);\n      border: none;\n      letter-spacing: 1px;\n    }\n    .mode-btn.active {\n      background: linear-gradient(135deg, var(--neon-cyan), var(--neon-pink));\n      color: #0a0c12;\n      box-shadow: 0 0 20px rgba(0, 243, 255, 0.5);\n    }\n\n    /* Search card */\n    .search-card {\n      padding: 36px;\n      margin-bottom: 48px;\n    }\n    select, input, button {\n      width: 100%;\n      padding: 18px 26px;\n      margin-bottom: 24px;\n      border-radius: 40px;\n      border: 1px solid var(--glass-edge);\n      background: rgba(0, 0, 0, 0.5);\n      color: var(--text-primary);\n      font-family: inherit;\n      font-size: 1rem;\n      outline: none;\n      transition: 0.2s;\n    }\n    select:focus, input:focus {\n      border-color: var(--neon-cyan);\n      box-shadow: 0 0 0 3px rgba(0, 243, 255, 0.2);\n    }\n    button {\n      background: linear-gradient(135deg, var(--neon-cyan), var(--neon-pink));\n      color: #0a0c12;\n      font-weight: 800;\n      border: none;\n      cursor: pointer;\n      letter-spacing: 2px;\n    }\n    button:active {\n      transform: scale(0.98);\n    }\n\n    /* Results */\n    .results-grid {\n      display: flex;\n      flex-direction: column;\n      gap: 24px;\n      margin-bottom: 56px;\n    }\n    .result-item {\n      padding: 28px 32px;\n      background: rgba(0, 0, 0, 0.4);\n      border-left: 6px solid var(--neon-cyan);\n      transition: 0.25s;\n    }\n    .result-item:hover {\n      background: rgba(0, 0, 0, 0.6);\n      transform: translateX(10px);\n    }\n    .result-brand {\n      font-size: 1.3rem;\n      font-weight: 800;\n      color: var(--neon-cyan);\n      margin-bottom: 10px;\n    }\n    .result-compat {\n      font-size: 0.95rem;\n      margin-bottom: 18px;\n      line-height: 1.6;\n    }\n    .result-footer {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n    }\n    .copy-btn {\n      background: rgba(255, 255, 255, 0.08);\n      border: none;\n      padding: 6px 24px;\n      border-radius: 40px;\n      font-size: 0.75rem;\n      cursor: pointer;\n    }\n    .copy-btn:hover {\n      background: var(--neon-cyan);\n      color: #000;\n    }\n\n    /* Device grid */\n    .device-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));\n      gap: 28px;\n    }\n    .device-tile {\n      padding: 28px 16px;\n      text-align: center;\n      cursor: pointer;\n      transition: 0.25s;\n    }\n    .device-tile:hover {\n      transform: translateY(-8px);\n      background: rgba(0, 0, 0, 0.6);\n      border-color: var(--neon-cyan);\n    }\n    .device-img {\n      width: 100%;\n      height: 140px;\n      object-fit: contain;\n      margin-bottom: 16px;\n    }\n    .device-name {\n      font-weight: 600;\n    }\n\n    /* Info panel */\n    .info-grid {\n      display: grid;\n      grid-template-columns: 2fr 1fr;\n      gap: 28px;\n      margin-bottom: 56px;\n    }\n    .info-card {\n      padding: 28px;\n    }\n    .updates-list {\n      max-height: 220px;\n      overflow-y: auto;\n      margin-top: 20px;\n    }\n    .update-entry {\n      padding: 12px 16px;\n      margin-bottom: 12px;\n      background: rgba(255, 255, 255, 0.03);\n      border-radius: 28px;\n      font-size: 0.85rem;\n      border-left: 3px solid var(--neon-pink);\n    }\n    .version-display {\n      font-size: 3rem;\n      font-weight: 800;\n      text-align: center;\n      color: var(--neon-gold);\n      margin: 25px 0;\n    }\n    .sync-status {\n      text-align: center;\n      font-size: 0.75rem;\n      color: var(--text-secondary);\n    }\n\n    /* Recent scans */\n    .recent-section {\n      margin-top: 24px;\n      margin-bottom: 48px;\n      padding: 20px 28px;\n    }\n    .chips-container {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 14px;\n      margin-top: 16px;\n    }\n    .recent-chip {\n      background: rgba(255, 255, 255, 0.05);\n      padding: 8px 22px;\n      border-radius: 60px;\n      font-size: 0.85rem;\n      cursor: pointer;\n      border: 1px solid var(--glass-edge);\n    }\n    .recent-chip:hover {\n      background: var(--neon-cyan);\n      color: #0a0c12;\n      border-color: var(--neon-cyan);\n    }\n\n    /* Footer */\n    .footer {\n      text-align: center;\n      padding: 36px 20px;\n      border-top: 1px solid var(--glass-edge);\n    }\n    .footer-name {\n      font-size: 1.5rem;\n      font-weight: 700;\n      background: linear-gradient(135deg, #fff, var(--neon-pink));\n      -webkit-background-clip: text;\n      background-clip: text;\n      color: transparent;\n    }\n    .footer-links {\n      display: flex;\n      justify-content: center;\n      gap: 32px;\n      margin: 16px 0;\n    }\n    .footer-links a {\n      color: var(--text-secondary);\n      text-decoration: none;\n      font-size: 0.9rem;\n    }\n    .footer-links a:hover {\n      color: var(--neon-cyan);\n    }\n\n    /* Loader */\n    .loader {\n      text-align: center;\n      padding: 70px;\n    }\n    .loader::after {\n      content: '';\n      display: inline-block;\n      width: 48px;\n      height: 48px;\n      border: 4px solid var(--neon-cyan);\n      border-top-color: transparent;\n      border-radius: 50%;\n      animation: spin 0.8s linear infinite;\n    }\n    @keyframes spin { to { transform: rotate(360deg); } }\n\n    @media (max-width: 800px) {\n      .stats-row { grid-template-columns: 1fr; gap: 20px; }\n      .info-grid { grid-template-columns: 1fr; }\n      .mode-btn { padding: 10px 24px; font-size: 0.85rem; }\n      .logo h1 { font-size: 1.8rem; }\n      .header { flex-direction: column; text-align: center; }\n    }\n  </style>\n</head>\n<body>\n  <div class=\"bg-aura\"></div>\n  <div class=\"orb orb-1\"></div>\n  <div class=\"orb orb-2\"></div>\n  <div class=\"scan-line\"></div>\n\n  <div class=\"app\">\n    <header class=\"glass-card header\">\n      <div class=\"logo\">\n        <h1><i class=\"fas fa-microchip\"></i> UNIPARTS</h1>\n        <p>NEURAL COMPATIBILITY SCANNER</p>\n      </div>\n      <div class=\"header-stats\">\n        <div class=\"stat-pill\"><i class=\"fas fa-code-branch\"></i> v<span id=\"headerVersion\">—</span></div>\n        <div class=\"stat-pill\"><i class=\"far fa-clock\"></i> <span id=\"liveClock\">--:--:--</span></div>\n      </div>\n    </header>\n\n    <div class=\"stats-row\">\n      <div class=\"glass-card stat-item\"><div class=\"stat-number\" id=\"statModels\">—</div><div class=\"stat-label\">TOTAL MODELS</div></div>\n      <div class=\"glass-card stat-item\"><div class=\"stat-number\" id=\"statCats\">—</div><div class=\"stat-label\">CATEGORIES</div></div>\n      <div class=\"glass-card stat-item\"><div class=\"stat-number\" id=\"statParts\">—</div><div class=\"stat-label\">COMPATIBILITY ENTRIES</div></div>\n    </div>\n\n    <div class=\"mode-toggle\">\n      <div class=\"mode-pill\">\n        <button class=\"mode-btn active\" data-mode=\"parts\"><i class=\"fas fa-tools\"></i> PARTS SCAN</button>\n        <button class=\"mode-btn\" data-mode=\"specs\"><i class=\"fas fa-database\"></i> DEVICE SPECS</button>\n      </div>\n    </div>\n\n    <div id=\"partsUI\" class=\"glass-card search-card\">\n      <select id=\"categorySelect\"><option>LOADING CATEGORIES...</option></select>\n      <input type=\"text\" id=\"modelInput\" placeholder=\"Enter model (e.g., Redmi 9, iPhone 12)\" autocomplete=\"off\">\n      <button id=\"searchBtn\"><i class=\"fas fa-brain\"></i> START NEURAL SCAN</button>\n    </div>\n\n    <div id=\"specsUI\" class=\"glass-card search-card\" style=\"display: none;\">\n      <input type=\"text\" id=\"specsInput\" placeholder=\"Search device (e.g., Samsung Galaxy S23)\" autocomplete=\"off\">\n      <button id=\"specsBtn\"><i class=\"fas fa-search\"></i> FETCH FROM GSMARENA</button>\n    </div>\n\n    <div id=\"resultsArea\" class=\"results-grid\">\n      <div class=\"glass-card\" style=\"padding: 70px; text-align: center;\"><i class=\"fas fa-robot\"></i> SELECT CATEGORY & MODEL TO BEGIN</div>\n    </div>\n\n    <div class=\"glass-card recent-section\">\n      <div style=\"font-size:0.75rem; letter-spacing:2px; color:var(--text-secondary);\"><i class=\"fas fa-history\"></i> RECENT SCANS</div>\n      <div class=\"chips-container\" id=\"recentChips\"></div>\n    </div>\n\n    <div class=\"info-grid\">\n      <div class=\"glass-card info-card\">\n        <div style=\"font-weight:700;\"><i class=\"fas fa-newspaper\"></i> LATEST UPDATES</div>\n        <div class=\"updates-list\" id=\"updatesList\">Loading updates...</div>\n      </div>\n      <div class=\"glass-card info-card\">\n        <div style=\"font-weight:700; text-align:center;\"><i class=\"fas fa-database\"></i> DATABASE VERSION</div>\n        <div class=\"version-display\" id=\"versionBadge\">—</div>\n        <div class=\"sync-status\" id=\"lastSyncText\">Sync status: --</div>\n      </div>\n    </div>\n\n    <footer class=\"footer\">\n      <div class=\"footer-name\">MUNavvir</div>\n      <div class=\"footer-links\">\n        <a href=\"tel:+918590822912\"><i class=\"fas fa-phone-alt\"></i> +91 85908 22912</a>\n        <a href=\"https://instagram.com/munavi.r_\" target=\"_blank\"><i class=\"fab fa-instagram\"></i> @munavi.r_</a>\n      </div>\n      <div style=\"font-size:0.7rem; opacity:0.5;\">PREMIUM NEURAL INTELLIGENCE · ULTIMATE EDITION</div>\n    </footer>\n  </div>\n\n  <script>\n    const API_BASE = '';\n    let currentMode = 'parts';\n    let categories = [];\n    let recentList = JSON.parse(localStorage.getItem('uniparts_pro_recent') || '[]');\n\n    const partsUI = document.getElementById('partsUI');\n    const specsUI = document.getElementById('specsUI');\n    const categorySelect = document.getElementById('categorySelect');\n    const modelInput = document.getElementById('modelInput');\n    const searchBtn = document.getElementById('searchBtn');\n    const specsInput = document.getElementById('specsInput');\n    const specsBtn = document.getElementById('specsBtn');\n    const resultsDiv = document.getElementById('resultsArea');\n    const recentChips = document.getElementById('recentChips');\n    const updatesListDiv = document.getElementById('updatesList');\n    const versionBadgeSpan = document.getElementById('versionBadge');\n    const lastSyncSpan = document.getElementById('lastSyncText');\n    const headerVersionSpan = document.getElementById('headerVersion');\n\n    function escapeHtml(str) {\n      if (!str) return '';\n      return str.replace(/[&<>]/g, m => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;' }[m]));\n    }\n\n    function showToast(msg, isError = false) {\n      const toast = document.createElement('div');\n      toast.innerHTML = `<i class=\"fas ${isError ? 'fa-exclamation-triangle' : 'fa-check-circle'}\"></i> ${msg}`;\n      toast.style.cssText = `position:fixed; bottom:30px; left:50%; transform:translateX(-50%); background:${isError ? '#ff2d75' : '#00f3ff'}; color:#000; padding:14px 28px; border-radius:60px; font-size:0.9rem; font-weight:700; z-index:9999; backdrop-filter:blur(12px); box-shadow:0 0 20px rgba(0,243,255,0.5); display:flex; align-items:center; gap:10px;`;\n      document.body.appendChild(toast);\n      setTimeout(() => toast.remove(), 2800);\n    }\n\n    function updateClock() {\n      const now = new Date();\n      document.getElementById('liveClock').innerText = now.toLocaleTimeString('en-GB');\n    }\n    setInterval(updateClock, 1000);\n    updateClock();\n\n    async function loadCategories() {\n      try {\n        const res = await fetch(`${API_BASE}/categories`);\n        const data = await res.json();\n        categories = data.categories || [];\n        const totalModels = categories.reduce((s, c) => s + (c.modelCount || 0), 0);\n        document.getElementById('statModels').innerText = totalModels.toLocaleString();\n        document.getElementById('statCats').innerText = categories.length;\n        document.getElementById('statParts').innerText = categories.filter(c => c.modelCount > 0).length;\n        categorySelect.innerHTML = '<option value=\"\">SELECT CATEGORY</option>' + \n          categories.map(c => `<option value=\"${c.key}\">${escapeHtml(c.name)} (${c.modelCount})</option>`).join('');\n      } catch(e) {\n        console.error(e);\n        categorySelect.innerHTML = '<option>ERROR LOADING CATEGORIES</option>';\n        showToast('Could not load categories', true);\n      }\n    }\n\n    async function loadUpdatesAndVersion() {\n      try {\n        const [updRes, verRes] = await Promise.all([\n          fetch(`${API_BASE}/updates`),\n          fetch(`${API_BASE}/version`)\n        ]);\n        const updatesData = await updRes.json();\n        const versionData = await verRes.json();\n        if (updatesData.success && updatesData.updates.length) {\n          updatesListDiv.innerHTML = updatesData.updates.map(u => `<div class=\"update-entry\"><i class=\"fas fa-sync-alt\"></i> ${escapeHtml(u)}</div>`).join('');\n        } else {\n          updatesListDiv.innerHTML = '<div class=\"update-entry\">✨ NO RECENT UPDATES</div>';\n        }\n        if (versionData.success) {\n          const ver = versionData.version;\n          versionBadgeSpan.innerText = ver;\n          headerVersionSpan.innerText = ver;\n          if (versionData.lastSync) {\n            const syncDate = new Date(versionData.lastSync);\n            lastSyncSpan.innerHTML = `<i class=\"fas fa-clock\"></i> Last sync: ${syncDate.toLocaleString()}`;\n          } else {\n            lastSyncSpan.innerHTML = '<i class=\"fas fa-sync-alt\"></i> Last sync: just now';\n          }\n        } else {\n          versionBadgeSpan.innerText = '—';\n          headerVersionSpan.innerText = '—';\n        }\n      } catch(e) {\n        updatesListDiv.innerHTML = '<div class=\"update-entry\">⚠️ UNABLE TO LOAD UPDATES</div>';\n        versionBadgeSpan.innerText = '?';\n      }\n    }\n\n    function saveRecent(cat, model) {\n      const key = `${cat}::${model}`;\n      recentList = [key, ...recentList.filter(r => r !== key)].slice(0, 6);\n      localStorage.setItem('uniparts_pro_recent', JSON.stringify(recentList));\n      renderRecents();\n    }\n\n    function renderRecents() {\n      if (!recentList.length) {\n        recentChips.innerHTML = '<span style=\"color:#555;\">—</span>';\n        return;\n      }\n      recentChips.innerHTML = recentList.map(item => {\n        const [catKey, model] = item.split('::');\n        return `<span class=\"recent-chip\" data-cat=\"${catKey}\" data-model=\"${model}\"><i class=\"fas fa-history\"></i> ${escapeHtml(model)}</span>`;\n      }).join('');\n      document.querySelectorAll('.recent-chip').forEach(chip => {\n        chip.addEventListener('click', () => {\n          const cat = chip.dataset.cat;\n          const model = chip.dataset.model;\n          if (currentMode === 'parts') {\n            if (cat && categorySelect.querySelector(`option[value=\"${cat}\"]`)) categorySelect.value = cat;\n            modelInput.value = model;\n            runPartsSearch();\n          } else {\n            specsInput.value = model;\n            runSpecsSearch();\n          }\n        });\n      });\n    }\n\n    async function runPartsSearch() {\n      const cat = categorySelect.value;\n      const model = modelInput.value.trim();\n      if (!cat) { showToast('SELECT A CATEGORY', true); return; }\n      if (!model) { showToast('ENTER A MODEL', true); return; }\n      saveRecent(cat, model);\n      setLoading(true);\n      try {\n        const res = await fetch(`${API_BASE}/search?part=${encodeURIComponent(cat)}&model=${encodeURIComponent(model)}`);\n        const data = await res.json();\n        if (data.error) throw new Error(data.error);\n        if (!data.results || data.results.length === 0) {\n          resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:60px; text-align:center;\"><i class=\"fas fa-frown\"></i> NO COMPATIBILITY FOUND FOR \"${escapeHtml(model)}\"</div>`;\n        } else {\n          let html = `<div style=\"margin-bottom:25px; color:var(--text-secondary);\"><i class=\"fas fa-chart-line\"></i> ${data.totalMatches} RESULT(S) FOR \"${escapeHtml(model)}\"</div>`;\n          data.results.forEach(r => {\n            html += `\n              <div class=\"glass-card result-item\">\n                <div class=\"result-brand\"><i class=\"fas fa-trademark\"></i> ${escapeHtml(r.brand)}</div>\n                <div class=\"result-compat\">${escapeHtml(r.compatibility)}</div>\n                <div class=\"result-footer\">\n                  <span><i class=\"fas fa-percent\"></i> MATCH: ${r.score}%</span>\n                  <button class=\"copy-btn\" data-text=\"${escapeHtml(r.compatibility).replace(/\"/g, '&quot;')}\"><i class=\"fas fa-copy\"></i> COPY</button>\n                </div>\n              </div>`;\n          });\n          resultsDiv.innerHTML = html;\n          document.querySelectorAll('.copy-btn').forEach(btn => {\n            btn.addEventListener('click', () => {\n              navigator.clipboard.writeText(btn.dataset.text);\n              showToast('COPIED TO CLIPBOARD!');\n            });\n          });\n        }\n      } catch(e) {\n        resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:60px; text-align:center;\"><i class=\"fas fa-exclamation-triangle\"></i> ERROR: ${e.message}</div>`;\n      } finally {\n        setLoading(false);\n      }\n    }\n\n    async function runSpecsSearch() {\n      const query = specsInput.value.trim();\n      if (!query) { showToast('ENTER A DEVICE NAME', true); return; }\n      saveRecent('specs', query);\n      setLoading(true);\n      try {\n        const res = await fetch(`${API_BASE}/specs-search?q=${encodeURIComponent(query)}`);\n        const data = await res.json();\n        if (!data.success || !data.results || data.results.length === 0) {\n          resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:60px; text-align:center;\"><i class=\"fas fa-search\"></i> NO DEVICES FOUND FOR \"${escapeHtml(query)}\"</div>`;\n          return;\n        }\n        let html = `<div class=\"device-grid\">`;\n        data.results.forEach(d => {\n          html += `\n            <div class=\"glass-card device-tile\" data-id=\"${escapeHtml(d.id)}\" data-name=\"${escapeHtml(d.title)}\">\n              <img src=\"${escapeHtml(d.img || '')}\" class=\"device-img\" onerror=\"this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22%3E%3Crect fill=%22%23333%22 width=%22100%22 height=%22100%22/%3E%3Ctext fill=%22%23999%22 x=%2250%22 y=%2255%22 text-anchor=%22middle%22%3E📱%3C/text%3E%3C/svg%3E'\">\n              <div class=\"device-name\">${escapeHtml(d.title)}</div>\n            </div>`;\n        });\n        html += `</div>`;\n        resultsDiv.innerHTML = html;\n        document.querySelectorAll('.device-tile').forEach(card => {\n          card.addEventListener('click', () => loadSpecsDetail(card.dataset.id));\n        });\n      } catch(e) {\n        resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:60px; text-align:center;\"><i class=\"fas fa-times-circle\"></i> SEARCH FAILED</div>`;\n      } finally {\n        setLoading(false);\n      }\n    }\n\n    async function loadSpecsDetail(id) {\n      resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:60px; text-align:center;\"><div class=\"loader\"></div> LOADING SPECIFICATIONS...</div>`;\n      try {\n        const res = await fetch(`${API_BASE}/specs-details?id=${encodeURIComponent(id)}`);\n        const data = await res.json();\n        if (!data.success) throw new Error();\n        let html = `<div class=\"glass-card\" style=\"padding:32px;\"><h3 style=\"margin-bottom:20px;\"><i class=\"fas fa-mobile-alt\"></i> ${escapeHtml(data.name)}</h3>`;\n        if (data.img) html += `<img src=\"${escapeHtml(data.img)}\" style=\"max-width:160px; border-radius:28px; margin-bottom:24px;\">`;\n        for (const [cat, specs] of Object.entries(data.specs)) {\n          html += `<details style=\"margin-top:18px;\"><summary style=\"font-weight:700; cursor:pointer; color:var(--neon-cyan);\"><i class=\"fas fa-list-ul\"></i> ${escapeHtml(cat)}</summary>`;\n          for (const [k, v] of Object.entries(specs)) {\n            html += `<div style=\"display:flex; justify-content:space-between; border-bottom:1px solid rgba(255,255,255,0.08); padding:12px 0;\"><span style=\"color:var(--text-secondary);\">${escapeHtml(k)}</span><span>${escapeHtml(v)}</span></div>`;\n          }\n          html += `</details>`;\n        }\n        html += `<button id=\"backToSpecs\" style=\"margin-top:28px;\"><i class=\"fas fa-arrow-left\"></i> BACK TO RESULTS</button></div>`;\n        resultsDiv.innerHTML = html;\n        document.getElementById('backToSpecs')?.addEventListener('click', () => runSpecsSearch());\n      } catch(e) {\n        resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:60px; text-align:center;\"><i class=\"fas fa-exclamation-triangle\"></i> COULD NOT LOAD SPECS</div>`;\n      }\n    }\n\n    function setLoading(loading) {\n      const btn = currentMode === 'parts' ? searchBtn : specsBtn;\n      btn.disabled = loading;\n      btn.innerHTML = loading ? '<i class=\"fas fa-spinner fa-pulse\"></i> SCANNING...' : (currentMode === 'parts' ? '<i class=\"fas fa-brain\"></i> START NEURAL SCAN' : '<i class=\"fas fa-search\"></i> FETCH FROM GSMARENA');\n      if (loading) resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:60px; text-align:center;\"><div class=\"loader\"></div> NEURAL SCAN IN PROGRESS...</div>`;\n    }\n\n    function setMode(mode) {\n      currentMode = mode;\n      partsUI.style.display = mode === 'parts' ? 'block' : 'none';\n      specsUI.style.display = mode === 'specs' ? 'block' : 'none';\n      document.querySelectorAll('.mode-btn').forEach(btn => {\n        btn.classList.toggle('active', btn.dataset.mode === mode);\n      });\n      resultsDiv.innerHTML = `<div class=\"glass-card\" style=\"padding:70px; text-align:center;\">${mode === 'parts' ? '<i class=\"fas fa-tools\"></i> SELECT CATEGORY & MODEL TO BEGIN COMPATIBILITY SCAN.' : '<i class=\"fas fa-database\"></i> ENTER A DEVICE NAME TO FETCH FULL SPECIFICATIONS FROM GSMARENA.'}</div>`;\n    }\n\n    searchBtn.addEventListener('click', runPartsSearch);\n    specsBtn.addEventListener('click', runSpecsSearch);\n    modelInput.addEventListener('keypress', e => { if (e.key === 'Enter') runPartsSearch(); });\n    specsInput.addEventListener('keypress', e => { if (e.key === 'Enter') runSpecsSearch(); });\n    document.querySelectorAll('.mode-btn').forEach(btn => {\n      btn.addEventListener('click', () => setMode(btn.dataset.mode));\n    });\n\n    loadCategories();\n    loadUpdatesAndVersion();\n    renderRecents();\n    setMode('parts');\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"uniparts-pro\",\n  \"version\": \"4.0.0\",\n  \"description\": \"UNIPARTS PRO – Ultimate Compatibility Database with Updates & Version\",\n  \"main\": \"server.js\",\n  \"scripts\": {\n    \"start\": \"node server.js\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.6.2\",\n    \"cheerio\": \"^1.0.0-rc.12\"\n  },\n  \"engines\": {\n    \"node\": \">=16\"\n  }\n}\n"
  },
  {
    "path": "server.js",
    "content": "\"use strict\";\n\nconst http = require(\"http\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst zlib = require(\"zlib\");\nconst axios = require(\"axios\");\nconst cheerio = require(\"cheerio\");\n\n// ==================== CONFIGURATION ====================\nconst PORT = parseInt(process.env.PORT, 10) || 8000;\nconst NODE_ENV = process.env.NODE_ENV || \"development\";\nconst ADMIN_KEY = process.env.ADMIN_KEY || \"munax_peak_2026\";\n\nconst DATA_SOURCES = {\n  master: \"https://combosupport.in/wp-content/uploads/appdatanew/data.php\",\n  updates: \"https://combosupport.in/wp-content/uploads/appdatanew/updates.php\",\n  version: \"https://combosupport.in/wp-content/uploads/appdatanew/version.json\"\n};\n\nconst REFRESH_MS = 2 * 60 * 60 * 1000;\nconst MAX_RESULTS = 30;\nconst GZIP_THRESHOLD = 1024;\n\nconst RATE_LIMITS = { loose: 120, normal: 40, strict: 12, admin: 15 };\nconst RL_WINDOW_MS = 60 * 1000;\n\n// ==================== GLOBAL STATE ====================\nlet searchIndex = {};\nlet updatesList = [];\nlet dataVersion = \"—\";\nlet lastSyncTime = null;\nlet syncCount = 0;\nlet dashboardHtml = null;\n\nconst metrics = {\n  started: new Date().toISOString(),\n  totalRequests: 0,\n  totalErrors: 0,\n  totalSearches: 0,\n  pathHits: {}\n};\n\n// ==================== ULTRA CLEANING FUNCTION ====================\nfunction cleanModelString(raw) {\n  if (!raw || typeof raw !== \"string\") return \"\";\n  let str = raw;\n\n  // Remove common headers\n  str = str.replace(/^Universal\\s+(Oca|Combo)\\s+Glass\\s+List\\s*/i, \"\");\n  str = str.replace(/^Universal\\s+Combo\\s+List\\s*/i, \"\");\n\n  // Remove leading numbers like \"1.\", \"2.\", \"8.\" (including spaces)\n  str = str.replace(/^\\d+\\.\\s*/, \"\");\n\n  // Remove shop credits in parentheses at the very beginning\n  str = str.replace(/^\\([^)]+\\)\\s*/, \"\");\n\n  // Remove ✅ symbol and any remaining emojis (simple Unicode range)\n  str = str.replace(/[\\u{1F300}-\\u{1F6FF}\\u{2600}-\\u{26FF}\\u{2700}-\\u{27BF}\\u{1F900}-\\u{1F9FF}\\u{1F1E0}-\\u{1F1FF}✅]/gu, \"\");\n\n  // Remove extra spaces and trim\n  str = str.replace(/\\s+/g, \" \").trim();\n\n  // If after cleaning the string is empty or just a placeholder, return empty\n  if (!str || str === \"New List\" || str.includes(\"Coming Soon\")) return \"\";\n\n  return str;\n}\n\nfunction cleanBrandName(raw) {\n  if (!raw || typeof raw !== \"string\") return \"\";\n  let brand = raw;\n  // Remove \"TM \" prefix\n  brand = brand.replace(/^TM\\s+/i, \"\");\n  // Remove leading numbers like \"1.\"\n  brand = brand.replace(/^\\d+\\.\\s*/, \"\");\n  return brand.trim();\n}\n\nfunction isValidModel(s) {\n  if (!s || typeof s !== \"string\") return false;\n  const t = s.trim();\n  if (t === \"\" || t === \"New List\" || t.includes(\"Coming Soon\")) return false;\n  return true;\n}\n\nfunction normalizeForSearch(s) {\n  if (!s) return \"\";\n  return s.toLowerCase().replace(/[^\\w\\s]/g, \" \").replace(/\\s+/g, \" \").trim();\n}\n\nfunction createCategoryKey(name) {\n  return name.toLowerCase()\n    .replace(/^\\d+\\.\\s*/, \"\")\n    .replace(/[^a-z0-9]+/g, \"_\")\n    .replace(/^_|_$/g, \"\");\n}\n\n// ==================== RATE LIMITER ====================\nconst rateStore = new Map();\nfunction checkRateLimit(ip, tier) {\n  const limit = RATE_LIMITS[tier] || RATE_LIMITS.normal;\n  const now = Date.now();\n  const hits = (rateStore.get(ip) || []).filter(t => now - t < RL_WINDOW_MS);\n  hits.push(now);\n  rateStore.set(ip, hits);\n  if (hits.length > limit) {\n    const retryAfter = Math.ceil((hits[0] + RL_WINDOW_MS - now) / 1000);\n    return { allowed: false, retryAfter };\n  }\n  return { allowed: true, remaining: limit - hits.length };\n}\nsetInterval(() => {\n  const cutoff = Date.now() - RL_WINDOW_MS;\n  for (const [ip, hits] of rateStore) {\n    const fresh = hits.filter(t => t > cutoff);\n    if (fresh.length) rateStore.set(ip, fresh);\n    else rateStore.delete(ip);\n  }\n}, 5 * 60 * 1000).unref();\n\n// ==================== LOGGING ====================\nconst LOG_DIR = path.join(__dirname, \"logs\");\nif (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });\n\nfunction writeLog(file, entry) {\n  fs.appendFile(path.join(LOG_DIR, file), JSON.stringify(entry) + \"\\n\", () => {});\n}\n\nfunction log(level, message, meta = {}) {\n  const entry = { ts: new Date().toISOString(), level, message, ...meta };\n  writeLog(`access_${entry.ts.slice(0, 10)}.jsonl`, entry);\n  if (NODE_ENV !== \"production\") {\n    const icon = { error: \"❌\", warn: \"⚠️\", info: \"📋\" }[level] || \"📋\";\n    console.log(`${icon} ${message}`, Object.keys(meta).length ? meta : \"\");\n  }\n}\n\nfunction logSearch(part, model, resultCount, ip) {\n  metrics.totalSearches++;\n  writeLog(`search_${new Date().toISOString().slice(0, 10)}.jsonl`, {\n    ts: new Date().toISOString(), part, model, resultCount, ip\n  });\n}\n\nasync function getSearchStats() {\n  const day = new Date().toISOString().slice(0, 10);\n  try {\n    const content = await fs.promises.readFile(path.join(LOG_DIR, `search_${day}.jsonl`), \"utf8\");\n    const logs = content.split(\"\\n\").filter(Boolean).map(l => JSON.parse(l));\n    const byModel = {}, byPart = {};\n    logs.forEach(l => {\n      byModel[l.model] = (byModel[l.model] || 0) + 1;\n      byPart[l.part] = (byPart[l.part] || 0) + 1;\n    });\n    const top = (obj, n) => Object.entries(obj).sort((a, b) => b[1] - a[1]).slice(0, n).map(([k, v]) => ({ name: k, count: v }));\n    return { date: day, total: logs.length, topModels: top(byModel, 10), topParts: top(byPart, 10), recent: logs.slice(-30).reverse() };\n  } catch {\n    return { date: day, total: 0, topModels: [], topParts: [], recent: [] };\n  }\n}\n\nfunction cleanOldLogs() {\n  const cutoff = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);\n  try {\n    fs.readdirSync(LOG_DIR).forEach(file => {\n      const m = file.match(/^(?:access|search)_(\\d{4}-\\d{2}-\\d{2})\\.jsonl$/);\n      if (m && m[1] < cutoff) fs.unlink(path.join(LOG_DIR, file), () => {});\n    });\n  } catch (err) {}\n}\n\n// ==================== INDEX BUILDER (PURE) ====================\nfunction buildSearchIndex(apiData) {\n  const idx = {};\n  if (!apiData.categories || !Array.isArray(apiData.categories)) return idx;\n  for (const cat of apiData.categories) {\n    if (!cat.name) continue;\n    const key = createCategoryKey(cat.name);\n    idx[key] = { name: cat.name, models: [] };\n    for (const brand of (cat.brands || [])) {\n      if (!brand.name) continue;\n      const cleanBrand = cleanBrandName(brand.name);\n      for (const rawModel of (brand.models || [])) {\n        if (!isValidModel(rawModel)) continue;\n        let compatibility = cleanModelString(rawModel);\n        if (!compatibility) continue;\n        idx[key].models.push({\n          brand: cleanBrand,\n          compatibility,\n          searchNorm: normalizeForSearch(compatibility)\n        });\n      }\n    }\n  }\n  return idx;\n}\n\n// ==================== SEARCH ENGINE ====================\nfunction searchCategory(categoryKey, query) {\n  const cat = searchIndex[categoryKey];\n  if (!cat) return null;\n  const qNorm = normalizeForSearch(query);\n  if (!qNorm) return [];\n  const words = qNorm.split(\" \").filter(w => w.length > 1);\n  const scored = [];\n  const seen = new Set();\n  for (const model of cat.models) {\n    const c = model.searchNorm;\n    if (seen.has(c)) continue;\n    let score = 0;\n    if (c === qNorm) score = 100;\n    else if (c.includes(qNorm)) score = 90;\n    else if (words.every(w => c.includes(w))) score = 75;\n    else {\n      const hits = words.filter(w => c.includes(w)).length;\n      if (hits > 0 && hits / words.length >= 0.7) score = 55;\n    }\n    if (score > 0) {\n      seen.add(c);\n      scored.push({ ...model, score });\n      if (scored.length >= MAX_RESULTS * 2) break;\n    }\n  }\n  return scored.sort((a, b) => b.score - a.score).slice(0, MAX_RESULTS)\n    .map(({ brand, compatibility, score }) => ({ brand, compatibility, score }));\n}\n\n// ==================== EXTERNAL DATA FETCH ====================\nasync function fetchAllExternalData() {\n  log(\"info\", \"🔄 Syncing external data...\");\n  let masterOk = false;\n  try {\n    const masterRes = await axios.get(DATA_SOURCES.master, { timeout: 30000 });\n    const fresh = buildSearchIndex(masterRes.data);\n    if (Object.keys(fresh).length === 0) throw new Error(\"Empty index\");\n    searchIndex = fresh;\n    masterOk = true;\n    lastSyncTime = new Date().toISOString();\n    syncCount++;\n    const totalModels = Object.values(searchIndex).reduce((s, c) => s + c.models.length, 0);\n    log(\"info\", `✅ Master data: ${Object.keys(searchIndex).length} categories, ${totalModels} models`);\n  } catch (err) {\n    log(\"error\", \"❌ Master fetch failed\", { error: err.message });\n  }\n  try {\n    const updatesRes = await axios.get(DATA_SOURCES.updates, { timeout: 10000 });\n    if (updatesRes.data && Array.isArray(updatesRes.data.updates)) {\n      updatesList = updatesRes.data.updates;\n      log(\"info\", `✅ Updates: ${updatesList.length} items`);\n    }\n  } catch (err) {\n    log(\"warn\", \"⚠️ Updates fetch failed\");\n  }\n  try {\n    const versionRes = await axios.get(DATA_SOURCES.version, { timeout: 5000 });\n    if (versionRes.data && versionRes.data.version) {\n      dataVersion = versionRes.data.version;\n      log(\"info\", `✅ Version: ${dataVersion}`);\n    }\n  } catch (err) {\n    log(\"warn\", \"⚠️ Version fetch failed\");\n  }\n  if (!masterOk) log(\"error\", \"❌ No master data available – search will be empty\");\n}\n\n// ==================== HTTP HELPERS ====================\nfunction getClientIp(req) {\n  return req.headers[\"x-forwarded-for\"]?.split(\",\")[0].trim() || req.socket?.remoteAddress || \"unknown\";\n}\n\nfunction formatUptime(sec) {\n  const d = Math.floor(sec / 86400), h = Math.floor((sec % 86400) / 3600), m = Math.floor((sec % 3600) / 60), s = Math.floor(sec % 60);\n  return [d && `${d}d`, h && `${h}h`, m && `${m}m`, `${s}s`].filter(Boolean).join(\" \");\n}\n\nfunction formatBytes(bytes) {\n  if (bytes < 1024) return `${bytes}B`;\n  if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)}KB`;\n  return `${(bytes / 1048576).toFixed(1)}MB`;\n}\n\nconst CORS = {\n  \"Access-Control-Allow-Origin\": \"*\",\n  \"Access-Control-Allow-Methods\": \"GET, OPTIONS\",\n  \"Access-Control-Allow-Headers\": \"Content-Type\"\n};\nconst SEC = {\n  \"X-Content-Type-Options\": \"nosniff\",\n  \"X-Frame-Options\": \"SAMEORIGIN\",\n  \"X-XSS-Protection\": \"1; mode=block\"\n};\n\nfunction sendJson(res, req, status, data, startTime) {\n  const body = JSON.stringify(data);\n  const buf = Buffer.from(body, \"utf8\");\n  const elapsed = startTime ? (Date.now() - startTime).toFixed(0) : \"—\";\n  const headers = {\n    \"Content-Type\": \"application/json; charset=utf-8\",\n    \"X-Response-Time\": `${elapsed}ms`,\n    ...CORS,\n    ...SEC\n  };\n  metrics.totalRequests++;\n  if (status >= 400) metrics.totalErrors++;\n  const acceptEncoding = req.headers[\"accept-encoding\"] || \"\";\n  if (buf.length >= GZIP_THRESHOLD && acceptEncoding.includes(\"gzip\")) {\n    zlib.gzip(buf, (err, compressed) => {\n      if (err) {\n        res.writeHead(status, { ...headers, \"Content-Length\": buf.length });\n        return res.end(buf);\n      }\n      res.writeHead(status, { ...headers, \"Content-Encoding\": \"gzip\", \"Content-Length\": compressed.length });\n      res.end(compressed);\n    });\n  } else {\n    res.writeHead(status, { ...headers, \"Content-Length\": buf.length });\n    res.end(buf);\n  }\n}\n\n// ==================== HTTP SERVER ====================\nconst server = http.createServer(async (req, res) => {\n  const start = Date.now();\n  const ip = getClientIp(req);\n  let url;\n  try {\n    url = new URL(req.url, `http://${req.headers.host}`);\n  } catch {\n    res.writeHead(400);\n    return res.end(\"Bad request\");\n  }\n  const pathname = url.pathname;\n  metrics.pathHits[pathname] = (metrics.pathHits[pathname] || 0) + 1;\n\n  if (req.method === \"OPTIONS\") {\n    res.writeHead(204, CORS);\n    return res.end();\n  }\n  if (req.method !== \"GET\") {\n    return sendJson(res, req, 405, { error: \"Method not allowed\" }, start);\n  }\n\n  // ---- Dashboard ----\n  if (pathname === \"/\" || pathname === \"/dashboard\") {\n    const rl = checkRateLimit(ip, \"loose\");\n    if (!rl.allowed) {\n      res.setHeader(\"Retry-After\", String(rl.retryAfter));\n      return sendJson(res, req, 429, { error: \"Too many requests\", retryAfter: `${rl.retryAfter}s` }, start);\n    }\n    if (dashboardHtml) {\n      res.writeHead(200, { \"Content-Type\": \"text/html\", ...SEC });\n      return res.end(dashboardHtml);\n    }\n    res.writeHead(200, { \"Content-Type\": \"text/html\" });\n    return res.end(\"<!DOCTYPE html><html><body><h1>UNIPARTS PRO</h1><p>Starting up...</p></body></html>\");\n  }\n\n  // ---- Health ----\n  if (pathname === \"/health\") {\n    const rl = checkRateLimit(ip, \"loose\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    const mem = process.memoryUsage();\n    const totalModels = Object.values(searchIndex).reduce((s, c) => s + c.models.length, 0);\n    return sendJson(res, req, 200, {\n      status: \"operational\",\n      uptime: formatUptime(process.uptime()),\n      startedAt: metrics.started,\n      environment: NODE_ENV,\n      node: process.version,\n      memory: {\n        rss: formatBytes(mem.rss),\n        heapUsed: formatBytes(mem.heapUsed),\n        heapTotal: formatBytes(mem.heapTotal)\n      },\n      data: {\n        categories: Object.keys(searchIndex).length,\n        models: totalModels,\n        version: dataVersion,\n        lastSync: lastSyncTime,\n        syncCount\n      },\n      metrics: {\n        requests: metrics.totalRequests,\n        errors: metrics.totalErrors,\n        searches: metrics.totalSearches\n      }\n    }, start);\n  }\n\n  // ---- Categories ----\n  if (pathname === \"/categories\") {\n    const rl = checkRateLimit(ip, \"loose\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    const cats = Object.keys(searchIndex).map(key => ({\n      key,\n      name: searchIndex[key].name,\n      modelCount: searchIndex[key].models.length\n    }));\n    return sendJson(res, req, 200, { success: true, total: cats.length, categories: cats }, start);\n  }\n\n  // ---- Search ----\n  if (pathname === \"/search\") {\n    const rl = checkRateLimit(ip, \"normal\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    let part = (url.searchParams.get(\"part\") || \"\").trim().slice(0, 80);\n    let model = (url.searchParams.get(\"model\") || \"\").trim().slice(0, 100);\n    if (!part || !model) {\n      return sendJson(res, req, 400, { error: \"Missing 'part' or 'model' parameters\" }, start);\n    }\n    let categoryKey = null;\n    if (searchIndex[part]) {\n      categoryKey = part;\n    } else {\n      const cleanPart = part.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n      categoryKey = Object.keys(searchIndex).find(k => k.replace(/[^a-z0-9]/g, \"\") === cleanPart)\n        || Object.keys(searchIndex).find(k => searchIndex[k].name.toLowerCase().includes(part.toLowerCase()));\n    }\n    if (!categoryKey) {\n      return sendJson(res, req, 404, {\n        error: \"Category not found\",\n        available: Object.keys(searchIndex).map(k => ({ key: k, name: searchIndex[k].name }))\n      }, start);\n    }\n    const results = searchCategory(categoryKey, model);\n    logSearch(part, model, results ? results.length : 0, ip);\n    return sendJson(res, req, 200, {\n      success: true,\n      category: { key: categoryKey, name: searchIndex[categoryKey].name },\n      query: model,\n      totalMatches: results ? results.length : 0,\n      results: results || []\n    }, start);\n  }\n\n  // ---- Updates ----\n  if (pathname === \"/updates\") {\n    const rl = checkRateLimit(ip, \"loose\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    return sendJson(res, req, 200, { success: true, updates: updatesList }, start);\n  }\n\n  // ---- Version ----\n  if (pathname === \"/version\") {\n    const rl = checkRateLimit(ip, \"loose\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    return sendJson(res, req, 200, { success: true, version: dataVersion, lastSync: lastSyncTime }, start);\n  }\n\n  // ---- GSMArena Search ----\n  if (pathname === \"/specs-search\") {\n    const rl = checkRateLimit(ip, \"strict\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    const query = (url.searchParams.get(\"q\") || \"\").trim().slice(0, 80);\n    if (!query) return sendJson(res, req, 400, { error: \"Missing query\" }, start);\n    try {\n      const gsmRes = await axios.get(`https://www.gsmarena.com/results.php3?sQuickSearch=yes&sName=${encodeURIComponent(query)}`, {\n        headers: { \"User-Agent\": \"Mozilla/5.0\" },\n        timeout: 10000\n      });\n      const $ = cheerio.load(gsmRes.data);\n      const results = [];\n      $(\".makers li\").each((_, el) => {\n        const a = $(el).find(\"a\");\n        const href = a.attr(\"href\");\n        const title = a.find(\"img\").attr(\"title\") || a.text().trim();\n        const img = a.find(\"img\").attr(\"src\");\n        if (href && title) results.push({ id: href, title, img });\n      });\n      return sendJson(res, req, 200, { success: true, results }, start);\n    } catch (err) {\n      return sendJson(res, req, 502, { error: \"GSMArena temporarily unavailable\" }, start);\n    }\n  }\n\n  // ---- GSMArena Details ----\n  if (pathname === \"/specs-details\") {\n    const rl = checkRateLimit(ip, \"strict\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    const id = (url.searchParams.get(\"id\") || \"\").trim().slice(0, 120);\n    if (!id || /[<>\"'\\\\]/.test(id) || id.includes(\"..\") || id.includes(\"//\")) {\n      return sendJson(res, req, 400, { error: \"Invalid id\" }, start);\n    }\n    try {\n      const detailRes = await axios.get(`https://www.gsmarena.com/${id}`, {\n        headers: { \"User-Agent\": \"Mozilla/5.0\" },\n        timeout: 10000\n      });\n      const $ = cheerio.load(detailRes.data);\n      const name = $(\".specs-phone-name-title\").text().trim();\n      const img = $(\".specs-photo-main img\").attr(\"src\");\n      const specs = {};\n      $(\"table\").each((_, table) => {\n        const head = $(table).find(\"th\").text().trim();\n        if (!head) return;\n        specs[head] = {};\n        $(table).find(\"tr\").each((_, row) => {\n          const k = $(row).find(\"td\").eq(0).text().trim();\n          const v = $(row).find(\"td\").eq(1).text().replace(/\\n/g, \" \").trim();\n          if (k && v) specs[head][k] = v;\n        });\n      });\n      return sendJson(res, req, 200, { success: true, name, img, specs }, start);\n    } catch (err) {\n      return sendJson(res, req, 502, { error: \"GSMArena temporarily unavailable\" }, start);\n    }\n  }\n\n  // ---- Admin Stats ----\n  if (pathname === \"/admin/stats\") {\n    const rl = checkRateLimit(ip, \"admin\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    const key = url.searchParams.get(\"key\");\n    if (key !== ADMIN_KEY) {\n      log(\"warn\", \"Admin auth fail\", { ip });\n      return sendJson(res, req, 403, { error: \"Forbidden\" }, start);\n    }\n    const stats = await getSearchStats();\n    return sendJson(res, req, 200, { ...stats, serverMetrics: metrics }, start);\n  }\n\n  // ---- Admin Refresh ----\n  if (pathname === \"/admin/refresh\") {\n    const rl = checkRateLimit(ip, \"admin\");\n    if (!rl.allowed) return sendJson(res, req, 429, { error: \"Rate limit\" }, start);\n    const key = url.searchParams.get(\"key\");\n    if (key !== ADMIN_KEY) return sendJson(res, req, 403, { error: \"Forbidden\" }, start);\n    log(\"info\", \"Manual refresh triggered\", { ip });\n    fetchAllExternalData().catch(err => log(\"error\", \"Refresh error\", { error: err.message }));\n    return sendJson(res, req, 200, { success: true, message: \"Refresh started\" }, start);\n  }\n\n  // ---- 404 ----\n  return sendJson(res, req, 404, { error: \"Endpoint not found\" }, start);\n});\n\nserver.timeout = 30000;\nserver.headersTimeout = 35000;\n\n// ==================== STARTUP ====================\nprocess.on(\"uncaughtException\", err => log(\"error\", \"Uncaught exception\", { message: err.message, stack: err.stack }));\nprocess.on(\"unhandledRejection\", reason => log(\"error\", \"Unhandled rejection\", { reason: String(reason) }));\n\nconst DASH_PATH = path.join(__dirname, \"dashboard.html\");\ntry {\n  if (fs.existsSync(DASH_PATH)) {\n    dashboardHtml = fs.readFileSync(DASH_PATH, \"utf8\");\n    log(\"info\", \"✅ Dashboard HTML loaded\");\n  } else {\n    log(\"warn\", \"⚠️ dashboard.html not found – using fallback\");\n  }\n} catch (err) {\n  log(\"warn\", \"⚠️ Could not read dashboard.html\", { error: err.message });\n}\n\n(async () => {\n  await fetchAllExternalData();\n  server.listen(PORT, \"0.0.0.0\", () => {\n    const totalModels = Object.values(searchIndex).reduce((s, c) => s + c.models.length, 0);\n    console.log(\"\\n🚀 ════════════════════════════════════════════════\");\n    console.log(\"   UNIPARTS PRO  ·  PURE DATA EDITION (NEW MODEL)\");\n    console.log(`   🌐 Port          : ${PORT}`);\n    console.log(`   🌍 Environment   : ${NODE_ENV}`);\n    console.log(`   📦 Categories    : ${Object.keys(searchIndex).length}`);\n    console.log(`   📱 Models        : ${totalModels.toLocaleString()}`);\n    console.log(`   🔄 Auto-refresh  : every ${REFRESH_MS / 3600000} hours`);\n    console.log(`   🔐 Admin key     : set via ADMIN_KEY env var`);\n    console.log(\"   ════════════════════════════════════════════════\\n\");\n  });\n})();\n\nsetInterval(async () => {\n  log(\"info\", \"🔄 Scheduled data refresh\");\n  await fetchAllExternalData();\n}, REFRESH_MS);\n\ncleanOldLogs();\nsetInterval(cleanOldLogs, 24 * 60 * 60 * 1000).unref();\n\nfunction shutdown(sig) {\n  console.log(`\\n🛑 ${sig} – shutting down gracefully…`);\n  server.close(() => {\n    log(\"info\", \"Server closed\", { sig, uptime: formatUptime(process.uptime()) });\n    process.exit(0);\n  });\n  setTimeout(() => process.exit(1), 10000).unref();\n}\nprocess.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\nprocess.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n"
  }
]