Hash the 40k hidden-players list and drop the old PayPal block
Deploy 40k-rankings / build-and-deploy (push) Successful in 4s

Hidden players are now matched by a hash of their full name or nickname
(src/hide.js) so the names aren't readable in the source or bundle.
Also removes the commented-out PayPal section from the Kingmaker
sign-up page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-22 19:14:12 -05:00
co-authored by Claude Opus 5.5
parent 95a54e9ec2
commit da2a5c42e7
5 changed files with 46 additions and 26 deletions
+6 -2
View File
@@ -53,8 +53,11 @@ Everything you'd normally change is in the `CONFIG` block at the top of
- `teamId`: the BCP team whose members are shown
- `leagueId` / `regionId`: which ITC league and region to rank against
- `lmcLeagueId`: the Lord Marshal Conference league used for the LMC columns
- `blacklistNames`: players to hide from the board (full name or nickname,
case-insensitive)
- `hiddenNameHashes`: players to hide from the board, stored as hashes of
their full name or nickname so the names aren't readable in the source. The
comment at the top of `src/hide.js` has a one-line command to hash a name.
This only obfuscates: the roster is public, so anyone could hash every
member's name to find who's hidden.
- `memberIds`: fallback roster, used if the team endpoint ever stops returning
members without auth
- `useSample`: set to `true` to render placeholder rows with no network calls
@@ -62,6 +65,7 @@ Everything you'd normally change is in the `CONFIG` block at the top of
## Files
- `index.html`, `src/main.js`, `src/style.css`: the page
- `src/hide.js`: name hashing for `hiddenNameHashes`
- `nav.html`: this site's header nav entries (see the root README)
- `public/elo-data.json`: generated ELO data. Don't edit it by hand; the
weekly sync overwrites it.
+27
View File
@@ -0,0 +1,27 @@
/* Hashing for CONFIG.hiddenNameHashes, so hidden players' names aren't
readable in the source or the deployed bundle. This is obfuscation, not
secrecy: the team roster is public, so anyone could hash every member's
name and find a match.
Get the hash for a name (full name or nickname) from sites/40k-rankings:
node --input-type=module -e "import { nameHash } from './src/hide.js'; console.log(nameHash('First Last'))"
*/
// cyrb53: small, fast, synchronous 53-bit string hash.
function cyrb53(str, seed = 0) {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0; i < str.length; i++) {
const ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
// Case- and whitespace-insensitive, so "jane DOE " matches "Jane Doe".
export function nameHash(name) {
const norm = String(name || "").trim().replace(/\s+/g, " ").toLowerCase();
return norm ? cyrb53(norm).toString(16) : "";
}
+12 -9
View File
@@ -1,3 +1,5 @@
import { nameHash } from './hide.js';
/* ---- Shared: header / footer ---- */
function loadHTML(file, elementId, callback) {
fetch(file)
@@ -48,19 +50,20 @@ const CONFIG = {
// Fallback roster if the team endpoint requires auth.
memberIds: [],
// Names to hide from the leaderboard entirely (case-insensitive).
// Match on full name ("First Last") or nickname, whichever the player goes by.
blacklistNames: ["Hidden Player"],
// Players to hide from the leaderboard entirely, as nameHash() values of
// their full name ("First Last") or nickname — see src/hide.js for how to
// generate one. Hashed so the names aren't readable in the source.
hiddenNameHashes: ["5b64057a31512"],
// Flip to true to preview the layout with placeholder rows (no network).
useSample: false,
};
const BLACKLIST = new Set(CONFIG.blacklistNames.map(n => n.trim().toLowerCase()));
function isBlacklisted(u = {}) {
const full = fullName(u).toLowerCase();
const nick = String(u.nickname || "").trim().toLowerCase();
return (full && BLACKLIST.has(full)) || (nick && BLACKLIST.has(nick));
const HIDDEN = new Set(CONFIG.hiddenNameHashes);
function isHidden(u = {}) {
const full = nameHash(fullName(u));
const nick = nameHash(u.nickname);
return (full && HIDDEN.has(full)) || (nick && HIDDEN.has(nick));
}
const API = "https://newprod-api.bestcoastpairings.com/v1";
@@ -290,7 +293,7 @@ function build(rows, ids) {
const kept = [];
for (const rec of rows) {
const uid = rec.userId || (rec.user && rec.user.id);
if (uid && ids.has(uid) && !isBlacklisted(rec.user || {})) kept.push(rec);
if (uid && ids.has(uid) && !isHidden(rec.user || {})) kept.push(rec);
}
kept.sort((a, b) => Number(b.ITCPoints || 0) - Number(a.ITCPoints || 0));
return kept;