Skip to content

Add fullscreen support: HUD button (desktop/Android) + iOS Add to Home Screen banner#3688

Open
baculinivan-web wants to merge 2 commits intoopenfrontio:mainfrom
baculinivan-web:feature/fullscreen-hud-button-3685
Open

Add fullscreen support: HUD button (desktop/Android) + iOS Add to Home Screen banner#3688
baculinivan-web wants to merge 2 commits intoopenfrontio:mainfrom
baculinivan-web:feature/fullscreen-hud-button-3685

Conversation

@baculinivan-web
Copy link
Copy Markdown
Contributor

@baculinivan-web baculinivan-web commented Apr 15, 2026

Resolves #3685

Description:

Adds fullscreen support for both desktop and mobile:

Desktop / Android — a fullscreen toggle button in the in-game HUD (right sidebar), next to the settings button. Icon switches between expand/compress depending on current state, synced with fullscreenchange event (works with F11 too). Hidden on browsers that don't support document.fullscreenEnabled.

iOS — since Safari doesn't support the Fullscreen API, a dismissible banner is shown on the main screen (above the lobby cards) explaining how to add the game to the Home Screen for a fullscreen experience. The banner includes:

  • How button — opens a step-by-step guide modal with iOS version detection (iOS 26+ shows updated steps for the new ··· menu location, including the extra Share step inside the menu)
  • Later — hides until next visit
  • Never — permanently dismisses via localStorage
  • Click here button styled as a speech bubble with a tail pointing toward the Share button location (center for iOS ≤18, right for iOS 26+)

All user-facing strings are wired through translateText() with keys added to en.json.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

UI changes:

For Fullscreen API supported browsers:

CleanShot.2026-04-16.at.00.02.59.mp4

For safari on ios: (add to homescreen modal)

IMG_2242 IMG_2243

Please put your Discord username so you can be contacted if a bug or regression is found:

fghjk_60845

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Walkthrough

Adds an iOS-only "add to home screen" banner with a guided modal and persistence, localization strings for iOS instructions and fullscreen labels, and a fullscreen toggle in the game HUD with Fullscreen API integration and reactive fullscreen state.

Changes

Cohort / File(s) Summary
Gitignore
/.gitignore
Added NOTES.md to ignore list.
Localization
resources/lang/en.json
Added ios_banner keys (modal, buttons, step instructions) and fullscreen.enter / fullscreen.exit keys.
iOS Home Screen Banner Component
src/client/components/IOSAddToHomeScreenBanner.ts
New LitElement ios-add-to-home-screen-banner that detects iOS and standalone mode, parses iOS/Safari version, shows banner and modal, and handles "never"/"later"/"got it" actions with localStorage/session state.
Integration
src/client/GameModeSelector.ts
Imports and renders the new ios-add-to-home-screen-banner component into the Game Mode Selector UI.
Fullscreen Toggle
src/client/graphics/layers/GameRightSidebar.ts
Added fullscreen toggle UI, isFullscreen reactive state, fullscreenchange listener, and methods to request/exit fullscreen; button shown only if document.fullscreenEnabled.

Sequence Diagram

sequenceDiagram
    participant User
    participant GameModeSelector
    participant IOSBanner as ios-add-to-home-screen-banner
    participant BrowserAPIs as Browser APIs

    User->>GameModeSelector: Open game selector
    GameModeSelector->>IOSBanner: Render component
    IOSBanner->>BrowserAPIs: Check Platform.isIOS & standalone/display-mode
    alt Not iOS or Standalone
        IOSBanner->>IOSBanner: Do not render
    else iOS & Not standalone
        IOSBanner->>BrowserAPIs: Read localStorage["ios_a2hs_banner_dismissed"]
        alt Dismissed
            IOSBanner->>IOSBanner: Hide banner
        else Not dismissed
            IOSBanner->>BrowserAPIs: Parse iOS/Safari version from userAgent
            IOSBanner->>User: Show banner (How / Later / Never)
            alt User -> How
                IOSBanner->>IOSBanner: Open modal guide (version-specific steps)
                User->>IOSBanner: Click "Got it"
                IOSBanner->>IOSBanner: Close modal
            end
            alt User -> Never
                IOSBanner->>BrowserAPIs: localStorage["ios_a2hs_banner_dismissed"]=true
                IOSBanner->>IOSBanner: Hide permanently
            else User -> Later
                IOSBanner->>IOSBanner: Hide for session
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A tiny banner on iOS shines bright,
Steps to home screen shown in gentle light,
Fullscreen button waits to clear the view,
LocalStorage remembers what you do,
The HUD grows kinder — ready, play, goodnight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main changes: adding fullscreen support via a HUD button for desktop/Android and an iOS banner.
Linked Issues check ✅ Passed The PR fully addresses #3685 by implementing a HUD fullscreen button (desktop/Android) using the Fullscreen API with state tracking, and an iOS alternative via a dismissible banner with version-aware instructions.
Out of Scope Changes check ✅ Passed All changes are in scope: .gitignore entry, translation strings, HUD button implementation, iOS banner component, and fullscreen state management are all directly necessary for the fullscreen feature.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description is directly related to the changeset, providing clear context about fullscreen support implementation for desktop, Android, and iOS platforms.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
.gitignore (1)

13-13: Change unrelated to PR objectives.

Adding NOTES.md to .gitignore is a valid change for keeping developer notes out of version control. However, this appears unrelated to the fullscreen support feature described in the PR objectives. Consider including unrelated changes like this in a separate commit or PR to keep the history clean and focused.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 13, The .gitignore change adding "NOTES.md" is unrelated
to the fullscreen support work; remove that line from the current commit/PR by
editing .gitignore to delete the "NOTES.md" entry (or revert that hunk), create
a separate commit that adds the NOTES.md ignore entry, and open a separate PR
for that commit so the fullscreen feature PR only contains relevant changes;
reference the .gitignore file and the "NOTES.md" entry when making the edits and
commits.
src/client/components/IOSAddToHomeScreenBanner.ts (3)

42-45: Consider wrapping localStorage access in try/catch.

localStorage.getItem() can throw in Safari private browsing mode or when storage is full. The component would fail to render if this throws.

🛡️ Proposed defensive fix
 connectedCallback() {
   super.connectedCallback();
-  this.dismissed = localStorage.getItem(DISMISSED_KEY) === "true";
+  try {
+    this.dismissed = localStorage.getItem(DISMISSED_KEY) === "true";
+  } catch {
+    // localStorage may be unavailable in private browsing
+    this.dismissed = false;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/components/IOSAddToHomeScreenBanner.ts` around lines 42 - 45, The
connectedCallback currently reads localStorage.getItem(DISMISSED_KEY) directly
which can throw (e.g., Safari private mode); wrap the access in a try/catch
inside connectedCallback, default this.dismissed to false on error, and
optionally log or ignore the exception. Update references to DISMISSED_KEY and
this.dismissed in connectedCallback so the component still renders safely if
localStorage access fails.

47-50: Same localStorage concern applies to the never() method.

If setItem throws, the banner will hide for the session but fail to persist the dismissal.

🛡️ Proposed defensive fix
 private never() {
-  localStorage.setItem(DISMISSED_KEY, "true");
+  try {
+    localStorage.setItem(DISMISSED_KEY, "true");
+  } catch {
+    // Persist fails in private browsing; session-only dismissal still works
+  }
   this.dismissed = true;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/components/IOSAddToHomeScreenBanner.ts` around lines 47 - 50, The
never() method currently calls localStorage.setItem(DISMISSED_KEY, "true")
without guarding against exceptions; wrap that call in a try/catch so any thrown
error won’t prevent the in-memory dismissal. In the catch block swallow or log
the error (e.g., console.warn/processLogger) and always set this.dismissed =
true; reference the never() method, DISMISSED_KEY constant, and this.dismissed
property when making the change.

52-54: Minor: later_ naming with trailing underscore.

The underscore suffix avoids collision with the reserved word later (though later is not actually reserved in JS/TS). Consider renaming to handleLater or dismissForSession for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/components/IOSAddToHomeScreenBanner.ts` around lines 52 - 54,
Rename the private method later_ to a clearer name (e.g., handleLater or
dismissForSession) and update all references to it; specifically rename the
method later_() { this.later = true; } to handleLater() { this.later = true; }
(or dismissForSession) and update any event handlers, bindings, tests, or usages
that call later_ to the new name so the component's behavior remains identical.
Ensure method visibility (private) and any type annotations remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.gitignore:
- Line 13: The .gitignore change adding "NOTES.md" is unrelated to the
fullscreen support work; remove that line from the current commit/PR by editing
.gitignore to delete the "NOTES.md" entry (or revert that hunk), create a
separate commit that adds the NOTES.md ignore entry, and open a separate PR for
that commit so the fullscreen feature PR only contains relevant changes;
reference the .gitignore file and the "NOTES.md" entry when making the edits and
commits.

In `@src/client/components/IOSAddToHomeScreenBanner.ts`:
- Around line 42-45: The connectedCallback currently reads
localStorage.getItem(DISMISSED_KEY) directly which can throw (e.g., Safari
private mode); wrap the access in a try/catch inside connectedCallback, default
this.dismissed to false on error, and optionally log or ignore the exception.
Update references to DISMISSED_KEY and this.dismissed in connectedCallback so
the component still renders safely if localStorage access fails.
- Around line 47-50: The never() method currently calls
localStorage.setItem(DISMISSED_KEY, "true") without guarding against exceptions;
wrap that call in a try/catch so any thrown error won’t prevent the in-memory
dismissal. In the catch block swallow or log the error (e.g.,
console.warn/processLogger) and always set this.dismissed = true; reference the
never() method, DISMISSED_KEY constant, and this.dismissed property when making
the change.
- Around line 52-54: Rename the private method later_ to a clearer name (e.g.,
handleLater or dismissForSession) and update all references to it; specifically
rename the method later_() { this.later = true; } to handleLater() { this.later
= true; } (or dismissForSession) and update any event handlers, bindings, tests,
or usages that call later_ to the new name so the component's behavior remains
identical. Ensure method visibility (private) and any type annotations remain
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d1a6594-40aa-47a0-b04c-965eac0f782d

📥 Commits

Reviewing files that changed from the base of the PR and between 3a49b9a and 2f6e214.

⛔ Files ignored due to path filters (2)
  • resources/images/ExitFullscreenIconWhite.svg is excluded by !**/*.svg
  • resources/images/FullscreenIconWhite.svg is excluded by !**/*.svg
📒 Files selected for processing (5)
  • .gitignore
  • resources/lang/en.json
  • src/client/GameModeSelector.ts
  • src/client/components/IOSAddToHomeScreenBanner.ts
  • src/client/graphics/layers/GameRightSidebar.ts

coderabbitai[bot]
coderabbitai bot previously approved these changes Apr 15, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

Copy link
Copy Markdown
Collaborator

@evanpelle evanpelle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we instead add this to the news modal? and then clicking it can open a modal that explains how to play full screen?

@baculinivan-web
Copy link
Copy Markdown
Contributor Author

baculinivan-web commented Apr 17, 2026

could we instead add this to the news modal? and then clicking it can open a modal that explains how to play full screen?

Idk, it seems that news modal is for news, whereas this is a crushing feature, which is proved by some players even taking name "unplayable on iOS"

@baculinivan-web
Copy link
Copy Markdown
Contributor Author

could we instead add this to the news modal? and then clicking it can open a modal that explains how to play full screen?

Comments?

@baculinivan-web
Copy link
Copy Markdown
Contributor Author

could we instead add this to the news modal? and then clicking it can open a modal that explains how to play full screen?

Merge, maybe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

Mobile and Main game feature - Adding fullscreen button to HUD

2 participants