Files
dark-lord/lib/shopProductSocial.ts
drjones 78a071ba02 Harden onion boot flow and deepen site surfaces
Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation.

Made-with: Cursor
2026-04-07 21:35:52 -07:00

116 lines
3.2 KiB
TypeScript

/**
* Deterministic “live” metrics per product id — stable across refresh/build.
*/
export type ProductTestimonial = {
handle: string;
text: string;
rating: number;
daysAgo: number;
};
export type ProductSocialMetrics = {
avgRating: number;
reviewCount: number;
purchaseCount: number;
/** Decorative hook — looks like marketplace activity */
boughtLast24h: number;
testimonials: ProductTestimonial[];
};
function hash32(s: string): number {
let h = 2166136261;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
function pick<T>(arr: T[], seed: number): T {
return arr[seed % arr.length]!;
}
const HANDLE_SUFFIXES = [
"_void",
"Buyer",
"anon_op",
"ledger_rat",
"EU_mix",
"PacificMesh",
"tor_only",
"no_logs_99",
"ghost_cart",
"relay_7",
];
const SNIPPETS: { text: string; r: number }[] = [
{ text: "Sealed exactly as pictured. Seller answered in under an hour.", r: 5 },
{ text: "Third order from this stall — consistent every time.", r: 5 },
{ text: "A bit slow on weekend but tracking matched mirror policy.", r: 4 },
{ text: "PGP match checked out. Product matches batch notes.", r: 5 },
{ text: "Pricey vs clearnet dupes but opsec packaging is on point.", r: 4 },
{ text: "Had a dispute; escrow released after proof — fair outcome.", r: 4 },
{ text: "Arrived faster than quoted. Will re-up next drop.", r: 5 },
{ text: "Not for everyone but description was honest — no BS.", r: 4 },
{ text: "Comms terse but professional. Goods matched listing.", r: 5 },
{ text: "Verified canary before order — same fingerprint as hub post.", r: 5 },
{ text: "Mixed feelings on shipping lane but item intact.", r: 4 },
{ text: "Solid vendor thread history. SKU lived up to hype.", r: 5 },
];
export function getProductSocialMetrics(productId: string): ProductSocialMetrics {
const h = hash32(productId);
const h2 = hash32(`${productId}:reviews`);
const h3 = hash32(`${productId}:buyers`);
const avgRating = Math.round((4.05 + (h % 90) / 100) * 100) / 100;
const reviewCount = 24 + (h2 % 1_247);
const purchaseCount = 180 + (h3 % 24_500);
const boughtLast24h = 8 + (h2 % 214);
let i1 = h % SNIPPETS.length;
let i2 = (h2 + 5) % SNIPPETS.length;
if (i2 === i1) i2 = (i2 + 1) % SNIPPETS.length;
let i3 = (h3 + 11) % SNIPPETS.length;
if (i3 === i1 || i3 === i2) i3 = (i3 + 1) % SNIPPETS.length;
if (i3 === i1) i3 = (i3 + 1) % SNIPPETS.length;
const t1 = SNIPPETS[i1]!;
const t2 = SNIPPETS[i2]!;
const t3 = SNIPPETS[i3]!;
const mkHandle = (seed: number) => {
const base = pick(["x", "q", "v", "k", "m", "s", "z", "r"], seed);
return `${base}${(seed % 900) + 100}${pick(HANDLE_SUFFIXES, seed + 11)}`;
};
const testimonials: ProductTestimonial[] = [
{
handle: mkHandle(h),
text: t1.text,
rating: t1.r,
daysAgo: 1 + (h % 6),
},
{
handle: mkHandle(h2),
text: t2.text,
rating: t2.r,
daysAgo: 7 + (h2 % 20),
},
{
handle: mkHandle(h3),
text: t3.text,
rating: t3.r,
daysAgo: 14 + (h3 % 45),
},
];
return {
avgRating,
reviewCount,
purchaseCount,
boughtLast24h,
testimonials,
};
}