PageSourceSearch

https://nbsva.web.app/vendors

html nbsva.web.app collected 2026-10-03 12:30:57 UTC 21,139 bytes, 352 lines download raw bytes

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">&#127942; Top 5 Vendors <span class="lb-sub">Ranked by trust score &amp; 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">&#9888; 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">&#128269;</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">&#128337;</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">&#128993;</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">&#128992;</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">&#9940;</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">&#128683;</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">&#9203; 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 = ['&#129351;', '&#129352;', '&#129353;'];
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">&#10003;</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.