import { LitElement, html } from "lit"; import { customElement } from "lit/decorators.js"; import { adGatekeeper } from "../../AdGatekeeper"; import { Controller } from "../../Controller"; import { GameView } from "../../view"; const AD_TYPES = [ { type: "standard_iab_left1", selectorId: "in-game-bottom-left-ad" }, { type: "standard_iab_left3", selectorId: "in-game-bottom-left-ad3" }, { type: "standard_iab_left4", selectorId: "in-game-bottom-left-ad4" }, ]; @customElement("in-game-promo") export class InGamePromo extends LitElement implements Controller { public game: GameView; private shouldShow: boolean = false; private adsVisible: boolean = false; private bottomRailDestroyed: boolean = false; private cornerAdShown: boolean = false; private adCheckInterval: ReturnType | null = null; private adGateOff: (() => void) | null = null; createRenderRoot() { return this; } init() {} tick() { if (!this.game.inSpawnPhase()) { if (!this.bottomRailDestroyed) { this.bottomRailDestroyed = true; this.destroyBottomRail(); } if (!this.cornerAdShown) { this.cornerAdShown = true; console.log("[InGamePromo] Spawn phase ended, triggering showAd"); this.showAd(); } } } private destroyBottomRail(): void { if (!window.ramp) return; try { window.ramp.destroyUnits("pw-oop-bottom_rail"); console.log("Bottom rail ad destroyed after spawn phase"); } catch (e) { console.error("Error destroying bottom_rail ad:", e); } } private showAd(): void { if (window.innerWidth < 1100) return; if (window.innerHeight < 750) return; if (!window.adsEnabled) return; // Show the intrusive in-game ad only to users who have been blocker-free. // Once a blocker is ever detected the gate latches suppressed forever // (persisted across sessions), so whenClear never fires for those users. this.adGateOff = adGatekeeper.whenClear(() => { this.shouldShow = true; this.requestUpdate(); this.updateComplete.then(() => { this.loadAd(); this.checkForAds(); }); }); } private checkForAds(): void { if (this.adCheckInterval) { clearInterval(this.adCheckInterval); } this.adCheckInterval = setInterval(() => { const hasAds = AD_TYPES.some(({ selectorId }) => { const el = document.getElementById(selectorId); return el && el.clientHeight > 50; }); if (hasAds) { this.adsVisible = true; this.requestUpdate(); if (this.adCheckInterval) { clearInterval(this.adCheckInterval); this.adCheckInterval = null; } } }, 1000); } private loadAd(): void { if (!window.ramp) { console.warn("Playwire RAMP not available for in-game ad"); return; } try { window.ramp.que.push(() => { try { window.ramp.spaAddAds( AD_TYPES.map(({ type, selectorId }) => ({ type, selectorId })), ); console.log( "In-game bottom-left ads loaded:", AD_TYPES.map((a) => a.type), ); } catch (e) { console.error("Failed to add in-game ads:", e); } }); } catch (error) { console.error("Failed to load in-game ads:", error); } } public hideAd(): void { if (this.adGateOff) { this.adGateOff(); this.adGateOff = null; } if (this.adCheckInterval) { clearInterval(this.adCheckInterval); this.adCheckInterval = null; } this.adsVisible = false; this.destroyBottomRail(); if (!window.ramp) { console.warn("Playwire RAMP not available for in-game ad"); return; } this.shouldShow = false; try { for (const { type } of AD_TYPES) { window.ramp.destroyUnits(type); } console.log("successfully destroyed in-game bottom-left ads"); } catch (e) { console.error("error destroying in-game ads:", e); } this.requestUpdate(); } render() { if (!this.shouldShow) { return html``; } return html`
${AD_TYPES.map( ({ selectorId }) => html`
`, )}
`; } }