diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0583c7c..86f31d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Lumi changelog
+## 0.2.13
+
+- Changed external overlay Website CSS from frame-only styling to document-level injection, matching OBS Browser Source Custom CSS behavior.
+- Applied OBS's transparent body stylesheet inside alert websites by default while allowing administrators to add selectors for provider-specific elements.
+- Added scoped revocable website-render tickets, opaque sandboxing, no-referrer handling, bounded public document retrieval, private-network blocking, and a direct compatibility fallback.
+
## 0.2.12
- Fixed OBS Browser Source transparency by applying OBS's standard transparent-page CSS to clean overlay pages and embedded frame surfaces.
diff --git a/TODO.md b/TODO.md
index 1e34e6c..425fff5 100644
--- a/TODO.md
+++ b/TODO.md
@@ -22,7 +22,10 @@ syncs supported OBS scene, transition, canvas, output, permission, and source
state without WebSocket credentials, with a one-action setup importer. The clean
render page and editor now share a fixed full-resolution canvas, transparent OBS
defaults, and explicit Browser Source dimensions so responsive third-party alerts
-render consistently in the preview and OBS.
+render consistently in the preview and OBS. Website CSS is injected into the
+third-party document itself through scoped revocable render tickets, matching OBS
+Custom CSS behavior while keeping the page isolated from Lumi sessions and local
+network targets.
Remaining work:
@@ -680,6 +683,7 @@ This section is for Lumi chat answer feedback and AI Improvement Center work, no
## Done
+- 2026-07-18: Replaced frame-only website styling with OBS-style document CSS injection. External alert pages now receive OBS's transparent body CSS by default plus administrator selectors inside the website document; scoped revocable source tickets, opaque sandboxing, no-referrer handling, bounded public fetching, and private-network blocking protect Lumi and its overlay secrets.
- 2026-07-18: Fixed OBS overlay transparency and preview fidelity by applying OBS's exact default Browser Source CSS, rendering the editor and clean output from the same full-resolution canvas coordinate system, keeping iframe surfaces transparent, and showing the required OBS width/height plus copyable CSS in setup.
- 2026-07-18: Reworked shared logging repo-wide with structured component/category/event/request metadata, recursive secret redaction, bounded details, automatic retention, scoped plugin loggers, platform/command/webhook/lifecycle/admin HTTP auditing, live `/admin/logs` updates, complete server-side filters and summaries, and text/JSONL exports.
diff --git a/docs/obs-overlays.md b/docs/obs-overlays.md
index 3acb7fe..df9a4e2 100644
--- a/docs/obs-overlays.md
+++ b/docs/obs-overlays.md
@@ -97,14 +97,18 @@ content. Retry count and refresh interval are configurable. Local/private websit
addresses are periodically refreshed but are not fetched by the Lumi server for
security; browser load-timeout recovery still applies.
-Website sources also support per-edge percentage cropping, zoom, and isolated
-frame appearance CSS. Custom CSS is scoped to the source frame so it cannot restyle
-the Lumi render page or other sources. Standard browser cross-origin security
-does not allow parent-page CSS to select elements inside a third-party iframe;
-use the provider's own appearance settings, crop, and zoom to hide unwanted
-portions of an external site. Frame-level CSS can target
-`.lumi-overlay-frame`, for example
-`.lumi-overlay-frame { filter: saturate(1.2); }`.
+Website sources support per-edge percentage cropping, zoom, and document-level
+Website CSS. Lumi retrieves the public website document through a scoped,
+revocable source URL and appends the stylesheet to its `
`, matching how OBS
+injects Custom CSS after the main document loads. The proxied document runs in a
+sandboxed opaque origin, receives no Lumi login/session data, does not expose the
+overlay token in its location, and cannot use Lumi as a private-network proxy.
+
+CSS injection is enabled by default and can target the website's own elements,
+for example `body`, `.alert`, or provider-specific containers. Direct
+compatibility mode is available for private/local pages and unusual sites that
+cannot run through document injection; direct mode intentionally cannot change
+cross-origin page content.
Lumi applies OBS's standard transparent Browser Source CSS to every clean render
page and shows the same copyable value in the Browser Source setup section:
@@ -113,9 +117,9 @@ page and shows the same copyable value in the Browser Source setup section:
body { background-color: rgba(0, 0, 0, 0); margin: 0px auto; overflow: hidden; }
```
-The Lumi document, canvas stage, and embedded frame surface are transparent by
-default. A third-party page can still explicitly paint its own background; a
-normal parent iframe cannot override that cross-origin page CSS.
+The Lumi document, canvas stage, embedded frame surface, and injected website
+body are transparent by default. Administrators can extend or override the
+default stylesheet per source just as they do in OBS Browser Source properties.
## OBS WebSocket
diff --git a/knowledge/core/lumi-core.md b/knowledge/core/lumi-core.md
index 8383a8c..6b5b156 100644
--- a/knowledge/core/lumi-core.md
+++ b/knowledge/core/lumi-core.md
@@ -14,7 +14,7 @@ editable: false
Lumi is the core web UI and bot runtime.
## Runtime
Package: lumi-bot
-Version: 0.2.12
+Version: 0.2.13
## Routes
- GET /api/events
- POST /api/destructive-confirmations
diff --git a/package-lock.json b/package-lock.json
index bc450b0..098a4d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "lumi-bot",
- "version": "0.2.12",
+ "version": "0.2.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lumi-bot",
- "version": "0.2.12",
+ "version": "0.2.13",
"dependencies": {
"adm-zip": "^0.5.12",
"better-sqlite3": "^11.5.0",
diff --git a/package.json b/package.json
index 0ec4226..83abddb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lumi-bot",
- "version": "0.2.12",
+ "version": "0.2.13",
"private": true,
"type": "commonjs",
"scripts": {
diff --git a/release-index.json b/release-index.json
index 9b1b185..2d55975 100644
--- a/release-index.json
+++ b/release-index.json
@@ -2,6 +2,36 @@
"schema_version": 1,
"channel": "stable",
"releases": [
+ {
+ "version": "0.2.13",
+ "ref": "refs/tags/v0.2.13",
+ "released_at": "2026-07-18",
+ "installable": true,
+ "rollback_safe": true,
+ "replaces_versions": [
+ "1.2.0"
+ ],
+ "data_policy": "preserve",
+ "dependency_policy": "sync_on_restart",
+ "migration_notes": "Adds OBS-style document CSS injection for website overlay sources with scoped render tickets and a compatibility fallback. Existing overlays, website URLs, CSS, tokens, scenes, sources, settings, databases, plugin data, models, uploads, feedback, and secrets are preserved.",
+ "plugins": {
+ "auto-vc": "0.1.6",
+ "birthday": "0.1.3",
+ "economy-framework": "0.2.10",
+ "economy-games": "0.1.7",
+ "expression-interaction": "0.2.1",
+ "lumi_ai": "0.8.5",
+ "moderation": "0.1.5",
+ "okf": "0.1.1",
+ "quotes": "0.1.2",
+ "sample-plugin": "0.1.0",
+ "throne_wishlist": "0.1.2",
+ "welcome_messages": "0.1.1"
+ },
+ "tools": {
+ "lumi_ai_web_search": "0.1.1"
+ }
+ },
{
"version": "0.2.12",
"ref": "refs/tags/v0.2.12",
diff --git a/scripts/verify-all.js b/scripts/verify-all.js
index 3492c97..1c0bbaa 100644
--- a/scripts/verify-all.js
+++ b/scripts/verify-all.js
@@ -24,6 +24,7 @@ const checks = [
"scripts/verify-command-preview-confirmations.js",
"scripts/verify-destructive-actions.js",
"scripts/verify-overlays.js",
+ "scripts/verify-overlay-web-documents.js",
"scripts/verify-webhooks.js"
];
diff --git a/scripts/verify-overlay-web-documents.js b/scripts/verify-overlay-web-documents.js
new file mode 100644
index 0000000..d611550
--- /dev/null
+++ b/scripts/verify-overlay-web-documents.js
@@ -0,0 +1,63 @@
+const assert = require("assert");
+const {
+ injectDocumentCss,
+ loadInjectedOverlayDocument
+} = require("../src/services/overlay-web-documents");
+const { OBS_BROWSER_DEFAULT_CSS } = require("../src/services/obs-browser-defaults");
+
+async function run() {
+ const injected = injectDocumentCss(
+ "Alerts