From 882f20fd3ea5c9348b60f4fed615c68a61e6ede5 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 19 Oct 2026 12:32:38 +0000 Subject: [PATCH] Add the pit.world global footer, trim the site footer PitFooter fetches https://pit.world/snippets/footer.html?site=datamart.help server side (revalidate 1h, 3s timeout, renders nothing on failure) and is the last thing in . It replaces the @profullstack/footer webring block; the site footer is now one row (copyright, About, API and MCP, Source) plus the not-a-government-service disclosure. The CrawlProof ad unit, the only one and previously pressed against the footer, moves to the top of the content. Co-Authored-By: Claude Opus 5.5 --- bun.lock | 3 --- package.json | 3 +-- src/app/globals.css | 4 ++-- src/app/layout.tsx | 32 +++++++++++++++----------------- src/components/ad-unit.tsx | 2 +- src/components/pit-footer.tsx | 17 +++++++++++++++++ 6 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 src/components/pit-footer.tsx diff --git a/bun.lock b/bun.lock index 3f6727c..e55d569 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,6 @@ "": { "name": "datamart-web", "dependencies": { - "@profullstack/footer": "latest", "hono": "4.13.12", "next": "16.3.8", "react": "19.2.3", @@ -95,8 +94,6 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.3.8", "", { "os": "win32", "cpu": "x64" }, "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg=="], - "@profullstack/footer": ["@profullstack/footer@0.2.1", "", { "peerDependencies": { "react": ">=18" }, "optionalPeers": ["react"], "bin": { "profullstack-footer": "bin/profullstack-footer.js" } }, "sha512-KHmKVwrlzCBrSAAJ4zobNFCxTg8VF0gvNMeAut00dX3+gbWCtk6eOwIR2AaqEC7smj6Aio6cIexzt/dUnv1McA=="], - "@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="], "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], diff --git a/package.json b/package.json index cb244aa..21a7121 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "datamart-web", - "version": "0.2.0", + "version": "0.2.1", "private": true, "description": "datamart.help: public libraries, schools, colleges and government services near you, with the source of every fact", "license": "MIT", @@ -15,7 +15,6 @@ "smoke": "bun scripts/smoke.ts" }, "dependencies": { - "@profullstack/footer": "latest", "hono": "4.13.12", "next": "16.3.8", "react": "19.2.3", diff --git a/src/app/globals.css b/src/app/globals.css index 8fe8fd2..e235c49 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -125,9 +125,9 @@ dl.facts dd { margin: 0; overflow-wrap: anywhere; } @media (min-width: 52rem) { .split { grid-template-columns: 2fr 1fr; } } .site-footer { border-top: 1px solid var(--line); padding: 1.5rem 0 0.5rem; color: var(--muted); font-size: 0.85rem; } -.site-footer nav { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.75rem; } +.site-footer nav { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 0.5rem; } +.site-footer p { margin: 0; } .site-footer a { color: var(--muted); } -.site-footer + div > .pfs-footer { border-top: 0; } /* CrawlProof ad unit: no box until ad.js starts filling it, so an unsold or blocked slot leaves no hole. */ .ad-unit { display: flex; flex-direction: column; align-items: center; height: 0; overflow: hidden; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 55b0411..eef43af 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,13 +1,13 @@ import type { Metadata, Viewport } from "next"; -import { Footer } from "@profullstack/footer/react"; import Link from "next/link"; import { AdUnit } from "@/components/ad-unit"; +import { PitFooter } from "@/components/pit-footer"; import { RegisterServiceWorker } from "@/components/register-sw"; import { DISCLOSURE } from "@/lib/site"; import "./globals.css"; -// Pages prerender at build; revalidating hourly lets the shared footer pick up a new -// @profullstack/footer template (served from jsDelivr @latest) without a redeploy. +// Pages prerender at build; revalidating hourly lets the pit.world footer +// (fetched server side from pit.world/snippets/footer.html) refresh without a redeploy. export const revalidate = 3600; export const metadata: Metadata = { @@ -64,25 +64,23 @@ export default function RootLayout({ children }: { children: React.ReactNode }) -
{children}
-
+
-
-
+ {children} + +
+

{DISCLOSURE}

-
-
+
+ ); diff --git a/src/components/ad-unit.tsx b/src/components/ad-unit.tsx index e608896..ccbd88e 100644 --- a/src/components/ad-unit.tsx +++ b/src/components/ad-unit.tsx @@ -15,7 +15,7 @@ declare global { } /** - * One CrawlProof unit above the footer. The aside is server-rendered without + * One CrawlProof unit at the top of the content. The aside is server-rendered without * data-slot, so ad.js's own DOMContentLoaded pass skips it; the format is picked * from the real viewport on mount, then we ask ad.js to scan (retrying briefly * until it has loaded, and giving up so an ad blocker leaves no timer running). diff --git a/src/components/pit-footer.tsx b/src/components/pit-footer.tsx new file mode 100644 index 0000000..78817ba --- /dev/null +++ b/src/components/pit-footer.tsx @@ -0,0 +1,17 @@ +const PIT_FOOTER_URL = "https://pit.world/snippets/footer.html?site=datamart.help"; + +/** + * The pit.world network footer, fetched server side (cached an hour, 3s timeout). + * Any failure renders nothing, so pit.world being down never breaks a page. + */ +export async function PitFooter() { + try { + const res = await fetch(PIT_FOOTER_URL, { next: { revalidate: 3600 }, signal: AbortSignal.timeout(3000) }); + if (!res.ok) return null; + const html = await res.text(); + if (!html.includes("data-pit-footer")) return null; + return
; + } catch { + return null; + } +}