feat(ads): collect ad details at Dodo checkout

This commit is contained in:
alam00000
2026-10-31 13:44:04 +05:30
parent 8b80a616a7
commit fac51527c8
9 changed files with 753 additions and 1 deletions
+399
View File
@@ -0,0 +1,399 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Advertise on BentoPDF - Homepage Logo Ads | BentoPDF</title>
<meta
name="description"
content="Put your logo on the BentoPDF homepage. Eight slots, a flat monthly price and no tracking. From $129 a month, cancel anytime."
/>
<meta name="author" content="BentoPDF" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://www.bentopdf.com/advertise" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://www.bentopdf.com/advertise" />
<meta
property="og:title"
content="Advertise on BentoPDF - Homepage Logo Ads | BentoPDF"
/>
<meta
property="og:description"
content="Put your logo on the BentoPDF homepage. Eight slots, a flat monthly price and no tracking. From $129 a month, cancel anytime."
/>
<meta
property="og:image"
content="https://www.bentopdf.com/images/og-tools.png"
/>
<meta property="og:site_name" content="BentoPDF" />
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="Advertise on BentoPDF - Homepage Logo Ads | BentoPDF"
/>
<meta
name="twitter:description"
content="Put your logo on the BentoPDF homepage. Eight slots, a flat monthly price and no tracking. From $129 a month, cancel anytime."
/>
<meta
name="twitter:image"
content="https://www.bentopdf.com/images/og-tools.png"
/>
<meta name="twitter:site" content="@BentoPDF" />
<link href="/src/css/styles.css" rel="stylesheet" />
<link href="/src/css/landing.css" rel="stylesheet" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
<link
rel="icon"
type="image/png"
sizes="192x192"
href="/images/favicon-192x192.png"
/>
<link
rel="icon"
type="image/png"
sizes="512x512"
href="/images/favicon-512x512.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/images/apple-touch-icon.png"
/>
<link rel="icon" href="/favicon.ico" sizes="32x32" />
</head>
<body class="lp antialiased">
{{> navbar }}
<div id="app">
<section id="ad-paid" class="lp-sec" hidden>
<div class="lp-frame">
<div class="lp-pad--tight">
<div class="lp-note">
<i data-lucide="circle-check"></i>
<p>
<strong>Payment received, thanks.</strong>
We review every ad and usually put it live within two business
days. To change anything, email
<a
href="mailto:[email protected]?subject=Ad%20for%20BentoPDF"
class="text-indigo-400 hover:underline"
>[email protected]</a
>.
</p>
</div>
</div>
</div>
</section>
<section id="ad-failed" class="lp-sec" hidden>
<div class="lp-frame">
<div class="lp-pad--tight">
<div class="lp-note">
<i data-lucide="circle-alert"></i>
<p>
<strong>Checkout didn't go through.</strong>
Nothing was charged. Try again below or email
<a
href="mailto:[email protected]?subject=Advertise%20on%20BentoPDF"
class="text-indigo-400 hover:underline"
>[email protected]</a
>.
</p>
</div>
</div>
</div>
</section>
<section id="advertise-hero" class="lp-sec lp-sec--first">
<div class="lp-frame">
<div class="lp-head lp-head--center">
<div>
<h1 class="lp-h1">
<span class="line">Advertise on</span>
<span class="line dim">BentoPDF</span>
</h1>
<p class="lp-head-p">
Put your logo on the BentoPDF homepage, right above the tools
people open to work on their PDFs. Eight slots, a flat monthly
price, and no tracking of any kind.
</p>
<div class="lp-actions">
<span class="lp-brackets">
<a href="#pricing" class="lp-btn lp-btn--primary">
<span>See pricing</span>
<i data-lucide="arrow-right"></i>
</a>
</span>
<a
href="mailto:[email protected]?subject=Advertise%20on%20BentoPDF"
class="lp-btn lp-btn--ghost"
>
<i data-lucide="mail"></i>
<span>Email us</span>
</a>
</div>
</div>
</div>
</div>
</section>
<section id="pricing" class="lp-sec lp-band">
<div class="lp-frame">
<div class="lp-head lp-head--center">
<div>
<h2 class="lp-h2">
<span class="line">Pick a</span>
<span class="line dim">row</span>
</h2>
<p class="lp-head-p">
Billed monthly. Cancel anytime and your ad stays up until the
end of the month you paid for.
</p>
</div>
</div>
<div class="lp-grid lp-grid--2">
<div class="lp-cell lp-tier lp-cell--accent lp-reveal">
<h3>Top row</h3>
<p class="lp-tier-for">
Four slots, first in line above the tools
</p>
<div class="lp-tier-price"><b>$199</b><span>/ month</span></div>
<ul>
<li><i data-lucide="check"></i>Larger tile in the first row</li>
<li><i data-lucide="check"></i>Your logo, name and link</li>
<li>
<i data-lucide="check"></i>On every language version of the
homepage
</li>
<li><i data-lucide="check"></i>Cancel anytime</li>
</ul>
<a
href="https://www.bentopdf.com/api/ad-checkout?row=top"
class="lp-btn lp-btn--primary lp-btn--sm"
target="_blank"
rel="noopener noreferrer"
>Book the top row</a
>
</div>
<div class="lp-cell lp-tier lp-reveal">
<h3>Second row</h3>
<p class="lp-tier-for">Four slots, right below the top row</p>
<div class="lp-tier-price"><b>$129</b><span>/ month</span></div>
<ul>
<li>
<i data-lucide="check"></i>Standard tile in the second row
</li>
<li><i data-lucide="check"></i>Your logo, name and link</li>
<li>
<i data-lucide="check"></i>On every language version of the
homepage
</li>
<li><i data-lucide="check"></i>Cancel anytime</li>
</ul>
<a
href="https://www.bentopdf.com/api/ad-checkout?row=second"
class="lp-btn lp-btn--ghost lp-btn--sm"
target="_blank"
rel="noopener noreferrer"
>Book the second row</a
>
</div>
</div>
</div>
</section>
<section class="lp-sec">
<div class="lp-frame">
<div class="lp-head lp-head--center">
<div>
<h2 class="lp-h2">
<span class="line">How it</span>
<span class="line dim">works</span>
</h2>
</div>
</div>
<div class="lp-grid lp-grid--3">
<div class="lp-cell lp-reveal">
<span class="lp-tile"><i data-lucide="credit-card"></i></span>
<h3>Book a row</h3>
<p>
Checkout is handled by Dodo Payments, our merchant of record, so
you get a proper invoice with sales tax taken care of.
</p>
</div>
<div class="lp-cell lp-reveal">
<span class="lp-tile"><i data-lucide="image"></i></span>
<h3>Add your ad</h3>
<p>
Checkout asks for the name to show, your link, and a link to
your logo as a square SVG or PNG of at least 128 by 128 pixels.
</p>
</div>
<div class="lp-cell lp-reveal">
<span class="lp-tile"><i data-lucide="rocket"></i></span>
<h3>Go live</h3>
<p>
We review every ad by hand and usually put it live within two
business days. Need a new logo or link later? Just email us.
</p>
</div>
</div>
</div>
</section>
<section class="lp-sec">
<div class="lp-frame">
<div class="lp-pad--tight">
<div class="lp-note lp-reveal">
<i data-lucide="eye-off"></i>
<p>
<strong
>We don't track our visitors, so we don't track your ad
either.</strong
>
No pixels, no scripts, no impression or click counts. Your logo
is served from bentopdf.com and links straight to your site. To
measure visits, add UTM parameters to your link and read them in
your own analytics.
</p>
</div>
</div>
</div>
</section>
<section class="lp-sec">
<div class="lp-frame">
<div class="lp-head lp-head--center">
<div>
<h2 class="lp-h2">
<span class="line">Frequently asked</span>
<span class="line dim">questions</span>
</h2>
</div>
</div>
<div class="lp-faq-wrap">
<div id="faq-accordion" class="lp-faq lp-reveal">
<div class="faq-item">
<button
class="faq-question w-full flex justify-between items-center text-left"
type="button"
>
<span>What can I advertise?</span>
<i data-lucide="plus" class="faq-icon"></i>
</button>
<div
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
>
<p>
Software, services and products that are useful to people
who work with documents. We don't accept gambling, adult
content, malware, or anything misleading. If we turn your ad
down, we refund you in full.
</p>
</div>
</div>
<div class="faq-item">
<button
class="faq-question w-full flex justify-between items-center text-left"
type="button"
>
<span>Where does my ad show?</span>
<i data-lucide="plus" class="faq-icon"></i>
</button>
<div
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
>
<p>
On the bentopdf.com homepage, in every language, directly
above the tool grid. Self-hosted copies of BentoPDF never
show ads.
</p>
</div>
</div>
<div class="faq-item">
<button
class="faq-question w-full flex justify-between items-center text-left"
type="button"
>
<span>Can you tell me how many people saw my ad?</span>
<i data-lucide="plus" class="faq-icon"></i>
</button>
<div
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
>
<p>
No. BentoPDF has no analytics, so there are no impression or
click numbers to share. Add UTM parameters to your link to
see the visits we send you in your own analytics.
</p>
</div>
</div>
<div class="faq-item">
<button
class="faq-question w-full flex justify-between items-center text-left"
type="button"
>
<span>How do I cancel?</span>
<i data-lucide="plus" class="faq-icon"></i>
</button>
<div
class="faq-answer max-h-0 overflow-hidden transition-all duration-300 ease-in-out"
>
<p>
Email us and we'll cancel your subscription. Your ad stays
up until the end of the month you paid for.
</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="lp-sec">
<div class="lp-frame">
<div class="lp-cta">
<h2 class="lp-h2">Questions before you book?</h2>
<p>Email us about slots, pricing or anything else.</p>
<div class="lp-cta-actions">
<span class="lp-brackets">
<a
href="mailto:[email protected]?subject=Advertise%20on%20BentoPDF"
class="lp-btn lp-btn--primary"
>
<span>[email protected]</span>
<i data-lucide="arrow-right"></i>
</a>
</span>
</div>
</div>
</div>
</section>
</div>
{{> footer }}
<script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/js/main.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script>
<script type="module" src="src/js/landing.ts"></script>
<script type="module" src="src/js/advertise.ts"></script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "Advertise on BentoPDF - Homepage Logo Ads | BentoPDF",
"url": "https://www.bentopdf.com/advertise"
}
</script>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
const DODO_API = 'https://test.dodopayments.com';
const PRODUCTS = new Map([
['top', 'pdt_0Nqt0672sYJEtrkIb2K6m'],
['second', 'pdt_0Nqt069LgdQFPo1tFS1yF'],
]);
const FIELDS = [
{
key: 'ad_name',
label: 'Name on the ad',
field_type: 'text',
placeholder: 'Acme Invoices',
required: true,
},
{
key: 'ad_link',
label: 'Link',
field_type: 'url',
placeholder: 'https://example.com',
required: true,
},
{
key: 'ad_logo',
label: 'Logo link (square SVG or PNG)',
field_type: 'url',
placeholder: 'https://example.com/logo.svg',
required: true,
},
];
function redirect(location) {
return new Response(null, {
status: 303,
headers: { Location: location, 'Cache-Control': 'no-store' },
});
}
async function createCheckout(productId, page) {
const response = await fetch(`${DODO_API}/checkouts`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.DODO_PAYMENTS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
product_cart: [{ product_id: productId, quantity: 1 }],
custom_fields: FIELDS,
return_url: `${page}?paid=1`,
cancel_url: `${page}#pricing`,
}),
signal: AbortSignal.timeout(10000),
});
const body = await response.text();
if (!response.ok) {
throw new Error(`Dodo returned ${response.status}: ${body}`);
}
const { checkout_url } = JSON.parse(body);
if (
typeof checkout_url !== 'string' ||
!checkout_url.startsWith('https://')
) {
throw new Error(`Dodo returned no checkout URL: ${body}`);
}
return checkout_url;
}
export async function GET(request) {
const url = new URL(request.url);
const page = `${url.origin}/advertise`;
const productId = PRODUCTS.get(url.searchParams.get('row'));
if (!productId) return redirect(`${page}#pricing`);
try {
return redirect(await createCheckout(productId, page));
} catch (error) {
console.error(error);
return redirect(`${page}?checkout=failed`);
}
}
+83
View File
@@ -348,6 +348,89 @@
</div>
</div>
<section
id="ads-section"
class="lp-ads hide-section"
aria-labelledby="ads-title"
hidden
>
<div class="lp-ads-head">
<p id="ads-title" class="lp-kicker">Sponsored</p>
<a href="/advertise" class="lp-ads-cta">
<span>Advertise here</span>
<i data-lucide="arrow-up-right"></i>
</a>
</div>
<div class="lp-ads-grid lp-cross">
<a
class="lp-ad lp-ad--top"
href="#"
rel="sponsored noopener"
target="_blank"
>
<span class="lp-ad-logo" style="--ad-tint: #f97316">A</span>
<span class="lp-ad-text"><b>Acme Invoices</b></span>
</a>
<a
class="lp-ad lp-ad--top"
href="#"
rel="sponsored noopener"
target="_blank"
>
<span class="lp-ad-logo" style="--ad-tint: #22c55e">G</span>
<span class="lp-ad-text"><b>Globex Mail</b></span>
</a>
<a
class="lp-ad lp-ad--top"
href="#"
rel="sponsored noopener"
target="_blank"
>
<span class="lp-ad-logo" style="--ad-tint: #0ea5e9">I</span>
<span class="lp-ad-text"><b>Initech Notes</b></span>
</a>
<a
class="lp-ad lp-ad--top"
href="#"
rel="sponsored noopener"
target="_blank"
>
<span class="lp-ad-logo" style="--ad-tint: #e11d48">H</span>
<span class="lp-ad-text"><b>Hooli Sign</b></span>
</a>
<a class="lp-ad" href="#" rel="sponsored noopener" target="_blank">
<span class="lp-ad-logo" style="--ad-tint: #a855f7">U</span>
<span class="lp-ad-text"><b>Umbrella VPN</b></span>
</a>
<a class="lp-ad" href="#" rel="sponsored noopener" target="_blank">
<span class="lp-ad-logo" style="--ad-tint: #eab308">S</span>
<span class="lp-ad-text"><b>Stark Backup</b></span>
</a>
<a
class="lp-ad lp-ad--open"
href="https://www.bentopdf.com/api/ad-checkout?row=second"
target="_blank"
rel="noopener noreferrer"
>
<span class="lp-ad-logo"><i data-lucide="plus"></i></span>
<span class="lp-ad-text"><b>Your logo here</b></span>
</a>
<a
class="lp-ad lp-ad--open"
href="https://www.bentopdf.com/api/ad-checkout?row=second"
target="_blank"
rel="noopener noreferrer"
>
<span class="lp-ad-logo"><i data-lucide="plus"></i></span>
<span class="lp-ad-text"><b>Your logo here</b></span>
</a>
</div>
<p class="lp-ads-note">
Ads are plain logos and links. No tracking pixels, no scripts, no
pop-ups.
</p>
</section>
<div
id="tool-grid"
class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6"
+6 -1
View File
@@ -507,7 +507,12 @@ export function processDocument(document, options) {
};
}
export const ENGLISH_ONLY_PAGES = new Set(['terms', 'privacy', 'licensing']);
export const ENGLISH_ONLY_PAGES = new Set([
'terms',
'privacy',
'licensing',
'advertise',
]);
export function processPage(document, pageName, options) {
if (ENGLISH_ONLY_PAGES.has(pageName)) {
+158
View File
@@ -2153,3 +2153,161 @@ body.lp ::selection {
display: block;
}
}
.lp-ads {
margin: 0 0 40px;
}
.lp-ads-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
}
.lp-ads-cta {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 500;
color: var(--lp-text-2);
transition: color 0.2s;
}
.lp-ads-cta:hover {
color: var(--lp-text);
}
.lp-ads-cta svg {
width: 15px;
height: 15px;
}
.lp-ads-grid {
position: relative;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1px;
background: var(--lp-line);
border: 1px solid var(--lp-line);
}
.lp-ad {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
min-height: 64px;
padding: 14px 18px;
background: var(--lp-bg);
transition: background-color 0.25s;
}
.lp-ad:hover {
background-color: var(--lp-bg-2);
}
.lp-ad--top {
min-height: 92px;
background: var(--lp-bg-2);
}
.lp-ad--top:hover {
background-color: var(--lp-bg-3);
}
.lp-ad-logo {
display: grid;
flex: none;
place-items: center;
width: 32px;
height: 32px;
border-radius: 6px;
background: var(--ad-tint, var(--lp-bg-3));
font-family: var(--lp-display);
font-size: 15px;
font-weight: 700;
color: #fff;
}
.lp-ad--top .lp-ad-logo {
width: 40px;
height: 40px;
font-size: 17px;
}
.lp-ad-text {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.lp-ad-text b {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-family: var(--lp-display);
font-size: 15px;
font-weight: 600;
letter-spacing: -0.01em;
color: var(--lp-text);
}
.lp-ad--open .lp-ad-logo {
border: 1px dashed var(--lp-line-2);
background: transparent;
color: var(--lp-dim);
transition:
color 0.2s,
border-color 0.2s;
}
.lp-ad--open .lp-ad-logo svg {
width: 16px;
height: 16px;
}
.lp-ad--open b {
font-weight: 500;
color: var(--lp-text-2);
}
.lp-ad--open:hover .lp-ad-logo {
border-color: var(--lp-accent-2);
color: var(--lp-accent-3);
}
.lp-ads-note {
margin: 12px 0 0;
font-size: 13px;
color: var(--lp-dim);
}
@media (min-width: 900px) {
.lp-ads-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
@media (max-width: 639px) {
.lp-ad,
.lp-ad--top {
gap: 10px;
min-height: 56px;
padding: 10px 12px;
}
.lp-ad-logo,
.lp-ad--top .lp-ad-logo {
width: 28px;
height: 28px;
font-size: 14px;
}
.lp-ad-text b {
font-size: 14px;
}
}
+12
View File
@@ -0,0 +1,12 @@
const params = new URLSearchParams(window.location.search);
const outcome = params.get('status') ?? '';
const failed =
params.get('checkout') === 'failed' ||
['failed', 'expired', 'cancelled'].includes(outcome);
const paid = params.has('paid') || params.has('subscription_id');
if (failed || paid) {
document
.getElementById(failed ? 'ad-failed' : 'ad-paid')
?.removeAttribute('hidden');
}
+9
View File
@@ -45,6 +45,15 @@ const FAVORITE_STAR_PATHS = [
] as const;
declare const __BRAND_NAME__: string;
const adsSection = document.getElementById('ads-section');
if (
adsSection &&
(['bentopdf.com', 'www.bentopdf.com'].includes(window.location.hostname) ||
import.meta.env.DEV)
) {
adsSection.hidden = false;
}
const init = async () => {
await initI18n();
await loadRuntimeConfig();
+4
View File
@@ -217,6 +217,10 @@
"source": "/(.*)\\.html",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=3600" }]
},
{
"source": "/api/(.*)",
"headers": [{ "key": "Cache-Control", "value": "no-store" }]
},
{
"source": "/(ar|be|da|de|es|fr|id|it|ja|ko|nl|pl|pt|ru|sk|sv|tr|uk|vi|zh|zh-TW|ca|fa)/(.*)",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=300" }]
+2
View File
@@ -76,6 +76,7 @@ function loadPages(): Set<string> {
'licensing',
'kura',
'hyper-compress',
'advertise',
'404',
'pdf-converter',
'pdf-editor',
@@ -803,6 +804,7 @@ export default defineConfig(({ command }) => {
licensing: resolve(__dirname, 'licensing.html'),
kura: resolve(__dirname, 'kura.html'),
'hyper-compress': resolve(__dirname, 'hyper-compress.html'),
advertise: resolve(__dirname, 'advertise.html'),
...Object.fromEntries(
fs
.readdirSync(resolve(__dirname, 'blog'))