diff --git a/.gitignore b/.gitignore index 4d11d0efa..43027469e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ resources/.DS_Store .DS_Store .clinic/ CLAUDE.md +NOTES.md .claude/ .idea/ # this is autogenerated by script diff --git a/resources/images/ExitFullscreenIconWhite.svg b/resources/images/ExitFullscreenIconWhite.svg new file mode 100644 index 000000000..b01820a2c --- /dev/null +++ b/resources/images/ExitFullscreenIconWhite.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/images/FullscreenIconWhite.svg b/resources/images/FullscreenIconWhite.svg new file mode 100644 index 000000000..7327db0f3 --- /dev/null +++ b/resources/images/FullscreenIconWhite.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/resources/lang/en.json b/resources/lang/en.json index e22b467c8..ae23fb7b2 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -1087,5 +1087,22 @@ "dismiss": "Dismiss", "go_to_item": "Go to item {num}", "firefox_warning": "OpenFront.io doesn't perform well with [Firefox-based browsers](https://simple.wikipedia.org/wiki/Web_browsers_based_on_Firefox). We recommend you to use a [Chromium-based browser](https://en.wikipedia.org/wiki/Chromium_(web_browser)#Browsers_based_on_Chromium) for best performance." + }, + "ios_banner": { + "text": "For fullscreen, add OpenFront to your Home Screen", + "how": "How", + "later": "Later", + "never": "Never", + "modal_title": "Fullscreen on iOS", + "modal_desc": "Your browser doesn't support fullscreen, but you can add OpenFront to your Home Screen for an immersive experience:", + "step_share": "Tap the Share button in your browser", + "step_scroll_and_tap": "Scroll down and tap", + "step_add_to_home_label": "Add to Home Screen", + "step_open": "Open from your Home Screen — launches fullscreen without the address bar", + "got_it": "Got it" + }, + "fullscreen": { + "enter": "Enter fullscreen", + "exit": "Exit fullscreen" } } diff --git a/src/client/GameModeSelector.ts b/src/client/GameModeSelector.ts index 3f75576b3..3ed313913 100644 --- a/src/client/GameModeSelector.ts +++ b/src/client/GameModeSelector.ts @@ -10,6 +10,7 @@ import { Trios, } from "../core/game/Game"; import { PublicGameInfo, PublicGames } from "../core/Schemas"; +import "./components/IOSAddToHomeScreenBanner"; import { crazyGamesSDK } from "./CrazyGamesSDK"; import { HostLobbyModal } from "./HostLobbyModal"; import { JoinLobbyModal } from "./JoinLobbyModal"; @@ -142,6 +143,9 @@ export class GameModeSelector extends LitElement { "bg-slate-600 hover:bg-slate-500 active:bg-slate-700", )} + + +
{ + if (e.target === e.currentTarget) this.closeGuide(); + }} + > +
+ +
+
+ `; + } + + render() { + if (!Platform.isIOS) return nothing; + if (this.dismissed || this.later) return nothing; + if ( + (navigator as any).standalone === true || + window.matchMedia("(display-mode: standalone)").matches + ) { + return nothing; + } + + return html` + ${this.renderGuideModal()} +
+
+ + ${translateText("ios_banner.text")} +
+ +
+ + + +
+
+ `; + } +} diff --git a/src/client/graphics/layers/GameRightSidebar.ts b/src/client/graphics/layers/GameRightSidebar.ts index 0daf3f323..1ec12be84 100644 --- a/src/client/graphics/layers/GameRightSidebar.ts +++ b/src/client/graphics/layers/GameRightSidebar.ts @@ -18,6 +18,8 @@ const FastForwardIconSolid = assetUrl("images/FastForwardIconSolidWhite.svg"); const pauseIcon = assetUrl("images/PauseIconWhite.svg"); const playIcon = assetUrl("images/PlayIconWhite.svg"); const settingsIcon = assetUrl("images/SettingIconWhite.svg"); +const fullscreenIcon = assetUrl("images/FullscreenIconWhite.svg"); +const exitFullscreenIcon = assetUrl("images/ExitFullscreenIconWhite.svg"); @customElement("game-right-sidebar") export class GameRightSidebar extends LitElement implements Layer { @@ -36,6 +38,9 @@ export class GameRightSidebar extends LitElement implements Layer { @state() private isPaused: boolean = false; + @state() + private isFullscreen: boolean = false; + @state() private timer: number = 0; @@ -80,6 +85,21 @@ export class GameRightSidebar extends LitElement implements Layer { this.requestUpdate(); } + private onFullscreenChange = () => { + this.isFullscreen = !!document.fullscreenElement; + }; + + connectedCallback() { + super.connectedCallback(); + document.addEventListener("fullscreenchange", this.onFullscreenChange); + this.onFullscreenChange(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener("fullscreenchange", this.onFullscreenChange); + } + getTickIntervalMs() { return 250; } @@ -177,6 +197,18 @@ export class GameRightSidebar extends LitElement implements Layer { ); } + private onFullscreenButtonClick() { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen().catch((err) => { + console.warn("Failed to enter fullscreen:", err); + }); + } else { + document.exitFullscreen().catch((err) => { + console.warn("Failed to exit fullscreen:", err); + }); + } + } + render() { if (this.game === undefined) return html``; @@ -204,6 +236,22 @@ export class GameRightSidebar extends LitElement implements Layer { settings + ${document.fullscreenEnabled + ? html`
+ ${this.isFullscreen +
` + : ""} +
exit