A watch page at /v/<code>, so shares run through our own domain

A link straight to the file is untrackable — the recipient fetches bytes from
the file host and we never hear about it. Routing through paytolaugh.com makes
every open a page load that can be counted, and gives somewhere to put a real
player and a preview card.

The code is the first eight hex characters of the project id. Those are already
unique across every project and the space is 4.3 billion, so there is no
mapping table to build or keep in sync — the server resolves by prefix and
checks for ambiguity. 33 characters against 74 for the raw file.

Sharing stays deliberate: the page only resolves a project whose owner has
pressed Share.
This commit is contained in:
gator 2026-09-09 01:13:35 +00:00
parent 6dcbfbd035
commit 789c4fef55

77
v/index.html Normal file
View file

@ -0,0 +1,77 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PayToLaugh</title>
<meta name="description" content="A short video from somebody's pet.">
<meta property="og:type" content="video.other">
<meta property="og:site_name" content="PayToLaugh">
<meta property="og:title" content="Your pet has opinions">
<meta property="og:description" content="A short video from somebody's pet.">
<meta name="twitter:card" content="summary_large_image">
<link rel="stylesheet" href="/assets/app.css">
<style>
body{display:flex;flex-direction:column;min-height:100vh}
.watch{max-width:420px;margin:0 auto;padding:28px 20px 40px;width:100%;box-sizing:border-box}
.watch video{width:100%;aspect-ratio:9/16;border-radius:14px;background:#000;display:block;
border:1px solid var(--line)}
.watch h1{font-size:23px;margin:20px 0 4px;line-height:1.25}
.watch .who{color:var(--muted);font:12px/1.6 var(--mono)}
.watch .cta{margin-top:26px;padding-top:22px;border-top:1px solid var(--line)}
.watch .cta p{color:var(--muted);font-size:14.5px;margin:0 0 14px}
.miss{text-align:center;padding:60px 20px;color:var(--muted)}
</style>
</head>
<body>
<div class="watch" id="watch">
<p style="color:var(--muted)">Loading…</p>
</div>
<script src="/assets/app.js"></script>
<script>
// The code is the first eight hex characters of the project id, taken from
// /v/<code>. No mapping table: the prefix is already unique, and the server
// checks for ambiguity.
var code = (location.pathname.match(/\/v\/([0-9a-fA-F]{6,12})/) || [])[1]
|| new URLSearchParams(location.search).get("c") || "";
function miss(msg) {
document.getElementById("watch").innerHTML =
'<div class="miss"><p>' + esc(msg) + '</p>'
+ '<a class="btn" style="display:inline-block;text-decoration:none;margin-top:16px" '
+ 'href="/">Make one of your own</a></div>';
}
(async function () {
if (!code) { miss("That link is missing its code."); return; }
var r = await api("resolve_share", { code: code });
if (!r.ok || !r.video) { miss(r.error || "That video is not here."); return; }
document.title = (r.title || "PayToLaugh") + " — PayToLaugh";
var who = [r.pet_name, r.seconds ? r.seconds + " seconds" : ""].filter(Boolean).join(" · ");
document.getElementById("watch").innerHTML =
'<video controls playsinline preload="metadata"'
+ (r.poster ? ' poster="' + esc(r.poster) + '"' : '')
+ ' src="' + esc(r.video) + '"></video>'
+ (r.title ? '<h1>' + esc(r.title) + '</h1>' : '')
+ (who ? '<p class="who">' + esc(who) + '</p>' : '')
+ '<div class="cta"><p>Made on PayToLaugh. Upload a photo of your pet, paste '
+ 'something worth reacting to, and they will have opinions about it.</p>'
+ '<a class="btn" style="display:inline-block;text-decoration:none" href="/">'
+ 'Make one for your pet</a></div>';
// Autoplay muted where the browser allows it; a tap unmutes. Full autoplay
// with sound is blocked everywhere and trying looks broken.
var v = document.querySelector(".watch video");
v.muted = true;
v.play().catch(function () {});
v.addEventListener("play", function once() {
v.muted = false;
v.removeEventListener("play", once);
}, { once: true });
})();
</script>
</body>
</html>