diff --git a/index.html b/index.html
index 21a65c5..28de876 100644
--- a/index.html
+++ b/index.html
@@ -10,11 +10,6 @@
-
OpenThorn — The BYOK AI Website Builder
diff --git a/public/ads.txt b/public/ads.txt
index 07e259e..41f79f1 100644
--- a/public/ads.txt
+++ b/public/ads.txt
@@ -1 +1,4 @@
-google.com, pub-7071904662816669, DIRECT, f08c47fec0942fa0
+# Authorized digital sellers for openthorn.app inventory.
+# Replace the Adsterra line below with the exact record shown in the Adsterra
+# dashboard if it includes a certification (4th) field.
+adsterra.com, 31110698, DIRECT
diff --git a/src/App.tsx b/src/App.tsx
index 9d52914..3fb5842 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,6 +10,7 @@ import Header from './components/Header/Header'
import HeroSection from './components/HeroSection/HeroSection'
import MeetOpenThornSection from './components/MeetOpenThornSection/MeetOpenThornSection'
import BYOKSection from './components/BYOKSection/BYOKSection'
+import AdsterraNativeBanner from './components/AdsterraNativeBanner/AdsterraNativeBanner'
import BottomCTA from './components/BottomCTA/BottomCTA'
import Footer from './components/Footer/Footer'
import AuthModal from './components/AuthModal/AuthModal'
@@ -75,6 +76,7 @@ function HomePage() {
+
>
)
diff --git a/src/components/AdsterraNativeBanner/AdsterraNativeBanner.module.css b/src/components/AdsterraNativeBanner/AdsterraNativeBanner.module.css
new file mode 100644
index 0000000..1794c07
--- /dev/null
+++ b/src/components/AdsterraNativeBanner/AdsterraNativeBanner.module.css
@@ -0,0 +1,39 @@
+.wrapper {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: var(--space-2xl) var(--space-lg);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--space-sm);
+ text-align: center;
+}
+
+.label {
+ font-family: var(--font-body);
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ color: var(--color-text-muted);
+}
+
+.container {
+ width: 100%;
+ min-height: 90px;
+ display: flex;
+ justify-content: center;
+ overflow: hidden;
+ border-radius: var(--radius-lg);
+}
+
+/* Nothing rendered (blocked tag, empty response) — collapse the slot. */
+.wrapper:has(.container:empty) {
+ display: none;
+}
+
+@media (max-width: 640px) {
+ .wrapper {
+ padding: var(--space-xl) var(--space-md);
+ }
+}
diff --git a/src/components/AdsterraNativeBanner/AdsterraNativeBanner.tsx b/src/components/AdsterraNativeBanner/AdsterraNativeBanner.tsx
new file mode 100644
index 0000000..cf4b5a8
--- /dev/null
+++ b/src/components/AdsterraNativeBanner/AdsterraNativeBanner.tsx
@@ -0,0 +1,52 @@
+import { useEffect, useRef, useState } from 'react'
+import styles from './AdsterraNativeBanner.module.css'
+
+/** Adsterra native-banner tag (zone acd496412d230553ca4f4a243557f98c). */
+const SCRIPT_SRC =
+ 'https://pl31110698.profitableratecpmnetwork.com/acd496412d230553ca4f4a243557f98c/invoke.js'
+const CONTAINER_ID = 'container-acd496412d230553ca4f4a243557f98c'
+
+/**
+ * Only one Adsterra tag may be active at a time. Tracked module-wide so a
+ * remount always replaces the previous tag instead of stacking duplicates.
+ */
+let activeScript: HTMLScriptElement | null = null
+
+export default function AdsterraNativeBanner() {
+ const containerRef = useRef(null)
+ const [failed, setFailed] = useState(false)
+
+ useEffect(() => {
+ const container = containerRef.current
+ if (!container) return
+
+ // Adsterra's invoke.js fills whichever element matches CONTAINER_ID when it
+ // executes, so re-append the tag on mount to refill a freshly mounted
+ // container (route re-entry, Strict Mode double-invoke).
+ activeScript?.remove()
+
+ const script = document.createElement('script')
+ script.async = true
+ script.setAttribute('data-cfasync', 'false')
+ script.src = SCRIPT_SRC
+ script.onerror = () => setFailed(true)
+ document.head.appendChild(script)
+ activeScript = script
+
+ return () => {
+ script.remove()
+ if (activeScript === script) activeScript = null
+ container.innerHTML = ''
+ }
+ }, [])
+
+ // Blocked or unreachable tags (ad blockers, offline) leave no empty gap.
+ if (failed) return null
+
+ return (
+
+ )
+}
diff --git a/vercel.json b/vercel.json
index 77aa222..8e05996 100644
--- a/vercel.json
+++ b/vercel.json
@@ -24,7 +24,7 @@
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" },
{
"key": "Content-Security-Policy",
- "value": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' blob: data: https://esm.sh https://pagead2.googlesyndication.com https://*.googlesyndication.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; worker-src 'self' blob:; child-src 'self' blob:; frame-src 'self' blob: data: https://www.youtube-nocookie.com https://googleads.g.doubleclick.net https://*.googlesyndication.com; connect-src 'self' https: wss: blob: data:; manifest-src 'self'"
+ "value": "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' blob: data: https://esm.sh https://pl31110698.profitableratecpmnetwork.com https://*.profitableratecpmnetwork.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; worker-src 'self' blob:; child-src 'self' blob:; frame-src 'self' blob: data: https://www.youtube-nocookie.com https://*.profitableratecpmnetwork.com; connect-src 'self' https: wss: blob: data:; manifest-src 'self'"
}
]
}