cc161cc37f
`WindowInsets.getInsets(systemBars())` returns physical pixels (e.g. 84 px on a 2.625× Pixel 7) but both Bevy's `Val::Px` (UI layer) and the world- space layout coordinate system use logical pixels. Dividing by `window.scale_factor()` before applying gives the correct 32 dp offset. - `safe_area.rs::apply_safe_area_anchors`: query `Window`, divide `insets.top` by `scale_factor()` before writing `Val::Px(base_top + top_logical)`. - `layout.rs::compute_layout`: new `safe_area_top: f32` parameter (logical px) subtracts from the vertical budget (`card_width_height_based`) and from `top_y` so both card sizing and pile positioning honour the status-bar band. - `table_plugin.rs`: `setup_table` and `on_window_resized` now read `SafeAreaInsets` and divide by scale before passing `safe_area_top` to `compute_layout`. New `on_safe_area_changed` system fires a synthetic `WindowResized` when insets arrive (~frame 2-3 on Android) so the full resize pipeline (layout → pile markers → card snap) re-runs automatically. - All test call-sites updated with `, 0.0` safe_area_top (desktop/no inset). - Two regression tests added: shift amount equals `safe_area_top` exactly; horizontal layout is unaffected by vertical inset. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>