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
116 lines
3.2 KiB
TypeScript
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,
|
|
};
|
|
}
|