diff --git a/public/promo.html b/public/promo.html
index 204f8e6..b5973e4 100644
--- a/public/promo.html
+++ b/public/promo.html
@@ -35,7 +35,7 @@
The teaser
Thirty seconds. Post it with your link, or send people to the page and let it play there.
- Download the mp4
+ Download the mp4 (16:9) Download the portrait cut (9:16 for TikTok, Reels, Shorts, Facebook)
diff --git a/public/video/polhunter-teaser-portrait-poster.jpg b/public/video/polhunter-teaser-portrait-poster.jpg
new file mode 100644
index 0000000..e6a82a1
Binary files /dev/null and b/public/video/polhunter-teaser-portrait-poster.jpg differ
diff --git a/public/video/polhunter-teaser-portrait.mp4 b/public/video/polhunter-teaser-portrait.mp4
new file mode 100644
index 0000000..9d14ff3
Binary files /dev/null and b/public/video/polhunter-teaser-portrait.mp4 differ
diff --git a/server.js b/server.js
index 83fe934..3584616 100644
--- a/server.js
+++ b/server.js
@@ -163,11 +163,17 @@ const server = http.createServer(async (req, res) => {
if (curtained(req, res, u)) return;
// a member's share link: /?r= becomes a 30-day cookie; the landing then
// sends sign-ups to instantadpay.com/join/[, so IAP's own last-touch sponsor rule applies
+ // Served in place, not redirected (Marty, 2026-09-21): Facebook's crawler follows redirects and canonicalizes a
+ // share to og:url, so a redirect to the bare home page lost the member's name on every share. og:url carries
+ // the ref; the cookie rides on the same response.
if (p === '/' && u.searchParams.get('r')) {
- const r = String(u.searchParams.get('r')).trim().slice(0, 40); u.searchParams.delete('r');
- const h = { Location: '/' + (u.searchParams.toString() ? '?' + u.searchParams : ''), 'Cache-Control': 'no-store' };
- if (/^[A-Za-z0-9_.-]{1,40}$/.test(r)) h['Set-Cookie'] = 'ph.ref=' + encodeURIComponent(r) + '; Path=/; Max-Age=2592000; SameSite=Lax; Secure';
- res.writeHead(302, h); return res.end();
+ const r = String(u.searchParams.get('r')).trim().slice(0, 40);
+ const ok = /^[A-Za-z0-9_.-]{1,40}$/.test(r);
+ let html = fs.readFileSync(path.join(PUBLIC_DIR, 'index.html'), 'utf8');
+ if (ok) html = html.replace('', '');
+ const h = Object.assign({ 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }, SEC);
+ if (ok) h['Set-Cookie'] = 'ph.ref=' + encodeURIComponent(r) + '; Path=/; Max-Age=2592000; SameSite=Lax; Secure';
+ res.writeHead(200, h); return res.end(html);
}
// ---- sign-in by hand-off from InstantAdPay
diff --git a/test/run.js b/test/run.js
index 35dd4b6..c665023 100644
--- a/test/run.js
+++ b/test/run.js
@@ -117,7 +117,7 @@ const sleep = ms => new Promise(r => setTimeout(r, ms));
eq([!!pr, pr && pr.status, pr && pr.pol, b4.body.badges.some(x => x.id === 'top'), b4.body.rank.all.finds], [true, 'due', 3, true, 1], 'the prize is a due drip on the board, Top Hunter badge on, finds unchanged');
const lb2 = await call('/api/leaders?period=all'); eq(lb2.body.rows[0].finds, 1, 'the prize drip is not a find on the leaderboard');
const b3 = await call('/api/my/board'); eq([b3.body.badges.some(b => b.id === 'first'), b3.body.rank.all.rank, b3.body.share.link], [true, 1, B + '/?r=hunter42'], 'board: First Find badge, rank #1, share link carries the username');
- const rv = await fetch(B + '/?r=hunter42', { redirect: 'manual' }); eq([rv.status, /ph\.ref=hunter42/.test(rv.headers.get('set-cookie') || ''), rv.headers.get('location')], [302, true, '/'], 'a share-link visit sets the referral cookie and lands on the landing');
+ const rv = await fetch(B + '/?r=hunter42', { redirect: 'manual' }); const rvb = await rv.text(); eq([rv.status, /ph\.ref=hunter42/.test(rv.headers.get('set-cookie') || ''), /og:url" content="https:\/\/polhunter\.com\/\?r=hunter42"/.test(rvb)], [200, true, true], 'a share-link visit serves the landing in place (no redirect: Facebook keeps the name), sets the referral cookie, and og:url carries the ref');
const cf = await (await fetch(B + '/api/config', { headers: { Cookie: 'ph.ref=hunter42' } })).json(); eq([cf.ref, cf.joinUrl], ['hunter42', 'https://instantadpay.com/join/hunter42?from=polhunter'], 'with the cookie, sign-ups go to that member\u2019s IAP join link');
const badRef = await fetch(B + '/?r=]