# JS Intrinsic ads Integration

{% file src="/files/DbclEB28aoqinqcbZ3zz" %}

## Guide

Complete guide for integrating Intrinsic in-game advertising using pure JavaScript (no frameworks or game engines required).

### Table of Contents

* Quick Start
* Basic Integration
* 2D Canvas Example
* WebGL Example
* Multiple Billboards
* Advanced Features
* Complete Examples

***

### Quick Start

#### Minimal HTML Setup

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Anzu Ads - Vanilla JS</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            font-family: Arial, sans-serif;
        }
        #game-canvas {
            display: block;
            background: #000;
        }
    </style>
</head>
<body>
    <canvas id="game-canvas"></canvas>

    <!-- Anzu Web SDK -->
    <script src="intrinsic-ads-js-sdk/core/SDKLoader.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuDebug.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuMath.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/UriSchema.js"></script>
    <script src="intrinsic-ads-js-sdk/core/AnzuCore.js"></script>
    <script src="intrinsic-ads-js-sdk/core/ChannelManager.js"></script>
    <script src="intrinsic-ads-js-sdk/core/RenderManager.js"></script>
    <script src="intrinsic-ads-js-sdk/AnzuAd.js"></script>

    <!-- Your game code -->
    <script src="game.js"></script>
</body>
</html>
```

***

### Basic Integration

#### 1. Initialize Anzu SDK

```javascript
// game.js

// Initialize Anzu (call once at startup)
AnzuCore.initialize({
    appKey: "YOUR_ANZU_APP_KEY",        // Get from Anzu dashboard
    logLevel: 1,                         // 0=Debug, 1=Info, 2=Warn, 3=Error, 4=None
    gdprConsentStatus: -1,               // -1=unknown, 0=no consent, 1=consent
    gdprConsentString: "",               // Optional GDPR consent string
    isCoppaRegulated: false              // Set to true if COPPA applies
});

console.log("Anzu SDK initialized!");
```

#### 2. Create an Ad Billboard

```javascript
// Create an ad instance
const billboard = new AnzuAd({
    channelName: "billboard_01",         // Unique identifier
    aspectRatio: 16 / 9,                 // Width / Height ratio
    isDynamic: true,                     // Allow dynamic ad switching
    isClickable: true,                   // Enable click interactions
    allowVideo: true,                    // Allow video ads
    allowAudio: false,                   // Mute videos (better for autoplay)
    shrinkToFit: true                    // Adjust to maintain aspect ratio
});

// Initialize the billboard
billboard.initialize();

console.log("Billboard created:", billboard.getChannelName());
```

#### 3. Main Game Loop

```javascript
let lastTime = performance.now();

function gameLoop(currentTime) {
    // Calculate delta time in seconds
    const deltaTime = (currentTime - lastTime) / 1000;
    lastTime = currentTime;

    // Update Anzu SDK (required every frame)
    AnzuCore.update(deltaTime);

    // Get the ad canvas
    const adCanvas = billboard.getCanvas();

    if (adCanvas) {
        // Use the canvas in your rendering
        // (see examples below)
    }

    // Report visibility to Anzu
    billboard.updateVisibility({
        angle: 0,           // Viewing angle (0-90 degrees)
        visibility: 1.0,    // Screen coverage (0-1)
        viewability: 1.0    // Viewability score (0-1)
    });

    // Continue loop
    requestAnimationFrame(gameLoop);
}

// Start the game loop
requestAnimationFrame(gameLoop);
```

***

### 2D Canvas Example

#### Complete 2D Billboard Implementation

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Anzu 2D Canvas Example</title>
    <style>
        body { margin: 0; background: #222; }
        canvas { display: block; margin: 20px auto; border: 2px solid #444; }
    </style>
</head>
<body>
    <canvas id="game-canvas"></canvas>

    <!-- Include Anzu SDK files -->
    <script src="intrinsic-ads-js-sdk/core/SDKLoader.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuDebug.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuMath.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/UriSchema.js"></script>
    <script src="intrinsic-ads-js-sdk/core/AnzuCore.js"></script>
    <script src="intrinsic-ads-js-sdk/core/ChannelManager.js"></script>
    <script src="intrinsic-ads-js-sdk/core/RenderManager.js"></script>
    <script src="intrinsic-ads-js-sdk/AnzuAd.js"></script>

    <script>
        // Setup canvas
        const canvas = document.getElementById('game-canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = 800;
        canvas.height = 600;

        // Initialize Anzu
        AnzuCore.initialize({
            appKey: "YOUR_ANZU_APP_KEY",
            logLevel: 1
        });

        // Create billboard
        const billboard = {
            ad: new AnzuAd({
                channelName: "billboard_2d",
                aspectRatio: 16 / 9,
                isDynamic: true,
                isClickable: true
            }),
            x: 200,
            y: 150,
            width: 400,
            height: 225 // 400 / (16/9)
        };

        billboard.ad.initialize();

        // Handle clicks
        canvas.addEventListener('click', (event) => {
            const rect = canvas.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;

            // Check if click is within billboard bounds
            if (x >= billboard.x && x <= billboard.x + billboard.width &&
                y >= billboard.y && y <= billboard.y + billboard.height) {
                billboard.ad.interact();
                console.log("Billboard clicked!");
            }
        });

        // Game loop
        let lastTime = performance.now();

        function gameLoop(currentTime) {
            const deltaTime = (currentTime - lastTime) / 1000;
            lastTime = currentTime;

            // Update Anzu
            AnzuCore.update(deltaTime);

            // Clear canvas
            ctx.fillStyle = '#1a1a1a';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // Draw billboard
            const adCanvas = billboard.ad.getCanvas();
            if (adCanvas && adCanvas.width > 0) {
                ctx.drawImage(
                    adCanvas,
                    billboard.x,
                    billboard.y,
                    billboard.width,
                    billboard.height
                );

                // Draw border
                ctx.strokeStyle = '#00ff00';
                ctx.lineWidth = 2;
                ctx.strokeRect(billboard.x, billboard.y, billboard.width, billboard.height);
            } else {
                // Placeholder while loading
                ctx.fillStyle = '#333';
                ctx.fillRect(billboard.x, billboard.y, billboard.width, billboard.height);
                ctx.fillStyle = '#fff';
                ctx.font = '20px Arial';
                ctx.textAlign = 'center';
                ctx.fillText('Loading Ad...', billboard.x + billboard.width/2, billboard.y + billboard.height/2);
            }

            // Calculate visibility
            const billboardBounds = {
                left: billboard.x,
                right: billboard.x + billboard.width,
                top: billboard.y,
                bottom: billboard.y + billboard.height
            };

            const canvasBounds = {
                left: 0,
                right: canvas.width,
                top: 0,
                bottom: canvas.height
            };

            // Check if billboard is visible
            const isVisible = !(
                billboardBounds.right < canvasBounds.left ||
                billboardBounds.left > canvasBounds.right ||
                billboardBounds.bottom < canvasBounds.top ||
                billboardBounds.top > canvasBounds.bottom
            );

            // Calculate screen coverage (simple approximation)
            const billboardArea = billboard.width * billboard.height;
            const canvasArea = canvas.width * canvas.height;
            const visibility = isVisible ? Math.min(1, billboardArea / canvasArea) : 0;

            // Report to Anzu
            billboard.ad.updateVisibility({
                angle: 0,              // 2D, always facing camera
                visibility: visibility,
                viewability: isVisible ? 1 : 0
            });

            requestAnimationFrame(gameLoop);
        }

        requestAnimationFrame(gameLoop);
    </script>
</body>
</html>
```

***

### WebGL Example

#### Complete WebGL Billboard Implementation

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Anzu WebGL Example</title>
    <style>
        body { margin: 0; background: #000; }
        canvas { display: block; }
    </style>
</head>
<body>
    <canvas id="webgl-canvas"></canvas>

    <!-- Include Anzu SDK files -->
    <script src="intrinsic-ads-js-sdk/core/SDKLoader.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuDebug.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuMath.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/UriSchema.js"></script>
    <script src="intrinsic-ads-js-sdk/core/AnzuCore.js"></script>
    <script src="intrinsic-ads-js-sdk/core/ChannelManager.js"></script>
    <script src="intrinsic-ads-js-sdk/core/RenderManager.js"></script>
    <script src="intrinsic-ads-js-sdk/AnzuAd.js"></script>

    <script>
        // WebGL setup
        const canvas = document.getElementById('webgl-canvas');
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');

        if (!gl) {
            alert('WebGL not supported!');
        }

        // Vertex shader
        const vertexShaderSource = `
            attribute vec2 a_position;
            attribute vec2 a_texCoord;
            varying vec2 v_texCoord;

            void main() {
                gl_Position = vec4(a_position, 0.0, 1.0);
                v_texCoord = a_texCoord;
            }
        `;

        // Fragment shader
        const fragmentShaderSource = `
            precision mediump float;
            uniform sampler2D u_texture;
            varying vec2 v_texCoord;

            void main() {
                gl_FragColor = texture2D(u_texture, v_texCoord);
            }
        `;

        // Compile shader
        function compileShader(source, type) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('Shader compile error:', gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }

            return shader;
        }

        // Create program
        const vertexShader = compileShader(vertexShaderSource, gl.VERTEX_SHADER);
        const fragmentShader = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER);

        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);

        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('Program link error:', gl.getProgramInfoLog(program));
        }

        gl.useProgram(program);

        // Create billboard geometry (quad)
        const positions = new Float32Array([
            -0.5,  0.28,  // Top left
             0.5,  0.28,  // Top right
            -0.5, -0.28,  // Bottom left
             0.5, -0.28   // Bottom right
        ]);

        const texCoords = new Float32Array([
            0, 0,  // Top left
            1, 0,  // Top right
            0, 1,  // Bottom left
            1, 1   // Bottom right
        ]);

        // Position buffer
        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

        const positionLocation = gl.getAttribLocation(program, 'a_position');
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

        // Texture coordinate buffer
        const texCoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);

        const texCoordLocation = gl.getAttribLocation(program, 'a_texCoord');
        gl.enableVertexAttribArray(texCoordLocation);
        gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

        // Create texture
        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

        // Initialize Anzu
        AnzuCore.initialize({
            appKey: "YOUR_ANZU_APP_KEY",
            logLevel: 1
        });

        // Create billboard
        const billboard = new AnzuAd({
            channelName: "webgl_billboard",
            aspectRatio: 16 / 9,
            isDynamic: true,
            isClickable: true
        });

        billboard.initialize();

        // Handle clicks
        canvas.addEventListener('click', () => {
            billboard.interact();
        });

        // Game loop
        let lastTime = performance.now();

        function render(currentTime) {
            const deltaTime = (currentTime - lastTime) / 1000;
            lastTime = currentTime;

            // Update Anzu
            AnzuCore.update(deltaTime);

            // Get ad canvas and update texture
            const adCanvas = billboard.getCanvas();
            if (adCanvas && adCanvas.width > 0) {
                gl.bindTexture(gl.TEXTURE_2D, texture);
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, adCanvas);
            }

            // Clear and render
            gl.clearColor(0.1, 0.1, 0.1, 1.0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

            // Report visibility
            billboard.updateVisibility({
                angle: 0,
                visibility: 1.0,
                viewability: 1.0
            });

            requestAnimationFrame(render);
        }

        requestAnimationFrame(render);
    </script>
</body>
</html>
```

***

### Multiple Billboards

#### Managing Multiple Ad Placements

```javascript
// Billboard manager
class BillboardManager {
    constructor() {
        this.billboards = [];
    }

    createBillboard(config) {
        const billboard = {
            ad: new AnzuAd({
                channelName: config.name,
                aspectRatio: config.aspectRatio || 16/9,
                isDynamic: true,
                isClickable: true,
                allowVideo: true,
                allowAudio: false
            }),
            x: config.x || 0,
            y: config.y || 0,
            width: config.width || 400,
            height: config.height || 225,
            visible: true
        };

        billboard.ad.initialize();
        this.billboards.push(billboard);

        return billboard;
    }

    update(deltaTime) {
        // Update all billboards
        this.billboards.forEach(billboard => {
            // Calculate visibility for each billboard
            const visibility = this.calculateVisibility(billboard);

            billboard.ad.updateVisibility({
                angle: 0,
                visibility: visibility,
                viewability: visibility > 0 ? 1 : 0
            });
        });
    }

    render(ctx) {
        this.billboards.forEach(billboard => {
            if (!billboard.visible) return;

            const adCanvas = billboard.ad.getCanvas();
            if (adCanvas && adCanvas.width > 0) {
                ctx.drawImage(
                    adCanvas,
                    billboard.x,
                    billboard.y,
                    billboard.width,
                    billboard.height
                );
            }
        });
    }

    calculateVisibility(billboard) {
        // Simple screen coverage calculation
        const screenArea = window.innerWidth * window.innerHeight;
        const billboardArea = billboard.width * billboard.height;
        return Math.min(1, billboardArea / screenArea);
    }

    handleClick(x, y) {
        // Check click against all billboards
        for (const billboard of this.billboards) {
            if (x >= billboard.x && x <= billboard.x + billboard.width &&
                y >= billboard.y && y <= billboard.y + billboard.height) {
                billboard.ad.interact();
                return true;
            }
        }
        return false;
    }

    destroy() {
        this.billboards.forEach(b => b.ad.destroy());
        this.billboards = [];
    }
}

// Usage
const manager = new BillboardManager();

manager.createBillboard({
    name: "top_banner",
    x: 100,
    y: 50,
    width: 600,
    height: 100,
    aspectRatio: 6
});

manager.createBillboard({
    name: "sidebar",
    x: 50,
    y: 200,
    width: 200,
    height: 200,
    aspectRatio: 1
});

// In game loop
function gameLoop(deltaTime) {
    AnzuCore.update(deltaTime);
    manager.update(deltaTime);
    manager.render(ctx);
}
```

***

### Advanced Features

#### 1. GDPR Consent Management

```javascript
// Check if user is in GDPR region
function isGDPRRegion() {
    // Implement your own logic or use a service
    return true; // Example
}

// Show consent dialog
function showConsentDialog() {
    return new Promise((resolve) => {
        // Your consent UI logic
        const hasConsent = confirm("Do you consent to personalized ads?");
        resolve(hasConsent ? 1 : 0);
    });
}

// Initialize with GDPR
async function initializeWithGDPR() {
    let consentStatus = -1; // Unknown

    if (isGDPRRegion()) {
        consentStatus = await showConsentDialog();
    }

    AnzuCore.initialize({
        appKey: "YOUR_ANZU_APP_KEY",
        logLevel: 1,
        gdprConsentStatus: consentStatus,
        gdprConsentString: "", // Optional CMP consent string
        isCoppaRegulated: false
    });
}

initializeWithGDPR();
```

#### 2. URI Schema Callbacks

```javascript
// Register custom URI scheme for deep linking
UriSchema.registerHook("myapp", (data) => {
    console.log("URI callback triggered!");
    console.log("Base URI:", data.baseUri);
    console.log("Arguments:", data.arguments);

    // Handle the callback
    if (data.arguments.action === "product") {
        const productId = data.arguments.id;
        showProduct(productId);
    }
});

function showProduct(productId) {
    console.log("Showing product:", productId);
    // Your product display logic
}
```

#### 3. Ad Statistics and Monitoring

```javascript
// Track ad performance
class AdMonitor {
    constructor(anzuAd) {
        this.ad = anzuAd;
        this.startTime = Date.now();
    }

    getStats() {
        const stats = this.ad.getChannelStats();
        const uptime = (Date.now() - this.startTime) / 1000;

        return {
            channelName: this.ad.getChannelName(),
            impressions: stats.impressions,
            empties: stats.empties,
            uptime: uptime.toFixed(1) + 's',
            angle: this.ad.getAngle().toFixed(1),
            visibility: (this.ad.getVisibility() * 100).toFixed(1) + '%',
            viewability: (this.ad.getViewability() * 100).toFixed(1) + '%'
        };
    }

    displayStats(ctx, x, y) {
        const stats = this.getStats();

        ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
        ctx.fillRect(x, y, 200, 140);

        ctx.fillStyle = '#00ff00';
        ctx.font = '12px monospace';
        let lineY = y + 20;

        Object.entries(stats).forEach(([key, value]) => {
            ctx.fillText(`${key}: ${value}`, x + 10, lineY);
            lineY += 18;
        });
    }
}

// Usage
const monitor = new AdMonitor(billboard.ad);

// In render loop
monitor.displayStats(ctx, 10, 10);
```

#### 4. Responsive Billboards

```javascript
// Auto-resize billboards on window resize
function createResponsiveBillboard(config) {
    const billboard = {
        ad: new AnzuAd(config),
        baseWidth: config.width,
        aspectRatio: config.aspectRatio || 16/9,
        scale: 1
    };

    billboard.ad.initialize();

    // Calculate responsive size
    function updateSize() {
        const maxWidth = window.innerWidth * 0.8;
        billboard.scale = Math.min(1, maxWidth / billboard.baseWidth);
        billboard.width = billboard.baseWidth * billboard.scale;
        billboard.height = billboard.width / billboard.aspectRatio;
    }

    updateSize();
    window.addEventListener('resize', updateSize);

    return billboard;
}
```

#### 5. Performance Optimization

```javascript
// Lazy update for off-screen billboards
class OptimizedBillboard {
    constructor(config) {
        this.ad = new AnzuAd(config);
        this.ad.initialize();
        this.isOnScreen = true;
        this.updateInterval = 0;
    }

    update(deltaTime, isVisible) {
        this.isOnScreen = isVisible;

        if (isVisible) {
            // Update every frame when visible
            this.updateVisibility();
        } else {
            // Update less frequently when off-screen
            this.updateInterval += deltaTime;
            if (this.updateInterval >= 1.0) {
                this.updateVisibility();
                this.updateInterval = 0;
            }
        }
    }

    updateVisibility() {
        this.ad.updateVisibility({
            angle: 0,
            visibility: this.isOnScreen ? 0.5 : 0,
            viewability: this.isOnScreen ? 1 : 0
        });
    }

    render(ctx, x, y, width, height) {
        if (!this.isOnScreen) return;

        const canvas = this.ad.getCanvas();
        if (canvas) {
            ctx.drawImage(canvas, x, y, width, height);
        }
    }
}
```

***

### Complete Examples

#### Example 1: Simple Billboard Game

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Billboard Runner</title>
    <style>
        body { margin: 0; background: #000; overflow: hidden; }
        canvas { display: block; }
        #stats {
            position: absolute;
            top: 10px;
            left: 10px;
            color: #0f0;
            font-family: monospace;
            background: rgba(0,0,0,0.7);
            padding: 10px;
        }
    </style>
</head>
<body>
    <div id="stats"></div>
    <canvas id="game"></canvas>

    <!-- Anzu SDK -->
    <script src="intrinsic-ads-js-sdk/core/SDKLoader.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuDebug.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/AnzuMath.js"></script>
    <script src="intrinsic-ads-js-sdk/utils/UriSchema.js"></script>
    <script src="intrinsic-ads-js-sdk/core/AnzuCore.js"></script>
    <script src="intrinsic-ads-js-sdk/core/ChannelManager.js"></script>
    <script src="intrinsic-ads-js-sdk/core/RenderManager.js"></script>
    <script src="intrinsic-ads-js-sdk/AnzuAd.js"></script>

    <script>
        const canvas = document.getElementById('game');
        const ctx = canvas.getContext('2d');
        canvas.width = 800;
        canvas.height = 600;

        // Initialize Anzu
        AnzuCore.initialize({
            appKey: "YOUR_ANZU_APP_KEY",
            logLevel: 1
        });

        // Create scrolling billboards
        const billboards = [
            {
                ad: new AnzuAd({
                    channelName: "billboard_1",
                    aspectRatio: 16/9,
                    isClickable: true
                }),
                x: 100,
                y: 200,
                width: 300,
                height: 169
            },
            {
                ad: new AnzuAd({
                    channelName: "billboard_2",
                    aspectRatio: 1,
                    isClickable: true
                }),
                x: 500,
                y: 200,
                width: 200,
                height: 200
            }
        ];

        billboards.forEach(b => b.ad.initialize());

        // Player
        const player = { x: 100, y: 500, width: 30, height: 30, speed: 200 };
        const keys = {};

        window.addEventListener('keydown', e => keys[e.key] = true);
        window.addEventListener('keyup', e => keys[e.key] = false);

        canvas.addEventListener('click', (e) => {
            const rect = canvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            billboards.forEach(b => {
                if (x >= b.x && x <= b.x + b.width &&
                    y >= b.y && y <= b.y + b.height) {
                    b.ad.interact();
                }
            });
        });

        let lastTime = performance.now();

        function gameLoop(currentTime) {
            const deltaTime = (currentTime - lastTime) / 1000;
            lastTime = currentTime;

            // Update Anzu
            AnzuCore.update(deltaTime);

            // Update player
            if (keys['ArrowLeft']) player.x -= player.speed * deltaTime;
            if (keys['ArrowRight']) player.x += player.speed * deltaTime;
            if (keys['ArrowUp']) player.y -= player.speed * deltaTime;
            if (keys['ArrowDown']) player.y += player.speed * deltaTime;

            player.x = Math.max(0, Math.min(canvas.width - player.width, player.x));
            player.y = Math.max(0, Math.min(canvas.height - player.height, player.y));

            // Clear
            ctx.fillStyle = '#1a1a2e';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // Draw billboards
            billboards.forEach(b => {
                const adCanvas = b.ad.getCanvas();
                if (adCanvas) {
                    ctx.drawImage(adCanvas, b.x, b.y, b.width, b.height);
                }

                // Update visibility
                const area = b.width * b.height;
                const screenArea = canvas.width * canvas.height;
                b.ad.updateVisibility({
                    angle: 0,
                    visibility: area / screenArea,
                    viewability: 1
                });
            });

            // Draw player
            ctx.fillStyle = '#00ff00';
            ctx.fillRect(player.x, player.y, player.width, player.height);

            // Stats
            const stats = billboards.map((b, i) => {
                const s = b.ad.getChannelStats();
                return `Billboard ${i+1}: ${s.impressions} impressions`;
            }).join('<br>');

            document.getElementById('stats').innerHTML = stats;

            requestAnimationFrame(gameLoop);
        }

        requestAnimationFrame(gameLoop);
    </script>
</body>
</html>
```

***

### Best Practices

#### 1. Initialization

* ✅ Initialize `AnzuCore` once at app startup
* ✅ Wait for initialization before creating ads
* ✅ Use appropriate log level for production (2-4)

#### 2. Performance

* ✅ Call `AnzuCore.update()` every frame
* ✅ Reuse canvas references, don't fetch every frame
* ✅ Update visibility only when billboard position/size changes
* ✅ Use `requestAnimationFrame` for smooth rendering

#### 3. Visibility Tracking

* ✅ Report accurate visibility (0-1 range)
* ✅ Set `viewability: 0` when billboard is off-screen
* ✅ Use `AnzuMath` utilities for complex calculations
* ✅ Update visibility every frame or when state changes

#### 4. Memory Management

* ✅ Call `ad.destroy()` when removing billboards
* ✅ Remove event listeners when cleaning up
* ✅ Clear references to destroyed ads

#### 5. Click Handling

* ✅ Implement proper hit testing
* ✅ Only call `interact()` when ad is clickable
* ✅ Verify billboard bounds before triggering interaction

***

### Troubleshooting

#### Ad not displaying

```javascript
// Debug checklist
console.log("Anzu initialized?", AnzuCore.isInitialized());
console.log("Canvas exists?", billboard.getCanvas() !== null);
console.log("Canvas size:", billboard.getTextureSize());
```

#### Visibility not tracking

```javascript
// Verify visibility reporting
const vis = billboard.getVisibility();
const view = billboard.getViewability();
console.log("Visibility:", vis, "Viewability:", view);
```

#### Click not working

```javascript
// Debug click detection
canvas.addEventListener('click', (e) => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    console.log("Click at:", x, y);
    console.log("Billboard bounds:", billboard.x, billboard.y, billboard.width, billboard.height);
});
```

***

### Resources

* **Main Documentation**: `README.md`
* **Migration Guide**: `MIGRATION_GUIDE.md`
* **Three.js Guide**: `examples/three-js/README.md`
* **Pixi.js Guide**: `examples/pixi-js/README.md`
* **Phaser Guide**: `examples/phaser-js/README.md`
* **Anzu Dashboard**: <https://dashboard.anzu.io/>

***

### Support

For issues or questions:

1. Check the main `README.md`
2. Review error messages in browser console
3. Verify all SDK files are loaded in correct order
4. Contact Playgama support with your app key


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wiki.playgama.com/playgama/intrinsic-in-game-ads/js-intrinsic-ads-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
