Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@
}

/* CrawlProof ad unit: zero height until ad.js starts a fill, so an unsold or
blocked slot leaves no hole. Reserves the format plus ad.js's caption. */
blocked slot leaves no hole. Low profile: one thin text_link line. */
.cp-ad { height: 0; overflow: hidden; }
.cp-ad[data-cp-filled] { height: auto; overflow: visible; margin: 2rem 0; }
.cp-ad[data-cp-filled][data-format="banner_728x90"] { min-height: 106px; }
.cp-ad[data-cp-filled][data-format="banner_300x250"] { min-height: 266px; }
.cp-ad[data-cp-filled] { height: auto; overflow: visible; margin: 0.5rem 0; }
34 changes: 12 additions & 22 deletions src/components/ad-unit.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,38 @@
"use client";

import { useEffect, useState } from "react";
import { useEffect } from "react";
import { usePathname } from "next/navigation";
import { AD_SLOT, isAdPage, requestAdScan, type AdFormat } from "@/lib/ads";

/** 728x90 needs the full content column; narrower viewports get the 300x250. */
const LEADERBOARD_QUERY = "(min-width: 768px)";
import { AD_SLOT, isAdPage, requestAdScan } from "@/lib/ads";

/**
* One CrawlProof unit at the top of the main content on smshub.dev public pages
* (never above or inside the footer).
* One low-profile CrawlProof text_link unit at the top of the main content on
* smshub.dev public pages (never the homepage, never above or inside the footer).
*
* The div is rendered without `data-slot`, so ad.js's own first pass skips it;
* the mount effect picks one format from the viewport, sets slot + format and
* asks for a scan. Exactly one fill, never a hidden twin. The wrapper collapses
* to nothing until ad.js marks the unit `data-cp-filled` (see globals.css).
* The wrapper collapses to nothing until ad.js marks the unit `data-cp-filled`
* (see globals.css). The mount effect asks ad.js to rescan, since client
* navigation mounts the unit after ad.js's first pass.
*/
export function AdUnit() {
const pathname = usePathname();
const show = isAdPage(pathname);
const [format, setFormat] = useState<AdFormat | null>(null);

useEffect(() => {
if (!show) return;
setFormat(window.matchMedia(LEADERBOARD_QUERY).matches ? "banner_728x90" : "banner_300x250");
}, [show]);

useEffect(() => {
if (!show || !format) return;
return requestAdScan();
}, [show, format, pathname]);
}, [show, pathname]);

if (!show) return null;

return (
<div className="cp-ad-wrap flex justify-center px-4">
<div className="cp-ad-wrap px-4">
<div
// Keyed by path so each client navigation mounts a fresh unit: ad.js
// will not refill an element it has already filled.
key={`${pathname}:${format ?? "pending"}`}
key={pathname}
className="cp-ad"
data-cp-ad=""
data-slot={format ? AD_SLOT : undefined}
data-format={format ?? undefined}
data-slot={AD_SLOT}
data-format="text_link"
/>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions src/lib/__tests__/ads.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { isAdPage } from "../ads";

describe("isAdPage", () => {
it("shows on public pages", () => {
for (const p of ["/", "/docs", "/docs/telnyx", "/privacy", "/terms/", "/sms", "/phonenumbers"]) {
for (const p of ["/docs", "/docs/telnyx", "/privacy", "/terms/", "/sms", "/phonenumbers"]) {
expect(isAdPage(p)).toBe(true);
}
});
it("never shows in the app, auth or checkout", () => {
for (const p of ["/inbox", "/numbers", "/settings", "/login", "/register", "/org/x", "/docsx", null]) {
it("never shows on the homepage, in the app, auth or checkout", () => {
for (const p of ["/", "/inbox", "/numbers", "/settings", "/login", "/register", "/org/x", "/docsx", null]) {
expect(isAdPage(p)).toBe(false);
}
});
Expand Down
6 changes: 3 additions & 3 deletions src/lib/ads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export const AD_HEIGHTS: Record<AdFormat, number> = {
};

/**
* Public marketing/docs pages that carry the unit. Everything else (inbox,
* numbers, settings, auth, checkout) stays ad-free.
* Public marketing/docs pages that carry the unit. The homepage and everything
* else (inbox, numbers, settings, auth, checkout) stays ad-free.
*/
const AD_EXACT = new Set(["/", "/privacy", "/terms", "/sms", "/install", "/phonenumbers", "/verification-numbers"]);
const AD_EXACT = new Set(["/privacy", "/terms", "/sms", "/install", "/phonenumbers", "/verification-numbers"]);
const AD_PREFIXES = ["/docs"];

export function isAdPage(pathname: string | null | undefined): boolean {
Expand Down
Loading