Best Practices
Critical topics and recommended patterns to follow when building mini-apps.
Screen Cutout (Notch / Dynamic Island)
Many iPhones and Android devices have a front-camera cutout at the top (notch, Dynamic Island, punch-hole) and a home indicator at the bottom. When a mini-app opens full-screen, these areas can overlap game UI elements.
The Golden Rule
Let the background bleed full-screen. Keep interactive UI inside the safe zone.
┌─────────────────────────────┐
│ [ Dynamic Island ] │ ← Background extends here (full-screen feel)
│─────────────────────────────│
│ ❤️❤️❤️ SCORE: 500 │ ← UI elements start below the safe area
│ │
│ GAME AREA │
│ │
│─────────────────────────────│
│ ▬ (swipe bar) │ ← Background extends here too
└─────────────────────────────┘Device Reference Values
| Device | top | bottom |
|---|---|---|
| iPhone 16 / 15 Pro (Dynamic Island) | 59dp | 34dp |
| iPhone 13 / 14 (notch) | 47dp | 34dp |
| iPhone SE / 8 (physical home) | 20dp | 0dp |
| Android gesture nav + cutout | 27–40dp | 24–48dp |
| Android gesture nav, no cutout | 0dp | 24–48dp |
CSS Usage (Recommended)
The platform automatically injects CSS custom properties when the WebView loads:
/* Background is full-screen — extends behind the notch */
body {
background: #1a1a2e;
/* Do NOT add padding here — let the background bleed */
}
/* Apply safe area only to UI containers */
.game-hud {
padding-top: var(--sa-top, 0px);
padding-bottom: var(--sa-bottom, 0px);
padding-left: var(--sa-left, 0px);
padding-right: var(--sa-right, 0px);
}
.score-panel {
margin-top: var(--sa-top, 0px);
}
.bottom-bar {
padding-bottom: calc(var(--sa-bottom, 0px) + 8px);
}JS Usage
const insets = supergame.getSafeAreaInsets()
// { top: 59, bottom: 34, left: 0, right: 0 }
// Manual layout in canvas-based games
if (insets.top > 0) {
uiCamera.y = insets.top
}
if (insets.bottom > 0) {
bottomBar.y = screenHeight - insets.bottom - bottomBar.height
}
// Check if device has a notch
const hasNotch = insets.top > 20Wrong ❌
/* WRONG: padding on body clips the background, leaving a white gap */
body {
padding-top: var(--sa-top, 0px); /* ❌ */
}Correct ✅
/* CORRECT: body is full-screen; only the content container gets insets */
body {
background: #1a1a2e; /* bleeds to edges */
}
.hud {
padding-top: var(--sa-top, 0px); /* ✅ content shifts down */
}Lifecycle: Background / Foreground
When the mini-app is scrolled off-screen in the Discovery feed or sent to background via the overlay menu, the platform automatically:
- Pauses all
<audio>and<video>elements in the WebView - Stops gyroscope / accelerometer streams
- Keeps multiplayer (WebSocket) connections alive
However, pausing/resuming the game loop and restarting background music is the developer's responsibility.
Music / Audio Management
Important
The platform pauses audio elements automatically but does not resume them. You must call play() yourself inside onForeground.
const bgMusic = new Audio('music.mp3')
bgMusic.loop = true
bgMusic.play()
supergame.onBackground(() => {
// Platform already paused the audio element
// Stop your game loop:
gameLoop.stop()
})
supergame.onForeground(() => {
// Restart music (platform does not do this)
bgMusic.play().catch(() => {})
// Resume game loop
gameLoop.start()
})void Start()
{
SuperGame.onBackground(() => {
bgMusicSource.Pause();
Time.timeScale = 0f;
});
SuperGame.onForeground(() => {
bgMusicSource.Play();
Time.timeScale = 1f;
});
}func _ready():
gameTegra.onBackground(func():
get_tree().paused = true
$BGMusic.stream_paused = true
)
gameTegra.onForeground(func():
get_tree().paused = false
$BGMusic.stream_paused = false
)Multiplayer Stays Alive
Sensor streams are paused but multiplayer connections remain active. Sync any missed events when the player returns:
supergame.onForeground(() => {
gameLoop.start()
bgMusic.play().catch(() => {})
// Fetch events that occurred while in background
syncMissedEvents()
})Unsubscribe
Remove callbacks when they are no longer needed to prevent memory leaks:
const unsubBg = await supergame.onBackground(() => { ... })
const unsubFg = await supergame.onForeground(() => { ... })
// On scene change or cleanup:
unsubBg()
unsubFg()Checklist
Before publishing, verify:
- [ ] Game HUD does not sit behind the notch / Dynamic Island
- [ ]
bodyhas a full-screen background color — no--sa-top/--sa-bottompadding on body - [ ]
onBackgroundstops the game loop - [ ]
onForegroundrestarts music - [ ] Multiplayer connection stays open in background (platform guarantees this)
- [ ] Unsubscribe functions are called during scene cleanup