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
5 changes: 0 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#09070B" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script
async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-7071904662816669"
crossorigin="anonymous"
></script>
<link rel="alternate" type="application/rss+xml" title="OpenThorn Blog" href="https://www.openthorn.app/rss.xml" />
<meta name="description" content="OpenThorn is the BYOK AI website builder — describe what you want, get a complete, deployable website. No subscription, no lock-in." />
<title>OpenThorn — The BYOK AI Website Builder</title>
Expand Down
5 changes: 4 additions & 1 deletion public/ads.txt
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -75,6 +76,7 @@ function HomePage() {
<HeroSection />
<MeetOpenThornSection />
<BYOKSection />
<AdsterraNativeBanner />
<BottomCTA />
</>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
52 changes: 52 additions & 0 deletions src/components/AdsterraNativeBanner/AdsterraNativeBanner.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>(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 = ''
}
Comment on lines +28 to +40
}, [])

// Blocked or unreachable tags (ad blockers, offline) leave no empty gap.
if (failed) return null

return (
<aside className={styles.wrapper} aria-label="Advertisement">
<span className={styles.label}>Advertisement</span>
<div id={CONTAINER_ID} className={styles.container} ref={containerRef} />
</aside>
)
}
2 changes: 1 addition & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -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'"
}
]
}
Expand Down