@keyframes fadeIn{from{opacity:0}to{opacity:1}}.fadeIn{animation-name:fadeIn}

/* Whole-page entrance fade.
 *
 * The mirror only carries per-widget animations (50 on the home page, 34 of them headings
 * or text blocks), so on load the backgrounds, images and section frames snapped in while
 * only the copy faded. This fades the page in as one, and the per-widget reveals then
 * layer on top.
 *
 * IMPLEMENTED AS AN OVERLAY THAT FADES OUT, not as `body{opacity:...}`, for two reasons —
 * both learned the hard way:
 *
 *  1. TEXT RENDERING. Animating opacity on body promotes it to its own compositing layer,
 *     and compositing disables subpixel antialiasing, so every glyph on the page renders
 *     differently. It is subtle but real: it showed up as 20k-50k differing pixels on the
 *     text-heavy pages at every scroll offset in the regression harness — a change to the
 *     actual site's text, not a harness artefact. An overlay contains no text, so content
 *     rendering is untouched.
 *
 *  2. FAILING VISIBLE. The overlay's resting state is `opacity:0` — invisible — and only
 *     the keyframes make it briefly opaque. So every failure mode ends with the page
 *     visible: no keyframes, no `db-anim` class, or animations blocked entirely all leave
 *     an invisible overlay. The obvious alternative (`opacity:0` on body plus a JS-added
 *     "ready" class) hides the whole site whenever that script fails to run.
 *
 * Keyed on `html.db-anim`, which the mirror's inline head guard sets, so the fade exists
 * only where animations are intended. `pointer-events:none` means it can never intercept
 * a click even while it is on screen. The page background is white, so fading a white
 * overlay out is indistinguishable from the page fading in.
 */
@keyframes dbPageIn{from{opacity:1}to{opacity:0}}

html.db-anim body::before{
  content:"";
  position:fixed;
  inset:0;
  z-index:100001;      /* above the highest z-index in the mirror (100000) */
  background:#fff;
  pointer-events:none;
  opacity:0;           /* resting state: invisible. The animation supplies the opaque frame. */
  animation:dbPageIn .6s ease;
}

/* Respect the OS setting. framework.css only neutralises `.animated`, which does not match
 * this, so it needs its own guard. With the animation off, the resting opacity:0 applies
 * and there is simply no fade. */
@media (prefers-reduced-motion:reduce){
  html.db-anim body::before{animation:none}
}
