@layer components {
  /* Touch screens: the thread fits what the reader sees, keyboard included, where iOS pans it (lib/visual_viewport.js);
     its composer sits on the keyboard. Translated, it also places its position: fixed descendants. --vv-top is unset
     at rest: a resting translate would keep the header on a composited layer that iOS rasterizes soft. */
  @media (pointer: coarse) {
    .app-main--flush { height: var(--vv-height, 100dvh); translate: 0 var(--vv-top); }
    /* Under a support or quota banner, the body is a column of that height and the shell fills what the banner
       leaves, however tall its text wraps (the banners' own rules assume one line; these outweigh them). */
    body:has(> :is(.support-banner, .quota-banner)):has(:is(.app-main--flush, .app-main--fit)) {
      display: flex; flex-direction: column; height: var(--vv-height, 100dvh); min-height: 0;
    }
    body:has(> :is(.support-banner, .quota-banner)):has(:is(.app-main--flush, .app-main--fit)) > .app-shell { flex: 1; min-height: 0; }
    body:has(> :is(.support-banner, .quota-banner)) > .app-shell > :is(.app-main--flush, .app-main--fit) { height: auto; }
    body:has(:is(.app-main--flush, .app-main--fit)) > :is(.support-banner, .quota-banner) { translate: 0 var(--vv-top); }
    /* Pulling the thread down at its end never reloads the page (Chrome Android): it catches up by itself. */
    .thread { overscroll-behavior-y: contain; }
    /* The keyboard covers the home indicator, and the tabs would ride on it. */
    html[data-keyboard] .composer { padding-bottom: 8px; }
    html[data-keyboard] .tab-bar { display: none; }
  }
}
