From 592dadf80df68bff0240e5f06f1a0aaccc182d52 Mon Sep 17 00:00:00 2001 From: Evan Date: Mon, 6 Apr 2026 16:44:41 -0700 Subject: [PATCH] Refactor home page ads into a single file, add corner video ad (#3601) ## Description: image ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan --- index.html | 2 +- src/client/GutterAds.ts | 162 ---------------- src/client/HomepagePromos.ts | 259 ++++++++++++++++++++++++++ src/client/Main.ts | 13 +- src/client/components/HomeFooterAd.ts | 86 --------- 5 files changed, 265 insertions(+), 257 deletions(-) delete mode 100644 src/client/GutterAds.ts create mode 100644 src/client/HomepagePromos.ts delete mode 100644 src/client/components/HomeFooterAd.ts diff --git a/index.html b/index.html index 3ef165efd..646e012db 100644 --- a/index.html +++ b/index.html @@ -174,7 +174,7 @@ class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-99999" > - + diff --git a/src/client/GutterAds.ts b/src/client/GutterAds.ts deleted file mode 100644 index f887fd26e..000000000 --- a/src/client/GutterAds.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { LitElement, css, html } from "lit"; -import { customElement, state } from "lit/decorators.js"; -import { FOOTER_AD_MIN_HEIGHT } from "./components/HomeFooterAd"; - -@customElement("gutter-ads") -export class GutterAds extends LitElement { - @state() - private isVisible: boolean = false; - - @state() - private adLoaded: boolean = false; - - @state() - private hasFooterAd: boolean = false; - - private onResize = () => { - const isDesktop = window.innerWidth >= 640; - this.hasFooterAd = isDesktop && window.innerHeight >= FOOTER_AD_MIN_HEIGHT; - }; - - private leftAdType: string = "standard_iab_left2"; - private rightAdType: string = "standard_iab_rght1"; - private leftContainerId: string = "gutter-ad-container-left"; - private rightContainerId: string = "gutter-ad-container-right"; - - // Override createRenderRoot to disable shadow DOM - createRenderRoot() { - return this; - } - - static styles = css``; - - connectedCallback() { - super.connectedCallback(); - this.onResize(); - window.addEventListener("resize", this.onResize); - document.addEventListener("userMeResponse", () => { - if (window.adsEnabled) { - console.log("showing gutter ads"); - this.show(); - } else { - console.log("not showing gutter ads"); - } - }); - } - - // Called after the component's DOM is first rendered - firstUpdated() { - // DOM is guaranteed to be available here - console.log("GutterAdModal DOM is ready"); - } - - public show(): void { - this.isVisible = true; - this.requestUpdate(); - - // Wait for the update to complete, then load ads - this.updateComplete.then(() => { - this.loadAds(); - }); - } - - public close(): void { - try { - window.ramp.destroyUnits(this.leftAdType); - window.ramp.destroyUnits(this.rightAdType); - console.log("successfully destroyed gutter ads"); - } catch (e) { - console.error("error destroying gutter ads", e); - } - } - - private loadAds(): void { - console.log("loading ramp ads"); - // Ensure the container elements exist before loading ads - const leftContainer = this.querySelector(`#${this.leftContainerId}`); - const rightContainer = this.querySelector(`#${this.rightContainerId}`); - - if (!leftContainer || !rightContainer) { - console.warn("Ad containers not found in DOM"); - return; - } - - if (!window.ramp) { - console.warn("Playwire RAMP not available"); - return; - } - - if (this.adLoaded) { - console.log("Ads already loaded, skipping"); - return; - } - - try { - window.ramp.que.push(() => { - try { - window.ramp.spaAddAds([ - { - type: this.leftAdType, - selectorId: this.leftContainerId, - }, - { - type: this.rightAdType, - selectorId: this.rightContainerId, - }, - ]); - this.adLoaded = true; - console.log( - "Playwire ads loaded:", - this.leftAdType, - this.rightAdType, - ); - } catch (e) { - console.log(e); - } - }); - } catch (error) { - console.error("Failed to load Playwire ads:", error); - } - } - - disconnectedCallback() { - super.disconnectedCallback(); - window.removeEventListener("resize", this.onResize); - } - - render() { - if (!this.isVisible) { - return html``; - } - - return html` - - - - - - `; - } -} diff --git a/src/client/HomepagePromos.ts b/src/client/HomepagePromos.ts new file mode 100644 index 000000000..a4c0c3266 --- /dev/null +++ b/src/client/HomepagePromos.ts @@ -0,0 +1,259 @@ +import { LitElement, css, html, nothing } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +export const FOOTER_AD_MIN_HEIGHT = 880; + +const FOOTER_AD_TYPE = "standard_iab_head2"; +const FOOTER_AD_CONTAINER_ID = "home-footer-ad-container"; + +// ─── Gutter Ads ────────────────────────────────────────────────────────────── + +@customElement("homepage-promos") +export class HomepagePromos extends LitElement { + @state() private isVisible: boolean = false; + @state() private adLoaded: boolean = false; + private cornerAdLoaded: boolean = false; + @state() private hasFooterAd: boolean = false; + + private onResize = () => { + const isDesktop = window.innerWidth >= 640; + this.hasFooterAd = isDesktop && window.innerHeight >= FOOTER_AD_MIN_HEIGHT; + }; + + private onUserMeResponse = () => { + if (window.adsEnabled) { + console.log("showing homepage ads"); + this.show(); + this.loadCornerAdVideo(); + } else { + console.log("not showing homepage ads"); + } + }; + + private leftAdType: string = "standard_iab_left2"; + private rightAdType: string = "standard_iab_rght1"; + private leftContainerId: string = "gutter-ad-container-left"; + private rightContainerId: string = "gutter-ad-container-right"; + + createRenderRoot() { + return this; + } + + static styles = css``; + + connectedCallback() { + super.connectedCallback(); + this.onResize(); + window.addEventListener("resize", this.onResize); + document.addEventListener("userMeResponse", this.onUserMeResponse); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener("resize", this.onResize); + document.removeEventListener("userMeResponse", this.onUserMeResponse); + } + + public show(): void { + this.isVisible = true; + this.requestUpdate(); + this.updateComplete.then(() => { + this.loadGutterAds(); + }); + } + + public close(): void { + try { + // Keep corner video ad alive. + window.ramp.destroyUnits(this.leftAdType); + window.ramp.destroyUnits(this.rightAdType); + console.log("successfully destroyed gutter ads"); + } catch (e) { + console.error("error destroying gutter ads", e); + } + } + + private loadGutterAds(): void { + console.log("loading ramp gutter ads"); + const leftContainer = this.querySelector(`#${this.leftContainerId}`); + const rightContainer = this.querySelector(`#${this.rightContainerId}`); + + if (!leftContainer || !rightContainer) { + console.warn("Ad containers not found in DOM"); + return; + } + + if (!window.ramp) { + console.warn("Playwire RAMP not available"); + return; + } + + if (this.adLoaded) { + console.log("Ads already loaded, skipping"); + return; + } + + try { + window.ramp.que.push(() => { + try { + window.ramp.spaAddAds([ + { type: this.leftAdType, selectorId: this.leftContainerId }, + { type: this.rightAdType, selectorId: this.rightContainerId }, + ]); + this.adLoaded = true; + console.log("Gutter ads loaded:", this.leftAdType, this.rightAdType); + } catch (e) { + console.log(e); + } + }); + } catch (error) { + console.error("Failed to load gutter ads:", error); + } + } + + private loadCornerAdVideo(): void { + if (this.cornerAdLoaded) return; + if (window.innerWidth < 1280) return; + if (!window.ramp) { + console.warn("Playwire RAMP not available for corner_ad_video"); + return; + } + try { + window.ramp.que.push(() => { + try { + window.ramp + .addUnits([{ type: "corner_ad_video" }]) + .then(() => { + this.cornerAdLoaded = true; + window.ramp.displayUnits(); + console.log("corner_ad_video loaded"); + }) + .catch((e: unknown) => { + console.error("Failed to display corner_ad_video:", e); + }); + } catch (e) { + console.error("Failed to add corner_ad_video:", e); + } + }); + } catch (error) { + console.error("Failed to load corner_ad_video:", error); + } + } + + render() { + if (!this.isVisible) { + return html``; + } + + return html` + + + + + + `; + } +} + +// ─── Footer Ad ─────────────────────────────────────────────────────────────── + +@customElement("home-footer-ad") +export class HomeFooterAd extends LitElement { + @state() private shouldShow: boolean = false; + + createRenderRoot() { + return this; + } + + connectedCallback() { + super.connectedCallback(); + this.style.display = "contents"; + document.addEventListener("userMeResponse", this.onUserMeResponse); + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener("userMeResponse", this.onUserMeResponse); + this.destroyAd(); + } + + private onUserMeResponse = () => { + const isDesktop = window.innerWidth >= 640; + if ( + !window.adsEnabled || + (isDesktop && window.innerHeight < FOOTER_AD_MIN_HEIGHT) + ) { + return; + } + this.shouldShow = true; + this.updateComplete.then(() => { + this.loadAd(); + }); + }; + + private loadAd(): void { + if (!window.ramp) { + console.warn("Playwire RAMP not available for footer ad"); + return; + } + try { + window.ramp.que.push(() => { + try { + window.ramp.spaAddAds([ + { type: FOOTER_AD_TYPE, selectorId: FOOTER_AD_CONTAINER_ID }, + ]); + console.log("Footer ad loaded:", FOOTER_AD_TYPE); + } catch (e) { + console.error("Failed to add footer ad:", e); + } + }); + } catch (error) { + console.error("Failed to load footer ad:", error); + } + } + + private destroyAd(): void { + try { + window.ramp.destroyUnits(FOOTER_AD_TYPE); + console.log("successfully destroyed footer ad"); + } catch (e) { + console.error("error destroying footer ad", e); + } + } + + render() { + if (!this.shouldShow) { + return nothing; + } + + return html` +
+ `; + } +} diff --git a/src/client/Main.ts b/src/client/Main.ts index a8331927c..bfe79d1e0 100644 --- a/src/client/Main.ts +++ b/src/client/Main.ts @@ -27,8 +27,8 @@ import "./GameModeSelector"; import { GameModeSelector } from "./GameModeSelector"; import { GameStartingModal } from "./GameStartingModal"; import "./GoogleAdElement"; -import { GutterAds } from "./GutterAds"; import { HelpModal } from "./HelpModal"; +import { HomepagePromos } from "./HomepagePromos"; import { HostLobbyModal as HostPrivateLobbyModal } from "./HostLobbyModal"; import { JoinLobbyModal } from "./JoinLobbyModal"; import "./LangSelector"; @@ -60,7 +60,6 @@ import { } from "./Utils"; import "./components/DesktopNavBar"; import "./components/Footer"; -import "./components/HomeFooterAd"; import "./components/MainLayout"; import "./components/MobileNavBar"; import "./components/PlayPage"; @@ -245,7 +244,6 @@ class Client { private tokenLoginModal: TokenLoginModal; private matchmakingModal: MatchmakingModal; - private gutterAds: GutterAds; private turnstileTokenPromise: Promise<{ token: string; createdAt: number; @@ -305,10 +303,9 @@ class Client { } }); - const gutterAds = document.querySelector("gutter-ads"); - if (!(gutterAds instanceof GutterAds)) - throw new Error("Missing gutter-ads"); - this.gutterAds = gutterAds; + const gutterAds = document.querySelector("homepage-promos"); + if (!(gutterAds instanceof HomepagePromos)) + throw new Error("Missing homepage-promos"); document.addEventListener("join-lobby", this.handleJoinLobby.bind(this)); document.addEventListener("leave-lobby", this.handleLeaveLobby.bind(this)); @@ -774,7 +771,7 @@ class Client { "token-login", "matchmaking-modal", "lang-selector", - "gutter-ads", + "homepage-promos", ].forEach((tag) => { const modal = document.querySelector(tag) as HTMLElement & { close?: () => void; diff --git a/src/client/components/HomeFooterAd.ts b/src/client/components/HomeFooterAd.ts deleted file mode 100644 index 0298bc09b..000000000 --- a/src/client/components/HomeFooterAd.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { LitElement, html, nothing } from "lit"; -import { customElement, state } from "lit/decorators.js"; - -export const FOOTER_AD_MIN_HEIGHT = 880; -const FOOTER_AD_TYPE = "standard_iab_head2"; -const FOOTER_AD_CONTAINER_ID = "home-footer-ad-container"; - -@customElement("home-footer-ad") -export class HomeFooterAd extends LitElement { - @state() private shouldShow: boolean = false; - - createRenderRoot() { - return this; - } - - connectedCallback() { - super.connectedCallback(); - this.style.display = "contents"; - document.addEventListener("userMeResponse", this.onUserMeResponse); - } - - disconnectedCallback() { - super.disconnectedCallback(); - document.removeEventListener("userMeResponse", this.onUserMeResponse); - this.destroyAd(); - } - - private onUserMeResponse = () => { - const isDesktop = window.innerWidth >= 640; - if ( - !window.adsEnabled || - (isDesktop && window.innerHeight < FOOTER_AD_MIN_HEIGHT) - ) { - return; - } - this.shouldShow = true; - this.updateComplete.then(() => { - this.loadAd(); - }); - }; - - private loadAd(): void { - if (!window.ramp) { - console.warn("Playwire RAMP not available for footer ad"); - return; - } - - try { - window.ramp.que.push(() => { - try { - window.ramp.spaAddAds([ - { type: FOOTER_AD_TYPE, selectorId: FOOTER_AD_CONTAINER_ID }, - ]); - console.log("Footer ad loaded:", FOOTER_AD_TYPE); - } catch (e) { - console.error("Failed to add footer ad:", e); - } - }); - } catch (error) { - console.error("Failed to load footer ad:", error); - } - } - - private destroyAd(): void { - try { - window.ramp.destroyUnits(FOOTER_AD_TYPE); - console.log("successfully destroyed footer ad"); - } catch (e) { - console.error("error destroying footer ad", e); - } - } - - render() { - if (!this.shouldShow) { - return nothing; - } - - return html` -
- `; - } -}