footer ad

This commit is contained in:
evanpelle
2026-03-10 11:16:55 -07:00
parent 08836e2a57
commit bb9078942d
4 changed files with 130 additions and 1 deletions
+2
View File
@@ -269,6 +269,7 @@
<!-- Bottom HUD: <sm=column, sm..lg=2col (HUD left | events right), lg+=3col grid centered -->
<div
id="bottom-hud"
class="fixed bottom-0 left-0 w-full z-[200] flex flex-col pointer-events-none sm:flex-row sm:items-end lg:grid lg:grid-cols-[1fr_460px_1fr] lg:items-end min-[1200px]:bottom-4 min-[1200px]:px-4"
style="
padding-bottom: env(safe-area-inset-bottom);
@@ -320,6 +321,7 @@
<spawn-timer></spawn-timer>
<immunity-timer></immunity-timer>
<in-game-header-ad></in-game-header-ad>
<in-game-footer-ad></in-game-footer-ad>
<spawn-video-ad></spawn-video-ad>
<game-info-modal></game-info-modal>
<alert-frame></alert-frame>
+5 -1
View File
@@ -180,7 +180,7 @@ declare global {
ramp: {
que: Array<() => void>;
passiveMode: boolean;
spaAddAds: (ads: Array<{ type: string; selectorId: string }>) => void;
spaAddAds: (ads: Array<{ type: string; selectorId?: string }>) => void;
destroyUnits: (adType: string) => void;
settings?: {
slots?: any;
@@ -737,6 +737,8 @@ class Client {
console.log("joining lobby, stopping existing game");
this.gameStop(true);
document.body.classList.remove("in-game");
const bottomHud = document.getElementById("bottom-hud");
if (bottomHud) bottomHud.style.paddingBottom = "";
}
if (lobby.source === "public") {
this.joinModal?.open(lobby.gameID, lobby.publicLobbyInfo);
@@ -879,6 +881,8 @@ class Client {
}
document.body.classList.remove("in-game");
const bottomHud = document.getElementById("bottom-hud");
if (bottomHud) bottomHud.style.paddingBottom = "";
crazyGamesSDK.gameplayStop();
}
+10
View File
@@ -21,6 +21,7 @@ import { GameLeftSidebar } from "./layers/GameLeftSidebar";
import { GameRightSidebar } from "./layers/GameRightSidebar";
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
import { ImmunityTimer } from "./layers/ImmunityTimer";
import { InGameFooterAd } from "./layers/InGameFooterAd";
import { InGameHeaderAd } from "./layers/InGameHeaderAd";
import { Layer } from "./layers/Layer";
import { Leaderboard } from "./layers/Leaderboard";
@@ -270,6 +271,14 @@ export function createRenderer(
}
inGameHeaderAd.game = game;
const inGameFooterAd = document.querySelector(
"in-game-footer-ad",
) as InGameFooterAd;
if (!(inGameFooterAd instanceof InGameFooterAd)) {
console.error("in-game footer ad not found");
}
inGameFooterAd.game = game;
const spawnVideoAd = document.querySelector("spawn-video-ad") as SpawnVideoAd;
if (!(spawnVideoAd instanceof SpawnVideoAd)) {
console.error("spawn video ad not found");
@@ -322,6 +331,7 @@ export function createRenderer(
headsUpMessage,
multiTabModal,
inGameHeaderAd,
inGameFooterAd,
spawnVideoAd,
alertFrame,
performanceOverlay,
@@ -0,0 +1,113 @@
import { LitElement, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
const AD_SHOW_TICKS = 10 * 60 * 10; // 2 minutes
const FOOTER_AD_TYPE = "bottom_rail";
const AD_HEIGHT_PX = 95;
@customElement("in-game-footer-ad")
export class InGameFooterAd extends LitElement implements Layer {
public game: GameView;
private isHidden: boolean = false;
createRenderRoot() {
return this;
}
init() {
if (!window.adsEnabled) {
console.log("InGameFooterAd: adsEnabled is false, skipping");
return;
}
if (typeof window.ramp?.spaAddAds !== "function") {
console.log(
"InGameFooterAd: ramp.spaAddAds not a function (adblock?), ramp=",
window.ramp,
);
return;
}
this.loadAd();
}
private loadAd(): void {
try {
window.ramp.que.push(() => {
try {
window.ramp.spaAddAds([{ type: FOOTER_AD_TYPE }]);
this.waitForAdSlot();
} catch (e) {
console.error("Failed to add in-game footer ad:", e);
}
});
} catch (error) {
console.error("Failed to load in-game footer ad:", error);
}
}
private waitForAdSlot(): void {
const start = Date.now();
const check = () => {
const slots = window.ramp?.settings?.slots;
console.log("InGameFooterAd: checking slots=", slots);
const filled =
slots &&
Object.values(slots).some(
(slot: any) =>
slot?.type === FOOTER_AD_TYPE && slot?.element?.offsetHeight > 0,
);
if (filled) {
const hud = document.getElementById("bottom-hud");
if (hud) hud.style.paddingBottom = `${AD_HEIGHT_PX}px`;
console.log("In-game footer ad slot filled");
return;
}
if (Date.now() - start < 5000) {
setTimeout(check, 200);
} else {
console.log("In-game footer ad did not fill after 5s, slots=", slots);
}
};
setTimeout(check, 200);
}
private hideFooterAd(): void {
const hud = document.getElementById("bottom-hud");
if (hud) hud.style.paddingBottom = "";
try {
window.ramp.destroyUnits(FOOTER_AD_TYPE);
console.log("successfully destroyed in-game footer ad");
} catch (e) {
console.error("error destroying in-game footer ad", e);
}
}
public tick() {
if (this.isHidden) {
return;
}
const gameTicks =
this.game.ticks() - this.game.config().numSpawnPhaseTurns();
if (gameTicks > AD_SHOW_TICKS) {
console.log("destroying in-game footer ad and refreshing PageOS");
this.hideFooterAd();
this.isHidden = true;
if (window.PageOS?.session?.newPageView) {
window.PageOS.session.newPageView();
}
return;
}
}
shouldTransform(): boolean {
return false;
}
render() {
return nothing;
}
}