Embed the Kreativsimulator via <iframe>
Embed the Kreativsimulator into your own website in seconds – and optionally drive it entirely from the outside: your own product picker, your own creatives, your own UI. This guide walks every step with copy-paste code and a live demo to try.
For AI coding agents (Claude Code, Cursor & co.)
This entire guide is also available as clean Markdown – one URL your coding agent can read directly. Just give it this prompt:
Read https://virtualcampaign.dev/embed.md and embed the Kreativsimulator into our website via iframe. In addition, https://virtualcampaign.dev/llms.txt links the docs following the llms.txt convention – AI agents discover them there automatically.
Get your domains whitelisted
For security reasons the browser only allows embedding from domains we have whitelisted in advance (Content-Security-Policy: frame-ancestors).
Before embedding, send us all domains the iframe should appear on. We add them to your tenant configuration. Wildcards are supported.
For example:
https://www.your-agency.comhttps://campaign.your-agency.comhttps://*.your-agency.com (wildcard)
Without whitelisting the iframe stays blank (white area). The live demo below also only loads if the embedding domain is whitelisted – otherwise use the “Open in new tab” link.
Embed code
A single <iframe> tag is enough. No API key, no extra configuration on your side – authentication runs entirely server-side.
View code HTML
<iframe
src="https://demo.kreativsimulator.com/"
title="Kreativsimulator"
style="width: 100%; height: 720px; border: 0;"
allow="fullscreen"
loading="lazy"
></iframe> Embed URL: https://<dein-mandant>.kreativsimulator.com/
The tenant is derived from the subdomain. For the public sandbox use demo.kreativsimulator.com.
Language and start product can optionally be preset via query parameters – see the next section.
URL parameters
Optionally append these query parameters to the embed URL (start with ?, chain with &):
| Parameter | Values | Effect | Default |
|---|---|---|---|
lang | de · en · fr · it | UI language | Tenant → browser → de |
product | Product code (ec5472c2) | Loads this product directly (deep link). Unknown code → first product. | First product |
header | 0 · 1 | Show/hide the header (product selector) | 1 (visible) |
sidebar | 0 · 1 | Show/hide the sidebar (incl. mobile drawer + hamburger) | 1 (visible) |
playback | 0 · 1 | Show/hide the playback/transport buttons in the bar | 1 (visible) |
timeline | 0 · 1 | Show/hide the timeline (scrubbing bar) | 1 (visible) |
export | 0 · 1 | Show/hide the “Export creation” button | 1 (visible) |
effects | 0 · 1 | Show/hide the effect dropdown (photo products only) – e.g. for your own picker | 1 (visible) |
fullscreen | 0 · 1 | Fullscreen icon: 0=never, 1=always (incl. desktop); omitted = mobile / in fullscreen only | adaptive |
embed | minimal · kiosk | Shorthand: hides header, sidebar and export (playback bar stays) | – |
theme | URL to a CSS file | Loads a custom theme (CSS variables) that overrides the tenant theme | – |
bg | any CSS color, transparent or image URL | Background of the area around the canvas (letterbox/frame), not the video | Theme background |
stage | auto · <width>x<height> (1024x1024) | Shape and size of the canvas. auto = film/clip 16:9, photo in its own aspect ratio (whole photo, fluid). WxH = fixed shape and fixed display pixel size for every product (the image is cover-fitted, edges are cropped). Changeable at runtime via setStage/setProduct{stage}. | auto |
placeholderBg | any CSS color | Background of the placeholder box (loader without a creative) – direct shortcut, no ?theme= file | Theme/default (#ffffff) |
placeholderText | any CSS color | Color of the letter inside the placeholder box | Theme/default (#000000) |
placeholderBorder | any CSS color | Border color of the placeholder box | Theme/default (#000000) |
watermark | 0 · 1 | 1 = every exported MP4 and every saved still image (JPG) gets the “Vorschau” (preview) watermark (dialog and postMessage export). Additive only – on the demo tenant it is always on and cannot be disabled. | 0 (demo tenant only) |
- No custom branding inside the UI: the simulator shows no logo and no title – the header contains only the product selector (and the menu icon). Branding comes from your page around the iframe. The exported MP4 and the saved still image keep the tenant's logo composited top-left; this is intentional and cannot be disabled.
- Boolean parameters accept
0/1(alsotrue/false,yes/no).embed=kioskis evaluated first – individual parameters override it afterwards (e.g.?embed=kiosk&header=1still shows the header). - The visibility switches (
header/sidebar/playback/timeline/export/effects) can also be toggled at runtime viasetChrome(see API). Playback, export and effect selection always work via postMessage – regardless of whether the matching controls are visible. So you can build a fully custom UI (your own effect picker viasetEffect, hide the built-in dropdown witheffects=0). - theme CSS: loaded as an extra
<link>after the tenant theme, overriding its CSS variables (e.g.--vc-accent,--vc-bg). CSS cannot run JavaScript; the file may be cross-origin (no CORS needed). Values for?theme=/?bg=must be URL-encoded (e.g.%23for#). bg=transparent: the parent page shows through the area around the canvas (best with?embed=kiosk). The video canvas itself always stays opaque – transparent only affects the frame/letterbox edges.- Placeholder colors directly in the URL (
placeholderBg/placeholderText/placeholderBorder): brands just the placeholder box (the generated letter motif shown until a creative is set) – a shortcut with no?theme=file. Accepts any CSS color (hex%23ff5a10,rgb(...),hsl(...), names likewhite) and overrides the tenant theme and?theme=; only the params you set override. Tip: don't setplaceholderBgequal toplaceholderText(invisible letter). For more branding (accent, buttons, spinner …) keep using?theme=.
Examples
View code URL
https://demo.kreativsimulator.com/ # default
https://demo.kreativsimulator.com/?lang=en # English UI
https://demo.kreativsimulator.com/?product=ec5472c2 # open a product
https://demo.kreativsimulator.com/?product=ec5472c2&lang=fr # language + product
https://demo.kreativsimulator.com/?embed=kiosk # canvas + playback bar only
https://demo.kreativsimulator.com/?header=0&sidebar=0 # header + sidebar off
https://demo.kreativsimulator.com/?timeline=0&playback=0 # timeline + transport off
https://demo.kreativsimulator.com/?effects=0 # effect dropdown off (own picker)
https://demo.kreativsimulator.com/?fullscreen=1 # fullscreen icon also on desktop
https://demo.kreativsimulator.com/?watermark=1 # every export gets the preview watermark
https://demo.kreativsimulator.com/?stage=1280x720 # fixed 1280×720 canvas (16:9)
https://demo.kreativsimulator.com/?stage=1024x1024 # square canvas, 1024 px
https://demo.kreativsimulator.com/?bg=%23000000 # black letterbox area
https://demo.kreativsimulator.com/?embed=kiosk&bg=transparent # parent page shows through
https://demo.kreativsimulator.com/?theme=https%3A%2F%2Fcdn.kunde.de%2Fks-theme.css
# Placeholder colors directly (no theme file) — encode # as %23:
https://demo.kreativsimulator.com/?placeholderBg=%23ff5a10&placeholderText=%23ffffff&placeholderBorder=%23ffffff
# … or with rgb()/color names (no %23 needed):
https://demo.kreativsimulator.com/?placeholderBg=rgb(255,90,16)&placeholderText=white&placeholderBorder=white Embed responsively
In embed mode (chrome off, e.g. ?embed=kiosk or ?sidebar=0) the canvas fills the iframe edge-to-edge and stays centered – no left offset, no 1280px cap. So just drop the iframe with width:100%; height:100% into any sized parent container (flexbox/grid too). If its aspect ratio differs from the canvas's you get a symmetric letterbox (color via ?bg=).
Recommended recipe – the iframe fills its flex/grid container:
View code HTML
<!-- Any parent container; the iframe fills it completely. -->
<div style="display:flex; width:100%; height:480px;">
<iframe
src="https://demo.kreativsimulator.com/?embed=kiosk"
title="Kreativsimulator"
style="flex:1; border:0;"
allow="fullscreen"
loading="lazy"
></iframe>
</div> For an exact aspect ratio without letterboxing, give the wrapper aspect-ratio: 16 / 9 – that fits film and clip products:
View code HTML
<!-- Exact 16:9, no letterbox: constrain the wrapper. -->
<div style="aspect-ratio: 16 / 9; width: 100%;">
<iframe
src="https://demo.kreativsimulator.com/?embed=kiosk"
title="Kreativsimulator"
style="width: 100%; height: 100%; border: 0;"
allow="fullscreen"
loading="lazy"
></iframe>
</div> What shape is the canvas? The stage
The shape is set by the stage (?stage=, default auto): film and clip products are 16:9, photo products use the aspect ratio of the photo (e.g. 3:2 or portrait) – the whole photo is visible, nothing is cropped. The simulator reports the current shape in the render block of ready and productChanged, and in the stageChanged event (width, height, aspectRatio). Size your iframe box from it and no letterbox is left:
View code JavaScript
// Let the iframe box follow the canvas shape — no letterbox, no matter
// whether a 16:9 film or a portrait photo is loaded.
window.addEventListener("message", (e) => {
const m = e.data;
if (!m || m.source !== "kreativsimulator") return;
// ready / productChanged carry `render`; stageChanged carries the fields itself.
const r = m.type === "stageChanged" ? m : m.render;
if (r) iframe.style.aspectRatio = r.width + " / " + r.height;
});
// Force ONE fixed shape for every product instead (e.g. square tiles):
// send({ type: "kreativsimulator:setStage", stage: "1024x1024" });
// send({ type: "kreativsimulator:setStage", stage: "auto" }); // back to per-product If you want one fixed shape for every product (e.g. square tiles), set ?stage=1024x1024 or send setStage. The image is then cover-fitted into that shape: a 16:9 film in a square loses about 22% on the left and on the right – placeholders near the edge can fall out of the picture. Check this per product.
With the sidebar visible (standalone view without ?embed/?sidebar=0) the canvas stays left-aligned (max 1280px) – the fill/center logic deliberately applies only in embed mode.
Just the canvas – drive everything yourself
The most common case: show only the WebGL canvas and build the entire UI yourself. Hide header, sidebar and buttons – via URL with ?embed=kiosk (header + sidebar + export off, playback bar stays) or selectively with ?header=0&sidebar=0&playback=0&export=0. At runtime the same works via setChrome.
The key point: playback and export always work via postMessage – regardless of whether the built-in buttons are visible. So your own buttons drive the iframe; for a custom progress bar, listen to the playbackState event.
Handy without the sidebar: when the sidebar is hidden, clicking a placeholder directly on the canvas opens the file dialog for exactly that placeholder – so end users can upload a creative even without your own upload UI.
embed=kiosk hides the built-in chrome – your buttons send play/pause/seek/export. Try it in the live demo below with the “Chrome” toggles and the custom playback bar.
Canvas in kiosk mode + your own playback controls, kept in sync via the playbackState event:
View code HTML + JS
<!-- Canvas only — your own playback UI drives it from the outside -->
<iframe id="ks" src="https://demo.kreativsimulator.com/?embed=kiosk"
style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>
<button id="play">▶︎</button>
<button id="pause">⏸</button>
<button id="stop">⏹</button>
<button id="back">⏮</button>
<button id="fwd">⏭</button>
<input id="scrub" type="range" min="0" value="0" />
<button id="export">Export</button>
<button id="still">Save image</button>
<button id="fs">⛶ Fullscreen</button>
<script>
const iframe = document.getElementById("ks");
const send = (msg) => iframe.contentWindow.postMessage(msg, "*");
const P = "kreativsimulator:";
play.onclick = () => send({ type: P + "play" });
pause.onclick = () => send({ type: P + "pause" });
stop.onclick = () => send({ type: P + "stop" });
back.onclick = () => send({ type: P + "stepBackward" });
fwd.onclick = () => send({ type: P + "stepForward" });
scrub.oninput = (e) => send({ type: P + "seek", frame: Number(e.target.value) });
// Direct export at 1280×720 (no dialog):
export.onclick = () =>
send({ type: P + "export", filename: "creation", width: 1280, height: 720 });
// Still image (JPG) of the currently shown frame (photo products: the whole photo):
still.onclick = () =>
send({ type: P + "exportImage", filename: "creation", width: 1280, height: 720 });
// Fullscreen ON reliably via requestFullscreen on the iframe (the user gesture
// is here, on the click). OFF/toggle could also go via the postMessage event.
fs.onclick = () => {
if (document.fullscreenElement) document.exitFullscreen();
else iframe.requestFullscreen(); // iframe needs allow="fullscreen"
};
window.addEventListener("message", (e) => {
const m = e.data;
if (!m || m.source !== "kreativsimulator") return;
if (m.type === "playbackState") {
scrub.max = m.frameCount - 1;
if (document.activeElement !== scrub) scrub.value = m.frame; // don't fight the drag
play.disabled = m.playing;
pause.disabled = !m.playing;
}
if (m.type === "fullscreenChanged") {
fs.textContent = m.fullscreen ? "⛶ Exit fullscreen" : "⛶ Fullscreen";
}
});
// Ask for the current state once (e.g. after load):
// send({ type: P + "getState" });
</script> Your own design via CSS (theme & bg)
The iframe adopts your branding through CSS variables. ?theme= loads a custom CSS file after the tenant theme and overrides its variables (e.g. --vc-accent, --vc-bg). ?bg= sets the letterbox area around the canvas (color or image URL), ?stage= the shape and size of the canvas.
Embed with theme + background
View code HTML
<!-- Your own theme + a black letterbox around the canvas -->
<iframe
src="https://demo.kreativsimulator.com/?embed=kiosk&theme=https%3A%2F%2Fcdn.kunde.de%2Fks-theme.css&bg=%23000000"
title="Kreativsimulator"
style="width: 100%; height: 720px; border: 0;"
allow="fullscreen"
loading="lazy"
></iframe> Theme template
Download templateks-theme.css View template kreativsimulator-theme-template.css
/* =============================================================================
Kreativsimulator – Theme-Vorlage (CSS Custom Properties)
=============================================================================
So passt du das Aussehen des eingebetteten Kreativsimulators an deine Marke an.
VERWENDUNG
----------
1. Diese Datei kopieren, Werte unten anpassen, auf einem öffentlich
erreichbaren HTTPS-URL ablegen (z. B. https://cdn.deine-agentur.de/ks-theme.css).
2. Den URL per `?theme=` an die iframe-Einbettung hängen – URL-kodiert:
<iframe src="https://demo.kreativsimulator.com/?theme=https%3A%2F%2Fcdn.deine-agentur.de%2Fks-theme.css"
style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>
HINWEISE
--------
• Die Datei wird als zusätzliches <link> NACH dem Mandanten-Theme geladen und
überschreibt dessen Werte (CSS-Kaskade). Cross-Origin ist erlaubt (kein CORS
nötig) – CSS kann kein JavaScript ausführen.
• Du musst NICHT alle Variablen setzen. Lösche einfach die Zeilen, die du nicht
ändern willst – dann gilt der jeweilige Standard des Simulators/Mandanten.
• Farben: jede gültige CSS-Farbe (#hex, rgb(), hsl(), benannt).
• Den Hintergrund UM den Canvas (Letterbox) steuerst du besser per
`?bg=<farbe|transparent|url>` – nicht über `--vc-bg` (das ist auch der
Canvas-Hintergrund während des Ladens).
========================================================================== */
:root {
/* ── Meta ──────────────────────────────────────────────────────────────
Hell-/Dunkel-Schema. Beeinflusst u. a. native Formular-Elemente.
Werte: dark | light */
--vc-color-scheme: dark;
/* Schriftfamilie der gesamten Oberfläche.
Eigene Webfont? Oben in der Datei per @import laden, z. B.:
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
und hier referenzieren: 'Inter', system-ui, sans-serif */
--vc-font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
/* ── Flächen ───────────────────────────────────────────────────────────
Grundfarben der UI-Container (Sidebar, Dialoge, Karten). */
--vc-bg: #0a0a0a; /* App-Hintergrund / Canvas-Hintergrund beim Laden */
--vc-surface: #181818; /* Sidebar, Dialog-Flächen */
--vc-surface-raised: #2a2a2a;/* erhöhte Elemente (Tracks, Chips, Inputs) */
--vc-surface-hover: #3a3a3a; /* Hover-Fläche */
--vc-border: #2a2a2a; /* Standard-Rahmenfarbe */
--vc-border-strong: #444444; /* betonte Rahmen */
/* ── Text ──────────────────────────────────────────────────────────────*/
--vc-text: #eeeeee; /* Standard-Textfarbe */
--vc-text-muted: #888888; /* sekundärer Text */
--vc-text-subtle: #666666; /* dezente Hinweise */
--vc-text-on-accent: #ffffff;/* Text auf Akzent-/Primärflächen */
/* ── Akzent (Marke) ────────────────────────────────────────────────────
Wird u. a. für aktive Zustände, Timeline/Abspielkopf und Slider-Punkte
verwendet. `--vc-accent-rgb` MUSS dieselbe Farbe als "R, G, B" enthalten
(ohne rgb()-Klammern) – wird für halbtransparente Hervorhebungen genutzt. */
--vc-accent: #df3b00;
--vc-accent-strong: #ff5722; /* reserviert — wird derzeit von keinem UI-Element gelesen */
--vc-accent-rgb: 223, 59, 0; /* = --vc-accent als R, G, B */
--vc-accent-muted: #3a3a3a; /* gedämpfter Akzent-Hintergrund */
/* ── Header (Kopfzeile mit Produktauswahl) ─────────────────────────────
Nur relevant, wenn der Header sichtbar ist (sonst per ?header=0 ausblenden). */
--vc-header-bg: transparent; /* Hintergrund der Kopfzeile */
--vc-header-text: #eeeeee; /* Header-Text (aktives Produkt-Label, Menü-Icon) */
--vc-header-text-muted: #888888; /* sekundärer Header-Text (inaktive Produkt-Labels) */
--vc-header-border: #1a1a1a; /* untere Trennlinie */
--vc-header-border-width: 1px; /* Stärke der Trennlinie (0px = keine) */
--vc-header-padding: 12px 20px; /* Innenabstand der Kopfzeile */
/* ── Buttons ───────────────────────────────────────────────────────────*/
--vc-button-bg: #2a2a2a; /* Button-Fläche */
--vc-button-bg-hover: #3a3a3a; /* Button-Fläche bei Hover */
--vc-button-text: inherit; /* Button-Textfarbe */
--vc-button-border: #444444; /* Button-Rahmen */
--vc-button-radius: 4px; /* Eckenradius der Buttons */
/* Inaktiver Auswahl-Button (Platzhalter-/Szenen-Selector). Bewusst NICHT die
Primärbutton-/Akzentfarbe — neutraler Ton, damit aktiv/inaktiv sichtbar trennt. */
--vc-button-inactive-bg: #3a3a3a; /* Fläche des nicht-aktiven Selector-Buttons */
--vc-button-inactive-text: #eeeeee; /* dessen Textfarbe (auf der Fläche lesbar) */
/* ── Slider / Zeitleiste ───────────────────────────────────────────────
Die In/Out-Marken nutzen --vc-accent (s. o.); der Abspielkopf lässt sich
darüber hinaus separat einfärben. */
--vc-slider-track: #3a3a3a; /* Track-Farbe der Regler */
/* Abspielkopf (Linie + Griff) BEIDER Zeitleisten — Haupttimeline und
Platzhalter-Mini-Leiste. Ohne diese Zeile folgt er --vc-accent. */
--vc-playhead-color: #df3b00;
/* Bereich außerhalb von In/Out in beiden Zeitleisten. Bei einem HELLEN Theme
unbedingt beide Werte anpassen (s. Beispielblock unten) — sonst wirkt der
inaktive Bereich dunkler als der aktive und die Timecodes verschwinden. */
--vc-timeline-dim: rgba(0, 0, 0, 0.55); /* Schleier außerhalb In/Out */
--vc-timeline-dim-text: #ffffff; /* Timecodes im Schleier */
/* ── Vorschau-Canvas (Rahmen um das Video) ─────────────────────────────*/
--vc-canvas-outline-color: #2a2a2a; /* Outline-Farbe des Canvas */
--vc-canvas-outline-width: 1px; /* Outline-Stärke (0px = kein Rahmen) */
--vc-canvas-radius: 0px; /* Abgerundete Canvas-Ecken (0px = eckig) */
/* ── Lade-Spinner (während Hintergrund/Produkt lädt) ───────────────────
Ohne diese Zeilen folgt der Spinner --vc-accent (Bogen) / --vc-border (Ring). */
--vc-spinner-color: #df3b00; /* rotierender Bogen (Default: Akzent) */
--vc-spinner-track: #2a2a2a; /* Ring/Spur (Default: Border) */
--vc-spinner-size: 32px; /* Durchmesser */
/* ── Viewport-Rahmen (optionaler Rahmen um die ganze Fläche) ───────────*/
--vc-frame-color: transparent; /* Farbe des Außenrahmens */
--vc-frame-width: 0px; /* Stärke (0px = aus) */
/* ── Platzhalter (Plakatflächen ohne hochgeladenes Motiv) ──────────────
Die generierte „A/B"-Platzhaltertextur. */
--vc-placeholder-bg: #ffffff; /* Hintergrund der Platzhalterfläche */
--vc-placeholder-text: #000000; /* Buchstabe/Beschriftung */
--vc-placeholder-border: #000000; /* Rahmen der Platzhalterfläche */
/* ── Dialoge & Eingabefelder ───────────────────────────────────────────
Export-Dialog („Export als MP4") sowie alle Input-/Select-Felder
(Dateiname, Auflösung, Effekt-Dropdown). Ohne diese Zeilen folgt alles
den Surface-/Text-Defaults oben. */
--vc-dialog-bg: #181818; /* Dialog-Fläche */
--vc-dialog-border: #2a2a2a; /* Dialog-Rahmen */
--vc-dialog-radius: 8px; /* Eckenradius des Dialogs */
--vc-dialog-backdrop: rgba(0,0,0,0.78); /* Abdunklung hinter dem Dialog */
--vc-input-bg: #181818; /* Fläche von Input/Select */
--vc-input-border: #444444; /* Rahmen von Input/Select */
--vc-input-text: #eeeeee; /* Textfarbe in Input/Select */
--vc-input-radius: 4px; /* Eckenradius von Input/Select (Default = --vc-button-radius) */
--vc-input-icon: #df3b00; /* Chevron-Pfeil des Dropdowns (Default = --vc-accent) */
--vc-input-focus: #df3b00; /* Fokus-Ring + Hover-Rahmen von Input/Select (Default = --vc-accent) */
--vc-input-selected-bg: #df3b00; /* Aufgeklappte Liste: gewählter Eintrag (Default = --vc-accent) */
--vc-input-selected-text: #ffffff; /* … dessen Textfarbe (Default = --vc-text-on-accent) */
--vc-input-option-hover: #3a3a3a; /* Aufgeklappte Liste: Eintrag unter der Maus (Default = --vc-surface-hover) */
--vc-label-text: #888888; /* Beschriftung über den Feldern */
}
/* =============================================================================
BEISPIEL: helles Marken-Theme (zum Vergleich auskommentiert).
Zum Ausprobieren das obige :root entfernen und dieses aktivieren.
============================================================================*/
/*
:root {
--vc-color-scheme: light;
--vc-bg: #ffffff;
--vc-surface: #ffffff;
--vc-surface-raised: #f2f2f2;
--vc-surface-hover: #e8e8e8;
--vc-border: #e2e2e2;
--vc-border-strong: #c2c2c2;
--vc-text: #1a1a22;
--vc-text-muted: #5e5e68;
--vc-text-subtle: #8c8c94;
--vc-text-on-accent: #ffffff;
--vc-accent: #0066ff;
--vc-accent-strong: #0047b3;
--vc-accent-rgb: 0, 102, 255;
--vc-accent-muted: #d6e4ff;
--vc-header-bg: #ffffff;
--vc-header-text: #1a1a22;
--vc-header-text-muted: #6a6a74;
--vc-header-border: #e2e2e2;
--vc-header-border-width: 1px;
--vc-button-bg: #0066ff;
--vc-button-bg-hover: #0047b3;
--vc-button-text: #ffffff;
--vc-button-border: #0066ff;
--vc-button-radius: 6px;
--vc-button-inactive-bg: #e8e8e8;
--vc-button-inactive-text: #1a1a22;
--vc-slider-track: #e0e0e0;
--vc-timeline-dim: rgba(0, 0, 0, 0.10);
--vc-timeline-dim-text: var(--vc-text-muted);
--vc-canvas-outline-color: #0066ff;
--vc-canvas-outline-width: 2px;
}
*/ All CSS variables at a glance
You don't have to set every variable – unset variables keep the simulator/tenant default. Colors accept any valid CSS color (#hex, rgb(), hsl(), named).
| Variable | Effect | Default |
|---|---|---|
--vc-color-scheme | light/dark scheme (dark · light) – affects native form elements among others | dark |
--vc-font-family | font family of the entire UI (load custom webfonts via @import at the top of the theme CSS) | system-ui, … |
--vc-bg | app background / canvas background while loading | #0a0a0a |
--vc-surface | sidebar, dialog surfaces | #181818 |
--vc-surface-raised | raised elements (tracks, chips, inputs) | #2a2a2a |
--vc-surface-hover | hover surface | #3a3a3a |
--vc-border | default border color | #2a2a2a |
--vc-border-strong | emphasized borders | #444444 |
--vc-text | default text color | #eeeeee |
--vc-text-muted | secondary text | #888888 |
--vc-text-subtle | subtle hints | #666666 |
--vc-text-on-accent | text on accent/primary surfaces | #ffffff |
--vc-accent | brand accent: active states, in/out marks, slider handles | tenant theme |
--vc-accent-rgb | the same color as “R, G, B” (without rgb() parentheses) – used for translucent highlights; must match --vc-accent | tenant theme |
--vc-accent-strong | reserved – currently read by no UI element | – |
--vc-accent-muted | muted accent background | #3a3a3a |
--vc-header-bg | header background | transparent |
--vc-header-text | header text (active product label, menu icon) | #eeeeee |
--vc-header-text-muted | secondary header text (inactive product labels) | #888888 |
--vc-header-border | bottom divider of the header | #1a1a1a |
--vc-header-border-width | divider width (0px = none) | 1px |
--vc-header-padding | header padding | 12px 20px |
--vc-button-bg | button surface | #2a2a2a |
--vc-button-bg-hover | button surface on hover | #3a3a3a |
--vc-button-text | button text color | inherit |
--vc-button-border | button border | #444444 |
--vc-button-radius | button corner radius | 4px |
--vc-button-inactive-bg | surface of the inactive selector button (placeholder/scene selector) – deliberately neutral instead of accent, so active/inactive separate visibly | #3a3a3a |
--vc-button-inactive-text | text color of the inactive selector button | #eeeeee |
--vc-slider-track | track color of the sliders | #3a3a3a |
--vc-playhead-color | playhead (line + handle) of both timelines – main timeline + placeholder mini bar | follows --vc-accent |
--vc-timeline-dim | veil outside the in/out marks – adjust for light themes (see note) | rgba(0, 0, 0, 0.55) |
--vc-timeline-dim-text | timecodes inside the veil – adjust for light themes (see note) | #ffffff |
--vc-canvas-outline-color | outline color of the preview canvas | #2a2a2a |
--vc-canvas-outline-width | outline width (0px = no frame) | 1px |
--vc-canvas-radius | rounded canvas corners (video, loading area and frame alike) | 0px (square) |
--vc-spinner-color | loading spinner: rotating arc | follows --vc-accent |
--vc-spinner-track | loading spinner: ring/track | follows --vc-border |
--vc-spinner-size | loading spinner: diameter | 32px |
--vc-frame-color | optional outer frame around the whole area: color | transparent |
--vc-frame-width | optional outer frame: width (0px = off) | 0px |
--vc-placeholder-bg | background of the placeholder box (generated “A/B” texture) | #ffffff |
--vc-placeholder-text | letter/label of the placeholder box | #000000 |
--vc-placeholder-border | border of the placeholder box | #000000 |
--vc-dialog-bg | dialog surface (“Export as MP4” dialog) | follows surface |
--vc-dialog-border | dialog border | follows border |
--vc-dialog-radius | dialog corner radius | 8px |
--vc-dialog-backdrop | dimming behind the dialog | rgba(0,0,0,0.78) |
--vc-input-bg | input/select surface (incl. the effect dropdown on photo products) | follows surface |
--vc-input-border | input/select border | follows border |
--vc-input-text | text color in input/select | follows --vc-text |
--vc-input-radius | input/select corner radius | follows --vc-button-radius (4px) |
--vc-input-icon | chevron arrow of the dropdown | follows --vc-accent |
--vc-input-focus | focus ring + hover border of input/select | follows --vc-accent |
--vc-input-selected-bg | open list: background of the selected entry | follows --vc-accent |
--vc-input-selected-text | open list: text color of the selected entry | follows --vc-text-on-accent |
--vc-input-option-hover | open list: entry under the mouse | follows --vc-surface-hover |
--vc-label-text | labels above the fields | follows --vc-text-muted |
- No CORS needed: the CSS file is loaded as a
<link>(not viafetchlike creatives) and may be cross-origin. CSS cannot run JavaScript. - URL-encode: values for
?theme=/?bg=must be URL-encoded (e.g.%23for#,%2Ffor/). - The loading spinner is themeable too:
--vc-spinner-color(rotating arc),--vc-spinner-track(ring) and--vc-spinner-size(diameter) – without an override it follows--vc-accent/--vc-borderautomatically. - Rounded canvas corners:
--vc-canvas-radius(default0px) rounds the preview canvas – video, loading area and frame alike – e.g.--vc-canvas-radius: 16px;in the?theme=CSS. - Color the playhead separately:
--vc-playhead-colorcolors the playhead of both timelines (main timeline + placeholder mini bar) independently of the accent; without an override it follows--vc-accent. Custom CSS rules on thevc-main-playheadclass do not apply (the elements are styled inline) – use the variable. - ⚠️ Timeline dimming – must be adjusted for light themes: the area outside the in/out marks is dimmed via
--vc-timeline-dim(veil) and--vc-timeline-dim-text(the timecodes inside it). The defaults target dark themes (rgba(0,0,0,0.55)+ white). For a light theme override both – a proven pair:--vc-timeline-dim: rgba(0, 0, 0, 0.10);and--vc-timeline-dim-text: var(--vc-text-muted);. - Dialogs & input fields are themeable too: the export dialog (
--vc-dialog-bg,--vc-dialog-border,--vc-dialog-radius,--vc-dialog-backdrop) and all input/select fields incl. the photo-effect dropdown (--vc-input-bg,--vc-input-border,--vc-input-text,--vc-input-radius,--vc-input-icon= dropdown chevron,--vc-input-focus= focus ring,--vc-input-selected-bg/--vc-input-selected-text= selected entry in the open list,--vc-input-option-hover= entry under the mouse,--vc-label-text) can be styled individually – without an override they follow the surface/accent variables; the corner radius follows--vc-button-radius. - Only placeholder colors? You don't need a theme file for that:
?placeholderBg=/?placeholderText=/?placeholderBorder=set the placeholder box directly via URL (see URL parameters above) and override the theme +?theme=. - You'll find every overridable CSS variable in the table and the theme template below.
Try remote control live
Here you see the real iframe (left) next to a completely custom control UI (right) – your own product images, your own creatives, your own buttons. Every click sends a postMessage to the iframe. This is exactly how you rebuild it on your site; the matching code is right below.
Real iframe from demo.kreativsimulator.com Open in new tab
Show / hide chrome
Hide header/sidebar/buttons live → setChrome. “Canvas only” hides everything.
Your playback bar
Works even without visible buttons. Scrubber via seek, synced via playbackState.
Ken-Burns effects
Photo products only: camera moves from ready.effects. Click → setEffect.
Your product picker
Your page builds these cards from the ready event. Click → setProduct. Photo products show their effect count via getEffects (pre-fetched, no product switch).
Your creatives
Your own placeholder creatives. Click → setCreative (loads the image via CORS).
Your own file per placeholder
Drag an image onto A or B → setCreative with loader. In production you upload the file to your CORS host and pass the URL; here it's a data URL for the demo.
Export
Resolution from ready.render.presets. Click → export (MP4).
Load variants (URL parameters)
Reloads the iframe with parameters – this is how embed/bg/stage take effect.
Placeholder colors (URL)
Brands just the placeholder box directly via URL – no ?theme= file. “Apply” reloads the iframe with ?placeholderBg/Text/Border.
Language
Event log (iframe → your page)
- No events received yet …
The following code drives exactly this live demo. Use it as your starting point:
View code HTML + JS
<iframe id="ks" src="https://demo.kreativsimulator.com/"
style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>
<script>
const iframe = document.getElementById("ks");
const send = (msg) => iframe.contentWindow.postMessage(msg, "*");
window.addEventListener("message", (e) => {
const m = e.data;
if (!m || m.source !== "kreativsimulator") return;
if (m.type === "ready") {
// Build your own buttons from the product list
for (const p of m.products) {
const btn = document.createElement("button");
btn.textContent = p.name;
btn.onclick = () =>
send({ type: "kreativsimulator:setProduct", code: p.code });
document.body.appendChild(btn);
}
// Build your own resolution picker + export button from m.render.presets
const sel = document.createElement("select");
for (const r of m.render.presets) {
const o = document.createElement("option");
o.value = r.width + "x" + r.height;
o.textContent = r.label;
sel.appendChild(o);
}
const exportBtn = document.createElement("button");
exportBtn.textContent = "Export";
exportBtn.onclick = () => {
const [width, height] = sel.value.split("x").map(Number);
send({ type: "kreativsimulator:export", filename: "creation", width, height });
};
document.body.append(sel, exportBtn);
}
// The product is active now → only here may you target a placeholder.
if (m.type === "productChanged") {
const first = m.loaders[0]; // take the name from the event
if (first) {
send({
type: "kreativsimulator:setCreative",
url: "https://cdn.your-agency.com/creative.png",
loader: first.name, // this placeholder only
});
}
}
if (m.type === "error") console.warn("KS error:", m.message);
});
// Creative on ALL placeholders (no loader) — fine right after `ready`:
// send({ type: "kreativsimulator:setCreative", url: "https://cdn.your-agency.com/creative.png" });
</script> postMessage API
Build your own UI and drive the iframe via window.postMessage. The iframe also sends events back (including the list of available product codes).
Incoming commands (your page → iframe)
| Message | Effect |
|---|---|
{ type: "kreativsimulator:setProduct", code: "ec5472c2", stage? } | Loads the product with this code. The optional stage ("auto", "1024x1024" or { width, height }) sets the canvas shape at the same time; without it the current stage is kept. |
{ type: "kreativsimulator:setStage", stage: "auto" | "WxH" | { width, height } } | Sets the stage (shape and size of the canvas) at runtime. "auto" = film/clip 16:9, photo in its own aspect ratio; WxH = fixed shape + display size for every product. Reply: stageChanged. The stage survives product switches until you set it again. |
{ type: "kreativsimulator:setCreative", url: "https://…/creative.png" } | Places the image/video as the creative on all placeholders. |
{ type: "kreativsimulator:setCreative", url: "…", loader: "Loader A" } | Fills only the named placeholder. |
{ type: "kreativsimulator:clearCreative", loader?: "Loader A" } | Resets one (or without loader all) placeholders to the default. |
{ type: "kreativsimulator:setLanguage", lang: "fr" } | Switches the UI language (de/en/fr/it). |
{ type: "kreativsimulator:setEffect", id: "static-5s" } | Photo products: selects an entry from ready.effects – a static preset (static-5s/static-10s, always present) or a named Ken-Burns effect. id: null = static still (1 frame). Changes the timeline length → a playbackState follows. |
{ type: "kreativsimulator:getEffects", code: "ec5472c2" } | Queries the effect list of any product without activating it (live view stays unchanged) → reply as an effects event. For a per-product effect picker before calling setProduct. |
{ type: "kreativsimulator:play" } · pause · stop | Starts / pauses / stops (back to start) playback. |
{ type: "kreativsimulator:stepForward", frames?: 1 } · stepBackward | Steps frames frames forward/back (default 1) and pauses. |
{ type: "kreativsimulator:seek", frame: 120 } | Jumps to an absolute frame index (0…frameCount-1). |
{ type: "kreativsimulator:export", filename?, width?, height?, watermark? } | Starts the MP4 export. With width+height directly at any resolution; without, the export dialog opens. watermark: true adds the “Vorschau” (preview) watermark (see notes). |
{ type: "kreativsimulator:exportImage", filename?, width?, height?, watermark? } | Saves a still image as JPG (directly, no dialog). Video products: the currently shown frame at width×height (default 1280×720, 16:9). Photo products: the whole photo (no 16:9 crop, no Ken-Burns) with the placed creatives, 1280 px wide at the photo's aspect ratio – width/height are ignored there. watermark as with export. |
{ type: "kreativsimulator:setChrome", header?, sidebar?, playback?, timeline?, export?, effects? } | Toggles individual UI areas at runtime (each true/false). effects = effect dropdown (photo products). |
{ type: "kreativsimulator:fullscreen", enabled? } | Fullscreen: true=on, false=off, without enabled=toggle (see note). |
{ type: "kreativsimulator:getState" } | Requests the current playback state → reply as playbackState. |
View code JS
// Save a specific frame of a video product as JPG: pause/seek first, then exportImage.
send({ type: "kreativsimulator:seek", frame: 120 });
send({ type: "kreativsimulator:exportImage", filename: "poster", width: 1920, height: 1080 }); Creatives from the VirtualCampaign asset management
Assets uploaded to your tenant's VirtualCampaign account can be used directly as creatives – without hosting them anywhere again and without any CORS configuration on your side.
You get the URLs ready-made: open the asset preview in the VirtualCampaign web UI and click the chain icon – it puts all URLs of the creative incl. their sizes on the clipboard, ready to paste into an e-mail.
The URLs look like this:
View code URL
https://virtualcampaign-prod.nbg1.your-objectstorage.com/assets-public/<token>/web_720.webp View code JS
// A published asset from the VirtualCampaign asset management — same host as
// the simulator, so no CORS setup on your side. Pick the web_* variant you need.
send({
type: "kreativsimulator:setCreative",
url: "https://virtualcampaign-prod.nbg1.your-objectstorage.com/assets-public/7f3a…/web_720.webp",
loader: "Loader A",
}); | Variant | Size | When |
|---|---|---|
web_720.mp4 · web_720.webp | up to 1280 × 720 | default – best quality |
web_640.mp4 · web_640.webp | up to 1138 × 640 | compromise |
web_320.mp4 · web_320.webp | up to 570 × 320 | slow connection, many creatives, mobile |
- Extension follows the asset type: video assets serve
.mp4, image assets.webp. Image variants keep a transparent background (cut-out creatives), which JPEG would lose. - The URLs open in the browser – handy for checking before you build them in. They are not guessable: the token in the URL is the secret. Treat them as confidentially as the creative itself.
- Revoking: “Unshare” in the same menu deletes the published files; the URLs return an error afterwards. If the same creative is shared again later, the same URLs come back – existing embeds work again.
- Why three sizes? The simulator downloads a creative completely before it appears (no streaming). File size = waiting time. A placeholder is usually just a small part of the video – the small variant is often enough. When in doubt use
web_320for the live preview andweb_720only for the export. - Aspect ratio is preserved: the variants are fitted proportionally into the box, never distorted, never upscaled (portrait e.g. 404 × 720, a 2.96:1 billboard creative 1280 × 432). Smaller source files stay unchanged.
- New uploads only: the variants are created on upload. Older assets don't show the chain icon – re-upload them once.
- The original file is deliberately not served (may be e.g. ProRes in a
.movand up to 500 MB). Always use aweb_*variant.
Outgoing events (iframe → your page)
View code JS
{ source: "kreativsimulator", v: 1, type: "ready",
product: "ec5472c2", locale: "de",
products: [{ code: "ec5472c2", name: "City-Light …" }, …],
loaders: [{ name: "Loader A", aspectRatio: 2.96 }, …],
// shape of the current stage (backing: long side 1280), stage mode and
// export presets relative to that shape (short side 360/720/1080)
render: { width: 1280, height: 720, aspectRatio: 1.7778, stage: "auto",
presets: [{ label: "Small (640×360)", width: 640, height: 360 },
{ label: "Standard (1280×720)", width: 1280, height: 720 },
{ label: "Large (1920×1080)", width: 1920, height: 1080 }] },
// photo products (productType "bild"): effects[] = two always-present static
// presets (static-5s, static-10s) + named Ken-Burns effects; default = static-5s.
// video products report isPhoto:false, effects:[], selectedEffect:null.
isPhoto: true,
effects: [{ id: "static-5s", name: "Static (5s)", durationFrames: 125 },
{ id: "static-10s", name: "Static (10s)", durationFrames: 250 },
{ id: "fx-a", name: "Effect A", durationFrames: 125 }, …],
selectedEffect: "static-5s" }
{ source: "kreativsimulator", v: 1, type: "productChanged", code: "ec5472c2",
loaders: [{ name: "Loader A", aspectRatio: 2.96 }, …],
// the shape can change with the product (a photo is not 16:9) → resize your iframe box
render: { width: 1280, height: 854, aspectRatio: 1.4988, stage: "auto", presets: [ … ] },
isPhoto: true,
effects: [{ id: "static-5s", name: "Static (5s)", durationFrames: 125 },
{ id: "static-10s", name: "Static (10s)", durationFrames: 250 },
{ id: "fx-a", name: "Effect A", durationFrames: 125 }, …],
selectedEffect: "static-5s" }
// reply to getEffects {code}: a product's effect list WITHOUT activating it.
// same fields as productChanged (incl. the static presets), plus code.
// video products report isPhoto:false, effects:[], selectedEffect:null.
{ source: "kreativsimulator", v: 1, type: "effects", code: "ec5472c2",
isPhoto: true,
effects: [{ id: "static-5s", name: "Static (5s)", durationFrames: 125 },
{ id: "static-10s", name: "Static (10s)", durationFrames: 250 },
{ id: "fx-a", name: "Effect A", durationFrames: 125 }, …],
selectedEffect: "static-5s" }
// playback state: on play/pause, ~4×/s while playing, after seek/stop/step, and on getState
// for photo products, frameCount equals the active effect's duration
{ source: "kreativsimulator", v: 1, type: "playbackState",
frame: 120, playing: true, frameCount: 250, fps: 25 }
// confirmation after setChrome, with the new visibility state
{ source: "kreativsimulator", v: 1, type: "chromeChanged",
header: false, sidebar: false, playback: true, timeline: true, export: false, effects: false }
// fullscreen state — on every change (e.g. ESC); for syncing your own button
{ source: "kreativsimulator", v: 1, type: "fullscreenChanged", fullscreen: true }
// the stage (canvas shape) changed: after setStage, setProduct{stage} or a product
// switch with a different aspect ratio. Same fields as the render block.
{ source: "kreativsimulator", v: 1, type: "stageChanged",
width: 1280, height: 1280, aspectRatio: 1, stage: "explicit", presets: [ … ] }
{ source: "kreativsimulator", v: 1, type: "error", message: "…", code, loader } Always filter incoming messages on source === "kreativsimulator". v is the protocol version (currently 1).
Complete example
Build your own buttons from the product list, place a creative on a targeted placeholder (correct ordering), and a custom resolution picker from render.presets:
View code HTML + JS
<iframe id="ks" src="https://demo.kreativsimulator.com/"
style="width:100%;height:720px;border:0" allow="fullscreen"></iframe>
<script>
const iframe = document.getElementById("ks");
const send = (msg) => iframe.contentWindow.postMessage(msg, "*");
window.addEventListener("message", (e) => {
const m = e.data;
if (!m || m.source !== "kreativsimulator") return;
if (m.type === "ready") {
// Build your own buttons from the product list
for (const p of m.products) {
const btn = document.createElement("button");
btn.textContent = p.name;
btn.onclick = () =>
send({ type: "kreativsimulator:setProduct", code: p.code });
document.body.appendChild(btn);
}
// Build your own resolution picker + export button from m.render.presets
const sel = document.createElement("select");
for (const r of m.render.presets) {
const o = document.createElement("option");
o.value = r.width + "x" + r.height;
o.textContent = r.label;
sel.appendChild(o);
}
const exportBtn = document.createElement("button");
exportBtn.textContent = "Export";
exportBtn.onclick = () => {
const [width, height] = sel.value.split("x").map(Number);
send({ type: "kreativsimulator:export", filename: "creation", width, height });
};
document.body.append(sel, exportBtn);
}
// The product is active now → only here may you target a placeholder.
if (m.type === "productChanged") {
const first = m.loaders[0]; // take the name from the event
if (first) {
send({
type: "kreativsimulator:setCreative",
url: "https://cdn.your-agency.com/creative.png",
loader: first.name, // this placeholder only
});
}
}
if (m.type === "error") console.warn("KS error:", m.message);
});
// Creative on ALL placeholders (no loader) — fine right after `ready`:
// send({ type: "kreativsimulator:setCreative", url: "https://cdn.your-agency.com/creative.png" });
</script> Important
- Ordering on product change: after
setProductthe new product is only active once theproductChangedevent arrives. Send placeholder-targeted commands (setCreative/clearCreativewithloader) only afterwards – before that the iframe only knows the old product's placeholders. A product change also resets all creatives. - Your own controls: playback (
play/pause/stop/step…/seek),exportandexportImagealways work – whether or not the built-in buttons are visible. Hide them with?embed=kioskand drive everything yourself. - Pick the export resolution:
exportwithwidth+heightrenders at any resolution. The current render shape (width/height/aspectRatioof the stage) and three matching presets (“Small/Standard/Large”, short side 360/720/1080 px) come from therenderfield inready,productChangedandstageChanged– build your own resolution picker from it. Tip: keeprender.aspectRatioor you get bars. - Stage = shape of the canvas:
setStage(or?stage=) decides the canvas shape –"auto"means film/clip 16:9 and photo in its own aspect ratio,"1024x1024"forces one fixed shape for every product. Every change emits astageChangedevent withwidth/height/aspectRatio– feed it into theaspect-ratioof your iframe box and no letterbox is left. - Video creatives – playback mode: a video creative runs in real time by default (independent of the product timeline). In the sidebar the user can switch each placeholder to synced start (the creative restarts whenever the placeholder appears) or to still frame, plus a separate loop switch. The MP4 export follows the chosen mode. There is currently no postMessage command for this – with the sidebar hidden (
?sidebar=0/?embed=kiosk) it stays real time + loop. Talk to us if you need to drive it remotely. - Save a still image (JPG): besides the video, every product can save a still – in the built-in export dialog via the “Video (MP4)” / “Image (JPG)” toggle, or via the
exportImagemessage. Video products: the currently shown frame (playhead) incl. creatives at the chosen resolution (default 1280×720). Photo products: the whole photo with creatives, 1280 px wide at the photo's aspect ratio. For a specific frame sendpause/seekfirst, thenexportImage. The top-left logo and the watermark rules apply as for MP4. - Add the watermark:
export/exportImagewithwatermark: truecomposites the diagonally tiled “Vorschau” (preview) watermark into the exported MP4 or JPG – e.g. for non-binding previews. The flag is additive only:false(or omitted) gives the normal behavior; on the demo tenant the watermark is always on and cannot be disabled. To mark all exports of an embed (including those from the built-in export dialog), use the URL parameter?watermark=1instead. - Photo products & Ken-Burns effects: some products are still images with named camera moves.
ready/productChangedthen reportisPhoto: trueand a list ofeffects({ id, name, durationFrames }). The list always starts with two static presets (static-5s= 5 s,static-10s= 10 s), followed by the named effects; on loadstatic-5sis selected automatically (selectedEffect). UsesetEffect { id }to switch entry (orid: nullfor a pure still image, 1 frame). Playback and export work just like video;playbackState.frameCountequals the active entry's duration. - Pre-fetch effects (without switching product):
ready/productChangedonly carry the effects of the active product. To build an effect picker for other products without switching the live view, sendgetEffects { code }and listen for theeffectsevent (same fields asproductChanged, pluscode). That gives you each effect'sid/nameto drive later viasetEffect(aftersetProductto that code). - Turn fullscreen on reliably: browsers only allow enabling it as a direct reaction to a user gesture – a
fullscreenpostMessage usually lacks that and may be blocked. So calliframe.requestFullscreen()on your page on click (the iframe needsallow="fullscreen"); the postMessage is fine for off/toggle. The state comes viafullscreenChanged. - Placeholder names are not free-form – use exactly one
namefrom theloaders[]array ofreadyorproductChanged. - CORS: on
setCreativethe iframe fetches the image/video URL. The host of your file must therefore sendAccess-Control-Allow-Origin, otherwise anerrorevent comes back. - Security: which pages may embed the iframe is set server-side via
frame-ancestors– only those can send messages at all.
Good to know
- No API key needed – authentication runs entirely server-side.
- Users can operate the simulator directly in the iframe or you drive it via postMessage from your page.
- The iframe loads video footage – a stable connection is recommended.
Troubleshooting
The iframe stays blank / white area
Most likely the embedding domain is not whitelisted. The browser console (F12) then shows “Refused to frame … frame-ancestors …”. Send us the exact domain (incl. https:// and any subdomain) and we'll whitelist it.
The iframe shows “403”
The tenant is not yet fully configured server-side (no API key stored). Please contact us.
A creative doesn't appear (error event)
The creative URL sends no CORS, or the loader name doesn't match the active product. Check the image URL's Access-Control-Allow-Origin and send loader-targeted commands only after the productChanged event.
Ready to embed?
Send us your domain(s) – we'll whitelist the embed and you'll be live in minutes.