From 8be21a515c32ad84f0f9447c377243008f89c50e Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 19 Oct 2026 12:34:01 +0000 Subject: [PATCH] Add the pit.world global footer, move the ad out of the footer dock The server now renders https://pit.world/snippets/footer.html?site=hdtilt.com into index.html's footer markers (fetched at most hourly, 3s timeout, last good copy or nothing on failure) instead of the @profullstack/footer webring block. The footer dock is a one-line copyright + source row and the pit.world footer, recolored for the always-dark app. The CrawlProof ad, which sat in the footer dock, now docks at the top of the home/settings sheet. Co-Authored-By: Claude Opus 5.5 --- bun.lock | 3 --- package.json | 1 - src/server/footer.js | 46 ++++++++++++++++++++++++++++----------- web/public/app.css | 50 +++++++++++++++++++++++++++++++------------ web/public/index.html | 10 +++++---- web/src/app.js | 2 +- 6 files changed, 76 insertions(+), 36 deletions(-) diff --git a/bun.lock b/bun.lock index a025da9..1822cc9 100644 --- a/bun.lock +++ b/bun.lock @@ -7,7 +7,6 @@ "dependencies": { "@profullstack/auth-system": "^0.8.3", "@profullstack/emailer": "^1.0.3", - "@profullstack/footer": "latest", "@profullstack/hqtui": "^0.10.3", "@profullstack/player": "^0.14.0", "jsonwebtoken": "^9.0.2", @@ -81,8 +80,6 @@ "@profullstack/emailer": ["@profullstack/emailer@1.0.3", "", {}, "sha512-VyAtCcfnSI6LOwJvWogCr3ZNmEsaZw+iuWrqnosLsdcKsbRyCrCmXigMNZJZEjlgYlR+lK//PwNPG+xtCFvWAw=="], - "@profullstack/footer": ["@profullstack/footer@0.2.1", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-KHmKVwrlzCBrSAAJ4zobNFCxTg8VF0gvNMeAut00dX3+gbWCtk6eOwIR2AaqEC7smj6Aio6cIexzt/dUnv1McA=="], - "@profullstack/hqtui": ["@profullstack/hqtui@0.10.3", "", { "bin": { "hqtui": "bin/hqtui.mjs" } }, "sha512-Z2jaDqmiaGyrMy4JXOoPGCyEkei8l3tHoCbmT3/1gAWDl72ynPvMuHcUj+U97KgsF/CFIpZzzBYnYqcOq5e46g=="], "@profullstack/player": ["@profullstack/player@0.14.0", "", { "dependencies": { "hls.js": "^1.7.1", "mpegts.js": "^1.8.2" }, "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"] }, "sha512-HBDNkb17jGhZrkpksCp1iB07yWjfKIeDQq8LarhA04bKBZdX+vkRRTN1gsxXiAM3T4t0Zxj+99dpTA0uPtKofw=="], diff --git a/package.json b/package.json index 7ab6170..722cd06 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "dependencies": { "@profullstack/auth-system": "^0.8.3", "@profullstack/emailer": "^1.0.3", - "@profullstack/footer": "latest", "@profullstack/hqtui": "^0.10.3", "@profullstack/player": "^0.14.0", "jsonwebtoken": "^9.0.2", diff --git a/src/server/footer.js b/src/server/footer.js index 06a2072..a4d92df 100644 --- a/src/server/footer.js +++ b/src/server/footer.js @@ -1,19 +1,39 @@ -// The shared Profullstack footer (@profullstack/footer), rendered into the -// markers of index.html from the @latest template on jsDelivr -// (cached an hour by the package; the bundled template if the CDN is down), so a -// footer release needs no redeploy and the ring's verifier sees it in the HTML. -import { footerHtml } from '@profullstack/footer'; +// The pit.world network footer, fetched server side from +// https://pit.world/snippets/footer.html and rendered into the +// markers of index.html on every page load. It refetches at +// most hourly with a 3-second timeout; on failure it keeps the last good copy, +// or renders nothing, so pit.world being down never breaks the page. +const URL_ = 'https://pit.world/snippets/footer.html?site=hdtilt.com'; +const TTL = 60 * 60 * 1000; +const REGION = /[\s\S]*?/; -const OPTIONS = { site: 'https://hdtilt.com/' }; -const REGION = /[\s\S]*?/; +let cached = ''; +let fetchedAt = 0; +/** @type {Promise | null} */ +let inflight = null; + +function refresh() { + if (Date.now() - fetchedAt < TTL) return Promise.resolve(); + inflight ??= (async () => { + try { + const res = await fetch(URL_, { signal: AbortSignal.timeout(3000) }); + if (res.ok) { + const html = await res.text(); + if (html.includes('data-pit-footer')) cached = html; + } + } catch { + // Keep the last good footer. + } finally { + fetchedAt = Date.now(); + inflight = null; + } + })(); + return inflight; +} /** @param {string} html */ export async function withFooter(html) { if (!REGION.test(html)) return html; - try { - const footer = await footerHtml(OPTIONS); - return html.replace(REGION, () => `${footer}`); - } catch { - return html; - } + await refresh(); + return html.replace(REGION, () => `${cached}`); } diff --git a/web/public/app.css b/web/public/app.css index 6258979..9b05004 100644 --- a/web/public/app.css +++ b/web/public/app.css @@ -1085,14 +1085,17 @@ body.gated .nav-items button:not(#nav-account) { min-width: 0; } -/* @profullstack/footer: only while the sheet is up, docked under it. */ -.ring-dock { +/* Footer (own copyright row + the pit.world network footer): only while the + sheet is up, docked under it. */ +.ring-dock, +.ad-dock { display: none; } #sheet:not([hidden]) { padding-bottom: 6rem; } -#sheet:not([hidden]) ~ .ring-dock { +#sheet:not([hidden]) ~ .ring-dock, +#sheet:not([hidden]):not([data-screen="account"]) ~ .ad-dock { position: absolute; left: 0; right: 0; @@ -1101,9 +1104,35 @@ body.gated .nav-items button:not(#nav-account) { display: block; color: var(--text); } -/* CrawlProof ad unit, docked above the footer: zero height until ad.js fills - it, and never on the sign-in / account screens. Filled only from +.site-foot { + margin: 0; + padding: 0.75rem 1rem 0; + text-align: center; + font-size: 0.85rem; + color: var(--dim); +} +.site-foot a { + color: inherit; +} +/* The pit.world snippet is styled for light pages; this app is always dark. */ +.ring-dock .pit-foot { + color: var(--dim); +} +.ring-dock .pit-foot b { + color: var(--text); +} +.ring-dock .pit-foot-links > a, +.ring-dock .pit-foot-links > button { + color: var(--accent); +} +/* CrawlProof ad unit, docked at the top of the sheet: zero height until ad.js + fills it, and never on the sign-in / account screens. Filled only from mountAd() in app.js, the first time a home or settings sheet opens. */ +#sheet:not([hidden]):not([data-screen="account"]) ~ .ad-dock { + top: 0; + bottom: auto; + padding-top: 0.75rem; +} .cp-ad { display: block; max-width: 48rem; @@ -1116,15 +1145,8 @@ body.gated .nav-items button:not(#nav-account) { height: auto; overflow: visible; } -#sheet[data-screen="account"]:not([hidden]) ~ .ring-dock .cp-ad { - display: none; -} -#sheet:not([hidden]):not([data-screen="account"]):has(~ .ring-dock .cp-ad[data-cp-filled]) { - padding-bottom: 10rem; -} -.ring-dock .pfs-footer { - --pfs-footer-muted: var(--dim); - border-top: 0; +#sheet:not([hidden]):not([data-screen="account"]):has(~ .ad-dock .cp-ad[data-cp-filled]) { + padding-top: 5rem; } /* ---- Movies & Shows ---------------------------------------------------------- */ diff --git a/web/public/index.html b/web/public/index.html index c812004..34795d2 100644 --- a/web/public/index.html +++ b/web/public/index.html @@ -80,10 +80,12 @@ - -
+ +
+ + +

© 2026 Profullstack, Inc. · Source

diff --git a/web/src/app.js b/web/src/app.js index af198c2..fb73869 100644 --- a/web/src/app.js +++ b/web/src/app.js @@ -2204,7 +2204,7 @@ function navCurrent() { return 'live'; } -// CrawlProof ad unit in the footer dock (index.html). The element ships without +// CrawlProof ad unit in the top ad dock (index.html). The element ships without // a slot so ad.js's own DOMContentLoaded pass skips it; the first home or // settings sheet sets the slot and asks ad.js to fill it, once. Bounded retry // while ad.js is still loading, so a blocker never leaves a timer running.