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`
-
- `;
- }
-}