What changed with edge-to-edge
For most of Android's life an app's content started below the status bar and ended above the navigation bar; the system reserved those strips. From Android 15, apps that target the current SDK — which every store-ready APK must — are drawn edge to edge by default: the page extends under both bars and it is the app's job to keep its own content clear of them. Builders handle the top for you when you choose a solid status-bar colour. The bottom, where the gesture bar sits, is still your page's responsibility, and a fixed "Add to cart" button that ends up half under the gesture handle is now the most common layout complaint in converted HTML apps.
The two lines that fix it
First, the viewport tag needs viewport-fit=cover. Without it, the WebView adds its own letterboxing and the env() values below are always zero. Second, pad the element that touches an edge by the matching inset:
.bottom-bar { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
header { padding-top: calc(12px + env(safe-area-inset-top)); }
env(safe-area-inset-*) resolves to the height of the bar on that edge — 24–48 dp at the top depending on the notch, 16–48 dp at the bottom depending on gesture or 3-button navigation — and to 0 on a phone where the bar does not overlap. So the same CSS is right on every device, which is the point.
Choosing the status bar colour
Android draws the clock and icons in either light or dark, and the builder picks which based on your theme colour's brightness. The previewer applies the same rule and reports the contrast ratio. Mid-tones are the trap: a #8899aa status bar gets light icons at about 2.5:1, which is legible in a dark room and invisible in sunlight. Go darker (icons stay light) or lighter (icons flip to dark) until the ratio is 3:1 or more.