1<!DOCTYPE html> 2<html lang="en"> 3<head> 4<meta charset="UTF-8"> 5<meta name="viewport" content="width=device-width, initial-scale=1.0"> 6<title>Vendor Directory | Verified Blood Strike Vendors Nigeria â N.B.S.V.A</title> 7<meta name="description" content="Search the official N.B.S.V.A vendor directory. Verified Blood Strike vendors in Nigeria: RATEDTV, YEET STORE, JMAROXS STORE, ATLASYT STORE, TRUSTED, KHAOS STORES, ZAI STORE."> 8<meta name="keywords" content="verified Blood Strike vendors Nigeria, trusted Blood Strike vendor list, how to check if a Blood Strike vendor is legit, Blood Strike UID top up Nigeria trusted seller, Blood Strike vendor directory"> 9<link rel="canonical" href="https://nbsva.web.app/vendors.html"> 10<link rel="icon" type="image/x-icon" href="/favicon.ico"> 11<link rel="icon" type="image/png" sizes="192x192" href="/assets/icon-192.png"> 12<link rel="icon" type="image/png" sizes="512x512" href="/assets/icon-512.png"> 13<link rel="apple-touch-icon" href="/assets/apple-touch-icon.png"> 14<meta name="mobile-web-app-capable" content="yes"> 15<meta name="apple-mobile-web-app-capable" content="yes"> 16<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> 17<meta name="apple-mobile-web-app-title" content="N.B.S.V.A"> 18<link rel="manifest" href="manifest.json"> 19<meta name="theme-color" content="#070907"> 20<link rel="preconnect" href="https://fonts.googleapis.com"> 21<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 22<link rel="preconnect" href="https://the-hunt-scrims.firebaseio.com" crossorigin> 23<link rel="preconnect" href="https://www.gstatic.com" crossorigin> 24<link rel="preload" as="script" href="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js" crossorigin> 25<link rel="preload" as="script" href="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js" crossorigin> 26<link rel="preload" as="script" href="https://www.gstatic.com/firebasejs/9.22.0/firebase-database-compat.js" crossorigin> 27<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@600;700;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> 28<link rel="stylesheet" href="css/style.css"> 29<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@800;900&display=swap" rel="stylesheet"> 30<link rel="stylesheet" href="css/vendors-page.css"> 31<link rel="stylesheet" href="css/vendors-list.css"> 32<link rel="stylesheet" href="css/responsive.css"> 33</head> 34<body class="vendors-v2"> 35<div id="nav-wrapper"></div> 36 37<section class="page-hero vph2"> 38 <img src="assets/vendors-hero.png" alt="" class="vph2-art" onerror="this.style.display='none'"> 39 <div class="container"> 40 <span class="label-caps">Trusted. Verified. Recommended.</span> 41 <h1>Verified Vendors</h1> 42 <p>Browse verified vendors and deal safely with confidence. If a seller is not listed here, they are not verified.</p> 43 </div> 44</section> 45 46<!-- TOP 5 LEADERBOARD --> 47<section class="container" style="padding-top:14px"> 48 <div class="lb-panel"> 49 <div class="lb-head">🏆 Top 5 Vendors <span class="lb-sub">Ranked by trust score & reviews â updates live</span></div> 50 <div id="lbRows"></div> 51 </div> 52</section> 53 54<!-- ANTI-SCAM DISCLAIMER --> 55<section class="container" style="padding-top:6px"> 56 <div class="card" style="border-color:rgba(240,68,56,.4);background:rgba(240,68,56,.06)"> 57 <p style="font-weight:700;color:#ff8d84;margin-bottom:6px">⚠ Anti-Scam Warning â read before you pay anyone</p> 58 <p style="font-size:.92rem">If you're dealing with an <strong>Upcoming</strong> or <strong>Standard Vendor</strong> (not yet fully Verified), or you searched and could not find a seller anywhere on this directory at all, do not send payment directly. Protect yourself by using a fully <strong>Verified Vendor</strong> as a middleman ("MM") to hold the deal. A middleman fee applies â it's a small cost compared to losing your money to a scammer with no recourse.</p> 59 </div> 60</section> 61 62<section class="section" style="padding-top:24px"> 63 <div class="container tabs-group"> 64 <div class="big-search">
65 <span class="mag">🔍</span> 66 <input type="text" id="vendorSearch" placeholder="Search vendor name, WhatsApp, service..." aria-label="Search vendors"> 67 </div> 68 69 <div class="dir-toolbar-row"> 70 <input type="hidden" id="dirSort" value="trust"> 71 </div> 72 <div class="cat-chip-row" id="dirCatChips"></div> 73 74 <div class="tabs"> 75 <button class="tab-btn active" data-tab="tabAll">All <span id="cntAll"></span></button> 76 <button class="tab-btn" data-tab="tabVerified">Verified <span id="cntVerified"></span></button> 77 <button class="tab-btn" data-tab="tabStandard">Standard <span id="cntStandard"></span></button> 78 <button class="tab-btn" data-tab="tabUpcoming">Upcoming <span id="cntUpcoming"></span></button> 79 <button class="tab-btn" data-tab="tabPending" id="tabBtnPending" style="display:none">Pending <span id="cntPending"></span></button> 80 <button class="tab-btn" data-tab="tabSuspended">Suspended <span id="cntSuspended"></span></button> 81 <button class="tab-btn" data-tab="tabBlacklistedVendors">Blacklisted <span id="cntBlacklisted"></span></button> 82 <button class="tab-btn" data-tab="tabScammers">Scammers</button> 83 </div> 84 85 <div class="tab-panel active" id="tabAll"> 86 <div class="grid-3" id="allGrid"></div> 87 </div> 88 89 <div class="tab-panel" id="tabVerified"> 90 <div class="grid-3" id="verifiedGrid"></div> 91 </div> 92 93 <div class="tab-panel" id="tabStandard"> 94 <p style="color:var(--muted);margin-bottom:22px;max-width:560px">Survived an observation period, but still not fully Verified. Read the anti-scam warning above before dealing with anyone here.</p> 95 <div class="grid-3" id="standardGrid"></div> 96 <div class="card" id="standardEmpty" style="text-align:center;padding:50px 24px;display:none"> 97 <p style="font-size:1.6rem;margin-bottom:8px">🕑</p> 98 <h3>No standard vendors right now</h3> 99 <p style="max-width:420px;margin:8px auto 0">Vendors who've been Upcoming for a while with a clean record move here before full verification.</p> 100 </div> 101 </div> 102 103 <div class="tab-panel" id="tabUpcoming"> 104 <p style="color:var(--muted);margin-bottom:22px;max-width:560px">Just approved â no track record yet. Read the anti-scam warning above before dealing with anyone here.</p> 105 <div class="grid-3" id="upcomingGrid"></div> 106 <div class="card" id="upcomingEmpty" style="text-align:center;padding:50px 24px;display:none"> 107 <p style="font-size:1.6rem;margin-bottom:8px">🟡</p> 108 <h3>No upcoming vendors right now</h3> 109 <p style="max-width:420px;margin:8px auto 0">Newly approved applicants will appear here first, before being promoted to Standard and eventually Verified.</p> 110 </div> 111 </div> 112 113 <div class="tab-panel" id="tabPending"> 114 <div id="pendingRows"></div> 115 <div class="card" id="pendingEmpty" style="text-align:center;padding:50px 24px;display:none"> 116 <p style="font-size:1.6rem;margin-bottom:8px">🟠</p> 117 <h3>No pending applications</h3> 118 <p style="max-width:420px;margin:8px auto 0">New applications submitted through <a href="become-a-vendor.html">Become a Vendor</a> are reviewed by the Verification Office.</p> 119 </div> 120 </div> 121 122 <div class="tab-panel" id="tabSuspended"> 123 <div class="grid-3" id="suspendedGrid"></div> 124 <div class="card" id="suspendedEmpty" style="text-align:center;padding:50px 24px;display:none"> 125 <p style="font-size:1.6rem;margin-bottom:8px">⛔</p> 126 <h3>No suspended vendors</h3> 127 <p style="max-width:420px;margin:8px auto 0">Temporary suspensions and their reasons are published here and in <a href="enforcement.html">Vendor Enforcement</a>.</p> 128 </div> 129 </div> 130 131 <div class="tab-panel" id="tabBlacklistedVendors"> 132 <p style="color:var(--muted);margin-bottom:22px;max-width:560px">Former N.B.S.V.A vendors permanently banned from the association. Do not deal with anyone listed here for any reason.</p> 133 <div class="grid-3" id="blacklistedGrid"></div> 134 <div class="card" id="blacklistedEmpty" style="text-align:center;padding:50px 24px;display:none"> 135 <p style="font-size:1.6rem;margin-bottom:8px">🚫</p> 136 <h3>No blacklisted vendors</h3> 137 <p style="max-width:420px;margin:8px auto 0">Vendors are only moved here for serious, confirmed violations â full reasons are published in <a href="enforcement.html">Vendor Enforcement</a>.</p> 138 </div> 139 </div> 140 141 <div class="tab-panel" id="tabScammers"> 142 <p style="color:var(--muted);margin-bottom:22px">Impersonators and fake profiles who were never real N.B.S.V.A vendors. Full records in <a href="enforcement.html">Vendor Enforcement</a>.</p> 143 <div class="table-wrap"> 144 <table> 145 <thead><tr><th>
145Name / Page</th><th>Violation</th><th>Status</th></tr></thead> 146 <tbody id="scammerRows"></tbody> 147 </table> 148 </div> 149 </div> 150 </div> 151</section> 152 153<div id="footer-wrapper"></div>
154<script src="js/nav.js"></script>
vendor: 1 bytes, line 154
154
155<script crossorigin="anonymous" src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
vendor: 1 bytes, line 155
155
156<script crossorigin="anonymous" src="https://www.gstatic.com/firebasejs/9.22.0/firebase-auth-compat.js"></script>
vendor: 1 bytes, line 156
156
157<script crossorigin="anonymous" src="https://www.gstatic.com/firebasejs/9.22.0/firebase-database-compat.js"></script>
vendor: 1 bytes, line 157
157
158<script crossorigin="anonymous" src="https://www.gstatic.com/firebasejs/9.22.0/firebase-functions-compat.js"></script>
vendor: 1 bytes, line 158
158
159<script>var NBSVA_NEEDS = ['vendors', 'scams', 'reports', 'catalog', 'reviews'];</script>
vendor: 1 bytes, line 159
159
160<script src="js/firebase-init.js"></script>
vendor: 1 bytes, line 160
160
161<script src="js/data.js"></script>
vendor: 1 bytes, line 161
161
162<script src="js/reviews.js"></script>
vendor: 1 bytes, line 162
162
163<script src="js/catalog.js"></script>
vendor: 1 bytes, line 163
163
164<script src="js/main.js"></script>
vendor: 1 bytes, line 164
164
165<script src="js/nova-client.js"></script>
vendor: 1 bytes, line 165
165
166<script> 167/* Category chips â derived automatically from each vendor's freeform 168 `sells` text via keyword match, no structured category field needed 169 (seed vendors predate the dashboard's category checkboxes). */ 170var CAT_CHIPS = [ 171 { label: 'Accounts', re: /account/i }, 172 { label: 'Top-ups', re: /top-?up|\bcp\b|gems|gold|coin/i }, 173 { label: 'Passes', re: /pass/i }, 174 { label: 'Recovery/Unlinking', re: /recover|unlink|unban|unbind/i }, 175 { label: 'Escrow/MM', re: /escrow|middleman|\bmm\b/i } 176]; 177var dirCategory = null; 178document.getElementById('dirCatChips').innerHTML = CAT_CHIPS.map(function (c, i) { 179 return '<button type="button" class="cat-chip" onclick="setDirCategory(' + i + ')">' + c.label + '</button>'; 180}).join(''); 181function setDirCategory(i) { 182 dirCategory = (dirCategory === i) ? null : i; 183 document.querySelectorAll('.cat-chip').forEach(function (el, idx) { el.classList.toggle('active', idx === dirCategory); }); 184 refreshDirectory(); 185} 186function matchesDirCategory(v) { return dirCategory === null || CAT_CHIPS[dirCategory].re.test(v.sells || ''); } 187 188function sortVendors(list) { 189 var mode = document.getElementById('dirSort').value; 190 var copy = list.slice(); 191 if (mode === 'trust') copy.sort(function (a, b) { return trustScore(b.id).score - trustScore(a.id).score; }); 192 else if (mode === 'rating') copy.sort(function (a, b) { return ratingSummary(b.id).avg - ratingSummary(a.id).avg || ratingSummary(b.id).count - ratingSummary(a.id).count; }); 193 else if (mode === 'reviews') copy.sort(function (a, b) { return ratingSummary(b.id).count - ratingSummary(a.id).count; }); 194 else if (mode === 'longest') copy.sort(function (a, b) { return (parseInt(a.since, 10) || 9999) - (parseInt(b.since, 10) || 9999); }); 195 else if (mode === 'newest') copy.sort(function (a, b) { return (parseInt(b.since, 10) || 0) - (parseInt(a.since, 10) || 0); }); 196 return copy; 197} 198/* Sort-by dropdown removed per request â category chips (above) are now 199 the only visible filter control. Vendors still sort by trust score 200 internally (dirSort stays hardcoded to 'trust' in the HTML). */ 201 202function refreshDirectory() { 203 // "All" really means all â every status, including Suspended and 204 // Blacklisted. Each other tab is a strict, mutually-exclusive slice by 205 // exact status (a Suspended/Blacklisted vendor no longer bleeds into 206 // the Verified tab just because it isn't Upcoming/Standard). 207 var all = sortVendors(allVendors(false, true).filter(matchesDirCategory)); 208 var verified = all.filter(function (v) { return (v.status || 'verified') === 'verified'; }); 209 var standard = all.filter(function (v) { return v.status === 'standard'; }); 210 var upcoming = all.filter(function (v) { return v.status === 'upcoming'; }); 211 var suspended = all.filter(function (v) { return v.status === 'suspended'; }); 212 var blacklisted = all.filter(function (v) { return v.status === 'blacklisted'; }); 213 214 document.getElementById('allGrid').innerHTML = all.map(vendorCard).join(''); 215 document.getElementById('cntAll').textContent = '(' + all.length + ')'; 216 217 document.getElementById('verifiedGrid').innerHTML = verified.map(vendorCard).join(''); 218 document.getElementById('cntVerified').textContent = '(' + verified.length + ')'; 219 220 document.getElementById('upcomingGrid').innerHTML = upcoming.map(vendorCard).join(''); 221 document.getElementById('upcomingEmpty').style.display = upcoming.length ? 'none' : 'block'; 222 document.getElementById('cntUpcoming').textContent = upcoming.length ? '(' + upcoming.length + ')' : ''; 223 224 document.getElementById('standardGrid').innerHTML = standard.map(vendorCard).join(''); 225 document.getElementById('standardEmpty').style.display = standard.length ? 'none' : 'block'; 226 document.getElementById('cntStandard').textContent = standard.length ? '(' + standard.length + ')' : ''; 227 228 document.getElementById('suspendedGrid').innerHTML = suspended.map(vendorCard).join(''); 229 document.getElementById('suspendedEmpty').style.display = suspended.length ? 'none' : 'block'; 230 document.getElementById('cntSuspended').textContent = suspended.length ? '(' + suspended.length + ')' : ''; 231 232 document.getElementById('blacklistedGrid').innerHTML = blacklisted.map(vendorCard).join(''); 233 document.getElementById('blacklistedEmpty').style.display = blacklisted.length ? 'none' : 'block'; 234 document.getElementById('cntBlacklisted').textContent = blacklisted.length ? '(' + blacklisted.length + ')' : ''; 235 236 document.getElementById('scammerRows').innerHTML = allScams().map(function (s) { 237 return '<tr><td data-label="Name / Page"><strong>' + s.name + '</strong></td><td data-label="Violation">' + s.type + ' â ' + s.reason 238 + '</td><td data-label="Status"><span class="tag tag-red">' + s.status + '</span></td></tr>'; 239 }).join(''); 240
241 var pending = pendingVendorApps(); 242 document.getElementById('pendingRows').innerHTML = pending.map(function (a) { 243 return '<div class="card" style="margin-bottom:10px;display:flex;align-items:center;justify-content:space-between;gap:14px">' 244 + '<div><strong>' + a.biz + '</strong><div style="color:var(--muted);font-size:.8rem;margin-top:2px">Submitted ' + new Date(a.ts).toLocaleDateString() + '</div></div>' 245 + '<span class="tag tag-gold">⏳ Pending Review</span></div>'; 246 }).join(''); 247 document.getElementById('pendingEmpty').style.display = pending.length ? 'none' : 'block'; 248 document.getElementById('cntPending').textContent = pending.length ? '(' + pending.length + ')' : ''; 249 var pendingTabBtn = document.getElementById('tabBtnPending'); 250 pendingTabBtn.style.display = pending.length ? '' : 'none'; 251 if (!pending.length && pendingTabBtn.classList.contains('active')) { 252 pendingTabBtn.classList.remove('active'); 253 document.getElementById('tabPending').classList.remove('active'); 254 var allBtn = document.querySelector('.tab-btn[data-tab="tabAll"]'); 255 allBtn.classList.add('active'); 256 document.getElementById('tabAll').classList.add('active'); 257 } 258 259 initReveal(); 260} 261/* ---- Loading gate ---- 262 Same root cause the homepage leaderboard already had: an empty cache 263 ({}) is indistinguishable from "hasn't loaded yet" (a genuinely-empty 264 result looks identical to "no data has arrived"), so rendering off a 265 fixed timer risked painting from a still-empty cache on a slow 266 connection instead of real data. _reviewsLoaded/_vendorsLoaded 267 (js/firebase-init.js) are only set true inside the real on('value') 268 callbacks, so gating on them â not a timer â is what actually 269 distinguishes "not loaded" from "loaded, and there's nothing there". 270 No skeleton/shimmer, no loading text â the grid stays blank/empty until 271 real data is ready, then the full grid replaces it in one pass, not 272 progressively. */ 273var _vdWaitStart = Date.now(); 274var VENDORS_LOAD_TIMEOUT_MS = 15000; 275document.body.classList.add('vendors-loading'); 276function tryInitialRender() { 277 var allGrid = document.getElementById('allGrid'); 278 if (!_reviewsLoaded || !_vendorsLoaded) { 279 if (Date.now() - _vdWaitStart > VENDORS_LOAD_TIMEOUT_MS) { 280 document.body.classList.remove('vendors-loading'); 281 if (allGrid) allGrid.innerHTML = '<p style="color:var(--muted);text-align:center;padding:20px 0;grid-column:1/-1">Couldnât load vendors â check your connection and reload the page.</p>'; 282 } else { 283 if (allGrid) allGrid.innerHTML = '<div style="grid-column:1/-1;display:flex;justify-content:center"><span class="vd-loading-pill"><span class="vd-loading-dot"></span>Loading vendorsâ¦</span></div>'; 284 } 285 return; 286 } 287 document.body.classList.remove('vendors-loading'); 288 refreshDirectory(); 289} 290tryInitialRender(); 291/* Firebase only re-triggers this (via _nbsvaRefresh below) when a listener 292 actually delivers a snapshot â if one never fires at all (offline, 293 blocked request, no network error callback), nothing would ever re-check 294 the timeout. This one-shot check fires once, after the timeout window, 295 purely to catch that case â same safety-net pattern as the homepage 296 leaderboard's own setTimeout(renderLeaderboard, ...). */ 297setTimeout(tryInitialRender, VENDORS_LOAD_TIMEOUT_MS + 500);
298initSearch('vendorSearch', '.vendor-card'); 299 300/* main.js's own _nbsvaRefresh re-renders #verifiedGrid unfiltered and the 301 open profile modal on live data changes â chain it so the modal refresh 302 still happens, then re-apply our status filtering/counts on top. 303 tryInitialRender() (not a bare refreshDirectory()) so a live update that 304 fires before _reviewsLoaded/_vendorsLoaded are BOTH true (e.g. catalog or 305 scams arriving first) can't sneak a render in ahead of the loading gate â 306 once both are true it behaves like a normal refresh from then on. */ 307var _vendorsRefresh = window._nbsvaRefresh; 308window._nbsvaRefresh = function () { if (_vendorsRefresh) _vendorsRefresh(); tryInitialRender(); renderLeaderboard(); }; 309 310/* ---- TOP 5 LEADERBOARD â verified vendors ranked by trust score, 311 then review count, then rating. Re-renders live with the database. ---- */ 312/* Same "don't trust an empty cache" gate as tryInitialRender() above â an 313 empty cache looks identical to "hasn't loaded yet", so this box stays 314 showing just a small loading line (not the skeleton/blank-page treatment 315 used for the main grid â this is a small side panel, not the page's main 316 content) until _reviewsLoaded/_vendorsLoaded are both true, then renders 317 the real ranking in one pass. */ 318function renderLeaderboard() { 319 var box = document.getElementById('lbRows'); 320 if (!box) return; 321 if (!_reviewsLoaded || !_vendorsLoaded) { 322 box.innerHTML = '<div style="display:flex;justify-content:center;padding:8px 0"><span class="vd-loading-pill"><span class="vd-loading-dot"></span>Loading rankingsâ¦</span></div>'; 323 return; 324 } 325 var top = allVendors() 326 .filter(function (v) { return (withOverride(v).status || 'verified') === 'verified'; }) 327 .map(function (v) { 328 var rs = ratingSummary(v.id); 329 return { v: v, t: trustScore(v.id).score, c: rs.count, a: rs.avg }; 330 }) 331 .sort(function (x, y) { return (y.t - x.t) || (y.c - x.c) || (y.a - x.a); }) 332 .slice(0, 5); 333 var medals = ['🥇', '🥈', '🥉']; 334 box.innerHTML = top.map(function (e, i) { 335 var v = withOverride(e.v); 336 return '<div class="lb-row' + (i === 0 ? ' first' : '') + '" onclick="openProfile(\'' + e.v.id + '\')">' 337 + '<span class="lb-rank">' + (i < 3 ? medals[i] : '#' + (i + 1)) + '</span>' 338 + vendorAvatar(v, 40) 339 + '<div class="lb-who"><span class="lb-name">' + v.name + ' <span class="vcheck">✓</span></span>' 340 + '<span class="lb-meta">' + starsOf(e.a || 0) + ' ' + (e.c ? '(' + e.c + ')' : 'No reviews yet') + '</span></div>' 341 + '<div class="lb-trust"><span class="l">Trust</span><span class="n">' + e.t + '%</span></div>' 342 + '</div>'; 343 }).join('') || '<p style="color:var(--muted);text-align:center;padding:12px 0">No verified vendors yet.</p>'; 344} 345renderLeaderboard(); 346 347// deep link: vendors.html?v=jmaroxs opens the profile popup (also good for sharing) 348var deep = new URLSearchParams(location.search).get('v'); 349if (deep) openProfile(deep); 350</script>
350 351</body> 352</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.