diff --git a/README.md b/README.md
index c38f48c..a444fdc 100644
--- a/README.md
+++ b/README.md
@@ -120,6 +120,7 @@ PUBLIC_DATA_CACHE_STALE_MS=86400000
PUBLIC_DATA_PREWARM_INTERVAL_MS=300000
PUBLIC_DATA_COLD_TIMEOUT_MS=8000
VITE_STATIC_DATA=true
+VITE_SITE_GATE=false
API_RATE_LIMIT_WINDOW_MS=60000
API_RATE_LIMIT_MAX=120
```
diff --git a/example.env b/example.env
index b5c6ae8..e7b0dad 100644
--- a/example.env
+++ b/example.env
@@ -62,4 +62,5 @@ DISCORD_INCLUDE_PATCH=true
# TURNSTILE_SECRET_KEY is the server-only secret used to call the Siteverify endpoint.
VITE_TURNSTILE_SITE_KEY=
TURNSTILE_SECRET_KEY=
+VITE_SITE_GATE=false
VITE_STATIC_DATA=true
diff --git a/frontend/index.html b/frontend/index.html
index b1c562b..eefd45b 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -2,12 +2,6 @@
__SEO_TITLE__
-
-
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 81626fe..68e337a 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -50,6 +50,7 @@ const liveRefreshMs = 15000
const siteVersion = '1.0.1'
const turnstileSiteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY || ''
+const siteGateEnabled = String(import.meta.env.VITE_SITE_GATE || 'false').toLowerCase() === 'true'
const staticDataBase = (import.meta.env.VITE_STATIC_DATA_BASE || '/data').replace(/\/+$/, '')
const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'true').toLowerCase() !== 'false'
const missingStaticDataPaths = new Set()
@@ -901,6 +902,16 @@ function TurnstileWidget({ siteKey, theme = 'auto', size = 'normal', action, onV
let cancelled = false
let pollTimer
+ let script = document.querySelector('script[data-tss-turnstile]')
+
+ if (!script) {
+ script = document.createElement('script')
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'
+ script.async = true
+ script.defer = true
+ script.dataset.tssTurnstile = 'true'
+ document.head.appendChild(script)
+ }
const renderWidget = () => {
if (cancelled) return
@@ -1019,6 +1030,11 @@ function SiteGate({ onVerified }) {
}
function App() {
+ if (!siteGateEnabled) return
+ return
+}
+
+function GatedAppContent() {
const embeddedGateState = document
.querySelector('meta[name="tss-turnstile-session"]')
?.getAttribute('content')