@@ -199,6 +199,47 @@ Do not introduce `document.write`, and do not build a parallel
199199sanitiser; ` escapeHtml ` is the one primitive. If you find yourself
200200reaching for ` DOMPurify ` or similar, stop and ask.
201201
202+ ### Frontend: iOS PWA safe-area insets
203+
204+ The site is installable as a standalone PWA
205+ (` apple-mobile-web-app-capable=yes ` ,
206+ ` viewport-fit=cover ` ), which means content paints under the iPhone
207+ notch / status bar / home indicator. Any surface that pins to a
208+ viewport edge on mobile MUST factor in ` env(safe-area-inset-*) ` or
209+ its controls will sit under hardware that the user can't tap through.
210+
211+ Pattern, in order of preference:
212+
213+ 1 . ** Pad the surface** that contains the controls — e.g.
214+ ` #sidebar { padding-top: env(safe-area-inset-top); } ` ,
215+ ` #sidebar-footer { padding-bottom: max(8px, env(safe-area-inset-bottom)); } ` .
216+ This is the cleanest fix because the surface still extends
217+ visually behind the notch (background colour fills the gap) but
218+ its children stay in the safe area.
219+ 2 . ** Offset position** when the element is ` position: fixed ` /` sticky `
220+ without a useful parent — e.g.
221+ ` body.compact-mode .leaflet-top { top: env(safe-area-inset-top); } ` ,
222+ ` #toast-host { top: calc(56px + env(safe-area-inset-top)); } ` .
223+ 3 . ** Shrink centred elements** so ` margin: auto ` keeps them inside
224+ the safe area — ` <dialog> ` uses
225+ ` max-height: calc(100dvh - 20px - 2 * max(env(safe-area-inset-top), env(safe-area-inset-bottom))) ` .
226+ The ` 2 * max(...) ` is right because ` margin: auto ` on a centred
227+ block puts equal space above and below; we need that half-margin
228+ to be ≥ the larger inset, so the total reduction is twice that.
229+
230+ Sticky elements (` #detail-header ` , dialog ` .about-close-form ` )
231+ ignore ancestor padding — apply padding * to the sticky element
232+ itself* so its content shifts. The `padding-top: calc(6px +
233+ env(safe-area-inset-top))` pattern on ` #detail-header` is the
234+ canonical example.
235+
236+ When adding any new fullscreen mobile surface (anything that uses
237+ ` inset: 0 ` , ` position: fixed; top: 0 ` , full-viewport ` <dialog> ` ,
238+ etc.), audit it against the four insets before merging. Test on a
239+ notched device (or the iPhone Pro simulator in Safari devtools) in
240+ PWA standalone mode — the browser tab itself reserves space for
241+ the status bar so the bug only manifests once installed.
242+
202243### BEAST wire format (` dotnet/src/FlightJar.Decoder/Beast/BeastFrameReader.cs ` )
203244
204245Frames are ` 0x1A <type> <6B MLAT ts> <1B sig> <msg> ` , where any ` 0x1A ` in
0 commit comments